Merge master

Signed-off-by: alperozturk <alper_ozturk@proton.me>
This commit is contained in:
alperozturk 2023-10-26 12:43:56 +02:00
commit f378d61e6b
No known key found for this signature in database
GPG key ID: 4E577DC593B59BDF
60 changed files with 1189 additions and 1914 deletions

View file

@ -33,7 +33,7 @@ jobs:
echo "pr=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
echo "repo=${{ github.event.pull_request.head.repo.full_name }}" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: ${{ steps.get-vars.outputs.repo }}
ref: ${{ steps.get-vars.outputs.branch }}

View file

@ -19,7 +19,7 @@ jobs:
matrix:
flavor: [ Generic, Gplay, Huawei ]
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:

View file

@ -19,7 +19,7 @@ jobs:
matrix:
task: [ detekt, spotlessKotlinCheck ]
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:

View file

@ -12,6 +12,10 @@ on:
permissions:
contents: read
concurrency:
group: code-ql-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
@ -26,13 +30,13 @@ jobs:
language: [ 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set Swap Space
uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0
with:
swap-size-gb: 10
- name: Initialize CodeQL
uses: github/codeql-action/init@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
uses: github/codeql-action/init@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
with:
languages: ${{ matrix.language }}
- name: Set up JDK 17
@ -46,4 +50,4 @@ jobs:
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
./gradlew assembleDebug
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
uses: github/codeql-action/analyze@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4

View file

@ -23,7 +23,7 @@ jobs:
steps:
- name: Add reaction on start
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
repository: ${{ github.event.repository.full_name }}
@ -31,7 +31,7 @@ jobs:
reaction-type: "+1"
- name: Checkout the latest code
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
token: ${{ secrets.COMMAND_BOT_PAT }}
@ -42,7 +42,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
- name: Add reaction on failure
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
if: failure()
with:
token: ${{ secrets.COMMAND_BOT_PAT }}

View file

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3
with:

View file

@ -18,5 +18,5 @@ jobs:
name: "Validation"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 # v1.1.0

View file

@ -19,7 +19,7 @@ jobs:
- name: Check if secrets are available
run: echo "::set-output name=ok::${{ secrets.KS_PASS != '' }}"
id: check-secrets
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
if: ${{ steps.check-secrets.outputs.ok == 'true' }}
- name: set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3

View file

@ -24,12 +24,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
@ -37,6 +37,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@0116bc2df50751f9724a2e35ef1f24d22f90e4e1 # v2.22.3
uses: github/codeql-action/upload-sarif@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
with:
sarif_file: results.sarif

View file

@ -22,7 +22,7 @@ jobs:
color: [ blue ]
api-level: [ 27 ]
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v3
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v3
- name: Gradle cache
uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84 # v3

View file

@ -18,7 +18,7 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Set up JDK 17
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
with:

View file

@ -1,9 +1,30 @@
## 3.24.1 (February 21, 2022)
## 3.26.0 (September 16, 2023)
- image editing
- image details, with map
- show other Nextcloud apps
Minimum: NC 16 Server, Android 6.0 Marshmallow
For a full list, please see https://github.com/nextcloud/android/milestone/84
## 3.25.0 (June 13, 2023)
- show Groupfolder
- Tag in file listing
Minimum: NC 16 Server, Android 6.0 Marshmallow
For a full list, please see https://github.com/nextcloud/android/milestone/81
## 3.24.1 (February 21, 2023)
- Fix crash in previous version when connecting to old server versions
Minimum: NC 16 Server, Android 6.0 Marshmallow
For a full list, please see https://github.com/nextcloud/android/milestone/80
## 3.24.0 (February 13, 2023)
- Several performance optimizations by @starypatyk

View file

@ -15,11 +15,17 @@ height="80">](https://f-droid.org/packages/com.nextcloud.client/)
## How to contribute :rocket:
If you want to [contribute](https://nextcloud.com/contribute/) to Nextcloud, you are very welcome:
If you want to [contribute](https://nextcloud.com/contribute/) to the Nextcloud Android client app, there are many ways to help whether or not you are a coder:
* our forum at https://help.nextcloud.com
* for translations of the app on [Transifex](https://app.transifex.com/nextcloud/nextcloud/android/)
* opening issues and PRs (including a corresponding issue)
* helping out other users on our forum at https://help.nextcloud.com
* providing translations of the app on [Transifex](https://app.transifex.com/nextcloud/nextcloud/android/)
* reporting problems / suggesting enhancements by [opening new issues](https://github.com/nextcloud/android/issues/new/choose)
* implementing proposed bug fixes and enhancement ideas by submitting PRs (associated with a corresponding issue preferably)
* reviewing [pull requests](https://github.com/nextcloud/android/pulls) and providing feedback on code, implementation, and functionality
* installing and testing [pull request builds](https://github.com/nextcloud/android/pulls), [daily/dev builds](https://github.com/nextcloud/android#development-version-hammer), or [RCs/release candidate builds](https://github.com/nextcloud/android/releases)
* enhancing Admin, User, or Developer [documentation](https://github.com/nextcloud/documentation/)
* hitting hard on the latest stable release by testing fundamental features and evaluating the user experience
* proactively getting familiar with [how to gather debug logs](https://github.com/nextcloud/android#getting-debug-info-via-logcat-mag) from your devices (so that you are prepared to provide a detailed report if you encounter a problem with the app in the future)
## Contribution Guidelines & License :scroll:
@ -38,7 +44,7 @@ More information on how to contribute: <https://nextcloud.com/contribute/>
## Start contributing :hammer\_and\_wrench:
Make sure you read [SETUP.md](https://github.com/nextcloud/android/blob/master/SETUP.md) and [CONTRIBUTING.md](https://github.com/nextcloud/android/blob/master/CONTRIBUTING.md) before you start working on this project. But basically: fork this repository and contribute back using pull requests to the master branch.
Easy starting points are also reviewing [pull requests](https://github.com/nextcloud/android/pulls) and working on [starter issues](https://github.com/nextcloud/android/issues?q=is%3Aopen+is%3Aissue+label%3A%22starter+issue%22).
Easy starting points are also reviewing [pull requests](https://github.com/nextcloud/android/pulls) and working on [starter issues](https://github.com/nextcloud/android/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
### Getting debug info via logcat :mag:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -314,7 +314,7 @@ public abstract class AbstractIT {
return currentActivity;
}
protected void shortSleep() {
protected static void shortSleep() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {

View file

@ -135,10 +135,20 @@ public abstract class AbstractOnServerIT extends AbstractIT {
.isSuccess());
}
assertTrue(new RemoveFileRemoteOperation(remoteFile.getRemotePath())
.execute(client)
.isSuccess()
);
boolean removeResult = false;
for (int i = 0; i < 5; i++) {
removeResult = new RemoveFileRemoteOperation(remoteFile.getRemotePath())
.execute(client)
.isSuccess();
if (removeResult) {
break;
}
shortSleep();
}
assertTrue(removeResult);
}
}
}

View file

@ -41,7 +41,6 @@ import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.FileStorageUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -106,29 +105,6 @@ public class UploadIT extends AbstractOnServerIT {
createDummyFiles();
}
@After
public void after() {
RemoteOperationResult result = new RefreshFolderOperation(getStorageManager().getFileByPath("/"),
System.currentTimeMillis() / 1000L,
false,
true,
getStorageManager(),
user,
targetContext)
.execute(client);
// cleanup only if folder exists
if (result.isSuccess() && getStorageManager().getFileByDecryptedRemotePath(FOLDER) != null) {
new RemoveFileOperation(getStorageManager().getFileByDecryptedRemotePath(FOLDER),
false,
user,
false,
targetContext,
getStorageManager())
.execute(client);
}
}
@Test
public void testEmptyUpload() {
OCUpload ocUpload = new OCUpload(FileStorageUtils.getTemporalPath(account.name) + "/empty.txt",
@ -529,8 +505,8 @@ public class UploadIT extends AbstractOnServerIT {
assertNotNull(ocFile);
assertEquals(remotePath, ocFile.getRemotePath());
assertEquals(new ImageDimension(451f, 529f), ocFile.getImageDimension());
assertEquals(new GeoLocation(49.99679166666667, 8.67198611111111), ocFile.getGeoLocation());
assertEquals(new ImageDimension(300f, 200f), ocFile.getImageDimension());
assertEquals(new GeoLocation(64, -46), ocFile.getGeoLocation());
}
private void verifyStoragePath(OCFile file) {

View file

@ -20,6 +20,7 @@
*/
package com.owncloud.android.ui
import android.os.Build
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
@ -65,7 +66,7 @@ class LoginIT : AbstractIT() {
* The CI/CD pipeline is encountering issues related to the Android version for this functionality.
* Therefore the test will only be executed on Android versions 10 and above.
*/
@SdkSuppress(minSdkVersion = 29)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
fun login() {
val arguments = InstrumentationRegistry.getArguments()
val baseUrl = arguments.getString("TEST_SERVER_URL")!!

View file

@ -73,14 +73,19 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
@Test
@ScreenshotTest
fun showFileDetailDetailsFragment() {
val sut = testActivityRule.launchActivity(null)
sut.addFragment(ImageDetailFragment.newInstance(oCFile, user))
val activity = testActivityRule.launchActivity(null)
val sut = ImageDetailFragment.newInstance(oCFile, user)
activity.addFragment(sut)
shortSleep()
shortSleep()
waitForIdleSync()
shortSleep()
shortSleep()
shortSleep()
screenshot(sut)
activity.runOnUiThread {
sut.hideMap()
}
screenshot(activity)
}
@Test
@ -182,6 +187,7 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
waitForIdleSync()
activity.runOnUiThread {
sut.fileDetailActivitiesFragment.disableLoadingActivities()
sut
.fileDetailActivitiesFragment
.setErrorContent(targetContext.resources.getString(R.string.file_detail_activity_error))

View file

@ -92,7 +92,6 @@ import com.owncloud.android.ui.dialog.IndeterminateProgressDialog;
import com.owncloud.android.ui.dialog.LoadingDialog;
import com.owncloud.android.ui.dialog.LocalStoragePathPickerDialogFragment;
import com.owncloud.android.ui.dialog.MultipleAccountsDialog;
import com.owncloud.android.ui.dialog.NoteDialogFragment;
import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
import com.owncloud.android.ui.dialog.RenamePublicShareDialogFragment;
@ -404,9 +403,6 @@ abstract class ComponentsModule {
@ContributesAndroidInjector
abstract Migrations migrations();
@ContributesAndroidInjector
abstract NoteDialogFragment noteDialogFragment();
@ContributesAndroidInjector
abstract NotificationWork notificationWork();

View file

@ -29,7 +29,6 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.graphics.BitmapFactory
import androidx.core.app.NotificationCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
@ -69,9 +68,7 @@ class FilesExportWork(
val successfulExports = exportFiles(fileIDs)
// show notification
showSuccessNotification(successfulExports)
return Result.success()
}
@ -105,7 +102,13 @@ class FilesExportWork(
@Throws(IllegalStateException::class)
private fun exportFile(ocFile: OCFile) {
FileExportUtils().exportFile(ocFile.fileName, ocFile.mimeType, contentResolver, ocFile, null)
FileExportUtils().exportFile(
ocFile.fileName,
ocFile.mimeType,
contentResolver,
ocFile,
null
)
}
private fun downloadFile(ocFile: OCFile) {
@ -119,19 +122,16 @@ class FilesExportWork(
}
private fun showErrorNotification(successfulExports: Int) {
if (successfulExports == 0) {
showNotification(
appContext.resources.getQuantityString(R.plurals.export_failed, successfulExports, successfulExports)
)
val message = if (successfulExports == 0) {
appContext.resources.getQuantityString(R.plurals.export_failed, successfulExports, successfulExports)
} else {
showNotification(
appContext.resources.getQuantityString(
R.plurals.export_partially_failed,
successfulExports,
successfulExports
)
appContext.resources.getQuantityString(
R.plurals.export_partially_failed,
successfulExports,
successfulExports
)
}
showNotification(message)
}
private fun showSuccessNotification(successfulExports: Int) {
@ -152,9 +152,7 @@ class FilesExportWork(
NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD
)
.setSmallIcon(R.drawable.notification_icon)
.setLargeIcon(BitmapFactory.decodeResource(appContext.resources, R.drawable.notification_icon))
.setSubText(user.accountName)
.setContentText(message)
.setContentTitle(message)
.setAutoCancel(true)
viewThemeUtils.androidx.themeNotificationCompatBuilder(appContext, notificationBuilder)
@ -166,7 +164,8 @@ class FilesExportWork(
appContext,
notificationId,
actionIntent,
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_CANCEL_CURRENT or
PendingIntent.FLAG_IMMUTABLE
)
notificationBuilder.addAction(
NotificationCompat.Action(
@ -176,7 +175,8 @@ class FilesExportWork(
)
)
val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationManager = appContext
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(notificationId, notificationBuilder.build())
}

View file

@ -30,6 +30,7 @@ import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.nextcloud.android.common.ui.theme.utils.ColorRole
@ -260,6 +261,11 @@ class ImageDetailFragment : Fragment(), Injectable {
binding.imageLocationMapCopyright.text = binding.imageLocationMap.tileProvider.tileSource.copyrightNotice
}
@VisibleForTesting
fun hideMap() {
binding.imageLocationMap.visibility = View.GONE
}
@SuppressLint("SimpleDateFormat")
private fun gatherMetadata() {
val fileSize = DisplayUtils.bytesToHumanReadable(file.fileLength)

View file

@ -25,6 +25,7 @@ package com.owncloud.android.ui.dialog;
import android.app.Dialog;
import android.os.Bundle;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
@ -59,7 +60,11 @@ public class AccountRemovalConfirmationDialog extends DialogFragment implements
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
user = getArguments().getParcelable(KEY_USER);
Bundle arguments = getArguments();
if (arguments != null) {
user = arguments.getParcelable(KEY_USER);
}
}
@Override
@ -67,9 +72,18 @@ public class AccountRemovalConfirmationDialog extends DialogFragment implements
super.onStart();
AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog != null) {
viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
MaterialButton positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
if (positiveButton != null) {
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
}
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
if (negativeButton != null) {
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
}
}
}
@NonNull
@ -82,7 +96,7 @@ public class AccountRemovalConfirmationDialog extends DialogFragment implements
.setPositiveButton(R.string.common_ok,
(dialogInterface, i) -> backgroundJobManager.startAccountRemovalJob(user.getAccountName(),
false))
.setNeutralButton(R.string.common_cancel, null);
.setNegativeButton(R.string.common_cancel, null);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireActivity(), builder);

View file

@ -35,6 +35,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.collect.Sets;
import com.nextcloud.client.account.CurrentAccountProvider;
@ -99,7 +100,7 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
private RichDocumentsTemplateAdapter adapter;
private OCFile parentFolder;
private OwnCloudClient client;
private Button positiveButton;
private MaterialButton positiveButton;
private DialogFragment waitDialog;
public enum Type {
@ -126,11 +127,18 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
AlertDialog alertDialog = (AlertDialog) getDialog();
positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
viewThemeUtils.platform.colorTextButtons(positiveButton,
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
positiveButton.setOnClickListener(this);
positiveButton.setEnabled(false);
if (alertDialog != null) {
positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
if (negativeButton != null) {
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
}
positiveButton.setOnClickListener(this);
positiveButton.setEnabled(false);
}
checkEnablingCreateButton();
}
@ -205,12 +213,14 @@ public class ChooseRichDocumentsTemplateDialogFragment extends DialogFragment im
}
});
int titleTextId = getTitle(type);
// Build the dialog
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
builder.setView(view)
.setPositiveButton(R.string.create, null)
.setNeutralButton(R.string.common_cancel, null)
.setTitle(getTitle(type));
.setNegativeButton(R.string.common_cancel, null)
.setTitle(titleTextId);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(activity, builder);

View file

@ -32,10 +32,10 @@ import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.android.lib.resources.directediting.DirectEditingCreateFileRemoteOperation
import com.nextcloud.android.lib.resources.directediting.DirectEditingObtainListOfTemplatesRemoteOperation
@ -90,7 +90,7 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
private var adapter: TemplateAdapter? = null
private var parentFolder: OCFile? = null
private var title: String? = null
private var positiveButton: Button? = null
private var positiveButton: MaterialButton? = null
private var creator: Creator? = null
enum class Type {
@ -103,17 +103,18 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
override fun onStart() {
super.onStart()
val alertDialog = dialog as AlertDialog
val button = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
viewThemeUtils.platform.colorTextButtons(
button,
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
)
button.setOnClickListener(this)
button.isEnabled = false
button.isClickable = false
val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton)
positiveButton = button
val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton)
positiveButton.setOnClickListener(this)
positiveButton.isEnabled = false
positiveButton.isClickable = false
this.positiveButton = positiveButton
checkEnablingCreateButton()
}
@ -128,6 +129,7 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
parentFolder = arguments.getParcelable(ARG_PARENT_FOLDER)
creator = arguments.getParcelable(ARG_CREATOR)
title = arguments.getString(ARG_HEADLINE, getString(R.string.select_template))
title = when (savedInstanceState) {
null -> arguments.getString(ARG_HEADLINE)
@ -175,7 +177,7 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
val builder = MaterialAlertDialogBuilder(activity)
builder.setView(view)
.setPositiveButton(R.string.create, null)
.setNeutralButton(R.string.common_cancel, null)
.setNegativeButton(R.string.common_cancel, null)
.setTitle(title)
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.list.context, builder)
@ -208,8 +210,8 @@ class ChooseTemplateDialogFragment : DialogFragment(), View.OnClickListener, Tem
}
fun setTemplateList(templateList: TemplateList?) {
adapter!!.setTemplateList(templateList)
adapter!!.notifyDataSetChanged()
adapter?.setTemplateList(templateList)
adapter?.notifyDataSetChanged()
}
override fun onClick(template: Template) {

View file

@ -29,6 +29,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
@ -70,7 +71,7 @@ public class ConflictsResolveDialog extends DialogFragment implements Injectable
public OnConflictDecisionMadeListener listener;
private User user;
private final List<ThumbnailsCacheManager.ThumbnailGenerationTask> asyncTasks = new ArrayList<>();
private Button positiveButton;
private MaterialButton positiveButton;
@Inject ViewThemeUtils viewThemeUtils;
@Inject SyncedFolderProvider syncedFolderProvider;
@ -119,9 +120,11 @@ public class ConflictsResolveDialog extends DialogFragment implements Injectable
return;
}
positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
viewThemeUtils.platform.colorTextButtons(positiveButton,
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
positiveButton.setEnabled(false);
}
@ -175,7 +178,7 @@ public class ConflictsResolveDialog extends DialogFragment implements Injectable
}
})
.setNeutralButton(R.string.common_cancel, (dialog, which) -> {
.setNegativeButton(R.string.common_cancel, (dialog, which) -> {
if (listener != null) {
listener.conflictDecisionMade(Decision.CANCEL);
}
@ -275,4 +278,5 @@ public class ConflictsResolveDialog extends DialogFragment implements Injectable
asyncTasks.clear();
}
}

View file

@ -31,6 +31,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.collect.Sets;
import com.nextcloud.client.di.Injectable;
@ -71,7 +72,7 @@ public class CreateFolderDialogFragment
private OCFile mParentFolder;
private Button positiveButton;
private MaterialButton positiveButton;
private EditBoxDialogBinding binding;
@ -101,13 +102,11 @@ public class CreateFolderDialogFragment
private void bindButton() {
Dialog dialog = getDialog();
if (dialog instanceof AlertDialog) {
AlertDialog alertDialog = (AlertDialog) dialog;
positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
viewThemeUtils.platform.colorTextButtons(positiveButton,
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
if (dialog instanceof AlertDialog alertDialog) {
positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
}
}
@ -186,17 +185,25 @@ public class CreateFolderDialogFragment
});
// Build the dialog
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
builder.setView(view)
.setPositiveButton(R.string.folder_confirm_create, this)
.setNeutralButton(R.string.common_cancel, this)
.setTitle(R.string.uploader_info_dirname);
MaterialAlertDialogBuilder builder = buildMaterialAlertDialog(view);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.userInputContainer.getContext(), builder);
return builder.create();
}
private MaterialAlertDialogBuilder buildMaterialAlertDialog(View view) {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
builder
.setView(view)
.setPositiveButton(R.string.folder_confirm_create, this)
.setNegativeButton(R.string.common_cancel, this)
.setTitle(R.string.uploader_info_dirname);
return builder;
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {

View file

@ -30,6 +30,7 @@ import android.os.Bundle;
import android.text.format.DateUtils;
import android.widget.DatePicker;
import com.google.android.material.button.MaterialButton;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.utils.theme.ViewThemeUtils;
@ -81,12 +82,24 @@ public class ExpirationDatePickerDialogFragment
public void onStart() {
super.onStart();
final Dialog currentDialog = getDialog();
if (currentDialog != null) {
final DatePickerDialog dialog = (DatePickerDialog) currentDialog;
viewThemeUtils.platform.colorTextButtons(dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL),
dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE),
dialog.getButton(DatePickerDialog.BUTTON_POSITIVE));
MaterialButton positiveButton = (MaterialButton) dialog.getButton(DatePickerDialog.BUTTON_POSITIVE);
if (positiveButton != null) {
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
}
MaterialButton negativeButton = (MaterialButton) dialog.getButton(DatePickerDialog.BUTTON_NEGATIVE);
if (negativeButton != null) {
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
}
MaterialButton neutralButton = (MaterialButton) dialog.getButton(DatePickerDialog.BUTTON_NEUTRAL);
if (neutralButton != null) {
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(neutralButton);
}
}
}
@ -118,7 +131,7 @@ public class ExpirationDatePickerDialogFragment
//show unset button only when date is already selected
if (chosenDateInMillis > 0) {
dialog.setButton(
Dialog.BUTTON_NEUTRAL,
Dialog.BUTTON_NEGATIVE,
getText(R.string.share_via_link_unset_password),
(dialog1, which) -> {
if (onExpiryDateListener != null) {

View file

@ -1,144 +0,0 @@
/*
* Nextcloud Android client application
*
* @author Tobias Kaminsky
* Copyright (C) 2018 Tobias Kaminsky
* Copyright (C) 2018 Nextcloud GmbH.
*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.databinding.NoteDialogBinding;
import com.owncloud.android.lib.resources.shares.OCShare;
import com.owncloud.android.ui.activity.ComponentsGetter;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.KeyboardUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
/**
* Dialog to input a multiline note for a share
*/
public class NoteDialogFragment extends DialogFragment implements DialogInterface.OnClickListener, Injectable {
private static final String ARG_SHARE = "SHARE";
@Inject ViewThemeUtils viewThemeUtils;
@Inject KeyboardUtils keyboardUtils;
private OCShare share;
private NoteDialogBinding binding;
public static NoteDialogFragment newInstance(OCShare share) {
NoteDialogFragment frag = new NoteDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_SHARE, share);
frag.setArguments(args);
return frag;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() == null) {
throw new IllegalArgumentException("Arguments may not be null");
}
share = getArguments().getParcelable(ARG_SHARE);
}
@Override
public void onStart() {
super.onStart();
AlertDialog alertDialog = (AlertDialog) getDialog();
viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
}
@Override
public void onResume() {
super.onResume();
keyboardUtils.showKeyboardForEditText(requireDialog().getWindow(), binding.noteText);
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Inflate the layout for the dialog
LayoutInflater inflater = requireActivity().getLayoutInflater();
binding = NoteDialogBinding.inflate(inflater, null, false);
View view = binding.getRoot();
// Setup layout
binding.noteText.setText(share.getNote());
viewThemeUtils.material.colorTextInputLayout(binding.noteContainer);
// Build the dialog
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(binding.noteContainer.getContext());
builder.setView(view)
.setPositiveButton(R.string.note_confirm, this)
.setNeutralButton(R.string.common_cancel, this)
.setTitle(R.string.send_note);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.noteContainer.getContext(), builder);
return builder.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
ComponentsGetter componentsGetter = (ComponentsGetter) getActivity();
if (componentsGetter != null) {
String note = "";
if (binding.noteText.getText() != null) {
note = binding.noteText.getText().toString().trim();
}
componentsGetter.getFileOperationsHelper().updateNoteToShare(share, note);
} else {
DisplayUtils.showSnackMessage(requireActivity(), R.string.note_could_not_sent);
}
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}

View file

@ -23,6 +23,7 @@ import android.app.Dialog;
import android.os.Bundle;
import android.view.ActionMode;
import com.google.android.material.button.MaterialButton;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
@ -96,15 +97,20 @@ public class RemoveFilesDialogFragment extends ConfirmationDialogFragment implem
R.string.confirmation_remove_files_alert;
}
int localRemoveButton = (containsFolder || containsDown) ? R.string.confirmation_remove_local : -1;
args.putInt(ARG_MESSAGE_RESOURCE_ID, messageStringId);
if (files.size() == SINGLE_SELECTION) {
args.putStringArray(ARG_MESSAGE_ARGUMENTS, new String[]{files.get(0).getFileName()});
args.putStringArray(ARG_MESSAGE_ARGUMENTS, new String[] { files.get(0).getFileName() } );
}
args.putInt(ARG_POSITIVE_BTN_RES, R.string.file_delete);
args.putInt(ARG_NEUTRAL_BTN_RES, R.string.file_keep);
args.putInt(ARG_NEGATIVE_BTN_RES, localRemoveButton);
if (containsFolder || containsDown) {
args.putInt(ARG_NEGATIVE_BTN_RES, R.string.confirmation_remove_local);
args.putInt(ARG_NEUTRAL_BTN_RES, R.string.file_keep);
} else {
args.putInt(ARG_NEGATIVE_BTN_RES, R.string.file_keep);
}
args.putParcelableArrayList(ARG_TARGET_FILES, files);
frag.setArguments(args);
@ -131,9 +137,16 @@ public class RemoveFilesDialogFragment extends ConfirmationDialogFragment implem
AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog != null) {
viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE),
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
MaterialButton positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
MaterialButton neutralButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
if (neutralButton != null) {
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(neutralButton);
}
}
}
@ -141,10 +154,14 @@ public class RemoveFilesDialogFragment extends ConfirmationDialogFragment implem
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
mTargetFiles = getArguments().getParcelableArrayList(ARG_TARGET_FILES);
Bundle arguments = getArguments();
if (arguments == null) {
return dialog;
}
mTargetFiles = arguments.getParcelableArrayList(ARG_TARGET_FILES);
setOnConfirmationListener(this);
return dialog;
}
@ -154,9 +171,7 @@ public class RemoveFilesDialogFragment extends ConfirmationDialogFragment implem
*/
@Override
public void onConfirmation(String callerTag) {
ComponentsGetter cg = (ComponentsGetter) getActivity();
cg.getFileOperationsHelper().removeFiles(mTargetFiles, false, false);
finishActionMode();
removeFiles(false);
}
/**
@ -164,8 +179,14 @@ public class RemoveFilesDialogFragment extends ConfirmationDialogFragment implem
*/
@Override
public void onCancel(String callerTag) {
removeFiles(true);
}
private void removeFiles(boolean onlyLocalCopy) {
ComponentsGetter cg = (ComponentsGetter) getActivity();
cg.getFileOperationsHelper().removeFiles(mTargetFiles, true, false);
if (cg != null) {
cg.getFileOperationsHelper().removeFiles(mTargetFiles, onlyLocalCopy, false);
}
finishActionMode();
}

View file

@ -28,6 +28,7 @@ import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
@ -52,8 +53,6 @@ public class RenamePublicShareDialogFragment
private static final String ARG_PUBLIC_SHARE = "PUBLIC_SHARE";
public static final String RENAME_PUBLIC_SHARE_FRAGMENT = "RENAME_PUBLIC_SHARE_FRAGMENT";
@Inject ViewThemeUtils viewThemeUtils;
@Inject KeyboardUtils keyboardUtils;
@ -75,8 +74,10 @@ public class RenamePublicShareDialogFragment
AlertDialog alertDialog = (AlertDialog) getDialog();
if (alertDialog != null) {
viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_POSITIVE),
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL));
MaterialButton positiveButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
MaterialButton negativeButton = (MaterialButton) alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton);
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton);
}
}
@ -104,7 +105,7 @@ public class RenamePublicShareDialogFragment
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(view.getContext());
builder.setView(view)
.setPositiveButton(R.string.file_rename, this)
.setNeutralButton(R.string.common_cancel, this)
.setNegativeButton(R.string.common_cancel, this)
.setTitle(R.string.public_share_name);
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.userInput.getContext(), builder);

View file

@ -1,329 +0,0 @@
/**
* ownCloud Android client application
*
* @author David A. Velasco
* Copyright (C) 2015 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
package com.owncloud.android.ui.dialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import com.owncloud.android.R;
import com.owncloud.android.databinding.SslValidatorLayoutBinding;
import com.owncloud.android.lib.common.network.CertificateCombinedException;
import com.owncloud.android.lib.common.network.NetworkUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.x500.X500Principal;
/**
* Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
*/
public class SslValidatorDialog extends Dialog {
private final static String TAG = SslValidatorDialog.class.getSimpleName();
private OnSslValidatorListener mListener;
private CertificateCombinedException mException;
private SslValidatorLayoutBinding binding;
/**
* Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should
* be trusted.
*
* @param context Android context where the dialog will live.
* @param result Result of a failed remote operation.
* @param listener Object to notice when the server certificate was added to the local certificates store.
* @return A new SslValidatorDialog instance. NULL if the operation can not be recovered
* by setting the certificate as reliable.
*/
public static SslValidatorDialog newInstance(Context context, RemoteOperationResult result, OnSslValidatorListener listener) {
if (result != null && result.isSslRecoverableException()) {
return new SslValidatorDialog(context, listener);
} else {
return null;
}
}
/**
* Private constructor.
*
* Instances have to be created through static {@link SslValidatorDialog#newInstance}.
*
* @param context Android context where the dialog will live
* @param listener Object to notice when the server certificate was added to the local certificates store.
*/
private SslValidatorDialog(Context context, OnSslValidatorListener listener) {
super(context);
mListener = listener;
}
/**
* {@inheritDoc}
*/
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
binding = SslValidatorLayoutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.ok.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
saveServerCert();
dismiss();
if (mListener != null) {
mListener.onSavedCertificate();
} else {
Log_OC.d(TAG, "Nobody there to notify the certificate was saved");
}
} catch (GeneralSecurityException | IOException e) {
dismiss();
if (mListener != null) {
mListener.onFailedSavingCertificate();
}
Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
}
}
});
binding.cancel.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel();
}
});
binding.detailsBtn.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
View detailsScroll = findViewById(R.id.details_scroll);
if (detailsScroll.getVisibility() == View.VISIBLE) {
detailsScroll.setVisibility(View.GONE);
((Button) v).setText(R.string.ssl_validator_btn_details_see);
} else {
detailsScroll.setVisibility(View.VISIBLE);
((Button) v).setText(R.string.ssl_validator_btn_details_hide);
}
}
});
}
public void updateResult(RemoteOperationResult result) {
if (result.isSslRecoverableException()) {
mException = (CertificateCombinedException) result.getException();
/// clean
binding.reasonCertNotTrusted.setVisibility(View.GONE);
binding.reasonCertExpired.setVisibility(View.GONE);
binding.reasonCertNotYetValid.setVisibility(View.GONE);
binding.reasonHostnameNotVerified.setVisibility(View.GONE);
binding.detailsScroll.setVisibility(View.GONE);
/// refresh
if (mException.getCertPathValidatorException() != null) {
binding.reasonCertNotTrusted.setVisibility(View.VISIBLE);
}
if (mException.getCertificateExpiredException() != null) {
binding.reasonCertExpired.setVisibility(View.VISIBLE);
}
if (mException.getCertificateNotYetValidException() != null) {
binding.reasonCertNotYetValid.setVisibility(View.VISIBLE);
}
if (mException.getSslPeerUnverifiedException() != null ) {
binding.reasonHostnameNotVerified.setVisibility(View.VISIBLE);
}
showCertificateData(mException.getServerCertificate());
}
}
private void showCertificateData(X509Certificate cert) {
if (cert != null) {
showSubject(cert.getSubjectX500Principal());
showIssuer(cert.getIssuerX500Principal());
showValidity(cert.getNotBefore(), cert.getNotAfter());
showSignature(cert);
} else {
// this should not happen, TODO
Log_OC.d("certNull", "This should not happen");
}
}
private void showSignature(X509Certificate cert) {
binding.valueSignature.setText(getHex(cert.getSignature()));
binding.valueSignatureAlgorithm.setText(cert.getSigAlgName());
}
public String getHex(final byte [] raw) {
if (raw == null) {
return null;
}
final StringBuilder hex = new StringBuilder(2 * raw.length);
for (final byte b : raw) {
final int hiVal = (b & 0xF0) >> 4;
final int loVal = b & 0x0F;
hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7))));
hex.append((char) ('0' + (loVal + (loVal / 10 * 7))));
}
return hex.toString();
}
@SuppressWarnings("deprecation")
private void showValidity(Date notBefore, Date notAfter) {
binding.valueValidityFrom.setText(notBefore.toLocaleString());
binding.valueValidityTo.setText(notAfter.toLocaleString());
}
private void showSubject(X500Principal subject) {
Map<String, String> s = parsePrincipal(subject);
if (s.get("CN") != null) {
binding.valueSubjectCN.setText(s.get("CN"));
binding.valueSubjectCN.setVisibility(View.VISIBLE);
} else {
binding.valueSubjectCN.setVisibility(View.GONE);
}
if (s.get("O") != null) {
binding.valueSubjectO.setText(s.get("O"));
binding.valueSubjectO.setVisibility(View.VISIBLE);
} else {
binding.valueSubjectO.setVisibility(View.GONE);
}
if (s.get("OU") != null) {
binding.valueSubjectOU.setText(s.get("OU"));
binding.valueSubjectOU.setVisibility(View.VISIBLE);
} else {
binding.valueSubjectOU.setVisibility(View.GONE);
}
if (s.get("C") != null) {
binding.valueSubjectC.setText(s.get("C"));
binding.valueSubjectC.setVisibility(View.VISIBLE);
} else {
binding.valueSubjectC.setVisibility(View.GONE);
}
if (s.get("ST") != null) {
binding.valueSubjectST.setText(s.get("ST"));
binding.valueSubjectST.setVisibility(View.VISIBLE);
} else {
binding.valueSubjectST.setVisibility(View.GONE);
}
if (s.get("L") != null) {
binding.valueSubjectL.setText(s.get("L"));
binding.valueSubjectL.setVisibility(View.VISIBLE);
} else {
binding.valueSubjectL.setVisibility(View.GONE);
}
}
private void showIssuer(X500Principal issuer) {
Map<String, String> s = parsePrincipal(issuer);
if (s.get("CN") != null) {
binding.valueIssuerCN.setText(s.get("CN"));
binding.valueIssuerCN.setVisibility(View.VISIBLE);
} else {
binding.valueIssuerCN.setVisibility(View.GONE);
}
if (s.get("O") != null) {
binding.valueIssuerO.setText(s.get("O"));
binding.valueIssuerO.setVisibility(View.VISIBLE);
} else {
binding.valueIssuerO.setVisibility(View.GONE);
}
if (s.get("OU") != null) {
binding.valueIssuerOU.setText(s.get("OU"));
binding.valueIssuerOU.setVisibility(View.VISIBLE);
} else {
binding.valueIssuerOU.setVisibility(View.GONE);
}
if (s.get("C") != null) {
binding.valueIssuerC.setText(s.get("C"));
binding.valueIssuerC.setVisibility(View.VISIBLE);
} else {
binding.valueIssuerC.setVisibility(View.GONE);
}
if (s.get("ST") != null) {
binding.valueIssuerST.setText(s.get("ST"));
binding.valueIssuerST.setVisibility(View.VISIBLE);
} else {
binding.valueIssuerST.setVisibility(View.GONE);
}
if (s.get("L") != null) {
binding.valueIssuerL.setText(s.get("L"));
binding.valueIssuerL.setVisibility(View.VISIBLE);
} else {
binding.valueIssuerL.setVisibility(View.GONE);
}
}
private Map<String, String> parsePrincipal(X500Principal principal) {
Map<String, String> result = new HashMap<>();
String toParse = principal.getName();
String[] pieces = toParse.split(",");
String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
for (String piece : pieces) {
for (String token : tokens) {
if (piece.startsWith(token + "=")) {
result.put(token, piece.substring(token.length() + 1));
}
}
}
return result;
}
private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
if (mException.getServerCertificate() != null) {
// TODO make this asynchronously, it can take some time
NetworkUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext());
}
}
public interface OnSslValidatorListener {
public void onSavedCertificate();
public void onFailedSavingCertificate();
}
}

View file

@ -24,15 +24,14 @@ import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.client.di.Injectable
import com.owncloud.android.R
import com.owncloud.android.databinding.StoragePermissionDialogBinding
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
@ -43,10 +42,7 @@ import javax.inject.Inject
* Allows choosing "full access" (MANAGE_ALL_FILES) or "read-only media" (READ_EXTERNAL_STORAGE)
*/
@RequiresApi(Build.VERSION_CODES.R)
class StoragePermissionDialogFragment :
DialogFragment(), Injectable {
private lateinit var binding: StoragePermissionDialogBinding
class StoragePermissionDialogFragment : DialogFragment(), Injectable {
private var permissionRequired = false
@ -64,51 +60,48 @@ class StoragePermissionDialogFragment :
super.onStart()
dialog?.let {
val alertDialog = it as AlertDialog
viewThemeUtils.platform.colorTextButtons(alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE))
val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as MaterialButton
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(positiveButton)
val negativeButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) as MaterialButton
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(negativeButton)
val neutralButton = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL) as MaterialButton
viewThemeUtils.material.colorMaterialButtonPrimaryBorderless(neutralButton)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Inflate the layout for the dialog
val inflater = requireActivity().layoutInflater
binding = StoragePermissionDialogBinding.inflate(inflater, null, false)
val view: View = binding.root
val title = when {
permissionRequired -> R.string.file_management_permission
else -> R.string.file_management_permission_optional
}
val explanationResource = when {
permissionRequired -> R.string.file_management_permission_text
else -> R.string.file_management_permission_optional_text
}
binding.storagePermissionExplanation.text = getString(explanationResource, getString(R.string.app_name))
val message = getString(explanationResource, getString(R.string.app_name))
// Setup layout
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.btnFullAccess)
binding.btnFullAccess.setOnClickListener {
setResult(Result.FULL_ACCESS)
dismiss()
}
viewThemeUtils.platform.colorTextButtons(binding.btnReadOnly)
binding.btnReadOnly.setOnClickListener {
setResult(Result.MEDIA_READ_ONLY)
dismiss()
}
// Build the dialog
val titleResource = when {
permissionRequired -> R.string.file_management_permission
else -> R.string.file_management_permission_optional
}
val builder = MaterialAlertDialogBuilder(binding.btnReadOnly.context)
.setTitle(titleResource)
.setView(view)
.setNegativeButton(R.string.common_cancel) { _, _ ->
val dialogBuilder = MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.storage_permission_full_access) { _, _ ->
setResult(Result.FULL_ACCESS)
dismiss()
}
.setNegativeButton(R.string.storage_permission_media_read_only) { _, _ ->
setResult(Result.MEDIA_READ_ONLY)
dismiss()
}
.setNeutralButton(R.string.common_cancel) { _, _ ->
setResult(Result.CANCEL)
dismiss()
}
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.btnReadOnly.context, builder)
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireContext(), dialogBuilder)
return builder.create()
return dialogBuilder.create()
}
private fun setResult(result: Result) {

View file

@ -284,6 +284,10 @@ public class FileDetailActivitiesFragment extends Fragment implements
});
return;
}
if (!isLoadingActivities) {
return;
}
Thread t = new Thread(() -> {
try {
@ -454,6 +458,11 @@ public class FileDetailActivitiesFragment extends Fragment implements
public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
return false;
}
@VisibleForTesting
public void disableLoadingActivities() {
isLoadingActivities = false;
}
private static class SubmitCommentTask extends AsyncTask<Void, Void, Boolean> {

View file

@ -1,691 +0,0 @@
/*
* Nextcloud Android client application
*
* @author Mario Danic
* @author TSI-mc
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud GmbH.
* Copyright (C) 2023 TSI-mc
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.fragment.contactsbackup;
import android.Manifest;
import android.app.DatePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.Toast;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.java.util.Optional;
import com.owncloud.android.R;
import com.owncloud.android.databinding.BackupFragmentBinding;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
import com.owncloud.android.ui.activity.SettingsActivity;
import com.owncloud.android.ui.fragment.FileFragment;
import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.PermissionUtil;
import com.owncloud.android.utils.theme.ThemeUtils;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment;
import third_parties.daveKoeller.AlphanumComparator;
import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP;
import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP;
public class BackupFragment extends FileFragment implements DatePickerDialog.OnDateSetListener, Injectable {
public static final String TAG = BackupFragment.class.getSimpleName();
private static final String ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR";
private static final String KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN";
private static final String KEY_CALENDAR_DAY = "CALENDAR_DAY";
private static final String KEY_CALENDAR_MONTH = "CALENDAR_MONTH";
private static final String KEY_CALENDAR_YEAR = "CALENDAR_YEAR";
public static final String PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED";
public static final String PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED";
private BackupFragmentBinding binding;
@Inject BackgroundJobManager backgroundJobManager;
@Inject ThemeUtils themeUtils;
@Inject ArbitraryDataProvider arbitraryDataProvider;
@Inject ViewThemeUtils viewThemeUtils;
private Date selectedDate;
private boolean calendarPickerOpen;
private DatePickerDialog datePickerDialog;
private CompoundButton.OnCheckedChangeListener dailyBackupCheckedChangeListener;
private CompoundButton.OnCheckedChangeListener contactsCheckedListener;
private CompoundButton.OnCheckedChangeListener calendarCheckedListener;
private User user;
private boolean showSidebar = true;
//flag to check if calendar backup should be shown and backup should be done or not
private boolean showCalendarBackup = true;
public static BackupFragment create(boolean showSidebar) {
BackupFragment fragment = new BackupFragment();
Bundle bundle = new Bundle();
bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar);
fragment.setArguments(bundle);
return fragment;
}
private boolean isCalendarBackupEnabled() {
return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CALENDAR_BACKUP_ENABLED);
}
private void setCalendarBackupEnabled(final boolean enabled) {
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CALENDAR_BACKUP_ENABLED, enabled);
}
private boolean isContactsBackupEnabled() {
return arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CONTACTS_BACKUP_ENABLED);
}
private void setContactsBackupEnabled(final boolean enabled) {
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PREFERENCE_CONTACTS_BACKUP_ENABLED, enabled);
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// use grey as fallback for elements where custom theming is not available
if (themeUtils.themingEnabled(getContext())) {
getContext().getTheme().applyStyle(R.style.FallbackThemingTheme, true);
}
binding = BackupFragmentBinding.inflate(inflater, container, false);
View view = binding.getRoot();
setHasOptionsMenu(true);
if (getArguments() != null) {
showSidebar = getArguments().getBoolean(ARG_SHOW_SIDEBAR);
}
showCalendarBackup = requireContext().getResources().getBoolean(R.bool.show_calendar_backup);
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new);
ActionBar actionBar = contactsPreferenceActivity != null ? contactsPreferenceActivity.getSupportActionBar() : null;
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
viewThemeUtils.files.themeActionBar(requireContext(), actionBar,
showCalendarBackup ? R.string.backup_title : R.string.contact_backup_title);
}
viewThemeUtils.androidx.colorSwitchCompat(binding.contacts);
viewThemeUtils.androidx.colorSwitchCompat(binding.calendar);
viewThemeUtils.androidx.colorSwitchCompat(binding.dailyBackup);
binding.dailyBackup.setChecked(arbitraryDataProvider.getBooleanValue(user,
PREFERENCE_CONTACTS_AUTOMATIC_BACKUP));
binding.contacts.setChecked(isContactsBackupEnabled() && checkContactBackupPermission());
binding.calendar.setChecked(isCalendarBackupEnabled() && checkCalendarBackupPermission(getContext()));
binding.calendar.setVisibility(showCalendarBackup ? View.VISIBLE : View.GONE);
setupCheckListeners();
setBackupNowButtonVisibility();
binding.backupNow.setOnClickListener(v -> backupNow());
binding.contactsDatepicker.setOnClickListener(v -> openCleanDate());
// display last backup
Long lastBackupTimestamp = arbitraryDataProvider.getLongValue(user, PREFERENCE_CONTACTS_LAST_BACKUP);
if (lastBackupTimestamp == -1) {
binding.lastBackupWithDate.setVisibility(View.GONE);
} else {
binding.lastBackupWithDate.setText(
String.format(getString(R.string.last_backup),
DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp)));
}
if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) {
if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 &&
savedInstanceState.getInt(KEY_CALENDAR_MONTH, -1) != -1 &&
savedInstanceState.getInt(KEY_CALENDAR_DAY, -1) != -1) {
selectedDate = new Date(savedInstanceState.getInt(KEY_CALENDAR_YEAR),
savedInstanceState.getInt(KEY_CALENDAR_MONTH), savedInstanceState.getInt(KEY_CALENDAR_DAY));
}
calendarPickerOpen = true;
}
viewThemeUtils.material.colorMaterialButtonPrimaryFilled(binding.backupNow);
viewThemeUtils.material.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker);
viewThemeUtils.platform.colorTextView(binding.dataToBackUpTitle);
viewThemeUtils.platform.colorTextView(binding.backupSettingsTitle);
return view;
}
private void setupCheckListeners() {
dailyBackupCheckedChangeListener = (buttonView, isChecked) -> {
if (checkAndAskForContactsReadPermission()) {
setAutomaticBackup(isChecked);
}
};
binding.dailyBackup.setOnCheckedChangeListener(dailyBackupCheckedChangeListener);
contactsCheckedListener = (buttonView, isChecked) -> {
if (isChecked) {
if (checkAndAskForContactsReadPermission()) {
setContactsBackupEnabled(true);
}
} else {
setContactsBackupEnabled(false);
}
setBackupNowButtonVisibility();
setAutomaticBackup(binding.dailyBackup.isChecked());
};
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener);
calendarCheckedListener = (buttonView, isChecked) -> {
if (isChecked) {
if (checkAndAskForCalendarReadPermission()) {
setCalendarBackupEnabled(true);
}
} else {
setCalendarBackupEnabled(false);
}
setBackupNowButtonVisibility();
setAutomaticBackup(binding.dailyBackup.isChecked());
};
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener);
}
private void setBackupNowButtonVisibility() {
if (binding.contacts.isChecked() || binding.calendar.isChecked()) {
binding.backupNow.setVisibility(View.VISIBLE);
} else {
binding.backupNow.setVisibility(View.INVISIBLE);
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
if (calendarPickerOpen) {
if (selectedDate != null) {
openDate(selectedDate);
} else {
openDate(null);
}
}
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
if (contactsPreferenceActivity != null) {
String backupFolderPath = getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
refreshBackupFolder(backupFolderPath,
contactsPreferenceActivity.getApplicationContext(),
contactsPreferenceActivity.getStorageManager());
}
}
private void refreshBackupFolder(final String backupFolderPath,
final Context context,
final FileDataStorageManager storageManager) {
AsyncTask<String, Integer, Boolean> task = new AsyncTask<String, Integer, Boolean>() {
@Override
protected Boolean doInBackground(String... path) {
OCFile folder = storageManager.getFileByPath(path[0]);
if (folder != null) {
RefreshFolderOperation operation = new RefreshFolderOperation(folder, System.currentTimeMillis(),
false, false, storageManager, user, context);
RemoteOperationResult result = operation.execute(user, context);
return result.isSuccess();
} else {
return Boolean.FALSE;
}
}
@Override
protected void onPostExecute(Boolean result) {
if (result && binding != null) {
OCFile backupFolder = storageManager.getFileByPath(backupFolderPath);
List<OCFile> backupFiles = storageManager
.getFolderContent(backupFolder, false);
Collections.sort(backupFiles, new AlphanumComparator<>());
if (backupFiles == null || backupFiles.isEmpty()) {
binding.contactsDatepicker.setVisibility(View.INVISIBLE);
} else {
binding.contactsDatepicker.setVisibility(View.VISIBLE);
}
}
}
};
task.execute(backupFolderPath);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
boolean retval;
switch (item.getItemId()) {
case android.R.id.home:
if (showSidebar) {
if (contactsPreferenceActivity.isDrawerOpen()) {
contactsPreferenceActivity.closeDrawer();
} else {
contactsPreferenceActivity.openDrawer();
}
} else if (getActivity() != null) {
getActivity().finish();
} else {
Intent settingsIntent = new Intent(getContext(), SettingsActivity.class);
settingsIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(settingsIntent);
}
retval = true;
break;
default:
retval = super.onOptionsItemSelected(item);
break;
}
return retval;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) {
for (int index = 0; index < permissions.length; index++) {
if (Manifest.permission.READ_CONTACTS.equalsIgnoreCase(permissions[index])) {
if (grantResults[index] >= 0) {
// if approved, exit for loop
setContactsBackupEnabled(true);
break;
}
// if not accepted, disable again
binding.contacts.setOnCheckedChangeListener(null);
binding.contacts.setChecked(false);
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener);
}
}
}
if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) {
boolean readGranted = false;
boolean writeGranted = false;
for (int index = 0; index < permissions.length; index++) {
if (Manifest.permission.WRITE_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) {
writeGranted = true;
} else if (Manifest.permission.READ_CALENDAR.equalsIgnoreCase(permissions[index]) && grantResults[index] >= 0) {
readGranted = true;
}
}
if (!readGranted || !writeGranted) {
// if not accepted, disable again
binding.calendar.setOnCheckedChangeListener(null);
binding.calendar.setChecked(false);
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener);
} else {
setCalendarBackupEnabled(true);
}
}
setBackupNowButtonVisibility();
setAutomaticBackup(binding.dailyBackup.isChecked());
}
public void backupNow() {
if (isContactsBackupEnabled() && checkContactBackupPermission()) {
startContactsBackupJob();
}
if (showCalendarBackup && isCalendarBackupEnabled() && checkCalendarBackupPermission(requireContext())) {
startCalendarBackupJob();
}
DisplayUtils.showSnackMessage(requireView().findViewById(R.id.contacts_linear_layout),
R.string.contacts_preferences_backup_scheduled);
}
private void startContactsBackupJob() {
ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
if (activity != null) {
Optional<User> optionalUser = activity.getUser();
if (optionalUser.isPresent()) {
backgroundJobManager.startImmediateContactsBackup(optionalUser.get());
}
}
}
private void startCalendarBackupJob() {
ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
if (activity != null) {
Optional<User> optionalUser = activity.getUser();
if (optionalUser.isPresent()) {
backgroundJobManager.startImmediateCalendarBackup(optionalUser.get());
}
}
}
private void setAutomaticBackup(final boolean enabled) {
final ContactsPreferenceActivity activity = (ContactsPreferenceActivity) getActivity();
if (activity == null) {
return;
}
Optional<User> optionalUser = activity.getUser();
if (!optionalUser.isPresent()) {
return;
}
User user = optionalUser.get();
if (enabled) {
if (isContactsBackupEnabled()) {
Log_OC.d(TAG, "Scheduling contacts backup job");
backgroundJobManager.schedulePeriodicContactsBackup(user);
} else {
Log_OC.d(TAG, "Cancelling contacts backup job");
backgroundJobManager.cancelPeriodicContactsBackup(user);
}
if (isCalendarBackupEnabled()) {
Log_OC.d(TAG, "Scheduling calendar backup job");
backgroundJobManager.schedulePeriodicCalendarBackup(user);
} else {
Log_OC.d(TAG, "Cancelling calendar backup job");
backgroundJobManager.cancelPeriodicCalendarBackup(user);
}
} else {
Log_OC.d(TAG, "Cancelling all backup jobs");
backgroundJobManager.cancelPeriodicContactsBackup(user);
backgroundJobManager.cancelPeriodicCalendarBackup(user);
}
arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(),
PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
String.valueOf(enabled));
}
private boolean checkAndAskForContactsReadPermission() {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
// check permissions
if (PermissionUtil.checkSelfPermission(contactsPreferenceActivity, Manifest.permission.READ_CONTACTS)) {
return true;
} else {
// No explanation needed, request the permission.
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC);
return false;
}
}
private boolean checkAndAskForCalendarReadPermission() {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
// check permissions
if (checkCalendarBackupPermission(contactsPreferenceActivity)) {
return true;
} else {
// No explanation needed, request the permission.
requestPermissions(new String[]{Manifest.permission.READ_CALENDAR, Manifest.permission.WRITE_CALENDAR},
PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC);
return false;
}
}
private boolean checkCalendarBackupPermission(final Context context) {
return PermissionUtil.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) && PermissionUtil.checkSelfPermission(context, Manifest.permission.WRITE_CALENDAR);
}
private boolean checkContactBackupPermission() {
return PermissionUtil.checkSelfPermission(getContext(), Manifest.permission.READ_CONTACTS);
}
public void openCleanDate() {
if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) {
openDate(null);
}
}
public void openDate(@Nullable Date savedDate) {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
if (contactsPreferenceActivity == null) {
Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show();
return;
}
String contactsBackupFolderString =
getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
String calendarBackupFolderString =
getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR;
FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager();
OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString);
OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString);
List<OCFile> backupFiles = storageManager.getFolderContent(contactsBackupFolder, false);
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false));
Collections.sort(backupFiles, (o1, o2) -> {
return Long.compare(o1.getModificationTimestamp(), o2.getModificationTimestamp());
});
Calendar cal = Calendar.getInstance();
int year;
int month;
int day;
if (savedDate == null) {
year = cal.get(Calendar.YEAR);
month = cal.get(Calendar.MONTH) + 1;
day = cal.get(Calendar.DAY_OF_MONTH);
} else {
year = savedDate.getYear();
month = savedDate.getMonth();
day = savedDate.getDay();
}
if (backupFiles.size() > 0 && backupFiles.get(backupFiles.size() - 1) != null) {
datePickerDialog = new DatePickerDialog(contactsPreferenceActivity, this, year, month, day);
datePickerDialog.getDatePicker().setMaxDate(backupFiles.get(backupFiles.size() - 1)
.getModificationTimestamp());
datePickerDialog.getDatePicker().setMinDate(backupFiles.get(0).getModificationTimestamp());
datePickerDialog.setOnDismissListener(dialog -> selectedDate = null);
datePickerDialog.setTitle("");
datePickerDialog.show();
viewThemeUtils.platform.colorTextButtons(datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE),
datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE));
// set background to transparent
datePickerDialog.getButton(DatePickerDialog.BUTTON_NEGATIVE).setBackgroundColor(0x00000000);
datePickerDialog.getButton(DatePickerDialog.BUTTON_POSITIVE).setBackgroundColor(0x00000000);
} else {
DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout),
R.string.contacts_preferences_something_strange_happened);
}
}
@Override
public void onDestroyView() {
binding = null;
super.onDestroyView();
}
@Override
public void onStop() {
super.onStop();
if (datePickerDialog != null) {
datePickerDialog.dismiss();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (datePickerDialog != null) {
outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, datePickerDialog.isShowing());
if (datePickerDialog.isShowing()) {
outState.putInt(KEY_CALENDAR_DAY, datePickerDialog.getDatePicker().getDayOfMonth());
outState.putInt(KEY_CALENDAR_MONTH, datePickerDialog.getDatePicker().getMonth());
outState.putInt(KEY_CALENDAR_YEAR, datePickerDialog.getDatePicker().getYear());
}
}
}
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
if (contactsPreferenceActivity == null) {
Toast.makeText(getContext(), getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show();
return;
}
selectedDate = new Date(year, month, dayOfMonth);
String contactsBackupFolderString =
getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
String calendarBackupFolderString =
getResources().getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR;
FileDataStorageManager storageManager = contactsPreferenceActivity.getStorageManager();
OCFile contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString);
OCFile calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString);
List<OCFile> backupFiles = storageManager.getFolderContent(contactsBackupFolder, false);
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false));
// find file with modification with date and time between 00:00 and 23:59
// if more than one file exists, take oldest
Calendar date = Calendar.getInstance();
date.set(year, month, dayOfMonth);
// start
date.set(Calendar.HOUR, 0);
date.set(Calendar.MINUTE, 0);
date.set(Calendar.SECOND, 1);
date.set(Calendar.MILLISECOND, 0);
date.set(Calendar.AM_PM, Calendar.AM);
long start = date.getTimeInMillis();
// end
date.set(Calendar.HOUR, 23);
date.set(Calendar.MINUTE, 59);
date.set(Calendar.SECOND, 59);
long end = date.getTimeInMillis();
OCFile contactsBackupToRestore = null;
List<OCFile> calendarBackupsToRestore = new ArrayList<>();
for (OCFile file : backupFiles) {
if (start < file.getModificationTimestamp() && end > file.getModificationTimestamp()) {
// contact
if (MimeTypeUtil.isVCard(file)) {
if (contactsBackupToRestore == null) {
contactsBackupToRestore = file;
} else if (contactsBackupToRestore.getModificationTimestamp() < file.getModificationTimestamp()) {
contactsBackupToRestore = file;
}
}
// calendars
if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) {
calendarBackupsToRestore.add(file);
}
}
}
List<OCFile> backupToRestore = new ArrayList<>();
if (contactsBackupToRestore != null) {
backupToRestore.add(contactsBackupToRestore);
}
backupToRestore.addAll(calendarBackupsToRestore);
if (backupToRestore.isEmpty()) {
DisplayUtils.showSnackMessage(getView().findViewById(R.id.contacts_linear_layout),
R.string.contacts_preferences_no_file_found);
} else {
final User user = contactsPreferenceActivity.getUser().orElseThrow(RuntimeException::new);
OCFile[] files = new OCFile[backupToRestore.size()];
Fragment contactListFragment = BackupListFragment.newInstance(backupToRestore.toArray(files), user);
contactsPreferenceActivity.getSupportFragmentManager().
beginTransaction()
.replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG)
.addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST)
.commit();
}
}
}

View file

@ -0,0 +1,726 @@
/*
* Nextcloud Android client application
*
* @author Mario Danic
* @author TSI-mc
* Copyright (C) 2017 Mario Danic
* Copyright (C) 2017 Nextcloud GmbH.
* Copyright (C) 2023 TSI-mc
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.owncloud.android.ui.fragment.contactsbackup
import android.Manifest
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.app.DatePickerDialog.OnDateSetListener
import android.content.Context
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.DatePicker
import android.widget.Toast
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.jobs.BackgroundJobManager
import com.owncloud.android.R
import com.owncloud.android.databinding.BackupFragmentBinding
import com.owncloud.android.datamodel.ArbitraryDataProvider
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.RefreshFolderOperation
import com.owncloud.android.ui.activity.ContactsPreferenceActivity
import com.owncloud.android.ui.activity.SettingsActivity
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.MimeTypeUtil
import com.owncloud.android.utils.PermissionUtil
import com.owncloud.android.utils.PermissionUtil.checkSelfPermission
import com.owncloud.android.utils.theme.ThemeUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import third_parties.daveKoeller.AlphanumComparator
import java.util.Calendar
import java.util.Collections
import java.util.Date
import javax.inject.Inject
@Suppress("TooManyFunctions")
class BackupFragment : FileFragment(), OnDateSetListener, Injectable {
private lateinit var binding: BackupFragmentBinding
@JvmField
@Inject
var backgroundJobManager: BackgroundJobManager? = null
@JvmField
@Inject
var themeUtils: ThemeUtils? = null
@JvmField
@Inject
var arbitraryDataProvider: ArbitraryDataProvider? = null
@JvmField
@Inject
var viewThemeUtils: ViewThemeUtils? = null
private var selectedDate: Date? = null
private var calendarPickerOpen = false
private var datePickerDialog: DatePickerDialog? = null
private var contactsCheckedListener: CompoundButton.OnCheckedChangeListener? = null
private var calendarCheckedListener: CompoundButton.OnCheckedChangeListener? = null
private var user: User? = null
private var showSidebar = true
// flag to check if calendar backup should be shown and backup should be done or not
private var showCalendarBackup = true
private var isCalendarBackupEnabled: Boolean
get() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CALENDAR_BACKUP_ENABLED) } ?: false
private set(enabled) {
arbitraryDataProvider!!.storeOrUpdateKeyValue(
user!!.accountName,
PREFERENCE_CALENDAR_BACKUP_ENABLED,
enabled
)
}
private var isContactsBackupEnabled: Boolean
get() = user?.let { arbitraryDataProvider?.getBooleanValue(it, PREFERENCE_CONTACTS_BACKUP_ENABLED) } ?: false
private set(enabled) {
arbitraryDataProvider!!.storeOrUpdateKeyValue(
user!!.accountName,
PREFERENCE_CONTACTS_BACKUP_ENABLED,
enabled
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// use grey as fallback for elements where custom theming is not available
if (themeUtils?.themingEnabled(context) == true) {
requireContext().theme.applyStyle(R.style.FallbackThemingTheme, true)
}
binding = BackupFragmentBinding.inflate(inflater, container, false)
val view: View = binding.root
setHasOptionsMenu(true)
if (arguments != null) {
showSidebar = requireArguments().getBoolean(ARG_SHOW_SIDEBAR)
}
showCalendarBackup = requireContext().resources.getBoolean(R.bool.show_calendar_backup)
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
user = contactsPreferenceActivity?.user?.orElseThrow { RuntimeException() }
setupSwitches(user)
setupCheckListeners()
setBackupNowButtonVisibility()
setOnClickListeners()
contactsPreferenceActivity?.let {
displayLastBackup(it)
applyUserColorToActionBar(it)
}
setupDates(savedInstanceState)
applyUserColor()
return view
}
private fun setupSwitches(user: User?) {
user?.let {
binding.dailyBackup.isChecked = arbitraryDataProvider?.getBooleanValue(
it,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP
) ?: false
}
binding.contacts.isChecked = isContactsBackupEnabled && checkContactBackupPermission()
binding.calendar.isChecked = isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())
binding.calendar.visibility = if (showCalendarBackup) View.VISIBLE else View.GONE
}
private fun setupCheckListeners() {
binding.dailyBackup.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (checkAndAskForContactsReadPermission()) {
setAutomaticBackup(isChecked)
}
}
initContactsCheckedListener()
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener)
initCalendarCheckedListener()
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener)
}
private fun initContactsCheckedListener() {
contactsCheckedListener =
CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) {
if (checkAndAskForContactsReadPermission()) {
isContactsBackupEnabled = true
}
} else {
isContactsBackupEnabled = false
}
setBackupNowButtonVisibility()
setAutomaticBackup(binding.dailyBackup.isChecked)
}
}
private fun initCalendarCheckedListener() {
calendarCheckedListener =
CompoundButton.OnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
if (isChecked) {
if (checkAndAskForCalendarReadPermission()) {
isCalendarBackupEnabled = true
}
} else {
isCalendarBackupEnabled = false
}
setBackupNowButtonVisibility()
setAutomaticBackup(binding.dailyBackup.isChecked)
}
}
private fun setBackupNowButtonVisibility() {
binding.backupNow.visibility =
if (binding.contacts.isChecked || binding.calendar.isChecked) View.VISIBLE else View.INVISIBLE
}
private fun setOnClickListeners() {
binding.backupNow.setOnClickListener { backupNow() }
binding.contactsDatepicker.setOnClickListener { openCleanDate() }
}
private fun displayLastBackup(contactsPreferenceActivity: ContactsPreferenceActivity) {
val lastBackupTimestamp = user?.let {
arbitraryDataProvider?.getLongValue(
it,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP
)
} ?: -1L
if (lastBackupTimestamp == -1L) {
binding.lastBackupWithDate.visibility = View.GONE
} else {
binding.lastBackupWithDate.text = String.format(
getString(R.string.last_backup),
DisplayUtils.getRelativeTimestamp(contactsPreferenceActivity, lastBackupTimestamp)
)
}
}
private fun applyUserColorToActionBar(contactsPreferenceActivity: ContactsPreferenceActivity) {
val actionBar = contactsPreferenceActivity.supportActionBar
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true)
viewThemeUtils?.files?.themeActionBar(
requireContext(),
actionBar,
if (showCalendarBackup) R.string.backup_title else R.string.contact_backup_title
)
}
}
private fun setupDates(savedInstanceState: Bundle?) {
if (savedInstanceState != null && savedInstanceState.getBoolean(KEY_CALENDAR_PICKER_OPEN, false)) {
if (savedInstanceState.getInt(KEY_CALENDAR_YEAR, -1) != -1 && savedInstanceState.getInt(
KEY_CALENDAR_MONTH,
-1
) != -1 && savedInstanceState.getInt(
KEY_CALENDAR_DAY, -1
) != -1
) {
val cal = Calendar.getInstance()
cal[Calendar.YEAR] = savedInstanceState.getInt(KEY_CALENDAR_YEAR)
cal[Calendar.MONTH] = savedInstanceState.getInt(KEY_CALENDAR_MONTH)
cal[Calendar.DAY_OF_MONTH] = savedInstanceState.getInt(KEY_CALENDAR_DAY)
selectedDate = cal.time
}
calendarPickerOpen = true
}
}
private fun applyUserColor() {
viewThemeUtils?.androidx?.colorSwitchCompat(binding.contacts)
viewThemeUtils?.androidx?.colorSwitchCompat(binding.calendar)
viewThemeUtils?.androidx?.colorSwitchCompat(binding.dailyBackup)
viewThemeUtils?.material?.colorMaterialButtonPrimaryFilled(binding.backupNow)
viewThemeUtils?.material?.colorMaterialButtonPrimaryOutlined(binding.contactsDatepicker)
viewThemeUtils?.platform?.colorTextView(binding.dataToBackUpTitle)
viewThemeUtils?.platform?.colorTextView(binding.backupSettingsTitle)
}
override fun onResume() {
super.onResume()
if (calendarPickerOpen) {
if (selectedDate != null) {
openDate(selectedDate)
} else {
openDate(null)
}
}
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
if (contactsPreferenceActivity != null) {
val backupFolderPath = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
refreshBackupFolder(
backupFolderPath,
contactsPreferenceActivity.applicationContext,
contactsPreferenceActivity.storageManager
)
}
}
private fun refreshBackupFolder(
backupFolderPath: String,
context: Context,
storageManager: FileDataStorageManager
) {
val task: AsyncTask<String, Int, Boolean> =
@SuppressLint("StaticFieldLeak")
object : AsyncTask<String, Int, Boolean>() {
@Deprecated("Deprecated in Java")
override fun doInBackground(vararg path: String): Boolean {
val folder = storageManager.getFileByPath(path[0])
return if (folder != null) {
val operation = RefreshFolderOperation(
folder,
System.currentTimeMillis(),
false,
false,
storageManager,
user,
context
)
val result = operation.execute(user, context)
result.isSuccess
} else {
java.lang.Boolean.FALSE
}
}
@Deprecated("Deprecated in Java")
override fun onPostExecute(result: Boolean) {
if (result) {
val backupFolder = storageManager.getFileByPath(backupFolderPath)
val backupFiles = storageManager
.getFolderContent(backupFolder, false)
Collections.sort(backupFiles, AlphanumComparator())
if (backupFiles == null || backupFiles.isEmpty()) {
binding.contactsDatepicker.visibility = View.INVISIBLE
} else {
binding.contactsDatepicker.visibility = View.VISIBLE
}
}
}
}
task.execute(backupFolderPath)
}
@Deprecated("Deprecated in Java")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
val retval: Boolean
when (item.itemId) {
android.R.id.home -> {
if (showSidebar) {
if (contactsPreferenceActivity!!.isDrawerOpen) {
contactsPreferenceActivity.closeDrawer()
} else {
contactsPreferenceActivity.openDrawer()
}
} else if (activity != null) {
requireActivity().finish()
} else {
val settingsIntent = Intent(context, SettingsActivity::class.java)
settingsIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(settingsIntent)
}
retval = true
}
else -> retval = super.onOptionsItemSelected(item)
}
return retval
}
@Deprecated("Deprecated in Java")
@Suppress("NestedBlockDepth")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC) {
for (index in permissions.indices) {
if (Manifest.permission.READ_CONTACTS.equals(permissions[index], ignoreCase = true)) {
if (grantResults[index] >= 0) {
// if approved, exit for loop
isContactsBackupEnabled = true
break
}
// if not accepted, disable again
binding.contacts.setOnCheckedChangeListener(null)
binding.contacts.isChecked = false
binding.contacts.setOnCheckedChangeListener(contactsCheckedListener)
}
}
}
if (requestCode == PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC) {
var readGranted = false
var writeGranted = false
for (index in permissions.indices) {
if (Manifest.permission.WRITE_CALENDAR.equals(
permissions[index],
ignoreCase = true
) && grantResults[index] >= 0
) {
writeGranted = true
} else if (Manifest.permission.READ_CALENDAR.equals(
permissions[index],
ignoreCase = true
) && grantResults[index] >= 0
) {
readGranted = true
}
}
if (!readGranted || !writeGranted) {
// if not accepted, disable again
binding.calendar.setOnCheckedChangeListener(null)
binding.calendar.isChecked = false
binding.calendar.setOnCheckedChangeListener(calendarCheckedListener)
} else {
isCalendarBackupEnabled = true
}
}
setBackupNowButtonVisibility()
setAutomaticBackup(binding.dailyBackup.isChecked)
}
private fun backupNow() {
if (isContactsBackupEnabled && checkContactBackupPermission()) {
startContactsBackupJob()
}
if (showCalendarBackup && isCalendarBackupEnabled && checkCalendarBackupPermission(requireContext())) {
startCalendarBackupJob()
}
DisplayUtils.showSnackMessage(
requireView().findViewById<View>(R.id.contacts_linear_layout),
R.string.contacts_preferences_backup_scheduled
)
}
private fun startContactsBackupJob() {
val activity = activity as ContactsPreferenceActivity?
if (activity != null) {
val optionalUser = activity.user
if (optionalUser.isPresent) {
backgroundJobManager!!.startImmediateContactsBackup(optionalUser.get())
}
}
}
private fun startCalendarBackupJob() {
val activity = activity as ContactsPreferenceActivity?
if (activity != null) {
val optionalUser = activity.user
if (optionalUser.isPresent) {
backgroundJobManager!!.startImmediateCalendarBackup(optionalUser.get())
}
}
}
private fun setAutomaticBackup(enabled: Boolean) {
val activity = activity as ContactsPreferenceActivity? ?: return
val optionalUser = activity.user
if (!optionalUser.isPresent) {
return
}
val user = optionalUser.get()
if (enabled) {
if (isContactsBackupEnabled) {
Log_OC.d(TAG, "Scheduling contacts backup job")
backgroundJobManager?.schedulePeriodicContactsBackup(user)
} else {
Log_OC.d(TAG, "Cancelling contacts backup job")
backgroundJobManager?.cancelPeriodicContactsBackup(user)
}
if (isCalendarBackupEnabled) {
Log_OC.d(TAG, "Scheduling calendar backup job")
backgroundJobManager?.schedulePeriodicCalendarBackup(user)
} else {
Log_OC.d(TAG, "Cancelling calendar backup job")
backgroundJobManager?.cancelPeriodicCalendarBackup(user)
}
} else {
Log_OC.d(TAG, "Cancelling all backup jobs")
backgroundJobManager?.cancelPeriodicContactsBackup(user)
backgroundJobManager?.cancelPeriodicCalendarBackup(user)
}
arbitraryDataProvider?.storeOrUpdateKeyValue(
user.accountName,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
enabled.toString()
)
}
private fun checkAndAskForContactsReadPermission(): Boolean {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
// check permissions
return if (checkSelfPermission(contactsPreferenceActivity!!, Manifest.permission.READ_CONTACTS)) {
true
} else {
// No explanation needed, request the permission.
requestPermissions(
arrayOf(Manifest.permission.READ_CONTACTS),
PermissionUtil.PERMISSIONS_READ_CONTACTS_AUTOMATIC
)
false
}
}
private fun checkAndAskForCalendarReadPermission(): Boolean {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
// check permissions
return if (contactsPreferenceActivity?.let { checkCalendarBackupPermission(it) } == true) {
true
} else {
// No explanation needed, request the permission.
requestPermissions(
arrayOf(
Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR
),
PermissionUtil.PERMISSIONS_READ_CALENDAR_AUTOMATIC
)
false
}
}
private fun checkCalendarBackupPermission(context: Context): Boolean {
return checkSelfPermission(requireContext(), Manifest.permission.READ_CALENDAR) && checkSelfPermission(
context, Manifest.permission.WRITE_CALENDAR
)
}
private fun checkContactBackupPermission(): Boolean {
return checkSelfPermission(requireContext(), Manifest.permission.READ_CONTACTS)
}
private fun openCleanDate() {
if (checkAndAskForCalendarReadPermission() && checkAndAskForContactsReadPermission()) {
openDate(null)
}
}
private fun openDate(savedDate: Date?) {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
if (contactsPreferenceActivity == null) {
Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show()
return
}
val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
val storageManager = contactsPreferenceActivity.storageManager
val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
backupFiles.sortWith { o1: OCFile?, o2: OCFile? ->
if (o1 != null && o2 != null) {
o1.modificationTimestamp.compareTo(o2.modificationTimestamp)
} else {
-1
}
}
val cal = Calendar.getInstance()
val year: Int
val month: Int
val day: Int
if (savedDate == null) {
year = cal[Calendar.YEAR]
month = cal[Calendar.MONTH] + 1
day = cal[Calendar.DAY_OF_MONTH]
} else {
year = savedDate.year
month = savedDate.month
day = savedDate.day
}
if (backupFiles.size > 0 && backupFiles[backupFiles.size - 1] != null) {
datePickerDialog = DatePickerDialog(contactsPreferenceActivity, this, year, month, day)
datePickerDialog?.datePicker?.maxDate = backupFiles[backupFiles.size - 1]!!
.modificationTimestamp
datePickerDialog?.datePicker?.minDate = backupFiles[0]!!.modificationTimestamp
datePickerDialog?.setOnDismissListener { selectedDate = null }
datePickerDialog?.setTitle("")
datePickerDialog?.show()
viewThemeUtils?.platform?.colorTextButtons(
datePickerDialog!!.getButton(DatePickerDialog.BUTTON_NEGATIVE),
datePickerDialog!!.getButton(DatePickerDialog.BUTTON_POSITIVE)
)
// set background to transparent
datePickerDialog?.getButton(DatePickerDialog.BUTTON_NEGATIVE)?.setBackgroundColor(0x00000000)
datePickerDialog?.getButton(DatePickerDialog.BUTTON_POSITIVE)?.setBackgroundColor(0x00000000)
} else {
DisplayUtils.showSnackMessage(
requireView().findViewById<View>(R.id.contacts_linear_layout),
R.string.contacts_preferences_something_strange_happened
)
}
}
override fun onStop() {
super.onStop()
datePickerDialog?.dismiss()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
datePickerDialog?.let {
outState.putBoolean(KEY_CALENDAR_PICKER_OPEN, it.isShowing)
if (it.isShowing) {
outState.putInt(KEY_CALENDAR_DAY, it.datePicker.dayOfMonth)
outState.putInt(KEY_CALENDAR_MONTH, it.datePicker.month)
outState.putInt(KEY_CALENDAR_YEAR, it.datePicker.year)
}
}
}
@Suppress("TooGenericExceptionCaught", "NestedBlockDepth", "ComplexMethod", "LongMethod", "MagicNumber")
override fun onDateSet(view: DatePicker, year: Int, month: Int, dayOfMonth: Int) {
val contactsPreferenceActivity = activity as ContactsPreferenceActivity?
if (contactsPreferenceActivity == null) {
Toast.makeText(context, getString(R.string.error_choosing_date), Toast.LENGTH_LONG).show()
return
}
selectedDate = Date(year, month, dayOfMonth)
val contactsBackupFolderString = resources.getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR
val calendarBackupFolderString = resources.getString(R.string.calendar_backup_folder) + OCFile.PATH_SEPARATOR
val storageManager = contactsPreferenceActivity.storageManager
val contactsBackupFolder = storageManager.getFileByDecryptedRemotePath(contactsBackupFolderString)
val calendarBackupFolder = storageManager.getFileByDecryptedRemotePath(calendarBackupFolderString)
val backupFiles = storageManager.getFolderContent(contactsBackupFolder, false)
backupFiles.addAll(storageManager.getFolderContent(calendarBackupFolder, false))
// find file with modification with date and time between 00:00 and 23:59
// if more than one file exists, take oldest
val date = Calendar.getInstance()
date[year, month] = dayOfMonth
// start
date[Calendar.HOUR] = 0
date[Calendar.MINUTE] = 0
date[Calendar.SECOND] = 1
date[Calendar.MILLISECOND] = 0
date[Calendar.AM_PM] = Calendar.AM
val start = date.timeInMillis
// end
date[Calendar.HOUR] = 23
date[Calendar.MINUTE] = 59
date[Calendar.SECOND] = 59
val end = date.timeInMillis
var contactsBackupToRestore: OCFile? = null
val calendarBackupsToRestore: MutableList<OCFile> = ArrayList()
for (file in backupFiles) {
if (start < file.modificationTimestamp && end > file.modificationTimestamp) {
// contact
if (MimeTypeUtil.isVCard(file)) {
if (contactsBackupToRestore == null) {
contactsBackupToRestore = file
} else if (contactsBackupToRestore.modificationTimestamp < file.modificationTimestamp) {
contactsBackupToRestore = file
}
}
// calendars
if (showCalendarBackup && MimeTypeUtil.isCalendar(file)) {
calendarBackupsToRestore.add(file)
}
}
}
val backupToRestore: MutableList<OCFile> = ArrayList()
if (contactsBackupToRestore != null) {
backupToRestore.add(contactsBackupToRestore)
}
backupToRestore.addAll(calendarBackupsToRestore)
if (backupToRestore.isEmpty()) {
DisplayUtils.showSnackMessage(
requireView().findViewById<View>(R.id.contacts_linear_layout),
R.string.contacts_preferences_no_file_found
)
} else {
val user = contactsPreferenceActivity.user.orElseThrow { RuntimeException() }
val files: Array<OCFile?> = arrayOfNulls(backupToRestore.size)
val contactListFragment = BackupListFragment.newInstance(files, user)
contactsPreferenceActivity.supportFragmentManager.beginTransaction()
.replace(R.id.frame_container, contactListFragment, BackupListFragment.TAG)
.addToBackStack(ContactsPreferenceActivity.BACKUP_TO_LIST)
.commit()
}
}
companion object {
val TAG = BackupFragment::class.java.simpleName
private const val ARG_SHOW_SIDEBAR = "SHOW_SIDEBAR"
private const val KEY_CALENDAR_PICKER_OPEN = "IS_CALENDAR_PICKER_OPEN"
private const val KEY_CALENDAR_DAY = "CALENDAR_DAY"
private const val KEY_CALENDAR_MONTH = "CALENDAR_MONTH"
private const val KEY_CALENDAR_YEAR = "CALENDAR_YEAR"
const val PREFERENCE_CONTACTS_BACKUP_ENABLED = "PREFERENCE_CONTACTS_BACKUP_ENABLED"
const val PREFERENCE_CALENDAR_BACKUP_ENABLED = "PREFERENCE_CALENDAR_BACKUP_ENABLED"
@JvmStatic
fun create(showSidebar: Boolean): BackupFragment {
val fragment = BackupFragment()
val bundle = Bundle()
bundle.putBoolean(ARG_SHOW_SIDEBAR, showSidebar)
fragment.arguments = bundle
return fragment
}
}
}

View file

@ -17,8 +17,8 @@
You should have received a copy of the GNU Affero General Public
License along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/contacts_linear_layout"
android:layout_width="match_parent"
@ -30,14 +30,14 @@
android:layout_margin="@dimen/standard_margin"
android:orientation="vertical">
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/data_to_back_up_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/data_to_back_up"
android:textStyle="bold" />
<androidx.appcompat.widget.SwitchCompat
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -46,22 +46,21 @@
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size" />
<androidx.appcompat.widget.SwitchCompat
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/calendar"
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size"
/>
android:textSize="@dimen/two_line_primary_text_size" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/backup_settings_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -73,7 +72,7 @@
android:layout_width="match_parent"
android:layout_height="48dp">
<androidx.appcompat.widget.SwitchCompat
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/daily_backup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -89,14 +88,14 @@
android:orientation="vertical"
android:gravity="center_vertical">
<TextView
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/daily_backup"
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size" />
<TextView
<com.google.android.material.textview.MaterialTextView
android:id="@+id/last_backup_with_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -113,32 +112,27 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/standard_margin"
android:gravity="center"
android:orientation="horizontal"
android:weightSum="1.0">
android:gravity="end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/contacts_datepicker"
style="@style/OutlinedButton"
android:layout_width="0dp"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="@dimen/backup_button_width"
android:layout_height="match_parent"
android:layout_weight="0.5"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/restore_backup"
android:visibility="invisible"
app:cornerRadius="@dimen/button_corner_radius"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/backup_now"
android:layout_width="0dp"
android:layout_width="@dimen/backup_button_width"
android:layout_height="match_parent"
android:layout_marginStart="@dimen/standard_half_margin"
android:layout_weight="0.5"
android:minHeight="@dimen/minimum_size_for_touchable_area"
android:text="@string/contacts_backup_button"
android:theme="@style/Button.Primary"
app:cornerRadius="@dimen/button_corner_radius" />
android:theme="@style/Widget.Material3.Button.IconButton.Filled" />
</LinearLayout>

View file

@ -1,443 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
ownCloud Android client application
Copyright (C) 2015 ownCloud Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2,
as published by the Free Software Foundation.
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 <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/standard_padding">
<TextView
android:id="@+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_header"
android:paddingBottom="@dimen/standard_padding"
android:textStyle="bold"
android:textColor="@color/text_color"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/reason_cert_not_trusted"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingStart="@dimen/standard_half_padding"
android:paddingEnd="@dimen/zero"
android:text="@string/ssl_validator_reason_cert_not_trusted"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/reason_cert_expired"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingStart="@dimen/standard_half_padding"
android:paddingEnd="@dimen/zero"
android:text="@string/ssl_validator_reason_cert_expired"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/reason_cert_not_yet_valid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingStart="@dimen/standard_half_padding"
android:paddingEnd="@dimen/zero"
android:text="@string/ssl_validator_reason_cert_not_yet_valid"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/reason_hostname_not_verified"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingStart="@dimen/standard_half_padding"
android:paddingEnd="@dimen/zero"
android:text="@string/ssl_validator_reason_hostname_not_verified"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<ScrollView
android:id="@+id/details_scroll"
android:visibility="gone"
android:padding="@dimen/standard_half_padding"
android:layout_width="wrap_content"
android:layout_height="@dimen/scroll_view_height">
<LinearLayout
android:id="@+id/details_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
android:orientation="vertical" >
<TextView
android:id="@+id/label_subject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text="@string/ssl_validator_label_subject"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:id="@+id/label_subject_CN"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_CN"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_subject_CN"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_subject_O"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_O"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_subject_O"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_subject_OU"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_OU"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_subject_OU"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_subject_ST"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_ST"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_subject_ST"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_subject_C"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_C"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_subject_C"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_subject_L"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_L"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_subject_L"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_issuer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text="@string/ssl_validator_label_issuer"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:id="@+id/label_issuer_CN"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_CN"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_issuer_CN"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_issuer_O"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_O"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_issuer_O"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_issuer_OU"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_OU"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_issuer_OU"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_issuer_ST"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_ST"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_issuer_ST"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_issuer_C"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_C"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_issuer_C"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_issuer_L"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_L"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_issuer_L"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_validity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text="@string/ssl_validator_label_validity"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:id="@+id/label_validity_from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_validity_from"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_validity_from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_validity_to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_validity_to"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_validity_to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/label_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text="@string/ssl_validator_label_signature"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<TextView
android:id="@+id/label_signature_algorithm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssl_validator_label_signature_algorithm"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_signature_algorithm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/value_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/alternate_half_padding"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/question"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/standard_padding"
android:text="@string/ssl_validator_question"
android:textAppearance="?android:attr/textAppearanceMedium"
>
</TextView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center" >
<com.google.android.material.button.MaterialButton
android:id="@+id/cancel"
style="@style/Button.Borderless"
android:layout_width="@dimen/zero"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/common_no" />
<com.google.android.material.button.MaterialButton
android:id="@+id/details_btn"
style="@style/Button.Borderless"
android:layout_width="@dimen/zero"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="@string/ssl_validator_btn_details_see" />
<com.google.android.material.button.MaterialButton
android:id="@+id/ok"
style="@style/Button.Borderless"
android:layout_width="@dimen/zero"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/common_yes" />
</LinearLayout>
</LinearLayout>

View file

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Android client application
~
~ @author Álvaro Brey Vilas
~ Copyright (C) 2022 Álvaro Brey Vilas
~ Copyright (C) 2022 Nextcloud GmbH
~
~ 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 <https://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:paddingHorizontal="?dialogPreferredPadding">
<TextView
android:id="@+id/storage_permission_explanation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/file_management_permission_optional_text" />
<com.google.android.material.button.MaterialButton
android:layout_marginTop="@dimen/standard_padding"
android:id="@+id/btn_full_access"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/storage_permission_full_access"
android:theme="@style/Button.Primary"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintTop_toBottomOf="@id/storage_permission_explanation" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_read_only"
style="@style/OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/storage_permission_media_read_only"
app:cornerRadius="@dimen/button_corner_radius"
app:layout_constraintTop_toBottomOf="@id/btn_full_access" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -307,7 +307,7 @@
<string name="etm_transfer_remote_path">Urruneko bide-izena</string>
<string name="etm_transfer_type">Transferitu</string>
<string name="etm_transfer_type_download">Deskargatu</string>
<string name="etm_transfer_type_upload">Kargatu</string>
<string name="etm_transfer_type_upload">Igo</string>
<string name="fab_label">Gehitu edo igo</string>
<string name="failed_to_download">Huts egin du fitxategia deskarga-kudeatzailera pasatzean</string>
<string name="failed_to_print">Huts egin du fitxategia inprimatzean</string>
@ -342,7 +342,7 @@
<string name="file_management_permission">Baimenak behar dira</string>
<string name="file_management_permission_optional">Biltegiratze-baimenak</string>
<string name="file_management_permission_optional_text">%1$shobeto dabil biltegia atzitzeko baimenekin. Fitxategi guztietarako sarbide osoa aukera dezakezu, edo argazki eta bideoak \"irakurtzeko soilik\" baimena eman.</string>
<string name="file_management_permission_text">%1$s-k fitxategiak kudeatzeko baimenak behar ditu fitxategiak kargatzeko. Fitxategi guztietarako sarbide osoa aukera dezakezu, edo \"irakurtzeko soilik\" baimena argazki eta bideoentzat.</string>
<string name="file_management_permission_text">%1$s-k fitxategiak kudeatzeko baimenak behar ditu fitxategiak igotzeko. Fitxategi guztietarako sarbide osoa aukera dezakezu, edo \"irakurtzeko soilik\" baimena argazki eta bideoentzat.</string>
<string name="file_migration_checking_destination">Helburua egiaztatzen...</string>
<string name="file_migration_cleaning">Garbitzen…</string>
<string name="file_migration_dialog_title">Datu-biltegiratze karpeta eguneratzen</string>
@ -424,7 +424,7 @@
<string name="image_preview_unit_millimetres">%s mm</string>
<string name="image_preview_unit_seconds">%s s</string>
<string name="in_folder">%1$skarpetan</string>
<string name="instant_upload_existing">Existitzen diren fitxategiak ere kargatu</string>
<string name="instant_upload_existing">Existitzen diren fitxategiak ere igo</string>
<string name="instant_upload_on_charging">Igo bakarrik gailua kargatzean</string>
<string name="instant_upload_path">/InstantUpload</string>
<string name="invalid_url">URL baliogabea</string>
@ -433,7 +433,7 @@
<string name="last_backup">Azken babeskopia: %1$s</string>
<string name="link">Esteka</string>
<string name="link_name">Esteka-izena</string>
<string name="link_share_allow_upload_and_editing">Onartu kargatzea eta edizioa</string>
<string name="link_share_allow_upload_and_editing">Onartu igotzea eta edizioa</string>
<string name="link_share_editing">Edizioa</string>
<string name="link_share_file_drop">Fitxategia jaregitea (igotzeko soilik)</string>
<string name="link_share_view_only">Ikustea soilik</string>
@ -522,7 +522,7 @@
<string name="notification_channel_push_description">Erakutsi zerbitzariak bidalitako push jakinarazpenak: Aipamenak iruzkinetan, urruneko partekatze berrien harrera, administratzaileak argitaratutako argitalpenak etab.</string>
<string name="notification_channel_push_name">Push jakinarazpenak</string>
<string name="notification_channel_upload_description">Erakutsi igoeraren egoera</string>
<string name="notification_channel_upload_name_short">Kargak</string>
<string name="notification_channel_upload_name_short">Igotzeak</string>
<string name="notification_icon">Jakinarazpen ikonoa</string>
<string name="notifications_no_results_headline">Jakinarazpenik ez</string>
<string name="notifications_no_results_message">Begiratu beranduago, mesedez.</string>
@ -556,13 +556,13 @@
<string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
<string name="player_stop">gelditu</string>
<string name="player_toggle">txandakatu</string>
<string name="power_save_check_dialog_message">Energia aurrezteko kontrola desgaituz gero, fitxategiak bateria baxu dagoenean kargatu litezke!</string>
<string name="power_save_check_dialog_message">Energia aurrezteko kontrola desgaituz gero, fitxategiak bateria baxu dagoenean igo litezke!</string>
<string name="pref_behaviour_entries_delete_file">ezabatua</string>
<string name="pref_behaviour_entries_keep_file">jatorrizko karpetan mantenduko da</string>
<string name="pref_behaviour_entries_move">aplikazioaren karpetara mugitu da</string>
<string name="pref_instant_name_collision_policy_dialogTitle">Zer egin fitxategia dagoeneko existitzen bada?</string>
<string name="pref_instant_name_collision_policy_entries_always_ask">Galdetu beti</string>
<string name="pref_instant_name_collision_policy_entries_cancel">Saltatu karga</string>
<string name="pref_instant_name_collision_policy_entries_cancel">Saltatu igotzea</string>
<string name="pref_instant_name_collision_policy_entries_overwrite">Gainidatzi urruneko bertsioa</string>
<string name="pref_instant_name_collision_policy_entries_rename">Aldatu izena bertsio berriari</string>
<string name="pref_instant_name_collision_policy_title">Zer egin fitxategia dagoeneko existitzen bada?</string>
@ -610,7 +610,7 @@
<string name="prefs_show_hidden_files">Erakutsi ezkutuko fitxategiak</string>
<string name="prefs_sourcecode">Eskuratu iturburu-kodea</string>
<string name="prefs_storage_path">Datu-biltegiratze karpeta</string>
<string name="prefs_sycned_folders_summary">Kudeatu karpeten kargatze automatikoa</string>
<string name="prefs_sycned_folders_summary">Kudeatu karpeten igotze automatikoa</string>
<string name="prefs_synced_folders_local_path_title">Karpeta lokala</string>
<string name="prefs_synced_folders_remote_path_title">Urruneko karpeta</string>
<string name="prefs_theme_title">Gaia</string>
@ -799,7 +799,7 @@
<string name="sync_fail_ticker_unauthorized">Sinkronizazioak huts egin du, hasi saioa berriz</string>
<string name="sync_file_nothing_to_do_msg">Fitxategien edukiak dagoeneko sinkronizaturik</string>
<string name="sync_folder_failed_content">%1$s karpetaren sinkronizazioa ezin izan da osatu</string>
<string name="sync_foreign_files_forgotten_explanation">1.3.16 bertsiotik aurrera, gailu honetatik kargatzen diren fitxategiak %1$s karpeta lokalera kopiatzen dira, fitxategi bat hainbat konturekin sinkronizatzen denean datuak gal ez daitezen.\n\nAldaketa honen ondorioz, aplikazio honen aurreko bertsioekin kargatutako fitxategi guztiak %2$s karpetara kopiatu dira. Hala ere, kontua sinkronizatzean gertatutako akats batek eragiketa hori osatzea eragotzi du. Fitxategiak dauden bezala utz ditzakezu eta %3$s(r)ako esteka ezabatu, edo fitxategia(k) %1$s karpetara eraman eta %4$s(r)ako esteka mantendu.\n\nBehean zerrendatuta daude fitxategi lokala(k) eta estekatutako %5$s(e)ko urruneko fitxategia(k).</string>
<string name="sync_foreign_files_forgotten_explanation">1.3.16 bertsiotik aurrera, gailu honetatik kargatzen diren fitxategiak %1$s karpeta lokalera kopiatzen dira, fitxategi bat hainbat konturekin sinkronizatzen denean datuak gal ez daitezen.\n\nAldaketa honen ondorioz, aplikazio honen aurreko bertsioekin igotako fitxategi guztiak %2$s karpetara kopiatu dira. Hala ere, kontua sinkronizatzean gertatutako akats batek eragiketa hori osatzea eragotzi du. Fitxategiak dauden bezala utz ditzakezu eta %3$s(r)ako esteka ezabatu, edo fitxategia(k) %1$s karpetara eraman eta %4$s(r)ako esteka mantendu.\n\nBehean zerrendatuta daude fitxategi lokala(k) eta estekatutako %5$s(e)ko urruneko fitxategia(k).</string>
<string name="sync_foreign_files_forgotten_ticker">Fitxategi lokal batzuk ahaztu dira</string>
<string name="sync_in_progress">Fitxategiaren bertsio berriena ekartzen.</string>
<string name="sync_not_enough_space_dialog_action_choose">Aukeratu zer sinkronizatu</string>
@ -810,7 +810,7 @@
<string name="sync_string_files">Fitxategiak</string>
<string name="synced_folder_settings_button">Ezarpenak botoia</string>
<string name="synced_folders_configure_folders">Konfiguratu karpetak</string>
<string name="synced_folders_new_info">Berehalako karga erabat aldatu da. Konfiguratu berriro zure karga automatikoa menu nagusitik.\n\nGozatu karga automatiko berri eta hedatuaz. </string>
<string name="synced_folders_new_info">Berehalako igotzeak guztiz aldatu dira. Konfiguratu berriro zure igotze automatikoa menu nagusitik.\n\nGozatu igotze automatiko berri eta hedatuaz. </string>
<string name="synced_folders_no_results">Ez da multimedia karpetarik aurkitu</string>
<string name="synced_folders_preferences_folder_path"> %1$s-(r)entzat</string>
<string name="synced_folders_type">Mota</string>
@ -856,7 +856,7 @@
<string name="upload_file_dialog_filetype_snippet_text">Kode-zati testu-fitxategia(.txt)</string>
<string name="upload_file_dialog_title">Sartu igoko den fitxategi-izena eta fitxategi mota</string>
<string name="upload_files">Igo fitxategiak</string>
<string name="upload_item_action_button">Kargatu elementuaren ekintza botoia</string>
<string name="upload_item_action_button">Elementua igotzeko ekintza botoia</string>
<string name="upload_list_delete">Ezabatu</string>
<string name="upload_list_empty_headline">Ezin dira fitxategiak igo</string>
<string name="upload_list_empty_text_auto_upload">Edukiak igo edo auto-igotzea aktiba ezazu.</string>
@ -875,7 +875,7 @@
<string name="uploader_error_message_read_permission_not_granted">%1$s ez dago baimendua jasotako fitxategia irakurtzeko</string>
<string name="uploader_error_message_source_file_not_copied">Fitxategia ezin izan da aldi baterako karpetan kopiatu. Saiatu berriro bidaltzen.</string>
<string name="uploader_error_message_source_file_not_found">Kargatzeko hautatutako fitxategia ez da aurkitu. Mesedez, egiaztatu fitxategia existitzen dela.</string>
<string name="uploader_error_title_file_cannot_be_uploaded">Fitxategi hau ezin da kargatu</string>
<string name="uploader_error_title_file_cannot_be_uploaded">Fitxategi hau ezin da igo</string>
<string name="uploader_error_title_no_file_to_upload">Ez dago fitxategirik kargatzeko</string>
<string name="uploader_info_dirname">Karpetaren izena</string>
<string name="uploader_top_message">Aukeratu karga-karpeta</string>

View file

@ -30,9 +30,11 @@
<string name="activity_icon">アクティビティ</string>
<string name="add_another_public_share_link">別のリンクを作成</string>
<string name="add_new_public_share">新規公開共有リンクを追加</string>
<string name="add_new_secure_file_drop">新しいセキュアなファイルドロップを追加</string>
<string name="add_to_cloud">%1$s に追加</string>
<string name="advanced_settings">高度な設定</string>
<string name="allow_resharing">再共有を許可</string>
<string name="app_widget_description">ダッシュボードから一つのウィジェットを表示</string>
<string name="appbar_search_in">%s の中を検索</string>
<string name="associated_account_not_found">関連付けられたアカウントが見つかりません!</string>
<string name="auth_access_failed">アクセスに失敗しました: %1$s</string>
@ -85,11 +87,14 @@
<string name="calendars">カレンダー</string>
<string name="certificate_load_problem">証明書の読み込みに問題がありました。</string>
<string name="changelog_dev_version">開発バージョンの変更履歴</string>
<string name="check_back_later_or_reload">後で確認するか、再読込をしてください</string>
<string name="checkbox">チェックボックス</string>
<string name="choose_local_folder">ローカルフォルダーを選択…</string>
<string name="choose_location">場所を選択</string>
<string name="choose_remote_folder">リモートフォルダを選択…</string>
<string name="choose_template_helper_text">テンプレートを選択してファイル名を入力してください。</string>
<string name="choose_which_file">保存するファイルを選択</string>
<string name="choose_widget">ウィジェットを選択</string>
<string name="clear_notifications_failed">通知の削除に失敗</string>
<string name="clear_status_message">メッセージを消去</string>
<string name="clear_status_message_after">ステータスメッセージの有効期限</string>
@ -149,6 +154,7 @@
<string name="conflict_local_file">ローカルファイル</string>
<string name="conflict_message_description">両方のバージョンを選択した場合、ローカルファイルはファイル名に数字が追加されます。</string>
<string name="conflict_server_file">サーバーファイル</string>
<string name="contact_backup_title">連絡先のバックアップ</string>
<string name="contactlist_item_icon">連絡先リストのユーザーアイコン</string>
<string name="contactlist_no_permission">許可がありません、インポートできません。</string>
<string name="contacts">連絡先</string>
@ -193,13 +199,20 @@
<string name="did_not_check_for_dupes">重複をチェックしませんでした</string>
<string name="digest_algorithm_not_available">このスマートフォンでは、ダイジェストアルゴリズムが利用できません。</string>
<string name="direct_login_failed">ダイレクトリンクからのログインに失敗しました!</string>
<string name="direct_login_text">%1$s で %2$s へログインする</string>
<string name="disable_new_media_folder_detection_notifications">無効</string>
<string name="dismiss">閉じる</string>
<string name="dismiss_notification_description">通知を閉じる</string>
<string name="dnd">取り込み中</string>
<string name="document_scan_export_dialog_images">複数の画像</string>
<string name="document_scan_export_dialog_pdf">PDFファイル</string>
<string name="document_scan_export_dialog_title">エクスポートする種類を選択</string>
<string name="document_scan_pdf_generation_failed">PDFの生成に失敗しました</string>
<string name="document_scan_pdf_generation_in_progress">PDFを生成中...</string>
<string name="done">完了</string>
<string name="dontClear">消去しない</string>
<string name="download_cannot_create_file">ローカルファイルが作成できません</string>
<string name="download_download_invalid_local_file_name">ローカルファイルに対して無効なファイル名</string>
<string name="download_latest_dev_version">最新の開発バージョンをダウンロード</string>
<string name="downloader_download_failed_content">%1$sをダウンロードできませんでした</string>
<string name="downloader_download_failed_credentials_error">ダウンロード失敗、要 再ログイン</string>
@ -229,10 +242,14 @@
<string name="drawer_quota">%2$s 中%1$s が使われています。</string>
<string name="drawer_quota_unlimited">%1$s使用中</string>
<string name="drawer_synced_folders">自動アップロード</string>
<string name="e2e_not_yet_setup">E2E暗号化が未設定</string>
<string name="e2e_offline">インターネット接続なしには不可能です</string>
<string name="ecosystem_apps_display_more">さらに表示</string>
<string name="ecosystem_apps_display_notes">ノート</string>
<string name="ecosystem_apps_display_talk">トーク</string>
<string name="ecosystem_apps_more">もっと Nextcloud アプリを見る</string>
<string name="ecosystem_apps_notes">Nextcloud ノート</string>
<string name="ecosystem_apps_talk">Nextcloud Talk</string>
<string name="encrypted">暗号化設定</string>
<string name="end_to_end_encryption_confirm_button">暗号化を設定する</string>
<string name="end_to_end_encryption_decrypting">復号化中…</string>
@ -262,7 +279,9 @@
<string name="error_report_issue_text">問題を報告しますか? (GitHubのアカウントが必要です)</string>
<string name="error_retrieving_file">ファイルの取得中にエラーが発生しました</string>
<string name="error_retrieving_templates">テンプレートの取得中にエラーが発生しました</string>
<string name="error_showing_encryption_dialog">暗号化設定ダイアログの表示エラー</string>
<string name="error_starting_direct_camera_upload">カメラ起動エラー</string>
<string name="error_starting_doc_scan">文書スキャンの開始エラー</string>
<string name="etm_accounts">アカウント</string>
<string name="etm_background_job_name">ジョブの名前</string>
<string name="etm_background_job_progress">進捗状況</string>
@ -293,6 +312,7 @@
<string name="failed_update_ui">UIの更新に失敗しました</string>
<string name="favorite">お気に入りに追加</string>
<string name="favorite_icon">お気に入り</string>
<string name="file_already_exists">ファイル名が既に存在します</string>
<string name="file_delete">削除</string>
<string name="file_detail_activity_error">ファイルのアクティビティ取得エラー</string>
<string name="file_details_no_content">詳細のロードに失敗しました</string>
@ -358,6 +378,7 @@
<string name="filename_hint">ファイル名</string>
<string name="first_run_1_text">あなたのデータをセキュアなままコントロールしましょう</string>
<string name="first_run_2_text">セキュアなコラボレーションとファイル交換</string>
<string name="first_run_3_text">使いやすいWebメールやカレンダーや &amp; 連絡先</string>
<string name="first_run_4_text">画面共有やオンラインミーティングやウェブ会議</string>
<string name="folder_already_exists">フォルダーはすでに存在します</string>
<string name="folder_confirm_create">作成</string>
@ -383,6 +404,18 @@
<string name="hint_password">パスワード</string>
<string name="host_not_available">サーバーが利用できません</string>
<string name="host_your_own_server">自分のサーバーをホストする</string>
<string name="icon_of_dashboard_widget">ダッシュボードウィジェットのアイコン</string>
<string name="image_editor_file_edited_suffix">編集された</string>
<string name="image_editor_flip_horizontal">左右反転</string>
<string name="image_editor_flip_vertical">上下反転</string>
<string name="image_editor_rotate_ccw">反時計回りに回す</string>
<string name="image_editor_rotate_cw">時計回りに回す</string>
<string name="image_editor_unable_to_edit_image">画像の編集が不可能です</string>
<string name="image_preview_filedetails">ファイルの詳細</string>
<string name="image_preview_unit_iso">ISO %s</string>
<string name="image_preview_unit_megapixel">%s MP</string>
<string name="image_preview_unit_millimetres">%s mm</string>
<string name="image_preview_unit_seconds">%s 秒</string>
<string name="in_folder">フォルダー%1$sの中で</string>
<string name="instant_upload_existing">既存のファイルもアップロード</string>
<string name="instant_upload_on_charging">充電中のみアップロード</string>
@ -403,12 +436,14 @@
<string name="local_file_not_found_message">ローカルファイルシステムにファイルが見つかりません</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">これ以上フォルダーがありません。</string>
<string name="locate_folder">フォルダーの配置</string>
<string name="lock_expiration_info">有効期限: %1$s</string>
<string name="lock_file">ファイルをロック</string>
<string name="locked_by">%1$sによりロック</string>
<string name="locked_by_app">%1$sアプリによりロック</string>
<string name="log_send_mail_subject">%1$s アンドロイドアプリログ</string>
<string name="log_send_no_mail_app">ログを送信するためのアプリが見つかりません。メールクライアントをインストールしてください。</string>
<string name="logged_in_as">%1$sとしてログインしました</string>
<string name="login">ログイン</string>
<string name="login_url_helper_text">%1$sをWeb画面でブラウザーで開くときのURL</string>
<string name="logs_menu_delete">ログを消去</string>
@ -459,6 +494,8 @@
<string name="no_browser_available">リンクを処理するアプリがありません</string>
<string name="no_calendar_exists">カレンダーがありません</string>
<string name="no_email_app_available">メールアドレスの利用可能なアプリはありません</string>
<string name="no_items">アイテムがありません</string>
<string name="no_map_app_availble">マップを処理するアプリケーションがありません</string>
<string name="no_mutliple_accounts_allowed">利用できるアカウントは1つだけです</string>
<string name="no_pdf_app_available">PDFを処理するアプリケーションがありません</string>
<string name="no_send_app">選択されたファイルの送信で利用可能なアプリがありません</string>
@ -497,11 +534,13 @@
<string name="pass_code_removed">パスコードを削除しました</string>
<string name="pass_code_stored">パスコードを保存しました</string>
<string name="pass_code_wrong">パスコードが正しくありません</string>
<string name="pdf_password_protected">パスワード保護されたPDFファイルを開くことは出来ません。外部ビューワを使ってください</string>
<string name="pdf_zoom_tip">ズームするにはページをタップ</string>
<string name="permission_allow">許可</string>
<string name="permission_deny">拒否</string>
<string name="permission_storage_access">ファイルをダウンロードとアップロードする追加の権限が必要です。</string>
<string name="picture_set_as_no_app">画像を設定するアプリが見つかりませんでした</string>
<string name="pin_home">ホームスクリーンにピン留めする</string>
<string name="placeholder_fileSize">389 KB</string>
<string name="placeholder_filename">placeholder.txt</string>
<string name="placeholder_media_time">12:23:45</string>
@ -531,6 +570,7 @@
<string name="prefs_category_more">もっと見る</string>
<string name="prefs_daily_backup_summary">カレンダーと連絡先を毎日バックアップ</string>
<string name="prefs_daily_contact_backup_summary">連絡先のデイリーバックアップ</string>
<string name="prefs_e2e_active">End-to-end 暗号化を設定中!</string>
<string name="prefs_e2e_mnemonic">E2Eニーモニック</string>
<string name="prefs_e2e_no_device_credentials">ニーモニックを表示するには、デバイスクレデンシャルを有効にしてください。</string>
<string name="prefs_enable_media_scan_notifications">メディアのスキャン結果通知を表示する</string>
@ -540,7 +580,9 @@
<string name="prefs_imprint">インプリント</string>
<string name="prefs_instant_behaviour_dialogTitle">元のファイルの扱い…</string>
<string name="prefs_instant_behaviour_title">元のファイルになります…</string>
<string name="prefs_instant_upload_path_use_date_subfolders_summary">日付を基にしたサブフォルダーに保存</string>
<string name="prefs_instant_upload_path_use_subfolders_title">サブフォルダーを利用</string>
<string name="prefs_keys_exist">このクライアントに End-to-End 暗号化を追加</string>
<string name="prefs_license">ライセンス</string>
<string name="prefs_lock">アプリパスコード</string>
<string name="prefs_lock_device_credentials_enabled">デバイスの資格情報が有効です</string>
@ -551,6 +593,7 @@
<string name="prefs_lock_using_passcode">パスコード</string>
<string name="prefs_manage_accounts">アカウント管理</string>
<string name="prefs_recommend">友達にすすめる</string>
<string name="prefs_setup_e2e">end-to-end 暗号化を設定</string>
<string name="prefs_show_hidden_files">隠しファイルを表示</string>
<string name="prefs_sourcecode">ソースコードを入手</string>
<string name="prefs_storage_path">データ保存フォルダー</string>
@ -574,6 +617,7 @@
<string name="recommend_subject">デバイスで %1$s をお試しください</string>
<string name="recommend_text">あなたのデバイスで %1$s を使用してください。\nダウンロードはこちらです: %2$s</string>
<string name="recommend_urls">%1$s または %2$s</string>
<string name="refresh_content">コンテンツを更新</string>
<string name="reload">再読み込み</string>
<string name="remote">(リモート)</string>
<string name="remote_file_fetch_failed">ファイルが見つかりません!</string>
@ -594,6 +638,7 @@
<string name="retrieving_file">ファイルを取得中...</string>
<string name="richdocuments_failed_to_load_document">ドキュメントのロードに失敗しました。</string>
<string name="scanQR_description">QRコードを用いてログイン</string>
<string name="scan_page">ページをスキャン</string>
<string name="screenshot_01_gridView_heading">あなたのデータを保護</string>
<string name="screenshot_01_gridView_subline">自己ホスト型の生産性</string>
<string name="screenshot_02_listView_heading">閲覧と共有</string>
@ -608,6 +653,7 @@
<string name="screenshot_06_davdroid_subline">DAVx5で同期</string>
<string name="search_error">検索結果の取得中にエラーが発生しました</string>
<string name="select_all">すべて選択</string>
<string name="select_media_folder">メディア用のフォルダーの設定</string>
<string name="select_one_template">テンプレートを選択してください</string>
<string name="select_template">テンプレートを選択する</string>
<string name="send">送信</string>
@ -643,6 +689,7 @@
<string name="share_password_title">パスワード保護</string>
<string name="share_permission_can_edit">編集可能</string>
<string name="share_permission_file_drop">ファイルを転送</string>
<string name="share_permission_secure_file_drop">セキュアなファイルドロップ</string>
<string name="share_permission_view_only">閲覧のみ</string>
<string name="share_permissions">共有権限</string>
<string name="share_remote_clarification">%1$s (リモート)</string>
@ -662,6 +709,8 @@
<string name="shared_icon_shared_via_link">リンク経由で共有</string>
<string name="shared_with_you_by">%1$sと共有中</string>
<string name="sharee_add_failed">共有を追加できませんでした</string>
<string name="show_images">写真を表示</string>
<string name="show_video">動画を表示</string>
<string name="signup_with_provider">他のサービスでサインアップ</string>
<string name="single_sign_on_request_token" formatted="true">%1$s があなたのNextcloudアカウント %2$s にアクセスできるようにしますか?</string>
<string name="sort_by">ソート</string>
@ -716,9 +765,13 @@
<string name="stream_not_possible_headline">内部ストリーミングは不可能</string>
<string name="stream_not_possible_message">代わりにメディアをダウンロードするか、外部アプリを使用してください。</string>
<string name="strict_mode">ストリクトモードHTTP接続が許可されていません</string>
<string name="sub_folder_rule_day">念/月/日</string>
<string name="sub_folder_rule_month">年/月</string>
<string name="sub_folder_rule_year"></string>
<string name="subject_shared_with_you">\"%1$s\" があなたに共有されました</string>
<string name="subject_user_shared_with_you">%1$s は \"%2$s\" をあなたと共有しました</string>
<string name="subtitle_photos_only">写真のみ</string>
<string name="subtitle_videos_only">動画のみ</string>
<string name="suggest">提案</string>
<string name="sync_conflicts_in_favourites_ticker">競合が見つかりました</string>
<string name="sync_current_folder_was_removed">フォルダー %1$s はもう存在しません</string>
@ -752,6 +805,7 @@
<string name="thumbnail">サムネイル</string>
<string name="thumbnail_for_existing_file_description">既存ファイルのサムネイル</string>
<string name="thumbnail_for_new_file_desc">新規ファイルのサムネイル</string>
<string name="timeout_richDocuments">ローディング中 期待した時間より長くかかっています</string>
<string name="today">今日</string>
<string name="trashbin_activity_title">ゴミ箱</string>
<string name="trashbin_empty_headline">削除されたファイルはありません</string>
@ -866,6 +920,7 @@
<string name="whats_new_skip">スキップ</string>
<string name="whats_new_title">%1$sの新機能</string>
<string name="whats_your_status">あなたのステータスは?</string>
<string name="widgets_not_available">ウィジェットは %1$s 25 以上でのみ利用可能です </string>
<string name="widgets_not_available_title">利用できません</string>
<string name="write_email">メールを送信</string>
<string name="wrong_storage_path">データ保存フォルダーが存在しません!</string>
@ -888,6 +943,18 @@
<plurals name="found_n_duplicates">
<item quantity="other">重複する応募が%d件見つかりました。</item>
</plurals>
<plurals name="export_successful">
<item quantity="other">エクスポートされた %d ファイル</item>
</plurals>
<plurals name="export_failed">
<item quantity="other">%dファイルのエクスポートに失敗しました</item>
</plurals>
<plurals name="export_partially_failed">
<item quantity="other">エラーのため、%d ファイルはエクスポートされ、他のファイルはスキップされました</item>
</plurals>
<plurals name="export_start">
<item quantity="other">%dファイルがエクスポートされます。詳細は通知を確認してください。</item>
</plurals>
<plurals name="file_list__footer__folder">
<item quantity="other">%1$d フォルダ</item>
</plurals>

View file

@ -420,7 +420,7 @@
<string name="last_backup">마지막 백업: %1$s</string>
<string name="link">링크</string>
<string name="link_name">링크 이름</string>
<string name="link_share_allow_upload_and_editing">업로드 및 편집 허용</string>
<string name="link_share_allow_upload_and_editing">업로드와 수정 허용</string>
<string name="link_share_editing">글 수정</string>
<string name="link_share_file_drop">파일 보관소 (업로드만 허용)</string>
<string name="link_share_view_only">읽기 전용</string>

View file

@ -125,7 +125,7 @@
<string name="common_switch_to_account">Mudar para a conta</string>
<string name="common_yes">Sim</string>
<string name="community_beta_headline">Teste a versão de desenvolvimento</string>
<string name="community_beta_text">Inclui todas as futuras e mais recentes funcionalidades. Falhas/erros podem ocorrer, se e quando tal acontecer, por favor, reporte as suas descobertas.</string>
<string name="community_beta_text">Isto inclui todas as futuras e mais recentes funcionalidades. Podem ocorrer falhas/erros, se e quando tal acontecer, por favor, reporte as suas descobertas.</string>
<string name="community_contribute_forum_forum">fórum</string>
<string name="community_contribute_forum_text">Ajude outros em</string>
<string name="community_contribute_github_text">Reveja, emende e escreva o código, consulte %1$s para detalhes.</string>
@ -137,12 +137,12 @@
<string name="community_rc_fdroid">Obter versão candidata a lançamento a partir da aplicação F-Droid</string>
<string name="community_rc_play_store">Obter versão candidata a lançamento a partir da loja Google Play</string>
<string name="community_release_candidate_headline">Versão candidata a lançamento</string>
<string name="community_release_candidate_text">A versão candidata a lançamento (RC) é um snapshot da próxima versão e é espectável que seja estável. Através do teste da sua configuração individual pode ajudar-nos a assegurá-lo. Para testar, inscreva-se na loja Play ou verifique a secção \"Version\" do F-Droid.</string>
<string name="community_release_candidate_text">A versão candidata de lançamento (RC) é um \'\'snapshot\'\' da próxima versão e é expetável que seja estável. Se testar a sua configuração individual pode ajudar-nos a assegurá-lo. Para testar, inscreva-se na loja Play ou consulte a secção \"Version\" do F-Droid.</string>
<string name="community_testing_bug_text">Encontrou um erro? Ocorrências estranhas?</string>
<string name="community_testing_headline">Ajude-nos, testando</string>
<string name="community_testing_report_text">Reportar um problema no Github</string>
<string name="configure_new_media_folder_detection_notifications">Configure</string>
<string name="confirm_removal">Remove localmente a criptografia</string>
<string name="confirm_removal">Remove encriptação local</string>
<string name="confirmation_remove_file_alert">Deseja realmente apagar %1$s?</string>
<string name="confirmation_remove_files_alert">Quer realmente remover os itens seleccionados?</string>
<string name="confirmation_remove_folder_alert">Deseja realmente apagar %1$s e o seu conteúdo?</string>
@ -152,7 +152,7 @@
<string name="conflict_file_headline">Ficheiro em conflito %1$s</string>
<string name="conflict_local_file">Ficheiro local</string>
<string name="conflict_message_description">Se selecionou ambas as versões, o ficheiro local terá um número acrescentado ao seu nome.</string>
<string name="conflict_server_file">Arquivo do servidor</string>
<string name="conflict_server_file">Ficheiro do servidor</string>
<string name="contact_backup_title">Cópia de segurança dos contactos</string>
<string name="contactlist_item_icon">Ícone de utilizador de lista de contactos</string>
<string name="contactlist_no_permission">Sem permissão concedida, nada para importar.</string>
@ -190,7 +190,7 @@
<string name="delete_entries">Apagar entradas</string>
<string name="delete_link">Eliminar hiperligação</string>
<string name="deselect_all">Desseleccionado tudo</string>
<string name="destination_filename">Nome do arquivo de destino</string>
<string name="destination_filename">Nome do ficheiro de destino</string>
<string name="dev_version_new_version_available">Nova versão disponível</string>
<string name="dev_version_no_information_available">Nenhuma informação disponível.</string>
<string name="dev_version_no_new_version_available">Nenhuma nova versão disponível</string>
@ -202,12 +202,16 @@
<string name="disable_new_media_folder_detection_notifications">Desativar</string>
<string name="dismiss">Dispensar</string>
<string name="dismiss_notification_description">Dispensar notificação</string>
<string name="displays_mnemonic">Apresenta a sua frase-chave de 12 palavras</string>
<string name="displays_mnemonic">Exibe a sua frase-chave de 12 palavras</string>
<string name="dnd">Não incomodar</string>
<string name="document_scan_export_dialog_images">Múltiplas imagens</string>
<string name="document_scan_export_dialog_pdf">Ficheiro PDF</string>
<string name="document_scan_export_dialog_title">Escolher tipo de exportação</string>
<string name="document_scan_pdf_generation_failed">Geração de PDF falhou</string>
<string name="document_scan_pdf_generation_in_progress">A gerar PDF...</string>
<string name="done">Concluído</string>
<string name="dontClear">Não limpar</string>
<string name="download_cannot_create_file">Não é possível criar ficheiro local</string>
<string name="download_cannot_create_file">Não é possível criar o ficheiro local</string>
<string name="download_latest_dev_version">Transferir a última versão de desenvolvimento</string>
<string name="downloader_download_failed_content">Não foi possível transferir %1$s</string>
<string name="downloader_download_failed_credentials_error">A transferência falhou, inicie novamente a sessão</string>
@ -238,7 +242,11 @@
<string name="drawer_quota_unlimited">%1$s utilizado</string>
<string name="drawer_synced_folders">Carregamento automático</string>
<string name="ecosystem_apps_display_more">Mais</string>
<string name="ecosystem_apps_display_notes">Notas</string>
<string name="ecosystem_apps_display_talk">Falar</string>
<string name="ecosystem_apps_more">Mais Aplicações Nextcloud</string>
<string name="ecosystem_apps_notes">Nextcloud Notes</string>
<string name="ecosystem_apps_talk">Nextcloud Talk</string>
<string name="encrypted">Definir como encriptado</string>
<string name="end_to_end_encryption_confirm_button">Definir encriptação</string>
<string name="end_to_end_encryption_decrypting">Decryption…</string>
@ -255,15 +263,19 @@
<string name="end_to_end_encryption_title">Definir encriptação</string>
<string name="end_to_end_encryption_unsuccessful">Não foi possível guardar as chaves, por favor, tente novamente.</string>
<string name="end_to_end_encryption_wrong_password">Erro ao encriptar. Palavra-passe errada?</string>
<string name="enter_destination_filename">Inserir nome do ficheiro de destino</string>
<string name="enter_filename">Por favor, introduza um nome para o ficheiro</string>
<string name="error__upload__local_file_not_copied">Não foi possível copiar %1$s para a pasta local %2$s</string>
<string name="error_cant_bind_to_operations_service">Erro crítico: impossível concluir a operação</string>
<string name="error_choosing_date">Erro ao escolher a data</string>
<string name="error_comment_file">Erro ao comentar ficheiro</string>
<string name="error_crash_title">%1$s crachou</string>
<string name="error_creating_file_from_template">Erro ao criar o ficheiro com o modelo</string>
<string name="error_file_actions">Erro ao mostrar as ações do ficheiro</string>
<string name="error_report_issue_action">Relatório</string>
<string name="error_retrieving_file">Erro ao obter o ficheiro</string>
<string name="error_retrieving_templates">Erro ao obter modelos</string>
<string name="error_showing_encryption_dialog">Erro ao mostrar a janela da configuração de encriptação!</string>
<string name="error_starting_direct_camera_upload">Erro ao iniciar câmara</string>
<string name="etm_accounts">Contas</string>
<string name="etm_background_job_name">Nome do Trabalho</string>
@ -289,11 +301,12 @@
<string name="etm_transfer_type_upload">Enviar</string>
<string name="fab_label">Adicionar ou enviar</string>
<string name="failed_to_download">Falhou a passagem do ficheiro ao gestor de transferências</string>
<string name="failed_to_print">Falhou a impressão do ficheiro</string>
<string name="failed_to_print">Não foi possível imprimir o ficheiro</string>
<string name="failed_to_start_editor">Falha ao iniciar o editor</string>
<string name="failed_update_ui">Falha ao atualizar a IU</string>
<string name="favorite">Adicionar aos favoritos</string>
<string name="favorite_icon">Favorito</string>
<string name="file_already_exists">O nome do ficheiro já existe</string>
<string name="file_delete">Apagar</string>
<string name="file_detail_activity_error">Erro ao obter as atividades para o ficheiro</string>
<string name="file_details_no_content">Falha ao carregar detalhes</string>
@ -307,10 +320,10 @@
<string name="file_list_empty_headline_search">Sem resultados nesta pasta</string>
<string name="file_list_empty_headline_server_search">Sem resultados</string>
<string name="file_list_empty_moving">Não está aqui nada. Pode adicionar uma pasta.</string>
<string name="file_list_empty_on_device">Ficheiros e pastas que foram descarregados aparecem aqui.</string>
<string name="file_list_empty_on_device">Os ficheiros e as pastas que foram transferidos serão mostrados aqui.</string>
<string name="file_list_empty_recently_modified">Não foi encontrado nenhum arquivo modificado nos últimos 7 dias</string>
<string name="file_list_empty_search">Poderá estar numa pasta diferente?</string>
<string name="file_list_empty_shared">Ficheiros e pastas que partilhou aparecem aqui.</string>
<string name="file_list_empty_shared">Os ficheiros e as pastas que partilhou serão mostrados aqui.</string>
<string name="file_list_empty_shared_headline">Ainda sem partilhas</string>
<string name="file_list_empty_unified_search_no_results">Nenhum resultado encontrado para a sua consulta</string>
<string name="file_list_folder">pasta</string>
@ -318,6 +331,7 @@
<string name="file_list_no_app_for_file_type">Nenhuma aplicação para usar este tipo de ficheiro.</string>
<string name="file_list_seconds_ago">há segundos</string>
<string name="file_management_permission">Permissões necessárias</string>
<string name="file_management_permission_optional">Permissões de armazenamento</string>
<string name="file_management_permission_text">%1$s precisa de permissões de gestão de ficheiros para carregar ficheiros. Pode escolher acesso total a todos os ficheiros ou acesso só de leitura a fotografias e vídeos.</string>
<string name="file_migration_checking_destination">Verificando destino…</string>
<string name="file_migration_cleaning">Limpando…</string>
@ -383,11 +397,11 @@
<string name="host_not_available">Servidor não disponível</string>
<string name="host_your_own_server">Hospede o seu próprio servidor</string>
<string name="icon_for_empty_list">Ícone para lista vazia</string>
<string name="icon_of_dashboard_widget">Ícone do widget do painel de controlo</string>
<string name="icon_of_widget_entry">Ícone do widget da entrada </string>
<string name="icon_of_dashboard_widget">Ícone do \'\'widget\'\' do painel de controlo</string>
<string name="icon_of_widget_entry">Ícone do \'\'widget\'\' da entrada </string>
<string name="image_editor_file_edited_suffix">editado</string>
<string name="image_editor_flip_horizontal">Virar horizontalmente</string>
<string name="image_editor_flip_vertical">Virar verticalmente</string>
<string name="image_editor_flip_horizontal">Inverter horizontalmente</string>
<string name="image_editor_flip_vertical">Inverter verticalmente</string>
<string name="image_editor_rotate_ccw">Rodar no sentido anti-horário</string>
<string name="image_editor_rotate_cw">Rodar no sentido horário</string>
<string name="image_editor_unable_to_edit_image">Não é possível editar a imagem.</string>
@ -404,7 +418,7 @@
<string name="instant_upload_path">/Envio Instantâneo </string>
<string name="invalid_url">URL inválido</string>
<string name="invisible">Invisível </string>
<string name="label_empty">Nome não pode ficar em branco</string>
<string name="label_empty">O nome não pode ficar em branco</string>
<string name="last_backup">Última cópia de segurança: %1$s</string>
<string name="link">Hiperligação</string>
<string name="link_name">Nome da hiperligação</string>
@ -418,9 +432,14 @@
<string name="local_file_not_found_message">Ficheiros não encontrados no sistema de ficheiros local</string>
<string name="local_folder_friendly_path">%1$s/%2$s</string>
<string name="local_folder_list_empty">Não existem mais pastas.</string>
<string name="locate_folder">Localizar pasta</string>
<string name="lock_expiration_info">Expira: %1$s</string>
<string name="lock_file">Bloquear ficheiro </string>
<string name="locked_by">Bloqueado por %1$s</string>
<string name="locked_by_app">Bloqueado por aplicação %1$s</string>
<string name="log_send_mail_subject">%1$s registos de aplicação Android</string>
<string name="log_send_no_mail_app">Não foi encontrada nenhuma aplicação para o envio de registos. Por favor, Instale um cliente de correio eletrónico.</string>
<string name="logged_in_as">Autenticado como %1$s</string>
<string name="login">Iniciar Sessão</string>
<string name="login_url_helper_text">A hiperligação para a sua interface da Web %1$s quando a abre no seu navegador.</string>
<string name="logs_menu_delete">Eliminar registos</string>
@ -467,10 +486,14 @@
<string name="new_media_folder_videos">vídeo</string>
<string name="new_notification">Nova notificação</string>
<string name="new_version_was_created">Uma nova versão foi criada</string>
<string name="no_actions">Sem ações para este utilizador</string>
<string name="no_browser_available">Nenhuma aplicação disponível para lidar com links</string>
<string name="no_calendar_exists">O calendário não existe</string>
<string name="no_items">Sem itens</string>
<string name="no_mutliple_accounts_allowed">Só é permitida uma conta.</string>
<string name="no_pdf_app_available">Nenhuma aplicação disponível para lidar com PDF</string>
<string name="no_send_app">Nenhuma aplicação disponível para enviar os ficheiros selecionados</string>
<string name="no_share_permission_selected">Por favor, selecione pelo menos uma permissão para partilhar.</string>
<string name="note_confirm">Enviar</string>
<string name="note_could_not_sent">Não foi possível enviar a nota</string>
<string name="note_icon_hint">Ícone de nota</string>
@ -509,6 +532,8 @@
<string name="permission_deny">Negar</string>
<string name="permission_storage_access">Permissões adicionais são necessárias para enviar e transferir ficheiros.</string>
<string name="picture_set_as_no_app">Nenhuma aplicação encontrada para definir a imagem</string>
<string name="pin_home">Afixar no ecrã Início</string>
<string name="pin_shortcut_label">Abrir %1$s</string>
<string name="placeholder_fileSize">389 KB</string>
<string name="placeholder_filename">placeholder.txt</string>
<string name="placeholder_media_time">12:23:45</string>
@ -548,7 +573,7 @@
<string name="prefs_imprint">Informação</string>
<string name="prefs_instant_behaviour_dialogTitle">O ficheiro original será…</string>
<string name="prefs_instant_behaviour_title">O ficheiro original será…</string>
<string name="prefs_instant_upload_path_use_date_subfolders_summary">Armazenar em subpastas com base na data</string>
<string name="prefs_instant_upload_path_use_date_subfolders_summary">Guardar nas subpastas com base na data</string>
<string name="prefs_instant_upload_path_use_subfolders_title">Usar Subpastas</string>
<string name="prefs_instant_upload_subfolder_rule_title">Opções de subpasta</string>
<string name="prefs_license">Licença</string>
@ -561,6 +586,7 @@
<string name="prefs_lock_using_passcode">Código</string>
<string name="prefs_manage_accounts">Gerir contas</string>
<string name="prefs_recommend">Recomendar a um amigo</string>
<string name="prefs_remove_e2e">Remover encriptação localmente</string>
<string name="prefs_setup_e2e">Configurar a encriptação ponta-a-ponta</string>
<string name="prefs_show_ecosystem_apps">Mostrar alternador de aplicações</string>
<string name="prefs_show_ecosystem_apps_summary">Sugestões de aplicações Nextcloud no cabeçalho da navegação</string>
@ -639,6 +665,7 @@
<string name="set_status_message">Defina a mensagem de estado</string>
<string name="setup_e2e">Durante a configuração da encriptação ponta-a-ponta, receberá uma mnemónica aleatória de 12 palavras, de que necessitará para abrir os seus ficheiros noutros dispositivos. Ela só será guardada neste dispositivo e pode ser mostrada novamente neste ecrã. Por favor, anote-a num local seguro!</string>
<string name="share">Partilhar</string>
<string name="share_copy_link">Hiperligação de Partilhar e Copiar</string>
<string name="share_dialog_title">Partilha</string>
<string name="share_expiration_date_format">%1$s</string>
<string name="share_expiration_date_label">Expira %1$s</string>
@ -679,6 +706,8 @@
<string name="shared_icon_shared_via_link">Partilhado via hiperligação</string>
<string name="shared_with_you_by">Partilhado consigo por %1$s</string>
<string name="sharee_add_failed">Adição de destinatário da partilha falhou</string>
<string name="show_images">Mostrar fotografias</string>
<string name="show_video">Mostrar vídeos</string>
<string name="signup_with_provider">Registar com fornecedor</string>
<string name="single_sign_on_request_token" formatted="true">Permitir que %1$s aceda à sua conta Nextcloud %2$s?</string>
<string name="sort_by">Ordenar por</string>
@ -731,9 +760,13 @@
<string name="stream">Transmita com…</string>
<string name="stream_not_possible_headline">Transmissão interna não é possível</string>
<string name="stream_not_possible_message">Em vez disso, por favor, transfira mediateca ou utilize uma aplicação externa.</string>
<string name="sub_folder_rule_day">Ano/Mês/Dia</string>
<string name="sub_folder_rule_month">Ano/Mês</string>
<string name="sub_folder_rule_year">Ano</string>
<string name="subject_shared_with_you">\"%1$s\" foi partilhado consigo</string>
<string name="subject_user_shared_with_you">%1$s partilhou \"%2$s\" consigo</string>
<string name="subtitle_photos_videos">Fotos &amp; videos</string>
<string name="subtitle_photos_only">Apenas fotografias</string>
<string name="subtitle_photos_videos">Fotografias e vídeos</string>
<string name="subtitle_videos_only">Apenas vídeos</string>
<string name="suggest">Sugerir </string>
<string name="sync_conflicts_in_favourites_ticker">Encontrados conflitos</string>
@ -784,12 +817,12 @@ Aproveite o novo e melhorado envio automático.</string>
<string name="unset_encrypted">Encriptação não definida</string>
<string name="unset_favorite">Remover dos favoritos</string>
<string name="unshare_link_file_error">Ocorreu um erro enquanto tentava remover a partilha deste ficheiro ou pasta. </string>
<string name="unshare_link_file_no_exist">Impossível eliminar a partilha. Verifique se o ficheiro existe.</string>
<string name="unshare_link_file_no_exist">Não é possível remover a partilha. Por favor, verifique se o ficheiro existe.</string>
<string name="unshare_link_forbidden_permissions">para cancelar a partilha deste ficheiro</string>
<string name="unsharing_failed">Cancelamento da partilha falhou</string>
<string name="untrusted_domain">Acesso através de domínio não confiável. Por favor, verifique a documentação para mais informações.</string>
<string name="update_link_file_error">Erro ao tentar modificar a partilha.</string>
<string name="update_link_file_no_exist">Impossível modificar. Verifique se o ficheiro existe.</string>
<string name="update_link_file_error">Ocorreu um erro enquanto tentava atualizar a partilha.</string>
<string name="update_link_file_no_exist">Não é possível atualizar. Por favor, verifique se o ficheiro existe.</string>
<string name="update_link_forbidden_permissions">para atualizar esta partilha</string>
<string name="updating_share_failed">Actualização da partilha falhou</string>
<string name="upload_cannot_create_file">Não é possível criar ficheiro local</string>
@ -828,7 +861,7 @@ Aproveite o novo e melhorado envio automático.</string>
<string name="uploader_top_message">Escolha uma pasta para envio</string>
<string name="uploader_upload_failed_content_single">Não foi possível enviar %1$s</string>
<string name="uploader_upload_failed_credentials_error">O envio falhou, inicie novamente a sessão</string>
<string name="uploader_upload_failed_sync_conflict_error">Exite um conflito com o ficheiro enviado</string>
<string name="uploader_upload_failed_sync_conflict_error">Conflito de envio do ficheiro</string>
<string name="uploader_upload_failed_sync_conflict_error_content">Escolha qual das versões manter %1$s</string>
<string name="uploader_upload_failed_ticker">Envio falhou</string>
<string name="uploader_upload_files_behaviour">Opção de envio:</string>
@ -900,6 +933,26 @@ Aproveite o novo e melhorado envio automático.</string>
<item quantity="many">Falha ao copiar %1$d ficheiros da pasta %2$s para</item>
<item quantity="other">Falha ao copiar %1$d ficheiros da pasta %2$s para</item>
</plurals>
<plurals name="processed_n_entries">
<item quantity="one">Processada %d entrada.</item>
<item quantity="many">Processadas %dentradas.</item>
<item quantity="other">Processadas %d entradas.</item>
</plurals>
<plurals name="found_n_duplicates">
<item quantity="one">Encontrada %d entrada duplicada.</item>
<item quantity="many">Encontradas %d entradas duplicadas.</item>
<item quantity="other">Encontradas %d entradas duplicadas.</item>
</plurals>
<plurals name="export_successful">
<item quantity="one">Exportado %d ficheiro</item>
<item quantity="many">Exportados %d ficheiros</item>
<item quantity="other">Exportados %d ficheiros</item>
</plurals>
<plurals name="export_failed">
<item quantity="one">Falhou exportação de %d ficheiro</item>
<item quantity="many">Falhou exportação de %d ficheiros</item>
<item quantity="other">Falhou exportação de %d ficheiros</item>
</plurals>
<plurals name="file_list__footer__folder">
<item quantity="one">%1$d pasta</item>
<item quantity="many">%1$d pastas</item>

View file

@ -653,6 +653,7 @@
<string name="share_no_password_title">Đặt mật khẩu</string>
<string name="share_password_title">Mật khẩu được bảo vệ</string>
<string name="share_permission_can_edit">Có thể chỉnh sửa</string>
<string name="share_permission_file_drop">Thả file</string>
<string name="share_permission_view_only">Chỉ xem</string>
<string name="share_permissions">Quyền kho</string>
<string name="share_remote_clarification">%1$s (từ xa)</string>

View file

@ -79,7 +79,6 @@
<dimen name="search_users_groups_layout_list_view_margin">20dp</dimen>
<dimen name="share_file_layout_text_size">12sp</dimen>
<dimen name="ssl_untrusted_cert_layout_padding">20dp</dimen>
<dimen name="scroll_view_height">180dp</dimen>
<dimen name="upload_list_item_frame_layout_width">60dp</dimen>
<dimen name="upload_list_item_text_size">12sp</dimen>
<dimen name="uploader_list_item_layout_image_margin">12dp</dimen>
@ -134,6 +133,8 @@
<dimen name="bottom_sheet_text_size">16sp</dimen>
<dimen name="permission_dialog_text_size">18sp</dimen>
<dimen name="button_corner_radius">24dp</dimen>
<dimen name="backup_button_width">160dp</dimen>
<integer name="media_grid_width">4</integer>
<dimen name="account_action_button_margin">12dp</dimen>
<dimen name="account_action_button_height">50dp</dimen>

View file

@ -799,8 +799,6 @@
<string name="permission_deny">Deny</string>
<string name="permission_allow">Allow</string>
<string name="share_send_note">Note to recipient</string>
<string name="note_confirm">Send</string>
<string name="send_note">Send note to recipient</string>
<string name="note_could_not_sent">Could not send note</string>
<string name="hint_note">Note</string>
<string name="no_browser_available">No app available to handle links</string>

View file

@ -128,7 +128,7 @@ else
# check for NotNull
if [[ $(grep org.jetbrains.annotations app/src/main/* -irl | wc -l) -gt 0 ]] ; then
notNull="org.jetbrains.annotations.NotNull is used. Please use androidx.annotation.NonNull instead.<br><br>"
notNull="org.jetbrains.annotations.* is used. Please use androidx.annotation.* instead.<br><br>"
fi
bodyContent="$codacyResult $lintResult $spotbugsResult $checkLibraryMessage $lintMessage $spotbugsMessage $gplayLimitation $notNull"

View file

@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
<span class="mdl-layout-title">Lint Report: 80 warnings</span>
<span class="mdl-layout-title">Lint Report: 79 warnings</span>