Export all habits as CSV from the settings menu

Closes #28
pull/77/merge
Alinson S. Xavier 10 years ago
parent 2d675ed9b0
commit e6b7b8b590

@ -31,7 +31,6 @@ import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
@ -43,7 +42,6 @@ import android.view.MenuItem;
import org.isoron.helpers.DateHelper; import org.isoron.helpers.DateHelper;
import org.isoron.helpers.DialogHelper; import org.isoron.helpers.DialogHelper;
import org.isoron.helpers.ReplayableActivity; import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.dialogs.FilePickerDialog;
import org.isoron.uhabits.fragments.ListHabitsFragment; import org.isoron.uhabits.fragments.ListHabitsFragment;
import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.helpers.ReminderHelper;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
@ -53,8 +51,6 @@ import org.isoron.uhabits.widgets.HistoryWidgetProvider;
import org.isoron.uhabits.widgets.ScoreWidgetProvider; import org.isoron.uhabits.widgets.ScoreWidgetProvider;
import org.isoron.uhabits.widgets.StreakWidgetProvider; import org.isoron.uhabits.widgets.StreakWidgetProvider;
import java.io.File;
public class MainActivity extends ReplayableActivity public class MainActivity extends ReplayableActivity
implements ListHabitsFragment.OnHabitClickListener implements ListHabitsFragment.OnHabitClickListener
{ {
@ -65,6 +61,9 @@ public class MainActivity extends ReplayableActivity
public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH"; public static final String ACTION_REFRESH = "org.isoron.uhabits.ACTION_REFRESH";
public static final int RESULT_IMPORT_DATA = 1;
public static final int RESULT_EXPORT_ALL_AS_CSV = 2;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
{ {
@ -129,16 +128,10 @@ public class MainActivity extends ReplayableActivity
{ {
switch (item.getItemId()) switch (item.getItemId())
{ {
case R.id.action_import:
{
onActionImportClicked();
return true;
}
case R.id.action_settings: case R.id.action_settings:
{ {
Intent intent = new Intent(this, SettingsActivity.class); Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent); startActivityForResult(intent, 0);
return true; return true;
} }
@ -154,6 +147,21 @@ public class MainActivity extends ReplayableActivity
} }
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (resultCode)
{
case RESULT_IMPORT_DATA:
onActionImportClicked();
break;
case RESULT_EXPORT_ALL_AS_CSV:
listHabitsFragment.exportAllHabits();
break;
}
}
private void onActionImportClicked() private void onActionImportClicked()
{ {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=

@ -21,13 +21,9 @@ package org.isoron.uhabits.dialogs;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import com.android.colorpicker.ColorPickerDialog; import com.android.colorpicker.ColorPickerDialog;
@ -42,11 +38,10 @@ import org.isoron.uhabits.commands.ChangeHabitColorCommand;
import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.DeleteHabitsCommand;
import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.commands.UnarchiveHabitsCommand;
import org.isoron.uhabits.fragments.EditHabitFragment; import org.isoron.uhabits.fragments.EditHabitFragment;
import org.isoron.uhabits.io.HabitsExporter; import org.isoron.uhabits.fragments.ExportHabitsTask;
import org.isoron.uhabits.loaders.HabitListLoader; import org.isoron.uhabits.loaders.HabitListLoader;
import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Habit;
import java.io.File;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -224,50 +219,6 @@ public class HabitSelectionCallback implements ActionMode.Callback
private void onExportHabitsClick(final LinkedList<Habit> selectedHabits) private void onExportHabitsClick(final LinkedList<Habit> selectedHabits)
{ {
new AsyncTask<Void, Void, Void>() new ExportHabitsTask(activity, selectedHabits, progressBar).execute();
{
String archiveFilename;
@Override
protected void onPreExecute()
{
if(progressBar != null)
{
progressBar.setIndeterminate(true);
progressBar.setVisibility(View.VISIBLE);
}
}
@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(progressBar != null)
progressBar.setVisibility(View.GONE);
}
@Override
protected Void doInBackground(Void... params)
{
String dirName = String.format("%s/export/", activity.getExternalCacheDir());
HabitsExporter exporter = new HabitsExporter(selectedHabits, dirName);
archiveFilename = exporter.writeArchive();
return null;
}
}.execute();
} }
} }

@ -0,0 +1,72 @@
package org.isoron.uhabits.fragments;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.view.View;
import android.widget.ProgressBar;
import org.isoron.helpers.ReplayableActivity;
import org.isoron.uhabits.R;
import org.isoron.uhabits.io.HabitsExporter;
import org.isoron.uhabits.models.Habit;
import java.io.File;
import java.util.List;
public class ExportHabitsTask extends AsyncTask<Void, Void, Void>
{
private final ReplayableActivity activity;
private ProgressBar progressBar;
private final List<Habit> selectedHabits;
String archiveFilename;
public ExportHabitsTask(ReplayableActivity activity, List<Habit> selectedHabits,
ProgressBar progressBar)
{
this.selectedHabits = selectedHabits;
this.progressBar = progressBar;
this.activity = activity;
}
@Override
protected void onPreExecute()
{
if(progressBar != null)
{
progressBar.setIndeterminate(true);
progressBar.setVisibility(View.VISIBLE);
}
}
@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(progressBar != null)
progressBar.setVisibility(View.GONE);
}
@Override
protected Void doInBackground(Void... params)
{
String dirName = String.format("%s/export/", activity.getExternalCacheDir());
HabitsExporter exporter = new HabitsExporter(selectedHabits, dirName);
archiveFilename = exporter.writeArchive();
return null;
}
}

@ -466,4 +466,9 @@ public class ListHabitsFragment extends Fragment
break; break;
} }
} }
public void exportAllHabits()
{
new ExportHabitsTask(activity, Habit.getAll(true), progressBar).execute();
}
} }

@ -22,8 +22,10 @@ package org.isoron.uhabits.fragments;
import android.app.backup.BackupManager; import android.app.backup.BackupManager;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import org.isoron.uhabits.MainActivity;
import org.isoron.uhabits.R; import org.isoron.uhabits.R;
public class SettingsFragment extends PreferenceFragment public class SettingsFragment extends PreferenceFragment
@ -34,6 +36,24 @@ public class SettingsFragment extends PreferenceFragment
{ {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
setResultOnPreferenceClick("importData", MainActivity.RESULT_IMPORT_DATA);
setResultOnPreferenceClick("exportCSV", MainActivity.RESULT_EXPORT_ALL_AS_CSV);
}
private void setResultOnPreferenceClick(String key, final int result)
{
Preference exportCSV = findPreference(key);
exportCSV.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener()
{
@Override
public boolean onPreferenceClick(Preference preference)
{
getActivity().setResult(result);
getActivity().finish();
return true;
}
});
} }
@Override @Override

@ -58,7 +58,7 @@ public class HabitsExporter
private void writeHabits() throws IOException private void writeHabits() throws IOException
{ {
String filename = "habits.csv"; String filename = "Habits.csv";
new File(exportDirName).mkdirs(); new File(exportDirName).mkdirs();
FileWriter out = new FileWriter(exportDirName + filename); FileWriter out = new FileWriter(exportDirName + filename);
generateFilenames.add(filename); generateFilenames.add(filename);
@ -67,7 +67,7 @@ public class HabitsExporter
for(Habit h : habits) for(Habit h : habits)
{ {
String habitDirName = String.format("%s/", h.name); String habitDirName = String.format("%03d %s/", h.position, h.name);
new File(exportDirName + habitDirName).mkdirs(); new File(exportDirName + habitDirName).mkdirs();
generateDirs.add(habitDirName); generateDirs.add(habitDirName);
@ -78,7 +78,7 @@ public class HabitsExporter
private void writeScores(String habitDirName, ScoreList scores) throws IOException private void writeScores(String habitDirName, ScoreList scores) throws IOException
{ {
String path = habitDirName + "scores.csv"; String path = habitDirName + "Scores.csv";
FileWriter out = new FileWriter(exportDirName + path); FileWriter out = new FileWriter(exportDirName + path);
generateFilenames.add(path); generateFilenames.add(path);
scores.writeCSV(out); scores.writeCSV(out);
@ -87,7 +87,7 @@ public class HabitsExporter
private void writeCheckmarks(String habitDirName, CheckmarkList checkmarks) throws IOException private void writeCheckmarks(String habitDirName, CheckmarkList checkmarks) throws IOException
{ {
String filename = habitDirName + "checkmarks.csv"; String filename = habitDirName + "Checkmarks.csv";
FileWriter out = new FileWriter(exportDirName + filename); FileWriter out = new FileWriter(exportDirName + filename);
generateFilenames.add(filename); generateFilenames.add(filename);
checkmarks.writeCSV(out); checkmarks.writeCSV(out);
@ -98,7 +98,7 @@ public class HabitsExporter
{ {
SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat(); SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat();
String date = dateFormat.format(DateHelper.getStartOfToday()); String date = dateFormat.format(DateHelper.getStartOfToday());
String zipFilename = String.format("%s/habits-%s.zip", exportDirName, date); String zipFilename = String.format("%s/Loop-Habits-%s.zip", exportDirName, date);
FileOutputStream fos = new FileOutputStream(zipFilename); FileOutputStream fos = new FileOutputStream(zipFilename);
ZipOutputStream zos = new ZipOutputStream(fos); ZipOutputStream zos = new ZipOutputStream(fos);

@ -481,13 +481,16 @@ public class Habit extends Model
public static void writeCSV(List<Habit> habits, Writer out) throws IOException public static void writeCSV(List<Habit> habits, Writer out) throws IOException
{ {
String header[] = { "Name", "Description", "FrequencyNumerator", "FrequencyDenominator", "Color" };
CSVWriter csv = new CSVWriter(out); CSVWriter csv = new CSVWriter(out);
csv.writeNext(header, false);
for(Habit habit : habits) for(Habit habit : habits)
{ {
String[] cols = { habit.name, habit.description, Integer.toString(habit.freqNum), String[] cols = { habit.name, habit.description, Integer.toString(habit.freqNum),
Integer.toString(habit.freqDen), ColorHelper.toHTML(habit.color) }; Integer.toString(habit.freqDen), ColorHelper.toHTML(habit.color) };
csv.writeAll(Collections.singletonList(cols)); csv.writeNext(cols, false);
} }
csv.close(); csv.close();

@ -28,12 +28,6 @@
android:enabled="true" android:enabled="true"
android:title="@string/show_archived"/> android:title="@string/show_archived"/>
<item
android:id="@+id/action_import"
android:orderInCategory="50"
android:title="Import data"
app:showAsAction="never"/>
<item <item
android:id="@+id/action_settings" android:id="@+id/action_settings"
android:orderInCategory="100" android:orderInCategory="100"

@ -106,7 +106,7 @@
<string name="any_weekday">Monday to Friday</string> <string name="any_weekday">Monday to Friday</string>
<string name="any_day">Any day of the week</string> <string name="any_day">Any day of the week</string>
<string name="select_weekdays">Select days</string> <string name="select_weekdays">Select days</string>
<string name="export_to_csv">Export data</string> <string name="export_to_csv">Export as CSV</string>
<string name="done_label">Done</string> <string name="done_label">Done</string>
<string name="clear_label">Clear</string> <string name="clear_label">Clear</string>
@ -142,4 +142,9 @@
<string name="could_not_import">Failed to import habits from file.</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="file_not_recognized">File type not recognized.</string>
<string name="habits_imported">Habits imported successfully.</string> <string name="habits_imported">Habits imported successfully.</string>
<string name="import_data_summary">Supports files exported by Loop, Tickmate, HabitBull or Rewire. See FAQ for more information.</string>
<string name="import_data">Import data</string>
<string name="export_as_csv_summary">This archive contains 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">This file contains all your data and can be imported back.</string>
</resources> </resources>

@ -36,7 +36,8 @@
android:entries="@array/snooze_interval_names" android:entries="@array/snooze_interval_names"
android:entryValues="@array/snooze_interval_values" android:entryValues="@array/snooze_interval_values"
android:key="pref_snooze_interval" android:key="pref_snooze_interval"
android:title="@string/pref_snooze_interval_title"/> android:title="@string/pref_snooze_interval_title"
android:summary="%s"/>
</PreferenceCategory> </PreferenceCategory>
@ -44,17 +45,21 @@
android:key="pref_key_links" android:key="pref_key_links"
android:title="Database"> android:title="Database">
<Preference android:title="Export data"> <Preference
<intent android:summary="@string/export_full_backup_summary"
android:action="android.intent.action.VIEW" android:title="@string/export_full_backup">
android:data="@string/helpURL"/>
</Preference> </Preference>
<Preference android:title="Import data" <Preference
android:summary="Supports files exported by Loop, Tickmate, HabitBull or Rewire. This feature is currently experimental."> android:key="exportCSV"
<intent android:summary="@string/export_as_csv_summary"
android:action="android.intent.action.VIEW" android:title="@string/export_to_csv">
android:data="@string/helpURL"/> </Preference>
<Preference
android:key="importData"
android:summary="@string/import_data_summary"
android:title="@string/import_data">
</Preference> </Preference>
</PreferenceCategory> </PreferenceCategory>

Loading…
Cancel
Save