Merge branch 'master' into 'master'

fix import and export on Android 10

See merge request tslocum/meditationassistant!6
This commit is contained in:
Trevor Slocum 2021-03-27 19:18:46 +00:00
commit c0ced6e825
18 changed files with 37 additions and 627 deletions

View File

@ -9,7 +9,8 @@
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/appNameShort"
android:theme="@style/MeditationDarkTheme">
android:theme="@style/MeditationDarkTheme"
android:requestLegacyExternalStorage="true">
<!-- Services -->
<activity

View File

@ -1,503 +0,0 @@
package sh.ftp.rocketninelabs.meditationassistant;
/*
* Copyright (C) 2007-2008 OpenIntents.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.MimeTypeMap;
import java.io.File;
import java.io.FileFilter;
import java.text.DecimalFormat;
import java.util.Comparator;
/**
* @author Peli
* @author paulburke (ipaulpro)
* @version 2013-12-11
*/
public class FileUtils {
public static final String MIME_TYPE_AUDIO = "audio/*";
public static final String MIME_TYPE_TEXT = "text/*";
public static final String MIME_TYPE_IMAGE = "image/*";
public static final String MIME_TYPE_VIDEO = "video/*";
public static final String MIME_TYPE_APP = "application/*";
public static final String HIDDEN_PREFIX = ".";
/**
* TAG for log messages.
*/
static final String TAG = "FileUtils";
private static final boolean DEBUG = false; // Set to true to enable logging
/**
* File and folder comparator.
*
* @author paulburke
*/
public static Comparator<File> sComparator = new Comparator<File>() {
@Override
public int compare(File f1, File f2) {
// Sort alphabetically by lower case, which is much cleaner
return f1.getName().toLowerCase().compareTo(
f2.getName().toLowerCase());
}
};
/**
* File (not directories) filter.
*
* @author paulburke
*/
public static FileFilter sFileFilter = new FileFilter() {
@Override
public boolean accept(File file) {
final String fileName = file.getName();
// Return files only (not directories) and skip hidden files
return file.isFile() && !fileName.startsWith(HIDDEN_PREFIX);
}
};
/**
* Folder (directories) filter.
*
* @author paulburke
*/
public static FileFilter sDirFilter = new FileFilter() {
@Override
public boolean accept(File file) {
final String fileName = file.getName();
// Return directories only and skip hidden directories
return file.isDirectory() && !fileName.startsWith(HIDDEN_PREFIX);
}
};
private FileUtils() {
} //private constructor to enforce Singleton pattern
/**
* Gets the extension of a file name, like ".png" or ".jpg".
*
* @param uri
* @return Extension including the dot("."); "" if there is no extension;
* null if uri was null.
*/
public static String getExtension(String uri) {
if (uri == null) {
return null;
}
int dot = uri.lastIndexOf(".");
if (dot >= 0) {
return uri.substring(dot);
} else {
// No extension.
return "";
}
}
/**
* @return Whether the URI is a local one.
*/
public static boolean isLocal(String url) {
return (url != null && !url.startsWith("http://") && !url.startsWith("https://"));
}
/**
* @return True if Uri is a MediaStore Uri.
* @author paulburke
*/
public static boolean isMediaUri(Uri uri) {
return "media".equalsIgnoreCase(uri.getAuthority());
}
/**
* Convert File into Uri.
*
* @param file
* @return uri
*/
public static Uri getUri(File file) {
if (file != null) {
return Uri.fromFile(file);
}
return null;
}
/**
* Returns the path only (without file name).
*
* @param file
* @return
*/
public static File getPathWithoutFilename(File file) {
if (file != null) {
if (file.isDirectory()) {
// no file to be split off. Return everything
return file;
} else {
String filename = file.getName();
String filepath = file.getAbsolutePath();
// Construct path without file name.
String pathwithoutname = filepath.substring(0,
filepath.length() - filename.length());
if (pathwithoutname.endsWith("/")) {
pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1);
}
return new File(pathwithoutname);
}
}
return null;
}
/**
* @return The MIME type for the given file.
*/
public static String getMimeType(File file) {
String extension = getExtension(file.getName());
if (extension.length() > 0)
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
return "application/octet-stream";
}
/**
* @return The MIME type for the give Uri.
*/
public static String getMimeType(Context context, Uri uri) {
File file = new File(getPath(context, uri));
return getMimeType(file);
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
* @author paulburke
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
* @author paulburke
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
* @author paulburke
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
* @author paulburke
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
if (DEBUG)
DatabaseUtils.dumpCursor(cursor);
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.<br>
* <br>
* Callers should check whether the path is local before assuming it
* represents a local file.
*
* @param context The context.
* @param uri The Uri to query.
* @author paulburke
* @see #isLocal(String)
* @see #getFile(Context, Uri)
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {
if (DEBUG)
Log.d(TAG + " File -",
"Authority: " + uri.getAuthority() +
", Fragment: " + uri.getFragment() +
", Port: " + uri.getPort() +
", Query: " + uri.getQuery() +
", Scheme: " + uri.getScheme() +
", Host: " + uri.getHost() +
", Segments: " + uri.getPathSegments().toString()
);
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* Convert Uri into File, if possible.
*
* @return file A local file that the Uri was pointing to, or null if the
* Uri is unsupported or pointed to a remote resource.
* @author paulburke
* @see #getPath(Context, Uri)
*/
public static File getFile(Context context, Uri uri) {
if (uri != null) {
String path = getPath(context, uri);
if (isLocal(path)) {
return new File(path);
}
}
return null;
}
/**
* Get the file size in a human-readable string.
*
* @param size
* @return
* @author paulburke
*/
public static String getReadableFileSize(int size) {
final int BYTES_IN_KILOBYTES = 1024;
final DecimalFormat dec = new DecimalFormat("###.#");
final String KILOBYTES = " KB";
final String MEGABYTES = " MB";
final String GIGABYTES = " GB";
float fileSize = 0;
String suffix = KILOBYTES;
if (size > BYTES_IN_KILOBYTES) {
fileSize = size / BYTES_IN_KILOBYTES;
if (fileSize > BYTES_IN_KILOBYTES) {
fileSize = fileSize / BYTES_IN_KILOBYTES;
if (fileSize > BYTES_IN_KILOBYTES) {
fileSize = fileSize / BYTES_IN_KILOBYTES;
suffix = GIGABYTES;
} else {
suffix = MEGABYTES;
}
}
}
return dec.format(fileSize) + suffix;
}
/**
* Attempt to retrieve the thumbnail of given File from the MediaStore. This
* should not be called on the UI thread.
*
* @param context
* @param file
* @return
* @author paulburke
*/
public static Bitmap getThumbnail(Context context, File file) {
return getThumbnail(context, getUri(file), getMimeType(file));
}
/**
* Attempt to retrieve the thumbnail of given Uri from the MediaStore. This
* should not be called on the UI thread.
*
* @param context
* @param uri
* @return
* @author paulburke
*/
public static Bitmap getThumbnail(Context context, Uri uri) {
return getThumbnail(context, uri, getMimeType(context, uri));
}
/**
* Attempt to retrieve the thumbnail of given Uri from the MediaStore. This
* should not be called on the UI thread.
*
* @param context
* @param uri
* @param mimeType
* @return
* @author paulburke
*/
public static Bitmap getThumbnail(Context context, Uri uri, String mimeType) {
if (DEBUG)
Log.d(TAG, "Attempting to get thumbnail");
if (!isMediaUri(uri)) {
Log.e(TAG, "You can only retrieve thumbnails for images and videos.");
return null;
}
Bitmap bm = null;
if (uri != null) {
final ContentResolver resolver = context.getContentResolver();
Cursor cursor = null;
try {
cursor = resolver.query(uri, null, null, null, null);
if (cursor.moveToFirst()) {
final int id = cursor.getInt(0);
if (DEBUG)
Log.d(TAG, "Got thumb ID: " + id);
if (mimeType.contains("video")) {
bm = MediaStore.Video.Thumbnails.getThumbnail(
resolver,
id,
MediaStore.Video.Thumbnails.MINI_KIND,
null);
} else if (mimeType.contains(FileUtils.MIME_TYPE_IMAGE)) {
bm = MediaStore.Images.Thumbnails.getThumbnail(
resolver,
id,
MediaStore.Images.Thumbnails.MINI_KIND,
null);
}
}
} catch (Exception e) {
if (DEBUG)
Log.e(TAG, "getThumbnail", e);
} finally {
if (cursor != null)
cursor.close();
}
}
return bm;
}
/**
* Get the Intent for selecting content to be used in an Intent Chooser.
*
* @return The intent for opening a file with Intent.createChooser()
* @author paulburke
*/
public static Intent createGetContentIntent() {
// Implicitly allow the user to select a particular kind of data
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
// The MIME data type filter
intent.setType("*/*");
// Only return URIs that can be opened with ContentResolver
intent.addCategory(Intent.CATEGORY_OPENABLE);
return intent;
}
}

View File

@ -62,12 +62,11 @@ import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.CookieHandler;
import java.net.CookieManager;
@ -2059,20 +2058,16 @@ public class MeditationAssistant extends Application {
}
}
public String filePickerResult(Intent intent) {
public Uri filePickerResult(Intent intent) {
if (Build.VERSION.SDK_INT >= 23) {
File file = FileUtils.getFile(this, intent.getData());
if (file != null && file.exists()) {
return file.toString();
}
return intent.getData();
} else {
List<Uri> files = Utils.getSelectedFilesFromResult(intent);
for (Uri uri : files) {
return uri.toString();
return uri;
}
}
return "";
return null;
}
public void askToDonate(Activity activity) {
@ -2120,12 +2115,12 @@ public class MeditationAssistant extends Application {
.show();
}
public void importSessions(Activity activity, File file, boolean useLocalTimeZone) {
public void importSessions(Activity activity, Uri uri, boolean useLocalTimeZone) {
final Pattern lengthPattern = Pattern.compile("^[0-9]{1,2}:[0-9][0-9]$");
FileReader inputfile;
InputStreamReader inputfile;
try {
inputfile = new FileReader(file);
inputfile = new InputStreamReader(getContentResolver().openInputStream(uri));
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
@ -2277,7 +2272,7 @@ public class MeditationAssistant extends Application {
sessionsImportedDialog.show();
}
private void closeCSVReader(CSVReader reader, FileReader file) {
private void closeCSVReader(CSVReader reader, InputStreamReader file) {
try {
reader.close();
} catch (IOException e) {
@ -2291,10 +2286,11 @@ public class MeditationAssistant extends Application {
}
}
public void exportSessions(Activity activity, File file) {
public void exportSessions(Activity activity, Uri uri) {
try {
FileWriter outputfile = new FileWriter(file);
CSVWriter writer = new CSVWriter(outputfile);
FileOutputStream outputStream = (FileOutputStream) getContentResolver().openOutputStream(uri);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
CSVWriter writer = new CSVWriter(outputStreamWriter);
List<String[]> data = new ArrayList<>();
ArrayList<SessionSQL> sessions = db.getAllSessions();
@ -2310,48 +2306,11 @@ public class MeditationAssistant extends Application {
writer.writeAll(data);
writer.close();
} catch (IOException e) {
longToast(getString(R.string.sessionExportFailed) + ": " + e.toString() + " - " + file);
Log.e("MeditationAssistant", "Error exporting sessions to " + file, e);
longToast(getString(R.string.sessionExportFailed) + ": " + e.toString() + " - " + uri.toString());
Log.e("MeditationAssistant", "Error exporting sessions to " + uri.toString(), e);
return;
}
View exp = LayoutInflater.from(activity).inflate(
R.layout.sessions_exported,
activity.findViewById(R.id.sessionsExported_root));
TextView txtSessionsExportedPath = exp.findViewById(R.id.txtSessionsExportedPath);
txtSessionsExportedPath.setText(file.getPath());
AlertDialog sessionsExportedDialog = new AlertDialog.Builder(activity)
.setIcon(getResources().getDrawable(getTheme().obtainStyledAttributes(getMATheme(true), new int[]{R.attr.actionIconSignOut}).getResourceId(0, 0)))
.setTitle(getString(R.string.exportSessions))
.setView(exp)
.setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface,
int which) {
Uri selectedUri = Uri.parse(file.getParent());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(selectedUri, "resource/folder");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (intent.resolveActivityInfo(getPackageManager(), 0) != null) {
startActivity(intent);
} else {
longToast(getString(R.string.installFileManager));
}
dialogInterface.dismiss();
}
})
.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface,
int which) {
dialogInterface.dismiss();
}
})
.create();
sessionsExportedDialog.show();
longToast(getString(R.string.sessionExportWasSuccessful));
}
public void updateWidgets() {

View File

@ -41,7 +41,6 @@ import androidx.core.content.ContextCompat;
import net.margaritov.preference.colorpicker.ColorPickerPreference;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@ -539,10 +538,12 @@ public class SettingsActivity extends PreferenceActivity {
}
if (requestCode == FILEPICKER_IMPORT_SESSIONS_UTC || requestCode == FILEPICKER_IMPORT_SESSIONS_LOCAL) {
getMeditationAssistant().importSessions(SettingsActivity.this, new File(getMeditationAssistant().filePickerResult(intent)), requestCode == FILEPICKER_IMPORT_SESSIONS_LOCAL);
Uri uri = getMeditationAssistant().filePickerResult(intent);
getMeditationAssistant().importSessions(SettingsActivity.this, uri, requestCode == FILEPICKER_IMPORT_SESSIONS_LOCAL);
return;
} else if (requestCode == FILEPICKER_EXPORT_SESSIONS) {
getMeditationAssistant().exportSessions(SettingsActivity.this, new File(getMeditationAssistant().filePickerResult(intent)));
Uri uri = getMeditationAssistant().filePickerResult(intent);
getMeditationAssistant().exportSessions(SettingsActivity.this, uri);
return;
}
@ -564,7 +565,7 @@ public class SettingsActivity extends PreferenceActivity {
return;
}
getMeditationAssistant().getPrefs().edit().putString(pref, getMeditationAssistant().filePickerResult(intent)).apply();
getMeditationAssistant().getPrefs().edit().putString(pref, getMeditationAssistant().filePickerResult(intent).toString()).apply();
if (requestCode == FILEPICKER_SELECT_SOUND_START) {
initialSoundChangeStart = true;

View File

@ -1,48 +0,0 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/sessionsExported_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:clickable="false"
android:includeFontPadding="true"
android:paddingBottom="4dp"
android:text="@string/showExportedSessions"
android:textAppearance="?android:attr/textAppearanceMedium"
tools:ignore="RtlSymmetry" />
<TextView
android:id="@+id/txtSessionsExportedPath"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:clickable="false"
android:includeFontPadding="true"
android:text=""
android:textAppearance="?android:attr/textAppearanceSmall"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</RelativeLayout>
</RelativeLayout>

View File

@ -110,7 +110,7 @@
<string name="edit">Bearbeiten</string>
<string name="exportSessions">Sitzungen exportieren</string>
<string name="sessionExportFailed">Exportieren der Sitzungen nicht möglich</string>
<string name="showExportedSessions">Sitzungen exportiert. Möchten Sie das Verzeichnis öffnen, in das der Export gespeichert wurde?</string>
<string name="sessionExportWasSuccessful">Sitzungen exportiert.</string>
<string name="sessionStartedAt">Begonnen um %s</string>
<string name="started">Begonnen</string>
<string name="completed">Abgeschlossen</string>

View File

@ -107,7 +107,7 @@
<string name="add">Πρόσθεσε</string>
<string name="exportSessions">Εξήγαγε συνεδρίες</string>
<string name="sessionExportFailed">Αδυνατόν να εξαγάγει συνεδρίες</string>
<string name="showExportedSessions">Συνεδρίες εξήχθησαν. Θα θέλατε να περιηγηθείτε στον φάκελο που περιέχει την εξαγωγή;</string>
<string name="sessionExportWasSuccessful">Συνεδρίες εξήχθησαν.</string>
<string name="sessionStartedAt">Ξεκίνησε στις %s</string>
<string name="started">Ξεκίνησε</string>
<string name="completed">Ολοκληρώθηκε</string>

View File

@ -105,7 +105,7 @@
<string name="add">Añadir</string>
<string name="exportSessions">Exportar sesiones </string>
<string name="sessionExportFailed">No se puede exportar sesiones </string>
<string name="showExportedSessions">Sesiones exportadas. ¿Quiere navegar a la carpeta conteniendo la exportación?</string>
<string name="sessionExportWasSuccessful">Sesiones exportadas.</string>
<string name="sessionStartedAt">Iniciada a las %s</string>
<string name="started">Iniciada</string>
<string name="completed">Completada</string>

View File

@ -300,7 +300,7 @@
<string name="editSessionMeditating">Incapaz de editar una sesión mientras se medita</string>
<string name="invalidLength">Duración inválida</string>
<string name="length">Duración</string>
<string name="showExportedSessions">Sesiones exportadas. Desearía usted elegir la observar la carpeta que contiene lo exportado\?</string>
<string name="sessionExportWasSuccessful">Sesiones exportadas.</string>
<string name="sessionExportFailed">Incapaz de exportar sesiones</string>
<string name="exportSessions">Exportar sesiones</string>
<string name="edit">Editar</string>

View File

@ -107,7 +107,7 @@
<string name="add">Ajouter</string>
<string name="exportSessions">Exporter les séances</string>
<string name="sessionExportFailed">Impossible d\'exporter les séances</string>
<string name="showExportedSessions">Les séances ont été exportées. Voulez-vous ouvrir le dossier contenant le fichier exporté \?</string>
<string name="sessionExportWasSuccessful">Les séances ont été exportées.</string>
<string name="sessionStartedAt">Démarré à %s</string>
<string name="started">Démarrée</string>
<string name="completed">Terminée</string>

View File

@ -98,7 +98,7 @@
<string name="add">Aggiungi</string>
<string name="exportSessions">Esporta sessione</string>
<string name="sessionExportFailed">Impossibile esportare le sessione</string>
<string name="showExportedSessions">Sessioni esportate. Vuoi visitare la cartella con i file esportati?</string>
<string name="sessionExportWasSuccessfull">Sessioni esportate.</string>
<string name="sessionStartedAt">Iniziata alle %s</string>
<string name="started">Iniziata</string>
<string name="completed">Completata</string>

View File

@ -110,7 +110,7 @@
<string name="edit">編集</string>
<string name="exportSessions">セッションをエクスポート</string>
<string name="sessionExportFailed">セッションをエクスポートできません</string>
<string name="showExportedSessions">セッションをエクスポートしました。エクスポートしたファイルを含むフォルダーを表示しますか?</string>
<string name="sessionExportWasSuccessful">セッションをエクスポートしました。</string>
<string name="sessionStartedAt">開始 %s</string>
<string name="started">開始しました</string>
<string name="completed">完了しました</string>

View File

@ -114,7 +114,7 @@
<string name="edit">Rediger</string>
<string name="exportSessions">Eksporter økter</string>
<string name="sessionExportFailed">Kunne ikke eksportere økter</string>
<string name="showExportedSessions">Økter eksportert. Ønsker du å utforske mappen inneholdende eksporten\?</string>
<string name="sessionExportWasSuccessful">Økter eksportert.</string>
<string name="sessionStartedAt">Startet klokken %s</string>
<string name="started">Startet</string>
<string name="completed">Fullført</string>

View File

@ -110,7 +110,7 @@
<string name="edit">Edytuj</string>
<string name="exportSessions">Eksportuj sesje</string>
<string name="sessionExportFailed">Nie można eksportować sesji</string>
<string name="showExportedSessions">Sesje wyeksportowane. Chciał(a)byś przejrzeć folder je zawierający?</string>
<string name="sessionExportWasSuccessful">Sesje wyeksportowane.</string>
<string name="sessionStartedAt">Rozpoczęto o %s</string>
<string name="started">Rozpoczęto</string>
<string name="completed">Ukończono</string>

View File

@ -116,7 +116,7 @@ Segure para escolher valor predefinido</string>
<string name="edit">Editar</string>
<string name="exportSessions">Exportar sessões</string>
<string name="sessionExportFailed">Não foi possível exportar as sessões</string>
<string name="showExportedSessions">Sessões exportadas. Quer localizar a pasta contendo os arquivos?</string>
<string name="sessionExportWasSuccessful">Sessões exportadas.</string>
<string name="sessionStartedAt">Iniciada em %s</string>
<string name="started">Iniciada</string>
<string name="completed">Completo</string>

View File

@ -167,7 +167,7 @@
<string name="edit">Editar</string>
<string name="exportSessions">Exportar sessões</string>
<string name="sessionExportFailed">Não foi possível exportar as sessões</string>
<string name="showExportedSessions">Sessões exportadas. Quer localizar a pasta contendo os exportados\?</string>
<string name="sessionExportWasSuccessful">Sessões exportadas.</string>
<string name="length">Duração</string>
<string name="invalidLength">Duração inválida</string>
<string name="editSessionMeditating">Impossível editar uma sessão enquanto medita</string>

View File

@ -236,7 +236,7 @@
<string name="edit">Изменить</string>
<string name="exportSessions">Экспорт сеансов</string>
<string name="sessionExportFailed">Невозможно экспортировать сеансы</string>
<string name="showExportedSessions">Сеансы экспортированы. Вы хотите открыть папку с ними\?</string>
<string name="sessionExportWasSuccessful">Сеансы экспортированы.</string>
<string name="length">Продолжительность</string>
<string name="invalidLength">Неверная продолжительность</string>
<string name="editSessionMeditating">Невозможно изменить сеанс во время медитации</string>

View File

@ -132,7 +132,7 @@
<string name="edit">Edit</string>
<string name="exportSessions">Export sessions</string>
<string name="sessionExportFailed">Unable to export sessions</string>
<string name="showExportedSessions">Sessions exported. Would you like to browse the folder containing the export?</string>
<string name="sessionExportWasSuccessful">Sessions exported.</string>
<string name="sessionStartedAt">Started at %s</string>
<string name="started">Started</string>
<string name="completed">Completed</string>