diff --git a/app/build.gradle b/app/build.gradle index 508e08a..6a7902c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,8 +48,14 @@ dependencies { compile 'com.android.support:support-v4:23.2.1' compile 'com.android.support:appcompat-v7:23.2.1' compile 'com.android.support:design:23.2.1' + // View annotation bindings compile 'com.jakewharton:butterknife:7.0.1' + // Dependency injection tool compile 'com.google.dagger:dagger:2.0.1' + // Charts + compile 'com.github.PhilJay:MPAndroidChart:v2.2.4' + // Advanced logging tool + compile 'com.jakewharton.timber:timber:4.1.2' apt 'com.google.dagger:dagger-compiler:2.0.1' provided 'org.glassfish:javax.annotation:10.0-b28' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 49cd21d..5054471 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -68,6 +68,11 @@ android:label="@string/title_settings" android:screenOrientation="portrait" android:theme="@style/Theme.Default" /> + recordList = recordController.readAll(); + List currencyNeeded = reportMaker.currencyNeeded(currency, recordList); + + IMonthReport monthReport = null; + if (currencyNeeded.isEmpty()) monthReport = reportMaker.getMonthReport(currency, recordList); + else barChart.setNoDataText(createRatesNeededList(currency, currencyNeeded)); + + if (monthReport != null) { + BarChartConverter barChartConverter = new BarChartConverter(ChartsActivity.this, + monthReport); + + BarData barData = new BarData(barChartConverter.getXAxisValueList(), + barChartConverter.getBarDataSetList()); + barData.setDrawValues(false); + + barChart.setData(barData); + barChart.setDescription(null); + barChart.setVisibleXRangeMinimum(8); + barChart.setScaleYEnabled(false); + barChart.setVisibleXRangeMaximum(34); + barChart.setHighlightPerDragEnabled(false); + barChart.setHighlightPerTapEnabled(false); + } + } + + protected String createRatesNeededList(String currency, List ratesNeeded) { + StringBuilder sb = new StringBuilder(getString(R.string.error_exchange_rates)); + + for (String str : ratesNeeded) { + sb.append("\n").append(str).append(getString(R.string.arrow)).append(currency); + } + + return sb.toString(); + } +} diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ExportActivity.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ExportActivity.java index 0842275..7fd3cad 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ExportActivity.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ExportActivity.java @@ -4,7 +4,6 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.v4.content.FileProvider; -import android.util.Log; import com.blogspot.e_kanivets.moneytracker.R; import com.blogspot.e_kanivets.moneytracker.activity.base.BaseBackActivity; @@ -18,6 +17,7 @@ import javax.inject.Inject; import butterknife.OnClick; +import timber.log.Timber; public class ExportActivity extends BaseBackActivity { @SuppressWarnings("unused") @@ -46,7 +46,7 @@ public void exportRecords() { File exportDir = new File(getCacheDir(), "export"); boolean exportDirCreated = exportDir.mkdirs(); - Log.d(TAG, "exportDirCreated: " + exportDirCreated); + Timber.d("ExportDirCreated: %b", exportDirCreated); File outFile; if (exportDir.exists()) outFile = new File(exportDir, DEFAULT_EXPORT_FILE_NAME); diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ReportActivity.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ReportActivity.java index c81ff4b..02be4fc 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ReportActivity.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/ReportActivity.java @@ -13,9 +13,9 @@ import com.blogspot.e_kanivets.moneytracker.controller.data.ExchangeRateController; import com.blogspot.e_kanivets.moneytracker.entity.Period; import com.blogspot.e_kanivets.moneytracker.entity.data.Record; -import com.blogspot.e_kanivets.moneytracker.report.ReportConverter; +import com.blogspot.e_kanivets.moneytracker.report.record.RecordReportConverter; import com.blogspot.e_kanivets.moneytracker.report.ReportMaker; -import com.blogspot.e_kanivets.moneytracker.report.base.IReport; +import com.blogspot.e_kanivets.moneytracker.report.record.IRecordReport; import com.blogspot.e_kanivets.moneytracker.ui.presenter.ShortSummaryPresenter; import java.util.List; @@ -77,13 +77,13 @@ protected void initViews() { private void update(String currency) { ReportMaker reportMaker = new ReportMaker(rateController); - IReport report = reportMaker.getReport(currency, period, recordList); + IRecordReport report = reportMaker.getRecordReport(currency, period, recordList); ExpandableListReportAdapter adapter = null; if (report != null) { - ReportConverter reportConverter = new ReportConverter(report); - adapter = new ExpandableListReportAdapter(ReportActivity.this, reportConverter); + RecordReportConverter recordReportConverter = new RecordReportConverter(report); + adapter = new ExpandableListReportAdapter(ReportActivity.this, recordReportConverter); } expandableListView.setAdapter(adapter); diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/base/BaseDrawerActivity.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/base/BaseDrawerActivity.java index 9025cea..afdd7e1 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/base/BaseDrawerActivity.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/base/BaseDrawerActivity.java @@ -9,6 +9,7 @@ import android.view.MenuItem; import com.blogspot.e_kanivets.moneytracker.R; +import com.blogspot.e_kanivets.moneytracker.activity.ChartsActivity; import com.blogspot.e_kanivets.moneytracker.activity.ExportActivity; import com.blogspot.e_kanivets.moneytracker.activity.SettingsActivity; import com.blogspot.e_kanivets.moneytracker.activity.account.AccountsActivity; @@ -77,6 +78,10 @@ public boolean onNavigationItemSelected(MenuItem item) { REQUEST_RATES); break; + case R.id.nav_charts: + startActivity(new Intent(BaseDrawerActivity.this, ChartsActivity.class)); + break; + case R.id.nav_export: startActivity(new Intent(BaseDrawerActivity.this, ExportActivity.class)); break; diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/MainActivity.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/MainActivity.java index aa24b0e..9177242 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/MainActivity.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/record/MainActivity.java @@ -24,7 +24,7 @@ import com.blogspot.e_kanivets.moneytracker.entity.data.Record; import com.blogspot.e_kanivets.moneytracker.entity.Period; import com.blogspot.e_kanivets.moneytracker.report.ReportMaker; -import com.blogspot.e_kanivets.moneytracker.report.base.IReport; +import com.blogspot.e_kanivets.moneytracker.report.record.IRecordReport; import com.blogspot.e_kanivets.moneytracker.ui.AppRateDialog; import com.blogspot.e_kanivets.moneytracker.ui.PeriodSpinner; import com.blogspot.e_kanivets.moneytracker.ui.presenter.ShortSummaryPresenter; @@ -194,7 +194,7 @@ protected void update() { String currency = currencyController.readDefaultCurrency(); ReportMaker reportMaker = new ReportMaker(rateController); - IReport report = reportMaker.getReport(currency, period, recordList); + IRecordReport report = reportMaker.getRecordReport(currency, period, recordList); summaryPresenter.update(report, currency, reportMaker.currencyNeeded(currency, recordList)); fillDefaultAccount(); diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/ExpandableListReportAdapter.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/ExpandableListReportAdapter.java index e32d48b..3b23966 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/ExpandableListReportAdapter.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/ExpandableListReportAdapter.java @@ -8,7 +8,7 @@ import android.widget.TextView; import com.blogspot.e_kanivets.moneytracker.R; -import com.blogspot.e_kanivets.moneytracker.report.ReportConverter; +import com.blogspot.e_kanivets.moneytracker.report.record.RecordReportConverter; import java.util.List; import java.util.Locale; @@ -32,7 +32,7 @@ public class ExpandableListReportAdapter extends SimpleExpandableListAdapter { private int red; private int green; - public ExpandableListReportAdapter(Context context, ReportConverter converter) { + public ExpandableListReportAdapter(Context context, RecordReportConverter converter) { this(context, converter.getGroupData(), converter.getGroupLayout(), converter.getGroupFrom(), converter.getGroupTo(), converter.getChildData(), converter.getChildLayout(), converter.getChildFrom(), converter.getChildTo()); @@ -75,7 +75,7 @@ private void customizeView(View view, Map values, boolean groupV if (viewHolder == null) viewHolder = new ViewHolder(view); /* Customize view to fit to model and UI */ - Double price = Double.parseDouble(values.get(ReportConverter.PRICE_PARAM_NAME)); + Double price = Double.parseDouble(values.get(RecordReportConverter.PRICE_PARAM_NAME)); if (groupView) view.setBackgroundColor(price < 0 ? whiteRed : whiteGreen); else view.setBackgroundColor(white); @@ -83,7 +83,7 @@ private void customizeView(View view, Map values, boolean groupV //Set color of total viewHolder.tvTotal.setTextColor(price >= 0 ? green : red); - viewHolder.tvCategory.setText(values.get(ReportConverter.TITLE_PARAM_NAME)); + viewHolder.tvCategory.setText(values.get(RecordReportConverter.TITLE_PARAM_NAME)); viewHolder.tvTotal.setText(format(price)); } 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 3d58913..1e9ef3d 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,5 +1,6 @@ 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.ReportActivity; import com.blogspot.e_kanivets.moneytracker.activity.SettingsActivity; @@ -47,6 +48,8 @@ public interface AppComponent { void inject(ReportActivity reportActivity); + void inject(ChartsActivity chartsActivity); + void inject(SettingsActivity.SettingsFragment settingsFragment); void inject(AccountsSummaryPresenter accountsSummaryPresenter); diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/repo/base/BaseRepo.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/repo/base/BaseRepo.java index 632d353..349d407 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/repo/base/BaseRepo.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/repo/base/BaseRepo.java @@ -5,13 +5,14 @@ import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; import com.blogspot.e_kanivets.moneytracker.repo.DbHelper; import com.blogspot.e_kanivets.moneytracker.entity.base.IEntity; import java.util.List; +import timber.log.Timber; + /** * Base implementation of {@link IRepo}. * No need to call db.close() at all, because SQLiteOpenHelper manage it for us + cache instances. @@ -21,8 +22,6 @@ * @author Evgenii Kanivets */ public abstract class BaseRepo implements IRepo { - private static final String TAG = "BaseRepo"; - protected DbHelper dbHelper; public BaseRepo(DbHelper dbHelper) { @@ -46,11 +45,11 @@ public T create(@Nullable T instance) { long id = db.insert(getTable(), null, contentValues(instance)); if (id == -1) { - Log.d(TAG, "Couldn't create record : " + instance); + Timber.d("Couldn't create record: %s", instance); return null; } else { T createdInstance = read(id); - Log.d(TAG, "Created record : " + createdInstance); + Timber.d("Created record: %s", createdInstance); return createdInstance; } } @@ -75,11 +74,11 @@ public T update(@Nullable T instance) { long rowsAffected = db.update(getTable(), contentValues(instance), "id=?", args); if (rowsAffected == 0) { - Log.d(TAG, "Couldn't update record : " + instance); + Timber.d("Couldn't update record: %s", instance); return null; } else { T updatedInstance = read(instance.getId()); - Log.d(TAG, "Updated record : " + updatedInstance); + Timber.d("Updated record: %s", updatedInstance); return updatedInstance; } } @@ -99,7 +98,7 @@ public boolean delete(@Nullable T instance) { String[] args = new String[]{Long.toString(instance.getId())}; long rowsAffected = db.delete(getTable(), "id=?", args); - Log.d(TAG, instance + (rowsAffected == 0 ? " didn't " : " ") + "deleted"); + Timber.d("%s %s deleted", instance, (rowsAffected == 0 ? " didn't " : " ")); return rowsAffected != 0; } diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/ReportMaker.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/ReportMaker.java index 9e6b5f7..261cacf 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/ReportMaker.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/ReportMaker.java @@ -8,9 +8,13 @@ import com.blogspot.e_kanivets.moneytracker.entity.data.ExchangeRate; import com.blogspot.e_kanivets.moneytracker.entity.Period; import com.blogspot.e_kanivets.moneytracker.entity.data.Record; -import com.blogspot.e_kanivets.moneytracker.report.base.IAccountsReport; +import com.blogspot.e_kanivets.moneytracker.report.account.AccountsReport; +import com.blogspot.e_kanivets.moneytracker.report.account.IAccountsReport; import com.blogspot.e_kanivets.moneytracker.report.base.IExchangeRateProvider; -import com.blogspot.e_kanivets.moneytracker.report.base.IReport; +import com.blogspot.e_kanivets.moneytracker.report.chart.IMonthReport; +import com.blogspot.e_kanivets.moneytracker.report.chart.MonthReport; +import com.blogspot.e_kanivets.moneytracker.report.record.IRecordReport; +import com.blogspot.e_kanivets.moneytracker.report.record.RecordReport; import java.util.ArrayList; import java.util.List; @@ -18,7 +22,7 @@ import java.util.TreeSet; /** - * Util class to encapsulate {@link Report} generation logic. + * Util class to encapsulate {@link RecordReport} generation logic. * Created on 2/26/16. * * @author Evgenii Kanivets @@ -31,11 +35,11 @@ public ReportMaker(ExchangeRateController exchangeRateController) { } @Nullable - public IReport getReport(String currency, Period period, List recordList) { + public IRecordReport getRecordReport(String currency, Period period, List recordList) { if (currencyNeeded(currency, recordList).size() != 0) return null; IExchangeRateProvider rateProvider = new ExchangeRateProvider(currency, rateController); - return new Report(currency, period, recordList, rateProvider); + return new RecordReport(currency, period, recordList, rateProvider); } @Nullable @@ -46,6 +50,14 @@ public IAccountsReport getAccountsReport(String currency, List accountL return new AccountsReport(currency, accountList, rateProvider); } + @Nullable + public IMonthReport getMonthReport(String currency, List recordList) { + if (currencyNeeded(currency, recordList).size() != 0) return null; + + IExchangeRateProvider rateProvider = new ExchangeRateProvider(currency, rateController); + return new MonthReport(recordList, currency, rateProvider); + } + @NonNull public List currencyNeeded(String currency, List recordList) { Set currencies = new TreeSet<>(); diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/AccountsReport.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/account/AccountsReport.java similarity index 92% rename from app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/AccountsReport.java rename to app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/account/AccountsReport.java index 69a9ff8..50f3635 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/AccountsReport.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/account/AccountsReport.java @@ -1,10 +1,10 @@ -package com.blogspot.e_kanivets.moneytracker.report; +package com.blogspot.e_kanivets.moneytracker.report.account; import android.support.annotation.NonNull; import com.blogspot.e_kanivets.moneytracker.entity.data.Account; import com.blogspot.e_kanivets.moneytracker.entity.data.ExchangeRate; -import com.blogspot.e_kanivets.moneytracker.report.base.IAccountsReport; +import com.blogspot.e_kanivets.moneytracker.report.account.IAccountsReport; import com.blogspot.e_kanivets.moneytracker.report.base.IExchangeRateProvider; import java.util.List; diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/base/IAccountsReport.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/account/IAccountsReport.java similarity index 87% rename from app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/base/IAccountsReport.java rename to app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/account/IAccountsReport.java index 3fbcfa0..e046e61 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/base/IAccountsReport.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/account/IAccountsReport.java @@ -1,4 +1,4 @@ -package com.blogspot.e_kanivets.moneytracker.report.base; +package com.blogspot.e_kanivets.moneytracker.report.account; import android.support.annotation.NonNull; diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/BarChartConverter.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/BarChartConverter.java new file mode 100644 index 0000000..8855acb --- /dev/null +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/BarChartConverter.java @@ -0,0 +1,78 @@ +package com.blogspot.e_kanivets.moneytracker.report.chart; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.annotation.NonNull; + +import com.blogspot.e_kanivets.moneytracker.R; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Util class to convert {@link IMonthReport} to {@link com.github.mikephil.charting.charts.BarChart} + * input data. + * Created on 4/27/16. + * + * @author Evgenii Kanivets + */ +public class BarChartConverter { + private final IMonthReport report; + + private final int green; + private final int red; + private final String incomesTitle; + private final String expensesTitle; + + @SuppressWarnings("deprecation") + public BarChartConverter(@NonNull Context context, @NonNull IMonthReport report) { + this.report = report; + + green = context.getResources().getColor(R.color.green_light); + red = context.getResources().getColor(R.color.red_light); + incomesTitle = context.getString(R.string.incomes); + expensesTitle = context.getString(R.string.expenses); + } + + @NonNull + public List getXAxisValueList() { + List valueList = new ArrayList<>(); + + @SuppressLint("SimpleDateFormat") SimpleDateFormat sdf = new SimpleDateFormat("MMM yy"); + for (long timestamp : report.getMonthList()) { + valueList.add(sdf.format(new Date(timestamp))); + } + + return valueList; + } + + @NonNull + public List getBarDataSetList() { + List incomeList = new ArrayList<>(); + for (int i = 0; i < report.getIncomeList().size(); i++) { + incomeList.add(new BarEntry(report.getIncomeList().get(i).floatValue(), i)); + } + + BarDataSet incomeDataSet = new BarDataSet(incomeList, incomesTitle); + incomeDataSet.setColor(green); + + List expenseList = new ArrayList<>(); + for (int i = 0; i < report.getExpenseList().size(); i++) { + expenseList.add(new BarEntry(report.getExpenseList().get(i).floatValue(), i)); + } + + BarDataSet dataSet2 = new BarDataSet(expenseList, expensesTitle); + dataSet2.setColor(red); + + List list = new ArrayList<>(); + list.add(incomeDataSet); + list.add(dataSet2); + + return list; + } +} diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/IMonthReport.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/IMonthReport.java new file mode 100644 index 0000000..00e449a --- /dev/null +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/IMonthReport.java @@ -0,0 +1,37 @@ +package com.blogspot.e_kanivets.moneytracker.report.chart; + +import android.support.annotation.NonNull; + +import java.util.List; + +/** + * Interface that represents a contract of access to record report data grouped by month + * for all records. All three methods must return list of the same size. + * Created on 4/28/16. + * + * @author Evgenii Kanivets + */ +public interface IMonthReport { + /** + * @return code of report currency + */ + @NonNull String getCurrency(); + + /** + * @return list of month timestamps with not zero record count + */ + @NonNull + List getMonthList(); + + /** + * @return list of summary month incomes + */ + @NonNull + List getIncomeList(); + + /** + * @return list of summary month expenses + */ + @NonNull + List getExpenseList(); +} diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/MonthReport.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/MonthReport.java new file mode 100644 index 0000000..8f582e1 --- /dev/null +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/chart/MonthReport.java @@ -0,0 +1,167 @@ +package com.blogspot.e_kanivets.moneytracker.report.chart; + +import android.support.annotation.NonNull; + +import com.blogspot.e_kanivets.moneytracker.entity.data.ExchangeRate; +import com.blogspot.e_kanivets.moneytracker.entity.data.Record; +import com.blogspot.e_kanivets.moneytracker.report.base.IExchangeRateProvider; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * First {@link IMonthReport} implementation. + * Created on 4/28/16. + * + * @author Evgenii Kanivets + */ +public class MonthReport implements IMonthReport { + private final String currency; + private final IExchangeRateProvider rateProvider; + + private final List monthList; + + public MonthReport(List recordList, String currency, IExchangeRateProvider rateProvider) { + if (recordList == null || currency == null || rateProvider == null) + throw new NullPointerException("Params can't be null"); + + this.currency = currency; + this.rateProvider = rateProvider; + + monthList = generateReport(recordList); + } + + @NonNull + @Override + public String getCurrency() { + return currency; + } + + @NonNull + @Override + public List getMonthList() { + List resultList = new ArrayList<>(); + + for (MonthNode node : monthList) { + resultList.add(node.getTimestamp()); + } + + return resultList; + } + + @NonNull + @Override + public List getIncomeList() { + List resultList = new ArrayList<>(); + + for (MonthNode node : monthList) { + resultList.add(node.getTotalIncome()); + } + + return resultList; + } + + @NonNull + @Override + public List getExpenseList() { + List resultList = new ArrayList<>(); + + for (MonthNode node : monthList) { + resultList.add(node.getTotalExpense()); + } + + return resultList; + } + + /** + * @param recordList to generate report on + * @return sorted by timestamp list of {@link MonthNode} + */ + @NonNull + private List generateReport(List recordList) { + SortedMap monthMap = new TreeMap<>(); + + for (Record record : recordList) { + long timestamp = getMonthTimestamp(record.getTime()); + + if (monthMap.get(timestamp) == null) monthMap.put(timestamp, new MonthNode(timestamp)); + MonthNode node = monthMap.get(timestamp); + + double convertedPrice = record.getPrice(); + if (!currency.equals(record.getCurrency())) { + ExchangeRate exchangeRate = rateProvider.getRate(record); + if (exchangeRate == null) throw new NullPointerException("No exchange rate found"); + convertedPrice *= exchangeRate.getAmount(); + } + + switch (record.getType()) { + case Record.TYPE_INCOME: + node.addIncome(convertedPrice); + break; + + case Record.TYPE_EXPENSE: + node.addExpense(convertedPrice); + break; + + default: + break; + } + } + + List resultList = new ArrayList<>(); + for (Long timestamp : monthMap.keySet()) { + resultList.add(monthMap.get(timestamp)); + } + + return resultList; + } + + private long getMonthTimestamp(long timestamp) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(timestamp)); + + calendar.set(Calendar.DAY_OF_MONTH, 0); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + + return calendar.getTimeInMillis(); + } + + private static class MonthNode { + private long timestamp; + private double totalIncome; + private double totalExpense; + + public MonthNode(long timestamp) { + this.timestamp = timestamp; + totalExpense = 0; + totalIncome = 0; + } + + public void addIncome(double income) { + totalIncome += income; + } + + public void addExpense(double expense) { + totalExpense += expense; + } + + public long getTimestamp() { + return timestamp; + } + + public double getTotalIncome() { + return totalIncome; + } + + public double getTotalExpense() { + return totalExpense; + } + } +} diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/base/IReport.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/IRecordReport.java similarity index 82% rename from app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/base/IReport.java rename to app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/IRecordReport.java index 086bfd7..90d8e72 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/base/IReport.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/IRecordReport.java @@ -1,9 +1,9 @@ -package com.blogspot.e_kanivets.moneytracker.report.base; +package com.blogspot.e_kanivets.moneytracker.report.record; import android.support.annotation.NonNull; import com.blogspot.e_kanivets.moneytracker.entity.Period; -import com.blogspot.e_kanivets.moneytracker.report.model.CategoryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.model.CategoryRecord; import java.util.List; @@ -13,7 +13,7 @@ * * @author Evgenii Kanivets */ -public interface IReport { +public interface IRecordReport { /** * @return code of report currency */ diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/Report.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/RecordReport.java similarity index 92% rename from app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/Report.java rename to app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/RecordReport.java index cc4a4d2..bac23af 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/Report.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/RecordReport.java @@ -1,4 +1,4 @@ -package com.blogspot.e_kanivets.moneytracker.report; +package com.blogspot.e_kanivets.moneytracker.report.record; import android.support.annotation.NonNull; @@ -6,9 +6,8 @@ import com.blogspot.e_kanivets.moneytracker.entity.Period; import com.blogspot.e_kanivets.moneytracker.entity.data.Record; import com.blogspot.e_kanivets.moneytracker.report.base.IExchangeRateProvider; -import com.blogspot.e_kanivets.moneytracker.report.base.IReport; -import com.blogspot.e_kanivets.moneytracker.report.model.CategoryRecord; -import com.blogspot.e_kanivets.moneytracker.report.model.SummaryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.model.CategoryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.model.SummaryRecord; import java.util.ArrayList; import java.util.Collections; @@ -18,14 +17,14 @@ import java.util.TreeMap; /** - * First {@link IReport} implementation. + * First {@link IRecordReport} implementation. * Created on 2/25/16. * * @author Evgenii Kanivets */ -public class Report implements IReport { +public class RecordReport implements IRecordReport { @SuppressWarnings("unused") - private static final String TAG = "Report"; + private static final String TAG = "RecordReport"; private String currency; private Period period; @@ -35,7 +34,7 @@ public class Report implements IReport { private double totalExpense; private List categoryRecordList; - public Report(String currency, Period period, List recordList, IExchangeRateProvider rateProvider) { + public RecordReport(String currency, Period period, List recordList, IExchangeRateProvider rateProvider) { if (currency == null || period == null || recordList == null || rateProvider == null) throw new NullPointerException("Params can't be null"); diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/ReportConverter.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/RecordReportConverter.java similarity index 82% rename from app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/ReportConverter.java rename to app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/RecordReportConverter.java index 912c602..07ad91a 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/ReportConverter.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/RecordReportConverter.java @@ -1,12 +1,11 @@ -package com.blogspot.e_kanivets.moneytracker.report; +package com.blogspot.e_kanivets.moneytracker.report.record; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import com.blogspot.e_kanivets.moneytracker.R; -import com.blogspot.e_kanivets.moneytracker.report.base.IReport; -import com.blogspot.e_kanivets.moneytracker.report.model.CategoryRecord; -import com.blogspot.e_kanivets.moneytracker.report.model.SummaryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.model.CategoryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.model.SummaryRecord; import java.util.ArrayList; import java.util.HashMap; @@ -14,18 +13,18 @@ import java.util.Map; /** - * Util class to convert {@link Report} to {@link android.widget.ExpandableListView} input data. + * Util class to convert {@link RecordReport} to {@link android.widget.ExpandableListView} input data. * Created on 2/26/16. * * @author Evgenii Kanivets */ -public class ReportConverter { +public class RecordReportConverter { public static final String TITLE_PARAM_NAME = "title"; public static final String PRICE_PARAM_NAME = "price"; - private final IReport report; + private final IRecordReport report; - public ReportConverter(@NonNull IReport report) { + public RecordReportConverter(@NonNull IRecordReport report) { this.report = report; } diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/model/CategoryRecord.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/model/CategoryRecord.java similarity index 95% rename from app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/model/CategoryRecord.java rename to app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/model/CategoryRecord.java index 306a59d..3563c06 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/model/CategoryRecord.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/model/CategoryRecord.java @@ -1,4 +1,4 @@ -package com.blogspot.e_kanivets.moneytracker.report.model; +package com.blogspot.e_kanivets.moneytracker.report.record.model; import android.support.annotation.NonNull; diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/model/SummaryRecord.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/model/SummaryRecord.java similarity index 95% rename from app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/model/SummaryRecord.java rename to app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/model/SummaryRecord.java index 94bd588..e60ab03 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/model/SummaryRecord.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/report/record/model/SummaryRecord.java @@ -1,4 +1,4 @@ -package com.blogspot.e_kanivets.moneytracker.report.model; +package com.blogspot.e_kanivets.moneytracker.report.record.model; import android.support.annotation.NonNull; diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/AccountsSummaryPresenter.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/AccountsSummaryPresenter.java index a0c75b2..31534ff 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/AccountsSummaryPresenter.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/AccountsSummaryPresenter.java @@ -14,7 +14,7 @@ import com.blogspot.e_kanivets.moneytracker.controller.data.AccountController; import com.blogspot.e_kanivets.moneytracker.controller.data.ExchangeRateController; import com.blogspot.e_kanivets.moneytracker.report.ReportMaker; -import com.blogspot.e_kanivets.moneytracker.report.base.IAccountsReport; +import com.blogspot.e_kanivets.moneytracker.report.account.IAccountsReport; import com.blogspot.e_kanivets.moneytracker.ui.presenter.base.BaseSummaryPresenter; import java.util.List; diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/ShortSummaryPresenter.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/ShortSummaryPresenter.java index 20b329f..2faeea6 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/ShortSummaryPresenter.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/ui/presenter/ShortSummaryPresenter.java @@ -6,7 +6,7 @@ import android.widget.TextView; import com.blogspot.e_kanivets.moneytracker.R; -import com.blogspot.e_kanivets.moneytracker.report.base.IReport; +import com.blogspot.e_kanivets.moneytracker.report.record.IRecordReport; import com.blogspot.e_kanivets.moneytracker.ui.presenter.base.BaseSummaryPresenter; import java.util.List; @@ -46,7 +46,7 @@ public View create(boolean shortSummary) { return view; } - public void update(IReport report, String currency, List ratesNeeded) { + public void update(IRecordReport report, String currency, List ratesNeeded) { ViewHolder viewHolder = (ViewHolder) view.getTag(); if (report == null) { diff --git a/app/src/main/res/drawable-xxxhdpi/ic_charts.png b/app/src/main/res/drawable-xxxhdpi/ic_charts.png new file mode 100755 index 0000000..b7c5e7d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_charts.png differ diff --git a/app/src/main/res/layout/activity_charts.xml b/app/src/main/res/layout/activity_charts.xml new file mode 100644 index 0000000..9c3df6a --- /dev/null +++ b/app/src/main/res/layout/activity_charts.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + \ 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 7a8b4a4..2865924 100644 --- a/app/src/main/res/menu/menu_nav_drawer.xml +++ b/app/src/main/res/menu/menu_nav_drawer.xml @@ -9,13 +9,20 @@ android:id="@+id/nav_rates" android:icon="@drawable/ic_exchange_rates" android:title="@string/title_exchange_rates" /> + + + + - + Счет по умолчанию Валюта по умолчанию + Графики + Доходы + Расходы + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 6700f77..85db8a6 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -59,4 +59,8 @@ Рахунок за замовчуванням Валюта за замовчуванням + Графіки + Доходи + Витрати + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 04a347c..65223a1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,7 +45,7 @@ To Exchange rates Add exchange rate - "]]> + " -> " Account was removed 1 = @@ -65,6 +65,9 @@ %1$s - %2$s Default currency - + Charts + Incomes + Expenses + diff --git a/app/src/test/java/com/blogspot/e_kanivets/moneytracker/report/ReportTest.java b/app/src/test/java/com/blogspot/e_kanivets/moneytracker/report/ReportTest.java index 68e743e..9ab1d7f 100644 --- a/app/src/test/java/com/blogspot/e_kanivets/moneytracker/report/ReportTest.java +++ b/app/src/test/java/com/blogspot/e_kanivets/moneytracker/report/ReportTest.java @@ -9,9 +9,10 @@ import com.blogspot.e_kanivets.moneytracker.entity.data.Record; import com.blogspot.e_kanivets.moneytracker.entity.Period; import com.blogspot.e_kanivets.moneytracker.report.base.IExchangeRateProvider; -import com.blogspot.e_kanivets.moneytracker.report.base.IReport; -import com.blogspot.e_kanivets.moneytracker.report.model.CategoryRecord; -import com.blogspot.e_kanivets.moneytracker.report.model.SummaryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.IRecordReport; +import com.blogspot.e_kanivets.moneytracker.report.record.model.CategoryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.model.SummaryRecord; +import com.blogspot.e_kanivets.moneytracker.report.record.RecordReport; import org.junit.After; import org.junit.Before; @@ -47,13 +48,13 @@ public void tearDown() throws Exception { @Test public void testForNulls() throws Exception { - IReport report; + IRecordReport report; Period period = new Period(new Date(1), new Date(), Period.TYPE_CUSTOM); List recordList = new ArrayList<>(); try { - report = new Report(null, period, recordList, rateProvider); + report = new RecordReport(null, period, recordList, rateProvider); } catch (NullPointerException e) { report = null; } @@ -61,7 +62,7 @@ public void testForNulls() throws Exception { assertNull(report); try { - report = new Report(currency, null, recordList, rateProvider); + report = new RecordReport(currency, null, recordList, rateProvider); } catch (NullPointerException e) { report = null; } @@ -69,7 +70,7 @@ public void testForNulls() throws Exception { assertNull(report); try { - report = new Report(currency, period, null, rateProvider); + report = new RecordReport(currency, period, null, rateProvider); } catch (NullPointerException e) { report = null; } @@ -77,7 +78,7 @@ public void testForNulls() throws Exception { assertNull(report); try { - report = new Report(currency, period, recordList, null); + report = new RecordReport(currency, period, recordList, null); } catch (NullPointerException e) { report = null; } @@ -85,7 +86,7 @@ public void testForNulls() throws Exception { assertNull(report); try { - report = new Report(null, null, null, null); + report = new RecordReport(null, null, null, null); } catch (NullPointerException e) { report = null; } @@ -93,7 +94,7 @@ public void testForNulls() throws Exception { assertNull(report); try { - report = new Report(currency, period, recordList, rateProvider); + report = new RecordReport(currency, period, recordList, rateProvider); } catch (NullPointerException e) { report = null; } @@ -106,12 +107,12 @@ public void testGetCurrency() throws Exception { Period period = new Period(new Date(1), new Date(), Period.TYPE_CUSTOM); List recordList = new ArrayList<>(); - IReport report = new Report(currency, period, recordList, rateProvider); + IRecordReport report = new RecordReport(currency, period, recordList, rateProvider); assertEquals(currency, report.getCurrency()); currency = "KHI"; - report = new Report(currency, period, recordList, rateProvider); + report = new RecordReport(currency, period, recordList, rateProvider); assertEquals(currency, report.getCurrency()); } @@ -121,12 +122,12 @@ public void testGetPeriod() throws Exception { Period period = new Period(new Date(1), new Date(), Period.TYPE_CUSTOM); List recordList = new ArrayList<>(); - IReport report = new Report(currency, period, recordList, rateProvider); + IRecordReport report = new RecordReport(currency, period, recordList, rateProvider); assertEquals(period, report.getPeriod()); period = new Period(new Date(3), new Date(100), Period.TYPE_CUSTOM); - report = new Report(currency, period, recordList, rateProvider); + report = new RecordReport(currency, period, recordList, rateProvider); assertEquals(period, report.getPeriod()); } @@ -137,7 +138,7 @@ public void testGetTotal() throws Exception { List recordList = getRecordList(); - IReport report = new Report(currency, period, recordList, rateProvider); + IRecordReport report = new RecordReport(currency, period, recordList, rateProvider); double expectedTotal = 10 * 4 - 2 + 5 - 10 * 4; assertEquals(expectedTotal, report.getTotal(), 0.0000000001); @@ -149,7 +150,7 @@ public void testGetTotalIncome() throws Exception { List recordList = getRecordList(); - IReport report = new Report(currency, period, recordList, rateProvider); + IRecordReport report = new RecordReport(currency, period, recordList, rateProvider); double expectedTotal = 10 * 4 + 5; assertEquals(expectedTotal, report.getTotalIncome(), 0.0000000001); @@ -161,7 +162,7 @@ public void testGetTotalExpense() throws Exception { List recordList = getRecordList(); - IReport report = new Report(currency, period, recordList, rateProvider); + IRecordReport report = new RecordReport(currency, period, recordList, rateProvider); double expectedTotal = -2 - 10 * 4; assertEquals(expectedTotal, report.getTotalExpense(), 0.0000000001); @@ -186,7 +187,7 @@ public void testGetSummary() throws Exception { Record record4 = new Record(4, 3, Record.TYPE_EXPENSE, "4", category, 10, account2, "USD"); recordList.add(record4); - IReport report = new Report(currency, period, recordList, rateProvider); + IRecordReport report = new RecordReport(currency, period, recordList, rateProvider); List categoryRecordList = new ArrayList<>(); diff --git a/build.gradle b/build.gradle index b180567..9ebcc91 100644 --- a/build.gradle +++ b/build.gradle @@ -11,8 +11,7 @@ buildscript { allprojects { repositories { mavenCentral() - maven{ - url 'https://oss.sonatype.org/content/repositories/snapshots/' - } + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } + maven { url "https://jitpack.io" } } } \ No newline at end of file