mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
@@ -27,12 +27,10 @@
|
|||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
android:maxSdkVersion="18"/>
|
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
android:maxSdkVersion="18"/>
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="HabitsApplication"
|
android:name="HabitsApplication"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package org.isoron.uhabits;
|
package org.isoron.uhabits;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.appwidget.AppWidgetManager;
|
import android.appwidget.AppWidgetManager;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -26,10 +27,15 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
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.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -37,6 +43,7 @@ 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;
|
||||||
@@ -46,6 +53,8 @@ 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
|
||||||
{
|
{
|
||||||
@@ -120,6 +129,12 @@ 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);
|
||||||
@@ -139,6 +154,22 @@ public class MainActivity extends ReplayableActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onActionImportClicked()
|
||||||
|
{
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
|
||||||
|
PackageManager.PERMISSION_GRANTED)
|
||||||
|
{
|
||||||
|
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String[] permissions = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE };
|
||||||
|
ActivityCompat.requestPermissions(this, permissions, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listHabitsFragment.showImportDialog();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHabitClicked(Habit habit)
|
public void onHabitClicked(Habit habit)
|
||||||
{
|
{
|
||||||
@@ -197,4 +228,14 @@ public class MainActivity extends ReplayableActivity
|
|||||||
listHabitsFragment.onPostExecuteCommand(null);
|
listHabitsFragment.onPostExecuteCommand(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||||
|
@NonNull int[] grantResults)
|
||||||
|
{
|
||||||
|
if (grantResults.length <= 0) return;
|
||||||
|
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) return;
|
||||||
|
|
||||||
|
listHabitsFragment.showImportDialog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* 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.dialogs;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.WindowManager.LayoutParams;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.ListView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class FilePickerDialog implements AdapterView.OnItemClickListener
|
||||||
|
{
|
||||||
|
private static final String PARENT_DIR = "..";
|
||||||
|
|
||||||
|
private final Activity activity;
|
||||||
|
private ListView list;
|
||||||
|
private Dialog dialog;
|
||||||
|
private File currentPath;
|
||||||
|
|
||||||
|
public interface OnFileSelectedListener
|
||||||
|
{
|
||||||
|
void onFileSelected(File file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnFileSelectedListener fileListener;
|
||||||
|
|
||||||
|
public FilePickerDialog(Activity activity, File initialDirectory)
|
||||||
|
{
|
||||||
|
this.activity = activity;
|
||||||
|
|
||||||
|
list = new ListView(activity);
|
||||||
|
list.setOnItemClickListener(this);
|
||||||
|
|
||||||
|
dialog = new Dialog(activity);
|
||||||
|
dialog.setContentView(list);
|
||||||
|
dialog.getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||||
|
|
||||||
|
navigateTo(initialDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int which, long id)
|
||||||
|
{
|
||||||
|
String filename = (String) list.getItemAtPosition(which);
|
||||||
|
File file;
|
||||||
|
|
||||||
|
if (filename.equals(PARENT_DIR))
|
||||||
|
file = currentPath.getParentFile();
|
||||||
|
else
|
||||||
|
file = new File(currentPath, filename);
|
||||||
|
|
||||||
|
if (file.isDirectory())
|
||||||
|
{
|
||||||
|
navigateTo(file);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (fileListener != null) fileListener.onFileSelected(file);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void show()
|
||||||
|
{
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileListener(OnFileSelectedListener fileListener)
|
||||||
|
{
|
||||||
|
this.fileListener = fileListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateTo(File path)
|
||||||
|
{
|
||||||
|
if (!path.exists()) return;
|
||||||
|
|
||||||
|
File[] dirs = path.listFiles(new ReadableDirFilter());
|
||||||
|
File[] files = path.listFiles(new RegularReadableFileFilter());
|
||||||
|
if(dirs == null || files == null) return;
|
||||||
|
|
||||||
|
this.currentPath = path;
|
||||||
|
dialog.setTitle(currentPath.getPath());
|
||||||
|
list.setAdapter(new FilePickerAdapter(getFileList(path, dirs, files)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String[] getFileList(File path, File[] dirs, File[] files)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
int length = dirs.length + files.length;
|
||||||
|
String[] fileList;
|
||||||
|
|
||||||
|
if (path.getParentFile() == null || !path.getParentFile().canRead())
|
||||||
|
{
|
||||||
|
fileList = new String[length];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fileList = new String[length + 1];
|
||||||
|
fileList[count++] = PARENT_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.sort(dirs);
|
||||||
|
Arrays.sort(files);
|
||||||
|
|
||||||
|
for (File dir : dirs)
|
||||||
|
fileList[count++] = dir.getName();
|
||||||
|
|
||||||
|
for (File file : files)
|
||||||
|
fileList[count++] = file.getName();
|
||||||
|
|
||||||
|
return fileList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FilePickerAdapter extends ArrayAdapter<String>
|
||||||
|
{
|
||||||
|
public FilePickerAdapter(@NonNull String[] fileList)
|
||||||
|
{
|
||||||
|
super(FilePickerDialog.this.activity, android.R.layout.simple_list_item_1, fileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int pos, View view, ViewGroup parent)
|
||||||
|
{
|
||||||
|
view = super.getView(pos, view, parent);
|
||||||
|
TextView tv = (TextView) view;
|
||||||
|
tv.setSingleLine(true);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ReadableDirFilter implements FileFilter
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file)
|
||||||
|
{
|
||||||
|
return (file.isDirectory() && file.canRead());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RegularReadableFileFilter implements FileFilter
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file)
|
||||||
|
{
|
||||||
|
return !file.isDirectory() && file.canRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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.fragments;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.io.GenericImporter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ImportHabitsAsyncTask extends AsyncTask<Void, Void, Void>
|
||||||
|
{
|
||||||
|
public static final int SUCCESS = 1;
|
||||||
|
public static final int NOT_RECOGNIZED = 2;
|
||||||
|
public static final int FAILED = 3;
|
||||||
|
|
||||||
|
public interface Listener
|
||||||
|
{
|
||||||
|
void onImportFinished(int result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final ProgressBar progressBar;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Listener listener;
|
||||||
|
|
||||||
|
int result;
|
||||||
|
|
||||||
|
public ImportHabitsAsyncTask(@NonNull File file, @Nullable ProgressBar progressBar)
|
||||||
|
{
|
||||||
|
this.file = file;
|
||||||
|
this.progressBar = progressBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(@Nullable Listener listener)
|
||||||
|
{
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute()
|
||||||
|
{
|
||||||
|
if(progressBar != null)
|
||||||
|
{
|
||||||
|
progressBar.setIndeterminate(true);
|
||||||
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid)
|
||||||
|
{
|
||||||
|
if(progressBar != null)
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if(listener != null) listener.onImportFinished(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GenericImporter importer = new GenericImporter();
|
||||||
|
if(importer.canHandle(file))
|
||||||
|
{
|
||||||
|
importer.importHabitsFromFile(file);
|
||||||
|
result = SUCCESS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = NOT_RECOGNIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
result = FAILED;
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.view.ActionMode;
|
import android.view.ActionMode;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
@@ -55,6 +56,7 @@ import org.isoron.helpers.DialogHelper.OnSavedListener;
|
|||||||
import org.isoron.helpers.ReplayableActivity;
|
import org.isoron.helpers.ReplayableActivity;
|
||||||
import org.isoron.uhabits.R;
|
import org.isoron.uhabits.R;
|
||||||
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
import org.isoron.uhabits.commands.ToggleRepetitionCommand;
|
||||||
|
import org.isoron.uhabits.dialogs.FilePickerDialog;
|
||||||
import org.isoron.uhabits.dialogs.HabitSelectionCallback;
|
import org.isoron.uhabits.dialogs.HabitSelectionCallback;
|
||||||
import org.isoron.uhabits.dialogs.HintManager;
|
import org.isoron.uhabits.dialogs.HintManager;
|
||||||
import org.isoron.uhabits.helpers.ListHabitsHelper;
|
import org.isoron.uhabits.helpers.ListHabitsHelper;
|
||||||
@@ -62,6 +64,7 @@ import org.isoron.uhabits.helpers.ReminderHelper;
|
|||||||
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.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -69,7 +72,7 @@ import java.util.List;
|
|||||||
public class ListHabitsFragment extends Fragment
|
public class ListHabitsFragment extends Fragment
|
||||||
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
|
implements OnSavedListener, OnItemClickListener, OnLongClickListener, DropListener,
|
||||||
OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener,
|
OnClickListener, HabitListLoader.Listener, AdapterView.OnItemLongClickListener,
|
||||||
HabitSelectionCallback.Listener
|
HabitSelectionCallback.Listener, ImportHabitsAsyncTask.Listener
|
||||||
{
|
{
|
||||||
long lastLongClick = 0;
|
long lastLongClick = 0;
|
||||||
private boolean isShortToggleEnabled;
|
private boolean isShortToggleEnabled;
|
||||||
@@ -426,4 +429,41 @@ public class ListHabitsFragment extends Fragment
|
|||||||
selectItem(position);
|
selectItem(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showImportDialog()
|
||||||
|
{
|
||||||
|
File dir = Environment.getExternalStorageDirectory();
|
||||||
|
FilePickerDialog picker = new FilePickerDialog(activity, dir);
|
||||||
|
picker.setFileListener(new FilePickerDialog.OnFileSelectedListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onFileSelected(File file)
|
||||||
|
{
|
||||||
|
ImportHabitsAsyncTask task = new ImportHabitsAsyncTask(file, progressBar);
|
||||||
|
task.setListener(ListHabitsFragment.this);
|
||||||
|
task.execute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
picker.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImportFinished(int result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case ImportHabitsAsyncTask.SUCCESS:
|
||||||
|
loader.updateAllHabits(true);
|
||||||
|
activity.showToast(R.string.habits_imported);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ImportHabitsAsyncTask.NOT_RECOGNIZED:
|
||||||
|
activity.showToast(R.string.file_not_recognized);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
activity.showToast(R.string.could_not_import);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public abstract class AbstractImporter
|
||||||
|
{
|
||||||
|
public abstract boolean canHandle(@NonNull File file) throws IOException;
|
||||||
|
|
||||||
|
public abstract void importHabitsFromFile(@NonNull File file) throws IOException;
|
||||||
|
|
||||||
|
public static boolean isSQLite3File(@NonNull File file) throws IOException
|
||||||
|
{
|
||||||
|
FileInputStream fis = new FileInputStream(file);
|
||||||
|
|
||||||
|
byte[] sqliteHeader = "SQLite format 3".getBytes();
|
||||||
|
byte[] buffer = new byte[sqliteHeader.length];
|
||||||
|
|
||||||
|
|
||||||
|
int count = fis.read(buffer);
|
||||||
|
if(count < sqliteHeader.length) return false;
|
||||||
|
|
||||||
|
return Arrays.equals(buffer, sqliteHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
app/src/main/java/org/isoron/uhabits/io/GenericImporter.java
Normal file
56
app/src/main/java/org/isoron/uhabits/io/GenericImporter.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class GenericImporter extends AbstractImporter
|
||||||
|
{
|
||||||
|
List<AbstractImporter> importers;
|
||||||
|
|
||||||
|
public GenericImporter()
|
||||||
|
{
|
||||||
|
importers = new LinkedList<>();
|
||||||
|
importers.add(new RewireDBImporter());
|
||||||
|
importers.add(new TickmateDBImporter());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canHandle(@NonNull File file) throws IOException
|
||||||
|
{
|
||||||
|
for(AbstractImporter importer : importers)
|
||||||
|
if(importer.canHandle(file)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||||
|
{
|
||||||
|
for(AbstractImporter importer : importers)
|
||||||
|
if(importer.canHandle(file))
|
||||||
|
importer.importHabitsFromFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
190
app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java
Normal file
190
app/src/main/java/org/isoron/uhabits/io/RewireDBImporter.java
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.isoron.helpers.ActiveAndroidHelper;
|
||||||
|
import org.isoron.helpers.DateHelper;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
public class RewireDBImporter extends AbstractImporter
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean canHandle(@NonNull File file) throws IOException
|
||||||
|
{
|
||||||
|
if(!isSQLite3File(file)) return false;
|
||||||
|
|
||||||
|
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
|
Cursor c = db.rawQuery("select count(*) from SQLITE_MASTER where name=? or name=?",
|
||||||
|
new String[]{"CHECKINS", "UNIT"});
|
||||||
|
|
||||||
|
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
||||||
|
|
||||||
|
c.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||||
|
{
|
||||||
|
final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
|
ActiveAndroidHelper.executeAsTransaction(new ActiveAndroidHelper.Command()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void execute()
|
||||||
|
{
|
||||||
|
createHabits(db);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHabits(SQLiteDatabase db)
|
||||||
|
{
|
||||||
|
Cursor c = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
c = db.rawQuery("select _id, name, description, schedule, active_days, " +
|
||||||
|
"repeating_count, days, period from habits", new String[0]);
|
||||||
|
if (!c.moveToFirst()) return;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int id = c.getInt(0);
|
||||||
|
String name = c.getString(1);
|
||||||
|
String description = c.getString(2);
|
||||||
|
int schedule = c.getInt(3);
|
||||||
|
String activeDays = c.getString(4);
|
||||||
|
int repeatingCount = c.getInt(5);
|
||||||
|
int days = c.getInt(6);
|
||||||
|
int periodIndex = c.getInt(7);
|
||||||
|
|
||||||
|
Habit habit = new Habit();
|
||||||
|
habit.name = name;
|
||||||
|
habit.description = description;
|
||||||
|
|
||||||
|
int periods[] = { 7, 31, 365 };
|
||||||
|
|
||||||
|
switch (schedule)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
habit.freqNum = activeDays.split(",").length;
|
||||||
|
habit.freqDen = 7;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
habit.freqNum = days;
|
||||||
|
habit.freqDen = periods[periodIndex];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
habit.freqNum = 1;
|
||||||
|
habit.freqDen = repeatingCount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
habit.save();
|
||||||
|
|
||||||
|
createReminder(db, habit, id);
|
||||||
|
createCheckmarks(db, habit, id);
|
||||||
|
|
||||||
|
}
|
||||||
|
while (c.moveToNext());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (c != null) c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createReminder(SQLiteDatabase db, Habit habit, int rewireHabitId)
|
||||||
|
{
|
||||||
|
String[] params = { Integer.toString(rewireHabitId) };
|
||||||
|
Cursor c = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
c = db.rawQuery("select time, active_days from reminders where habit_id=? limit 1", params);
|
||||||
|
|
||||||
|
if (!c.moveToFirst()) return;
|
||||||
|
int rewireReminder = Integer.parseInt(c.getString(0));
|
||||||
|
if (rewireReminder <= 0 || rewireReminder >= 1440) return;
|
||||||
|
|
||||||
|
boolean reminderDays[] = new boolean[7];
|
||||||
|
|
||||||
|
String activeDays[] = c.getString(1).split(",");
|
||||||
|
for(String d : activeDays)
|
||||||
|
{
|
||||||
|
int idx = (Integer.parseInt(d) + 1) % 7;
|
||||||
|
reminderDays[idx] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
habit.reminderDays = DateHelper.packWeekdayList(reminderDays);
|
||||||
|
habit.reminderHour = rewireReminder / 60;
|
||||||
|
habit.reminderMin = rewireReminder % 60;
|
||||||
|
habit.save();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if(c != null) c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull Habit habit, int rewireHabitId)
|
||||||
|
{
|
||||||
|
Cursor c = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String[] params = { Integer.toString(rewireHabitId) };
|
||||||
|
c = db.rawQuery("select distinct date from checkins where habit_id=? and type=2", params);
|
||||||
|
if (!c.moveToFirst()) return;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
String date = c.getString(0);
|
||||||
|
int year = Integer.parseInt(date.substring(0, 4));
|
||||||
|
int month = Integer.parseInt(date.substring(4, 6));
|
||||||
|
int day = Integer.parseInt(date.substring(6, 8));
|
||||||
|
|
||||||
|
GregorianCalendar cal = DateHelper.getStartOfTodayCalendar();
|
||||||
|
cal.set(year, month - 1, day);
|
||||||
|
|
||||||
|
habit.repetitions.toggle(cal.getTimeInMillis());
|
||||||
|
}
|
||||||
|
while (c.moveToNext());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (c != null) c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
130
app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java
Normal file
130
app/src/main/java/org/isoron/uhabits/io/TickmateDBImporter.java
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* 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.io;
|
||||||
|
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.isoron.helpers.ActiveAndroidHelper;
|
||||||
|
import org.isoron.helpers.DateHelper;
|
||||||
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
public class TickmateDBImporter extends AbstractImporter
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean canHandle(@NonNull File file) throws IOException
|
||||||
|
{
|
||||||
|
if(!isSQLite3File(file)) return false;
|
||||||
|
|
||||||
|
SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
|
Cursor c = db.rawQuery("select count(*) from SQLITE_MASTER where name=? or name=?",
|
||||||
|
new String[]{"tracks", "track2groups"});
|
||||||
|
|
||||||
|
boolean result = (c.moveToFirst() && c.getInt(0) == 2);
|
||||||
|
|
||||||
|
c.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importHabitsFromFile(@NonNull File file) throws IOException
|
||||||
|
{
|
||||||
|
final SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getPath(), null,
|
||||||
|
SQLiteDatabase.OPEN_READONLY);
|
||||||
|
|
||||||
|
ActiveAndroidHelper.executeAsTransaction(new ActiveAndroidHelper.Command()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void execute()
|
||||||
|
{
|
||||||
|
createHabits(db);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createHabits(SQLiteDatabase db)
|
||||||
|
{
|
||||||
|
Cursor c = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
c = db.rawQuery("select _id, name, description from tracks", new String[0]);
|
||||||
|
if (!c.moveToFirst()) return;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int id = c.getInt(0);
|
||||||
|
String name = c.getString(1);
|
||||||
|
String description = c.getString(2);
|
||||||
|
|
||||||
|
Habit habit = new Habit();
|
||||||
|
habit.name = name;
|
||||||
|
habit.description = description;
|
||||||
|
habit.freqNum = 1;
|
||||||
|
habit.freqDen = 1;
|
||||||
|
habit.save();
|
||||||
|
|
||||||
|
createCheckmarks(db, habit, id);
|
||||||
|
|
||||||
|
}
|
||||||
|
while (c.moveToNext());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (c != null) c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCheckmarks(@NonNull SQLiteDatabase db, @NonNull Habit habit, int tickmateTrackId)
|
||||||
|
{
|
||||||
|
Cursor c = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String[] params = { Integer.toString(tickmateTrackId) };
|
||||||
|
c = db.rawQuery("select distinct year, month, day from ticks where _track_id=?", params);
|
||||||
|
if (!c.moveToFirst()) return;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
int year = c.getInt(0);
|
||||||
|
int month = c.getInt(1);
|
||||||
|
int day = c.getInt(2);
|
||||||
|
|
||||||
|
GregorianCalendar cal = DateHelper.getStartOfTodayCalendar();
|
||||||
|
cal.set(year, month, day);
|
||||||
|
|
||||||
|
habit.repetitions.toggle(cal.getTimeInMillis());
|
||||||
|
}
|
||||||
|
while (c.moveToNext());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (c != null) c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,12 @@
|
|||||||
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"
|
||||||
|
|||||||
@@ -139,4 +139,7 @@
|
|||||||
<string name="custom_frequency">Custom …</string>
|
<string name="custom_frequency">Custom …</string>
|
||||||
<string name="help">Help & FAQ</string>
|
<string name="help">Help & FAQ</string>
|
||||||
<string name="could_not_export">Failed to export data.</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="habits_imported">Habits imported successfully.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -40,6 +40,25 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:key="pref_key_links"
|
||||||
|
android:title="Database">
|
||||||
|
|
||||||
|
<Preference android:title="Export data">
|
||||||
|
<intent
|
||||||
|
android:action="android.intent.action.VIEW"
|
||||||
|
android:data="@string/helpURL"/>
|
||||||
|
</Preference>
|
||||||
|
|
||||||
|
<Preference android:title="Import data"
|
||||||
|
android:summary="Supports files exported by Loop, Tickmate, HabitBull or Rewire. This feature is currently experimental.">
|
||||||
|
<intent
|
||||||
|
android:action="android.intent.action.VIEW"
|
||||||
|
android:data="@string/helpURL"/>
|
||||||
|
</Preference>
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:key="pref_key_links"
|
android:key="pref_key_links"
|
||||||
android:title="@string/links">
|
android:title="@string/links">
|
||||||
|
|||||||
Reference in New Issue
Block a user