diff --git a/README.md b/README.md index a0d45a7..0c7c4da 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Money Tracker +Get it on Google Play + ## Short overview Money Tracker is an Android native application with open source code. Development of this project is guided by simplicity and effeciency for end user. diff --git a/app/build.gradle b/app/build.gradle index 4c2a881..69f17d5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId 'com.blogspot.e_kanivets.moneytracker' minSdkVersion 17 targetSdkVersion 23 - versionCode 12 - versionName '1.7.0' + versionCode 14 + versionName '1.7.6' } signingConfigs { releaseConfig { diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/external/BackupActivity.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/external/BackupActivity.java index 27c66e0..5a44458 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/external/BackupActivity.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/activity/external/BackupActivity.java @@ -94,6 +94,8 @@ public void backupNow() { @Override public void onBackupSuccess() { Timber.d("Backup success."); + if (isFinishing()) return; + stopProgress(); fetchBackups(); } @@ -101,6 +103,8 @@ public void onBackupSuccess() { @Override public void onBackupFailure(String reason) { Timber.d("Backup failure."); + if (isFinishing()) return; + stopProgress(); showToast(R.string.failed_create_backup); @@ -132,6 +136,8 @@ private void restoreBackup(final String backupName) { @Override public void onRestoreSuccess() { Timber.d("Restore success."); + if (isFinishing()) return; + stopProgress(); AlertDialog.Builder builder = new AlertDialog.Builder(BackupActivity.this); @@ -152,6 +158,8 @@ public void onDismiss(DialogInterface dialog) { @Override public void onRestoreFailure(String reason) { Timber.d("Restore failure."); + if (isFinishing()) return; + stopProgress(); showToast(R.string.failed_restore_backup); @@ -166,6 +174,8 @@ private void fetchBackups() { backupController.fetchBackups(dbApi, new BackupController.OnFetchBackupListListener() { @Override public void onBackupsFetched(@NonNull List backupList) { + if (isFinishing()) return; + stopProgress(); ArrayAdapter adapter = new ArrayAdapter<>(BackupActivity.this, android.R.layout.simple_list_item_1, backupList); diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/CategoryAutoCompleteAdapter.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/CategoryAutoCompleteAdapter.java index 22103b7..234c1bc 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/CategoryAutoCompleteAdapter.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/adapter/CategoryAutoCompleteAdapter.java @@ -1,15 +1,23 @@ package com.blogspot.e_kanivets.moneytracker.adapter; import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Filter; import android.widget.Filterable; +import android.widget.TextView; +import com.blogspot.e_kanivets.moneytracker.R; import com.blogspot.e_kanivets.moneytracker.util.CategoryAutoCompleter; import java.util.ArrayList; import java.util.List; +import butterknife.Bind; +import butterknife.ButterKnife; + /** * Custom adapter to autocomplete categories. * Created on 3/18/16. @@ -25,6 +33,31 @@ public CategoryAutoCompleteAdapter(Context context, int resource, CategoryAutoCo this.autoCompleter = autoCompleter; } + @Override + public View getView(int position, View convertView, ViewGroup parent) { + ViewHolder viewHolder; + + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.view_category_item, parent, false); + viewHolder = new ViewHolder(convertView); + convertView.setTag(viewHolder); + } else viewHolder = (ViewHolder) convertView.getTag(); + + final String category = resultList.get(position); + + viewHolder.tvCategory.setText(category); + viewHolder.ivCancel.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + autoCompleter.removeFromAutoComplete(category); + resultList.remove(category); + notifyDataSetChanged(); + } + }); + + return convertView; + } + @Override public int getCount() { return resultList.size(); @@ -63,4 +96,15 @@ protected void publishResults(CharSequence constraint, FilterResults results) { } }; } + + public static class ViewHolder { + @Bind(R.id.tv_category) + TextView tvCategory; + @Bind(R.id.iv_cancel) + View ivCancel; + + public ViewHolder(View view) { + ButterKnife.bind(this, view); + } + } } diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/PreferenceController.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/PreferenceController.java index a3130f3..266866b 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/PreferenceController.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/PreferenceController.java @@ -8,6 +8,9 @@ import com.blogspot.e_kanivets.moneytracker.R; +import java.util.HashSet; +import java.util.Set; + /** * Controller class to encapsulate Shared Preferences handling logic. * Not deal with {@link com.blogspot.e_kanivets.moneytracker.repo.base.IRepo} instances as others. @@ -22,6 +25,7 @@ public class PreferenceController { private static final String KEY_LAST_TS = "key_last_ts"; private static final String KEY_PERIOD_TYPE = "key_period_type"; private static final String KEY_DROPBOX_ACCESS_TOKEN = "key_dropbox_access_token"; + private static final String KEY_FILTERED_CATEGORIES = "key_filtered_categories"; private static final int RATE_PERIOD = 5; @@ -34,7 +38,7 @@ public PreferenceController(@NonNull Context context) { public void addLaunchCount() { SharedPreferences preferences = getDefaultPrefs(); - SharedPreferences.Editor editor = preferences.edit(); + SharedPreferences.Editor editor = getEditor(); editor.putInt(LAUNCH_COUNT, preferences.getInt(LAUNCH_COUNT, 0) + 1); editor.apply(); } @@ -50,16 +54,13 @@ public boolean checkRateDialog() { } public void appRated() { - SharedPreferences preferences = getDefaultPrefs(); - SharedPreferences.Editor editor = preferences.edit(); + SharedPreferences.Editor editor = getEditor(); editor.putBoolean(APP_RATED, true); editor.apply(); } public void writeFirstTs(long firstTs) { - SharedPreferences preferences = getDefaultPrefs(); - SharedPreferences.Editor editor = preferences.edit(); - + SharedPreferences.Editor editor = getEditor(); editor.putLong(KEY_FIRST_TS, firstTs); editor.apply(); } @@ -73,21 +74,23 @@ public void writeLastTs(long lastTs) { } public void writePeriodType(String periodType) { - SharedPreferences preferences = getDefaultPrefs(); - SharedPreferences.Editor editor = preferences.edit(); - + SharedPreferences.Editor editor = getEditor(); editor.putString(KEY_PERIOD_TYPE, periodType); editor.apply(); } public void writeDropboxAccessToken(String accessToken) { - SharedPreferences preferences = getDefaultPrefs(); - SharedPreferences.Editor editor = preferences.edit(); - + SharedPreferences.Editor editor = getEditor(); editor.putString(KEY_DROPBOX_ACCESS_TOKEN, accessToken); editor.apply(); } + public void writeFilteredCategories(@Nullable Set categorySet) { + SharedPreferences.Editor editor = getEditor(); + editor.putStringSet(KEY_FILTERED_CATEGORIES, categorySet); + editor.apply(); + } + public long readDefaultAccountId() { String defaultAccountPref = context.getString(R.string.pref_default_account); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); @@ -129,7 +132,18 @@ public String readDropboxAccessToken() { return getDefaultPrefs().getString(KEY_DROPBOX_ACCESS_TOKEN, null); } + @NonNull + public Set readFilteredCategories() { + return getDefaultPrefs().getStringSet(KEY_FILTERED_CATEGORIES, new HashSet()); + } + + @NonNull private SharedPreferences getDefaultPrefs() { return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); } + + @NonNull + private SharedPreferences.Editor getEditor() { + return getDefaultPrefs().edit(); + } } diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryController.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryController.java index e95ed12..dd8bbca 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryController.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryController.java @@ -3,12 +3,15 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.blogspot.e_kanivets.moneytracker.controller.PreferenceController; import com.blogspot.e_kanivets.moneytracker.repo.DbHelper; import com.blogspot.e_kanivets.moneytracker.controller.base.BaseController; import com.blogspot.e_kanivets.moneytracker.entity.data.Category; import com.blogspot.e_kanivets.moneytracker.repo.base.IRepo; +import java.util.ArrayList; import java.util.List; +import java.util.Set; /** * Controller class to encapsulate category handling logic. @@ -20,13 +23,23 @@ public class CategoryController extends BaseController { @SuppressWarnings("unused") private static final String TAG = "CategoryController"; - public CategoryController(@NonNull IRepo categoryRepo) { + @NonNull + private final PreferenceController preferenceController; + @NonNull + private Set filteredCategories; + + public CategoryController(@NonNull IRepo categoryRepo, + @NonNull PreferenceController preferenceController) { super(categoryRepo); + this.preferenceController = preferenceController; + filteredCategories = preferenceController.readFilteredCategories(); } public Category readOrCreate(@Nullable String categoryName) { if (categoryName == null || categoryName.trim().isEmpty()) return null; + enableCategory(categoryName); + String condition = DbHelper.NAME_COLUMN + "=?"; String[] args = {categoryName}; List categoryList = repo.readWithCondition(condition, args); @@ -34,4 +47,31 @@ public Category readOrCreate(@Nullable String categoryName) { if (categoryList.size() >= 1) return categoryList.get(0); else return repo.create(new Category(categoryName)); } -} \ No newline at end of file + + @NonNull + public List readFiltered() { + List filteredList = new ArrayList<>(); + + for (Category category : readAll()) { + if (!filteredCategories.contains(category.getName())) filteredList.add(category); + } + + return filteredList; + } + + /** + * @param category to disable when request filtered list. + */ + public void disableCategory(@Nullable String category) { + filteredCategories.add(category); + preferenceController.writeFilteredCategories(filteredCategories); + } + + /** + * @param category to enable when request filtered list. + */ + public void enableCategory(@Nullable String category) { + filteredCategories.remove(category); + preferenceController.writeFilteredCategories(filteredCategories); + } +} 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 d54f1be..af86465 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 @@ -52,8 +52,9 @@ public AccountController providesAccountController(IRepo accountRepo, @Provides @NonNull @Singleton - public CategoryController providesCategoryController(IRepo categoryRepo) { - return new CategoryController(categoryRepo); + public CategoryController providesCategoryController(IRepo categoryRepo, + PreferenceController preferenceController) { + return new CategoryController(categoryRepo, preferenceController); } @Provides diff --git a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/util/CategoryAutoCompleter.java b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/util/CategoryAutoCompleter.java index d1a7d87..86ea779 100644 --- a/app/src/main/java/com/blogspot/e_kanivets/moneytracker/util/CategoryAutoCompleter.java +++ b/app/src/main/java/com/blogspot/e_kanivets/moneytracker/util/CategoryAutoCompleter.java @@ -14,11 +14,13 @@ */ public class CategoryAutoCompleter { private List categoryList; + private CategoryController categoryController; public CategoryAutoCompleter(CategoryController categoryController) { + this.categoryController = categoryController; categoryList = new ArrayList<>(); - for (Category category : categoryController.readAll()) { + for (Category category : categoryController.readFiltered()) { categoryList.add(category.getName()); } } @@ -32,4 +34,9 @@ public List completeByPart(String part) { return resultList; } + + public void removeFromAutoComplete(String category) { + categoryList.remove(category); + categoryController.disableCategory(category); + } } diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 0000000..e247f84 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/layout/view_category_item.xml b/app/src/main/res/layout/view_category_item.xml index 88e399c..cb6c6b2 100644 --- a/app/src/main/res/layout/view_category_item.xml +++ b/app/src/main/res/layout/view_category_item.xml @@ -1,7 +1,28 @@ - \ No newline at end of file + android:gravity="center_vertical" + android:orientation="horizontal" + android:paddingEnd="@dimen/half_margin" + android:paddingStart="@dimen/normal_margin" + tools:ignore="UseCompoundDrawables"> + + + + + + diff --git a/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryControllerTest.java b/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryControllerTest.java index 057d1bd..c961f01 100644 --- a/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryControllerTest.java +++ b/app/src/test/java/com/blogspot/e_kanivets/moneytracker/controller/data/CategoryControllerTest.java @@ -3,10 +3,13 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.blogspot.e_kanivets.moneytracker.controller.PreferenceController; import com.blogspot.e_kanivets.moneytracker.entity.data.Category; import com.blogspot.e_kanivets.moneytracker.repo.base.IRepo; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.List; @@ -20,10 +23,16 @@ * @author Evgenii Kanivets */ public class CategoryControllerTest { + private PreferenceController mockPrefs; + + @Before + public void setUp() throws Exception { + mockPrefs = Mockito.mock(PreferenceController.class); + } @Test public void testReadOrCreate() throws Exception { - CategoryController categoryController = new CategoryController(emptyRepo); + CategoryController categoryController = new CategoryController(emptyRepo, mockPrefs); assertNull(categoryController.readOrCreate(null)); assertNull(categoryController.readOrCreate("")); @@ -34,7 +43,7 @@ public void testReadOrCreate() throws Exception { result = categoryController.readOrCreate("category 1"); assertEquals("category 1", result.getName()); - categoryController = new CategoryController(repo); + categoryController = new CategoryController(repo, mockPrefs); result = categoryController.readOrCreate("category 1"); assertEquals("category 1", result.getName());