diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5054471..78b87df 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -59,7 +59,7 @@
android:screenOrientation="portrait"
android:theme="@style/Theme.Default" />
@@ -73,6 +73,11 @@
android:label="@string/title_charts"
android:screenOrientation="portrait"
android:theme="@style/Theme.Default" />
+
records = recordController.getRecordsForExport(0, Long.MAX_VALUE);
+ List records = exportController.getRecordsForExport(0, Long.MAX_VALUE);
File exportDir = new File(getCacheDir(), "export");
boolean exportDirCreated = exportDir.mkdirs();
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/external/ImportActivity.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/external/ImportActivity.java
new file mode 100644
index 0000000..6144918
--- /dev/null
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/external/ImportActivity.java
@@ -0,0 +1,70 @@
+package com.blogspot.e_kanivets.moneytracker.activity.external;
+
+import android.support.v7.app.AlertDialog;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+
+import com.blogspot.e_kanivets.moneytracker.R;
+import com.blogspot.e_kanivets.moneytracker.activity.base.BaseBackActivity;
+import com.blogspot.e_kanivets.moneytracker.controller.external.ImportController;
+import com.blogspot.e_kanivets.moneytracker.entity.data.Record;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+import butterknife.Bind;
+import butterknife.OnClick;
+
+public class ImportActivity extends BaseBackActivity {
+ @Inject
+ ImportController importController;
+
+ @Bind(R.id.et_import_data)
+ EditText etImportData;
+
+ @Override
+ protected int getContentViewId() {
+ return R.layout.activity_import;
+ }
+
+ @Override
+ protected boolean initData() {
+ boolean result = super.initData();
+ getAppComponent().inject(ImportActivity.this);
+ return result;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_import, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_help:
+ AlertDialog.Builder builder = new AlertDialog.Builder(ImportActivity.this);
+ builder.setTitle(R.string.help)
+ .setMessage(R.string.import_help)
+ .setPositiveButton(android.R.string.ok, null)
+ .show();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @OnClick(R.id.btn_import)
+ public void importRecords() {
+ String data = etImportData.getText().toString().trim();
+ List recordList = importController.importRecordsFromCsv(data);
+ showToast(getString(R.string.records_imported, recordList.size()));
+
+ setResult(RESULT_OK);
+ finish();
+ }
+}
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/AddRecordActivity.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/AddRecordActivity.java
index eb14cd8..6c46889 100644
--- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/AddRecordActivity.java
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/AddRecordActivity.java
@@ -5,6 +5,8 @@
import android.os.Build;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatSpinner;
+import android.text.InputFilter;
+import android.text.Spanned;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -153,6 +155,10 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return false;
}
});
+
+ // Restrict ';' for input, because it's used as delimiter when exporting
+ etTitle.setFilters(new InputFilter[]{new SemicolonInputFilter()});
+ etCategory.setFilters(new InputFilter[]{new SemicolonInputFilter()});
}
@Override
@@ -270,4 +276,13 @@ private boolean doRecord(String title, String category, double price, @Nullable
}
public enum Mode {MODE_ADD, MODE_EDIT}
+
+ private static class SemicolonInputFilter implements InputFilter {
+
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+ if (source != null && ";".equals(source.toString())) return "";
+ else return null;
+ }
+ }
}
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordController.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordController.java
index b3bbf22..a17b984 100644
--- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordController.java
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordController.java
@@ -12,6 +12,8 @@
import com.blogspot.e_kanivets.moneytracker.repo.base.IRepo;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
/**
@@ -90,6 +92,14 @@ public List readAll() {
public List readWithCondition(String condition, String[] args) {
List recordList = super.readWithCondition(condition, args);
+ // Sort record list by time field from smallest to biggest
+ Collections.sort(recordList, new Comparator() {
+ @Override
+ public int compare(Record lhs, Record rhs) {
+ return lhs.getTime() < rhs.getTime() ? -1 : (lhs.getTime() == rhs.getTime() ? 0 : 1);
+ }
+ });
+
// Data read from DB through Repo layer doesn't contain right nested objects, so construct them
List completedRecordList = new ArrayList<>();
for (Record record : recordList) {
@@ -117,45 +127,6 @@ public List getRecordsForPeriod(Period period) {
return readWithCondition(condition, args);
}
- public List getRecordsForExport(long fromDate, long toDate) {
- final String DELIMITER = ";";
- List result = new ArrayList<>();
-
- /* First of all add a header */
- @SuppressWarnings("StringBufferReplaceableByString")
- StringBuilder sb = new StringBuilder();
- sb.append(DbHelper.ID_COLUMN).append(DELIMITER);
- sb.append(DbHelper.TIME_COLUMN).append(DELIMITER);
- sb.append(DbHelper.TITLE_COLUMN).append(DELIMITER);
- sb.append(DbHelper.CATEGORY_ID_COLUMN).append(DELIMITER);
- sb.append(DbHelper.PRICE_COLUMN);
-
- result.add(sb.toString());
-
- String condition = DbHelper.TIME_COLUMN + " BETWEEN ? AND ?";
- String[] args = new String[]{Long.toString(fromDate), Long.toString(toDate)};
-
- List recordList = readWithCondition(condition, args);
-
- for (Record record : recordList) {
- sb = new StringBuilder();
- sb.append(record.getId()).append(DELIMITER);
- sb.append(record.getTime()).append(DELIMITER);
- sb.append(record.getTitle()).append(DELIMITER);
-
- Category category = null;
- if (record.getCategory() != null)
- category = categoryController.read(record.getCategory().getId());
-
- sb.append(category == null ? "NONE" : category.getName()).append(DELIMITER);
- sb.append(record.getType() == 0 ? record.getPrice() : -record.getPrice());
-
- result.add(sb.toString());
- }
-
- return result;
- }
-
private Record validateRecord(@NonNull Record record) {
if (record.getCategory() == null) return record;
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/ExportController.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/ExportController.java
new file mode 100644
index 0000000..8493275
--- /dev/null
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/ExportController.java
@@ -0,0 +1,66 @@
+package com.blogspot.e_kanivets.moneytracker.controller.external;
+
+import com.blogspot.e_kanivets.moneytracker.controller.data.CategoryController;
+import com.blogspot.e_kanivets.moneytracker.controller.data.RecordController;
+import com.blogspot.e_kanivets.moneytracker.entity.data.Category;
+import com.blogspot.e_kanivets.moneytracker.entity.data.Record;
+import com.blogspot.e_kanivets.moneytracker.repo.DbHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller class to encapsulate export logic.
+ * Created on 6/28/16.
+ *
+ * @author Evgenii Kanivets
+ */
+public class ExportController {
+ private final RecordController recordController;
+ private final CategoryController categoryController;
+
+ public ExportController(RecordController recordController, CategoryController categoryController) {
+ this.recordController = recordController;
+ this.categoryController = categoryController;
+ }
+
+ public List getRecordsForExport(long fromDate, long toDate) {
+ List result = new ArrayList<>();
+
+ /* First of all add a header */
+ @SuppressWarnings("StringBufferReplaceableByString")
+ StringBuilder sb = new StringBuilder();
+ sb.append(Head.TIME).append(Head.DELIMITER);
+ sb.append(Head.TITLE).append(Head.DELIMITER);
+ sb.append(Head.CATEGORY).append(Head.DELIMITER);
+ sb.append(Head.PRICE).append(Head.DELIMITER);
+ sb.append(Head.CURRENCY);
+
+ result.add(sb.toString());
+
+ String condition = DbHelper.TIME_COLUMN + " BETWEEN ? AND ?";
+ String[] args = new String[]{Long.toString(fromDate), Long.toString(toDate)};
+
+ List recordList = recordController.readWithCondition(condition, args);
+
+ for (Record record : recordList) {
+ sb = new StringBuilder();
+ sb.append(record.getTime()).append(Head.DELIMITER);
+ sb.append(record.getTitle()).append(Head.DELIMITER);
+
+ Category category = null;
+ if (record.getCategory() != null)
+ category = categoryController.read(record.getCategory().getId());
+
+ sb.append(category == null ? "NONE" : category.getName()).append(Head.DELIMITER);
+ sb.append(record.getType() == 0 ? record.getFullPrice()
+ : -record.getFullPrice()).append(Head.DELIMITER);
+ sb.append(record.getCurrency() == null ? DbHelper.DEFAULT_ACCOUNT_CURRENCY
+ : record.getCurrency());
+
+ result.add(sb.toString());
+ }
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/Head.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/Head.java
new file mode 100644
index 0000000..396c9a3
--- /dev/null
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/Head.java
@@ -0,0 +1,17 @@
+package com.blogspot.e_kanivets.moneytracker.controller.external;
+
+/**
+ * Interface for head titles in import/export features.
+ * Created on 6/28/16.
+ *
+ * @author Evgenii Kanivets
+ */
+public interface Head {
+ String TIME = "time";
+ String TITLE = "title";
+ String CATEGORY = "category";
+ String PRICE = "price";
+ String CURRENCY = "currency";
+ String DELIMITER = ";";
+ int COLUMN_COUNT = 5;
+}
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/ImportController.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/ImportController.java
new file mode 100644
index 0000000..f0737e4
--- /dev/null
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/external/ImportController.java
@@ -0,0 +1,70 @@
+package com.blogspot.e_kanivets.moneytracker.controller.external;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import com.blogspot.e_kanivets.moneytracker.controller.data.RecordController;
+import com.blogspot.e_kanivets.moneytracker.entity.data.Account;
+import com.blogspot.e_kanivets.moneytracker.entity.data.Category;
+import com.blogspot.e_kanivets.moneytracker.entity.data.Record;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controller class to encapsulate import logic.
+ * Created on 6/28/16.
+ *
+ * @author Evgenii Kanivets
+ */
+public class ImportController {
+ private final RecordController recordController;
+
+ public ImportController(RecordController recordController) {
+ this.recordController = recordController;
+ }
+
+ @NonNull
+ public List importRecordsFromCsv(@Nullable String csv) {
+ List recordList = new ArrayList<>();
+ if (csv == null) return recordList;
+
+ String lines[] = csv.split("\\r?\\n");
+ for (String line : lines) {
+ String[] words = line.split(Head.DELIMITER);
+ if (words.length != Head.COLUMN_COUNT) continue;
+
+ String timeCol = words[0];
+ String titleCol = words[1];
+ String categoryCol = words[2];
+ String priceCol = words[3];
+ String currencyCol = words[4];
+
+ try {
+ long time = Long.parseLong(timeCol);
+ String title = titleCol.trim();
+ String categoryName = categoryCol.trim();
+ double price = Double.parseDouble(priceCol);
+ String currency = currencyCol.trim();
+
+ if (currency.length() != 3) continue;
+ if (categoryName.isEmpty()) continue;
+
+ int type;
+ if (price < 0.0) type = Record.TYPE_EXPENSE;
+ else type = Record.TYPE_INCOME;
+
+ Category category = new Category(categoryName);
+ Account account = new Account(-1, "MOCK", -1, currency, 0);
+
+ Record record = new Record(time, type, title, category, Math.abs(price), account, currency);
+ Record createdRecord = recordController.create(record);
+ if (createdRecord != null) recordList.add(createdRecord);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ return recordList;
+ }
+}
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/AppComponent.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/AppComponent.java
index 019e9ac..e3f47c7 100644
--- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/AppComponent.java
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/AppComponent.java
@@ -1,7 +1,8 @@
package com.blogspot.e_kanivets.moneytracker.di;
import com.blogspot.e_kanivets.moneytracker.activity.ChartsActivity;
-import com.blogspot.e_kanivets.moneytracker.activity.ExportActivity;
+import com.blogspot.e_kanivets.moneytracker.activity.external.ExportActivity;
+import com.blogspot.e_kanivets.moneytracker.activity.external.ImportActivity;
import com.blogspot.e_kanivets.moneytracker.activity.ReportActivity;
import com.blogspot.e_kanivets.moneytracker.activity.SettingsActivity;
import com.blogspot.e_kanivets.moneytracker.activity.account.AccountsActivity;
@@ -48,6 +49,8 @@ public interface AppComponent {
void inject(TransferActivity transferActivity);
+ void inject(ImportActivity importActivity);
+
void inject(ExportActivity exportActivity);
void inject(ReportActivity reportActivity);
diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/module/ControllerModule.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/module/ControllerModule.java
index fbba9b1..48163b5 100644
--- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/module/ControllerModule.java
+++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/di/module/ControllerModule.java
@@ -3,6 +3,7 @@
import android.content.Context;
import android.support.annotation.NonNull;
+import com.blogspot.e_kanivets.moneytracker.controller.external.ExportController;
import com.blogspot.e_kanivets.moneytracker.controller.FormatController;
import com.blogspot.e_kanivets.moneytracker.controller.PeriodController;
import com.blogspot.e_kanivets.moneytracker.controller.data.AccountController;
@@ -12,6 +13,7 @@
import com.blogspot.e_kanivets.moneytracker.controller.PreferenceController;
import com.blogspot.e_kanivets.moneytracker.controller.data.RecordController;
import com.blogspot.e_kanivets.moneytracker.controller.data.TransferController;
+import com.blogspot.e_kanivets.moneytracker.controller.external.ImportController;
import com.blogspot.e_kanivets.moneytracker.entity.data.Account;
import com.blogspot.e_kanivets.moneytracker.entity.data.Category;
import com.blogspot.e_kanivets.moneytracker.entity.data.ExchangeRate;
@@ -105,4 +107,19 @@ public PeriodController providesPeriodController(PreferenceController preference
public FormatController providesFormatController(PreferenceController preferenceController) {
return new FormatController(preferenceController);
}
+
+ @Provides
+ @NonNull
+ @Singleton
+ public ExportController providesExportController(RecordController recordController,
+ CategoryController categoryController) {
+ return new ExportController(recordController, categoryController);
+ }
+
+ @Provides
+ @NonNull
+ @Singleton
+ public ImportController providesImportController(RecordController recordController) {
+ return new ImportController(recordController);
+ }
}
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_help.png b/app/src/main/res/drawable-xxxhdpi/ic_help.png
new file mode 100755
index 0000000..2f932ef
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_help.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_import.png b/app/src/main/res/drawable-xxxhdpi/ic_import.png
new file mode 100755
index 0000000..de9501e
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_import.png differ
diff --git a/app/src/main/res/layout/activity_export.xml b/app/src/main/res/layout/activity_export.xml
index d7609b6..b1fd8fa 100644
--- a/app/src/main/res/layout/activity_export.xml
+++ b/app/src/main/res/layout/activity_export.xml
@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
- tools:context=".activity.ExportActivity">
+ tools:context=".activity.external.ExportActivity">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_import.xml b/app/src/main/res/menu/menu_import.xml
new file mode 100644
index 0000000..1800de9
--- /dev/null
+++ b/app/src/main/res/menu/menu_import.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_nav_drawer.xml b/app/src/main/res/menu/menu_nav_drawer.xml
index 2865924..ab88a53 100644
--- a/app/src/main/res/menu/menu_nav_drawer.xml
+++ b/app/src/main/res/menu/menu_nav_drawer.xml
@@ -16,6 +16,10 @@
+
- Расходы
Точность отображения
+
+ Импорт
+ %1$s записей было импортировано
+ Справка
+ Для импорта записей в приложение:\n
+ 1. Создайте CSV файл в формате - \n
+ время;заголовок;категория;цена;валюта.\n
+ 2. Скопируйте его контент в текстовое поле.\n
+ 3. Нажмите кнопку импорта. Через несколько секунд Вы увидите сколько записей было импортировано.\n
+ \n
+ Важно: время - метка времени в миллисекундах; валюта - 3-х символьный код валюты.\n
+ Пример: 1466948795712;метро;транспорт;-20.0;UAH
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index b062f81..d07a98c 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -64,4 +64,16 @@
Витрати
Точність відображення
-
\ No newline at end of file
+
+ Iмпорт
+ %1$s записів було імпортовано
+ Довідка
+ Для импорту записів до застосунку:\n
+ 1. Створіть CSV файл у форматі - \n
+ час;заголовок;категорія;ціна;валюта.\n
+ 2. Скопіюйте його контент до текстового поля.\n
+ 3. Натисніть кнопку імпорту. Через дікілька секунд Ви побачите скільки записів було імпортовано.\n
+ \n
+ Важливо: час - мітка часу у мілісекундах; валюта - 3-х символьний код валюти.\n
+ Приклад: 1466948795712;метро;транспорт;-20.0;UAH
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 84d23a8..34041c5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -74,5 +74,18 @@
9.99$ = 10$
9.99$ = 9$
9.99$ = 9.99$
+
+ Import
+ %1$s records has been imported
+ Help
+ To import records into application:\n
+ 1. Create CSV file in format - \n
+ time;title;category;price;currency.\n
+ 2. Copy its content to text field.\n
+ 3. Press import button. In few seconds you will see how much records has been imported.\n
+ \n
+ Note: time - timestamp value in milliseconds; currency - 3 symbols code of currency.\n
+ Example: 1466948795712;metro;transport;-20.0;UAH
+
diff --git a/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordControllerTest.java b/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordControllerTest.java
index ae7ee43..d9a4020 100644
--- a/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordControllerTest.java
+++ b/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/RecordControllerTest.java
@@ -142,13 +142,6 @@ public void testGetRecordsForPeriod() throws Exception {
new Period(new Date(), new Date(), Period.TYPE_CUSTOM)));
}
- @Test
- public void testGetRecordsForExport() throws Exception {
- ArrayList expected = new ArrayList<>();
- expected.add("id;time;title;category_id;price");
- assertEquals(expected, recordController.getRecordsForExport(0, 1));
- }
-
private class TestRepo implements IRepo {
private Map recordHashMap = new HashMap<>();