mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Refactor and write tests for IO tasks
This commit is contained in:
BIN
app/src/androidTest/assets/icon.png
Normal file
BIN
app/src/androidTest/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -52,17 +52,13 @@ public class ImportTest
|
||||
{
|
||||
private File baseDir;
|
||||
private Context context;
|
||||
private Context targetContext;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
HabitFixtures.purgeHabits();
|
||||
|
||||
context = InstrumentationRegistry.getInstrumentation().getContext();
|
||||
targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
baseDir = DatabaseHelper.getFilesDir(targetContext, "Backups");
|
||||
baseDir = DatabaseHelper.getFilesDir("Backups");
|
||||
if(baseDir == null) fail("baseDir should not be null");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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.
|
||||
*
|
||||
* Loop Habit Tracker 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 org.isoron.uhabits.unit.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
import org.isoron.uhabits.tasks.ExportCSVTask;
|
||||
import org.isoron.uhabits.unit.models.HabitFixtures;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ExportCSVTaskTest
|
||||
{
|
||||
@Test
|
||||
public void exportCSV() throws InterruptedException
|
||||
{
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
HabitFixtures.createNonDailyHabit();
|
||||
List<Habit> habits = Habit.getAll(true);
|
||||
ProgressBar bar = new ProgressBar(context);
|
||||
|
||||
ExportCSVTask task = new ExportCSVTask(habits, bar);
|
||||
task.setListener(new ExportCSVTask.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onExportCSVFinished(String archiveFilename)
|
||||
{
|
||||
assertThat(archiveFilename, is(not(nullValue())));
|
||||
|
||||
File f = new File(archiveFilename);
|
||||
assertTrue(f.exists());
|
||||
assertTrue(f.canRead());
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
task.execute();
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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.
|
||||
*
|
||||
* Loop Habit Tracker 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 org.isoron.uhabits.unit.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.tasks.ExportDBTask;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.IsNot.not;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ExportDBTaskTest
|
||||
{
|
||||
@Test
|
||||
public void exportCSV() throws InterruptedException
|
||||
{
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
ProgressBar bar = new ProgressBar(context);
|
||||
ExportDBTask task = new ExportDBTask(bar);
|
||||
task.setListener(new ExportDBTask.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onExportDBFinished(String filename)
|
||||
{
|
||||
assertThat(filename, is(not(nullValue())));
|
||||
|
||||
File f = new File(filename);
|
||||
assertTrue(f.exists());
|
||||
assertTrue(f.canRead());
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
task.execute();
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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.
|
||||
*
|
||||
* Loop Habit Tracker 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 org.isoron.uhabits.unit.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.tasks.ImportDataTask;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@SmallTest
|
||||
public class ImportDataTaskTest
|
||||
{
|
||||
private Context context;
|
||||
private File baseDir;
|
||||
|
||||
@Before
|
||||
public void setup()
|
||||
{
|
||||
context = InstrumentationRegistry.getContext();
|
||||
|
||||
baseDir = DatabaseHelper.getFilesDir("Backups");
|
||||
if(baseDir == null) fail("baseDir should not be null");
|
||||
}
|
||||
|
||||
private void copyAssetToFile(String assetPath, File dst) throws IOException
|
||||
{
|
||||
InputStream in = context.getAssets().open(assetPath);
|
||||
DatabaseHelper.copy(in, dst);
|
||||
}
|
||||
|
||||
private void assertTaskResult(final int expectedResult, String assetFilename)
|
||||
throws IOException, InterruptedException
|
||||
{
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
ImportDataTask task = createTask(assetFilename);
|
||||
|
||||
task.setListener(new ImportDataTask.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onImportFinished(int result)
|
||||
{
|
||||
assertThat(result, equalTo(expectedResult));
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
task.execute();
|
||||
latch.await(30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ImportDataTask createTask(String assetFilename) throws IOException
|
||||
{
|
||||
ProgressBar bar = new ProgressBar(context);
|
||||
File file = new File(String.format("%s/%s", baseDir.getPath(), assetFilename));
|
||||
copyAssetToFile(assetFilename, file);
|
||||
|
||||
return new ImportDataTask(file, bar);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importInvalidData() throws Throwable
|
||||
{
|
||||
assertTaskResult(ImportDataTask.NOT_RECOGNIZED, "icon.png");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void importValidData() throws Throwable
|
||||
{
|
||||
assertTaskResult(ImportDataTask.SUCCESS, "loop.db");
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
|
||||
void onFileSelected(File file);
|
||||
}
|
||||
|
||||
private OnFileSelectedListener fileListener;
|
||||
private OnFileSelectedListener listener;
|
||||
|
||||
public FilePickerDialog(Activity activity, File initialDirectory)
|
||||
{
|
||||
@@ -81,7 +81,7 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fileListener != null) fileListener.onFileSelected(file);
|
||||
if (listener != null) listener.onFileSelected(file);
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
@@ -91,9 +91,9 @@ public class FilePickerDialog implements AdapterView.OnItemClickListener
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public void setFileListener(OnFileSelectedListener fileListener)
|
||||
public void setListener(OnFileSelectedListener listener)
|
||||
{
|
||||
this.fileListener = fileListener;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void navigateTo(File path)
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
@@ -73,7 +74,8 @@ import java.util.List;
|
||||
public class ListHabitsFragment extends Fragment
|
||||
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
|
||||
OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener,
|
||||
HabitSelectionCallback.Listener, ImportDataTask.Listener
|
||||
HabitSelectionCallback.Listener, ImportDataTask.Listener, ExportCSVTask.Listener,
|
||||
ExportDBTask.Listener
|
||||
{
|
||||
long lastLongClick = 0;
|
||||
private boolean isShortToggleEnabled;
|
||||
@@ -437,7 +439,7 @@ public class ListHabitsFragment extends Fragment
|
||||
if(dir == null) return;
|
||||
|
||||
FilePickerDialog picker = new FilePickerDialog(activity, dir);
|
||||
picker.setFileListener(new FilePickerDialog.OnFileSelectedListener()
|
||||
picker.setListener(new FilePickerDialog.OnFileSelectedListener()
|
||||
{
|
||||
@Override
|
||||
public void onFileSelected(File file)
|
||||
@@ -447,6 +449,7 @@ public class ListHabitsFragment extends Fragment
|
||||
task.execute();
|
||||
}
|
||||
});
|
||||
|
||||
picker.show();
|
||||
}
|
||||
|
||||
@@ -472,11 +475,49 @@ public class ListHabitsFragment extends Fragment
|
||||
|
||||
public void exportAllHabits()
|
||||
{
|
||||
new ExportCSVTask(activity, Habit.getAll(true), progressBar).execute();
|
||||
ExportCSVTask task = new ExportCSVTask(Habit.getAll(true), progressBar);
|
||||
task.setListener(this);
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportCSVFinished(@Nullable String archiveFilename)
|
||||
{
|
||||
if(archiveFilename != null)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("application/zip");
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename)));
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.showToast(R.string.could_not_export);
|
||||
}
|
||||
}
|
||||
|
||||
public void exportDB()
|
||||
{
|
||||
new ExportDBTask(activity, progressBar).execute();
|
||||
ExportDBTask task = new ExportDBTask(progressBar);
|
||||
task.setListener(this);
|
||||
task.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExportDBFinished(@Nullable String filename)
|
||||
{
|
||||
if(filename != null)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("application/octet-stream");
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename)));
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.showToast(R.string.could_not_export);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +124,11 @@ public class DatabaseHelper
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File getFilesDir(Context context, String prefix)
|
||||
public static File getFilesDir(String prefix)
|
||||
{
|
||||
Context context = HabitsApplication.getContext();
|
||||
if(context == null) return null;
|
||||
|
||||
File chosenDir = null;
|
||||
File externalFilesDirs[] = ContextCompat.getExternalFilesDirs(context, null);
|
||||
if(externalFilesDirs == null) return null;
|
||||
|
||||
@@ -19,14 +19,11 @@
|
||||
|
||||
package org.isoron.uhabits.tasks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.ReplayableActivity;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
import org.isoron.uhabits.io.HabitsCSVExporter;
|
||||
import org.isoron.uhabits.models.Habit;
|
||||
@@ -37,17 +34,25 @@ import java.util.List;
|
||||
|
||||
public class ExportCSVTask extends AsyncTask<Void, Void, Void>
|
||||
{
|
||||
private final ReplayableActivity activity;
|
||||
public interface Listener
|
||||
{
|
||||
void onExportCSVFinished(@Nullable String archiveFilename);
|
||||
}
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private final List<Habit> selectedHabits;
|
||||
String archiveFilename;
|
||||
private String archiveFilename;
|
||||
private ExportCSVTask.Listener listener;
|
||||
|
||||
public ExportCSVTask(ReplayableActivity activity, List<Habit> selectedHabits,
|
||||
ProgressBar progressBar)
|
||||
public ExportCSVTask(List<Habit> selectedHabits, ProgressBar progressBar)
|
||||
{
|
||||
this.selectedHabits = selectedHabits;
|
||||
this.progressBar = progressBar;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public void setListener(Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,19 +68,8 @@ public class ExportCSVTask extends AsyncTask<Void, Void, Void>
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if(archiveFilename != null)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("application/zip");
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename)));
|
||||
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.showToast(R.string.could_not_export);
|
||||
}
|
||||
if(listener != null)
|
||||
listener.onExportCSVFinished(archiveFilename);
|
||||
|
||||
if(progressBar != null)
|
||||
progressBar.setVisibility(View.GONE);
|
||||
@@ -86,7 +80,7 @@ public class ExportCSVTask extends AsyncTask<Void, Void, Void>
|
||||
{
|
||||
try
|
||||
{
|
||||
File dir = DatabaseHelper.getFilesDir(activity, "CSV");
|
||||
File dir = DatabaseHelper.getFilesDir("CSV");
|
||||
if(dir == null) return null;
|
||||
|
||||
HabitsCSVExporter exporter = new HabitsCSVExporter(selectedHabits, dir);
|
||||
|
||||
@@ -19,14 +19,11 @@
|
||||
|
||||
package org.isoron.uhabits.tasks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.isoron.uhabits.R;
|
||||
import org.isoron.uhabits.ReplayableActivity;
|
||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||
|
||||
import java.io.File;
|
||||
@@ -34,14 +31,23 @@ import java.io.IOException;
|
||||
|
||||
public class ExportDBTask extends AsyncTask<Void, Void, Void>
|
||||
{
|
||||
private final ReplayableActivity activity;
|
||||
public interface Listener
|
||||
{
|
||||
void onExportDBFinished(@Nullable String filename);
|
||||
}
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private String filename;
|
||||
private Listener listener;
|
||||
|
||||
public ExportDBTask(ReplayableActivity activity, ProgressBar progressBar)
|
||||
public ExportDBTask(ProgressBar progressBar)
|
||||
{
|
||||
this.progressBar = progressBar;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public void setListener(Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,19 +63,8 @@ public class ExportDBTask extends AsyncTask<Void, Void, Void>
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid)
|
||||
{
|
||||
if(filename != null)
|
||||
{
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Intent.ACTION_SEND);
|
||||
intent.setType("application/octet-stream");
|
||||
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename)));
|
||||
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.showToast(R.string.could_not_export);
|
||||
}
|
||||
if(listener != null)
|
||||
listener.onExportDBFinished(filename);
|
||||
|
||||
if(progressBar != null)
|
||||
progressBar.setVisibility(View.GONE);
|
||||
@@ -82,7 +77,7 @@ public class ExportDBTask extends AsyncTask<Void, Void, Void>
|
||||
|
||||
try
|
||||
{
|
||||
File dir = DatabaseHelper.getFilesDir(activity, "Backups");
|
||||
File dir = DatabaseHelper.getFilesDir("Backups");
|
||||
if(dir == null) return null;
|
||||
|
||||
filename = DatabaseHelper.saveDatabaseCopy(dir);
|
||||
|
||||
@@ -139,13 +139,13 @@
|
||||
<string name="custom_frequency">Custom …</string>
|
||||
<string name="help">Help & FAQ</string>
|
||||
<string name="could_not_export">Failed to export data.</string>
|
||||
<string name="could_not_import">Failed to import habits from file.</string>
|
||||
<string name="file_not_recognized">File type not recognized.</string>
|
||||
<string name="could_not_import">Failed to import data.</string>
|
||||
<string name="file_not_recognized">File not recognized.</string>
|
||||
<string name="habits_imported">Habits imported successfully.</string>
|
||||
<string name="import_data_summary">Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information.</string>
|
||||
<string name="import_data">Import data</string>
|
||||
<string name="export_as_csv_summary">Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc, but cannot be imported back.</string>
|
||||
<string name="export_full_backup">Export full backup</string>
|
||||
<string name="export_full_backup_summary">Generates a file that contains all your data, and that can be imported back.</string>
|
||||
<string name="full_backup_success">Full backup successfully exported.</string>
|
||||
<string name="import_data">Import data</string>
|
||||
<string name="export_full_backup">Export full backup</string>
|
||||
<string name="import_data_summary">Supports full backups exported by this app, as well as files generated by Tickmate, HabitBull or Rewire. See FAQ for more information.</string>
|
||||
<string name="export_as_csv_summary">Generates files that can be opened by spreadsheet software such as Microsoft Excel or OpenOffice Calc. This file cannot be imported back.</string>
|
||||
<string name="export_full_backup_summary">Generates a file that contains all your data. This file can be imported back.</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user