From 85a91d0af0eeda8b6f68efba17335d3dc0677915 Mon Sep 17 00:00:00 2001 From: Billy Brawner Date: Sun, 16 Sep 2018 19:57:47 -0500 Subject: [PATCH] Add categories to transactions --- .project | 17 +++ .settings/org.eclipse.buildship.core.prefs | 2 + app/.classpath | 6 + app/.project | 23 ++++ .../org.eclipse.buildship.core.prefs | 2 + app/build.gradle | 10 +- .../2.json | 113 ++++++++++++++++++ app/src/main/AndroidManifest.xml | 3 +- .../wbrawner/budget/AllowanceApplication.kt | 24 ++-- .../categories/AddEditCategoryActivity.kt | 96 +++++++++++++++ .../budget/categories/CategoryAdapter.kt | 64 ++++++++++ .../budget/categories/CategoryListFragment.kt | 69 +++++++++++ .../budget/categories/CategoryViewModel.kt | 26 ++++ .../wbrawner/budget/data/BudgetDatabase.kt | 16 +++ .../budget/data/CategoryRepository.kt | 44 +++++++ .../budget/data/TransactionRepository.kt | 40 ++----- .../budget/data/TransactionsDatabase.kt | 11 -- .../wbrawner/budget/data/TypeConverters.kt | 1 + .../wbrawner/budget/data/dao/CategoryDao.kt | 37 ++++++ .../budget/data/{ => dao}/TransactionDao.kt | 15 ++- .../budget/data/migrations/MIGRATION_1_2.kt | 13 ++ .../wbrawner/budget/data/model/Category.kt | 15 +++ .../budget/data/{ => model}/Transaction.kt | 4 +- .../budget/data/model/TransactionCategory.kt | 10 ++ .../data/model/TransactionWithCategory.kt | 14 +++ .../budget/overview/OverviewFragment.kt | 7 +- .../AddEditTransactionActivity.kt | 41 ++++++- .../budget/transactions/TransactionAdapter.kt | 27 +++-- .../transactions/TransactionListActivity.kt | 16 ++- .../transactions/TransactionListFragment.kt | 7 +- .../transactions/TransactionViewModel.kt | 17 +-- .../drawable/ic_baseline_category_24px.xml | 15 +++ .../res/layout/activity_add_edit_category.xml | 81 +++++++++++++ .../layout/activity_add_edit_transaction.xml | 23 +++- .../main/res/layout/list_item_category.xml | 50 ++++++++ app/src/main/res/menu/main_navigation.xml | 4 + app/src/main/res/values/strings.xml | 9 ++ app/src/main/res/xml/shortcuts.xml | 4 +- build.gradle | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 40 files changed, 885 insertions(+), 97 deletions(-) create mode 100644 .project create mode 100644 .settings/org.eclipse.buildship.core.prefs create mode 100644 app/.classpath create mode 100644 app/.project create mode 100644 app/.settings/org.eclipse.buildship.core.prefs create mode 100644 app/schemas/com.wbrawner.budget.data.BudgetDatabase/2.json create mode 100644 app/src/main/java/com/wbrawner/budget/categories/AddEditCategoryActivity.kt create mode 100644 app/src/main/java/com/wbrawner/budget/categories/CategoryAdapter.kt create mode 100644 app/src/main/java/com/wbrawner/budget/categories/CategoryListFragment.kt create mode 100644 app/src/main/java/com/wbrawner/budget/categories/CategoryViewModel.kt create mode 100644 app/src/main/java/com/wbrawner/budget/data/BudgetDatabase.kt create mode 100644 app/src/main/java/com/wbrawner/budget/data/CategoryRepository.kt delete mode 100644 app/src/main/java/com/wbrawner/budget/data/TransactionsDatabase.kt create mode 100644 app/src/main/java/com/wbrawner/budget/data/dao/CategoryDao.kt rename app/src/main/java/com/wbrawner/budget/data/{ => dao}/TransactionDao.kt (64%) create mode 100644 app/src/main/java/com/wbrawner/budget/data/migrations/MIGRATION_1_2.kt create mode 100644 app/src/main/java/com/wbrawner/budget/data/model/Category.kt rename app/src/main/java/com/wbrawner/budget/data/{ => model}/Transaction.kt (73%) create mode 100644 app/src/main/java/com/wbrawner/budget/data/model/TransactionCategory.kt create mode 100644 app/src/main/java/com/wbrawner/budget/data/model/TransactionWithCategory.kt rename app/src/main/java/com/wbrawner/budget/{addedittransaction => transactions}/AddEditTransactionActivity.kt (65%) create mode 100644 app/src/main/res/drawable/ic_baseline_category_24px.xml create mode 100644 app/src/main/res/layout/activity_add_edit_category.xml create mode 100644 app/src/main/res/layout/list_item_category.xml diff --git a/.project b/.project new file mode 100644 index 0000000..02da4bc --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + MyAllowance + Project MyAllowance created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..e889521 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/app/.classpath b/app/.classpath new file mode 100644 index 0000000..eb19361 --- /dev/null +++ b/app/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/.project b/app/.project new file mode 100644 index 0000000..ac485d7 --- /dev/null +++ b/app/.project @@ -0,0 +1,23 @@ + + + app + Project app created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + diff --git a/app/.settings/org.eclipse.buildship.core.prefs b/app/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..b1886ad --- /dev/null +++ b/app/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=.. +eclipse.preferences.version=1 diff --git a/app/build.gradle b/app/build.gradle index e1cfc8d..3d11e8a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,7 +18,7 @@ android { } defaultConfig { applicationId "com.wbrawner.budget" - minSdkVersion 23 + minSdkVersion 24 targetSdkVersion 27 versionCode 1 versionName "1.0" @@ -29,6 +29,11 @@ android { buildConfigField "String", "ACRA_URL", "\"${acra.getProperty("url")}\"" buildConfigField "String", "ACRA_USER", "\"${acra.getProperty("user")}\"" buildConfigField "String", "ACRA_PASS", "\"${acra.getProperty("pass")}\"" + javaCompileOptions { + annotationProcessorOptions { + arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] + } + } } buildTypes { release { @@ -36,6 +41,9 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + sourceSets { + androidTest.assets.srcDirs += files("$projectDir/schemas".toString()) + } } dependencies { diff --git a/app/schemas/com.wbrawner.budget.data.BudgetDatabase/2.json b/app/schemas/com.wbrawner.budget.data.BudgetDatabase/2.json new file mode 100644 index 0000000..73db9a0 --- /dev/null +++ b/app/schemas/com.wbrawner.budget.data.BudgetDatabase/2.json @@ -0,0 +1,113 @@ +{ + "formatVersion": 1, + "database": { + "version": 2, + "identityHash": "b68fd456557e5153b2fe6514a5193c07", + "entities": [ + { + "tableName": "Transaction", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `title` TEXT NOT NULL, `date` TEXT NOT NULL, `description` TEXT NOT NULL, `amount` REAL NOT NULL, `categoryId` INTEGER, `type` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Category", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT NOT NULL, `amount` REAL NOT NULL, `repeat` TEXT, `color` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "repeat", + "columnName": "repeat", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"b68fd456557e5153b2fe6514a5193c07\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0e89ae7..36c5963 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,7 +20,8 @@ android:name="android.app.shortcuts" android:resource="@xml/shortcuts" /> - + + \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/AllowanceApplication.kt b/app/src/main/java/com/wbrawner/budget/AllowanceApplication.kt index c0f379a..30efa53 100644 --- a/app/src/main/java/com/wbrawner/budget/AllowanceApplication.kt +++ b/app/src/main/java/com/wbrawner/budget/AllowanceApplication.kt @@ -3,9 +3,10 @@ package com.wbrawner.budget import android.app.Application import android.arch.persistence.room.Room import android.content.Context -import com.wbrawner.budget.data.TransactionDao -import com.wbrawner.budget.data.TransactionsDatabase -import com.wbrawner.budget.BuildConfig +import com.wbrawner.budget.data.dao.TransactionDao +import com.wbrawner.budget.data.BudgetDatabase +import com.wbrawner.budget.data.dao.CategoryDao +import com.wbrawner.budget.data.migrations.MIGRATION_1_2 import org.acra.ACRA import org.acra.annotation.AcraCore import org.acra.annotation.AcraHttpSender @@ -18,23 +19,28 @@ import org.acra.sender.HttpSender basicAuthPassword = BuildConfig.ACRA_PASS, httpMethod = HttpSender.Method.POST) class AllowanceApplication: Application() { - lateinit var database: TransactionsDatabase + lateinit var database: BudgetDatabase private set - lateinit var dao: TransactionDao + lateinit var transactionDao: TransactionDao + private set + + lateinit var categoryDao: CategoryDao private set override fun onCreate() { super.onCreate() - database = Room.databaseBuilder(applicationContext, TransactionsDatabase::class.java, "transactions") + database = Room.databaseBuilder(applicationContext, BudgetDatabase::class.java, "transactions") + .addMigrations(MIGRATION_1_2()) .build() - dao = database.dao() + transactionDao = database.transactionDao() + categoryDao = database.categoryDao() } override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) - ACRA.init(this) + if (!BuildConfig.DEBUG) ACRA.init(this) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/wbrawner/budget/categories/AddEditCategoryActivity.kt b/app/src/main/java/com/wbrawner/budget/categories/AddEditCategoryActivity.kt new file mode 100644 index 0000000..29cf14e --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/categories/AddEditCategoryActivity.kt @@ -0,0 +1,96 @@ +package com.wbrawner.budget.categories + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders +import android.graphics.Color +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.Menu +import android.view.MenuItem +import com.wbrawner.budget.R +import com.wbrawner.budget.data.model.Category +import kotlinx.android.synthetic.main.activity_add_edit_category.* + +class AddEditCategoryActivity : AppCompatActivity() { + lateinit var viewModel: CategoryViewModel + var id: Int? = null + var menu: Menu? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_add_edit_category) + setSupportActionBar(action_bar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + viewModel = ViewModelProviders.of(this).get(CategoryViewModel::class.java) + + if (intent?.hasExtra(EXTRA_CATEGORY_ID) == false) { + setTitle(R.string.title_add_category) + return + } + + viewModel.getCategory(intent!!.extras!!.getInt(EXTRA_CATEGORY_ID)) + .observe(this, Observer { category -> + if (category == null) { + menu?.findItem(R.id.action_delete)?.isVisible = false + return@Observer + } + id = category.id + setTitle(R.string.title_edit_category) + menu?.findItem(R.id.action_delete)?.isVisible = true + edit_category_name.setText(category.name) + edit_category_amount.setText(String.format("%.02f", category.amount)) + }) + } + + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_add_edit, menu) + if (id != null) { + menu?.findItem(R.id.action_delete)?.isVisible = true + } + return true + } + + override fun onOptionsItemSelected(item: MenuItem?): Boolean { + when (item?.itemId) { + android.R.id.home -> onBackPressed() + R.id.action_save -> { + if (!validateFields()) return true + viewModel.saveCategory(Category( + id = id, + name = edit_category_name.text.toString(), + amount = edit_category_amount.text.toString().toDouble(), + color = Color.parseColor("#FF0000"), + repeat = "never" + )) + finish() + } + R.id.action_delete -> { + viewModel.deleteCategoryById(this@AddEditCategoryActivity.id!!) + finish() + } + } + return true + } + + + private fun validateFields(): Boolean { + var errors = false + if (edit_category_name.text.isEmpty()) { + edit_category_name.error = getString(R.string.required_field_name) + errors = true + } + + if (edit_category_amount.text.isEmpty()) { + edit_category_amount.error = getString(R.string.required_field_amount) + errors = true + } + + return !errors + } + + + companion object { + const val EXTRA_CATEGORY_ID = "EXTRA_CATEGORY_ID" + } +} diff --git a/app/src/main/java/com/wbrawner/budget/categories/CategoryAdapter.kt b/app/src/main/java/com/wbrawner/budget/categories/CategoryAdapter.kt new file mode 100644 index 0000000..07d8005 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/categories/CategoryAdapter.kt @@ -0,0 +1,64 @@ +package com.wbrawner.budget.categories + +import android.arch.lifecycle.LifecycleOwner +import android.arch.lifecycle.Observer +import android.content.Intent +import android.support.v4.content.ContextCompat.startActivity +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ProgressBar +import android.widget.TextView +import com.wbrawner.budget.R +import com.wbrawner.budget.categories.AddEditCategoryActivity.Companion.EXTRA_CATEGORY_ID +import com.wbrawner.budget.data.model.Category + +class CategoryAdapter( + private val lifecycleOwner: LifecycleOwner, + private val data: List, + private val viewModel: CategoryViewModel +) : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.list_item_category, parent, false) + return ViewHolder(view) + } + + override fun getItemCount(): Int = data.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val category = data[position] + holder.title?.text = category.name + holder.amount?.text = String.format("${'$'}%.02f", category.amount) + viewModel.getCurrentBalance(category.id!!) + .observe(lifecycleOwner, Observer { balance -> + holder.progress?.isIndeterminate = false + if (balance == null) { + holder.progress?.progress = 0 + } else { + holder.progress?.max = category.amount.toInt() + holder.progress?.setProgress( + Math.abs(balance).toInt(), + true + ) + } + }) + holder.itemView.setOnClickListener { + startActivity( + it.context.applicationContext, + Intent(it.context.applicationContext, AddEditCategoryActivity::class.java) + .apply { + putExtra(EXTRA_CATEGORY_ID, category.id) + }, + null + ) + } + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val title: TextView? = itemView.findViewById(R.id.category_title) + val amount: TextView? = itemView.findViewById(R.id.category_amount) + val progress: ProgressBar? = itemView.findViewById(R.id.category_progress) + } +} diff --git a/app/src/main/java/com/wbrawner/budget/categories/CategoryListFragment.kt b/app/src/main/java/com/wbrawner/budget/categories/CategoryListFragment.kt new file mode 100644 index 0000000..0878b4e --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/categories/CategoryListFragment.kt @@ -0,0 +1,69 @@ +package com.wbrawner.budget.categories + +import android.arch.lifecycle.Observer +import android.arch.lifecycle.ViewModelProviders +import android.content.Intent +import android.os.Bundle +import android.support.design.widget.FloatingActionButton +import android.support.text.emoji.widget.EmojiTextView +import android.support.v4.app.Fragment +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.wbrawner.budget.R +import com.wbrawner.budget.data.model.Category + +class CategoryListFragment : Fragment() { + lateinit var viewModel: CategoryViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (activity == null) { + return + } + + if (savedInstanceState != null) { + return + } + + viewModel = ViewModelProviders.of(activity!!).get(CategoryViewModel::class.java) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_transaction_list, container, false) + val recyclerView = view.findViewById(R.id.list_transactions) + val fab = view.findViewById(R.id.fab_add_transaction) + recyclerView.layoutManager = LinearLayoutManager(activity) + viewModel.getCategories() + .observe(this, Observer> { data -> + val noDataView = view.findViewById(R.id.transaction_list_no_data) + if (data == null || data.isEmpty()) { + recyclerView.adapter = null + noDataView?.setText("No data FIX ME") + recyclerView?.visibility = View.GONE + noDataView?.visibility = View.VISIBLE + } else { + recyclerView.adapter = CategoryAdapter(this, data, viewModel) + recyclerView.visibility = View.VISIBLE + noDataView.visibility = View.GONE + } + }) + recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) { + if (dy > 0) fab.hide() else fab.show() + } + }) + fab.setOnClickListener { + startActivity(Intent(activity, AddEditCategoryActivity::class.java)) + } + + return view + } + + companion object { + const val TAG_FRAGMENT = "categories" + const val TITLE_FRAGMENT = R.string.title_categories + } +} diff --git a/app/src/main/java/com/wbrawner/budget/categories/CategoryViewModel.kt b/app/src/main/java/com/wbrawner/budget/categories/CategoryViewModel.kt new file mode 100644 index 0000000..3f351b7 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/categories/CategoryViewModel.kt @@ -0,0 +1,26 @@ +package com.wbrawner.budget.categories + +import android.app.Application +import android.arch.lifecycle.AndroidViewModel +import android.arch.lifecycle.LiveData +import com.wbrawner.budget.AllowanceApplication +import com.wbrawner.budget.data.model.Category +import com.wbrawner.budget.data.CategoryRepository + +class CategoryViewModel(application: Application): AndroidViewModel(application) { + private val categoryRepo = CategoryRepository((application as AllowanceApplication).categoryDao) + + fun getCategory(id: Int): LiveData = categoryRepo.getCategory(id) + + fun getCategories(): LiveData> = categoryRepo.getCategories() + + fun saveCategory(category: Category) = categoryRepo.save(category) + + fun deleteCategory(category: Category) = categoryRepo.delete(category) + + fun deleteCategoryById(id: Int) = categoryRepo.deleteById(id) + + fun getCurrentBalance(id: Int) = categoryRepo.getCurrentBalance(id) + + fun getTransactions(id: Int) = categoryRepo.getTransactions(id) +} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/BudgetDatabase.kt b/app/src/main/java/com/wbrawner/budget/data/BudgetDatabase.kt new file mode 100644 index 0000000..004a6b3 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/data/BudgetDatabase.kt @@ -0,0 +1,16 @@ +package com.wbrawner.budget.data + +import android.arch.persistence.room.Database +import android.arch.persistence.room.RoomDatabase +import android.arch.persistence.room.TypeConverters +import com.wbrawner.budget.data.dao.CategoryDao +import com.wbrawner.budget.data.dao.TransactionDao +import com.wbrawner.budget.data.model.Category +import com.wbrawner.budget.data.model.Transaction + +@Database(entities = [(Transaction::class), (Category::class)], version = 2) +@TypeConverters(DateTypeConverter::class, TransactionTypeTypeConverter::class) +abstract class BudgetDatabase: RoomDatabase() { + abstract fun transactionDao(): TransactionDao + abstract fun categoryDao(): CategoryDao +} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/CategoryRepository.kt b/app/src/main/java/com/wbrawner/budget/data/CategoryRepository.kt new file mode 100644 index 0000000..51168f8 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/data/CategoryRepository.kt @@ -0,0 +1,44 @@ +package com.wbrawner.budget.data + +import android.arch.lifecycle.LiveData +import android.os.Handler +import android.os.HandlerThread +import com.wbrawner.budget.data.dao.CategoryDao +import com.wbrawner.budget.data.model.Category +import com.wbrawner.budget.data.model.Transaction + +class CategoryRepository(private val dao: CategoryDao) { + private val handler: Handler + + init { + val thread = HandlerThread("category") + thread.start() + handler = Handler(thread.looper) + } + + fun getCategories(): LiveData> = dao.loadMultiple() + + + fun getCategory(id: Int): LiveData = dao.load(id) + + + fun save(category: Category) { + handler.post { dao.save(category) } + } + + + fun delete(category: Category) { + handler.post { dao.delete(category) } + } + + + fun deleteById(id: Int) { + handler.post { dao.deleteById(id) } + } + + + fun getCurrentBalance(id: Int): LiveData = dao.getBalanceForCategory(id) + + + fun getTransactions(id: Int): LiveData> = dao.getTransactions(id) +} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/TransactionRepository.kt b/app/src/main/java/com/wbrawner/budget/data/TransactionRepository.kt index 30babaa..8691d7a 100644 --- a/app/src/main/java/com/wbrawner/budget/data/TransactionRepository.kt +++ b/app/src/main/java/com/wbrawner/budget/data/TransactionRepository.kt @@ -3,10 +3,13 @@ package com.wbrawner.budget.data import android.arch.lifecycle.LiveData import android.os.Handler import android.os.HandlerThread +import com.wbrawner.budget.data.dao.TransactionDao +import com.wbrawner.budget.data.model.Transaction +import com.wbrawner.budget.data.model.TransactionCategory +import com.wbrawner.budget.data.model.TransactionWithCategory -class TransactionRepository(val dao: TransactionDao) { - val handler: Handler - val uiHandler: Handler = Handler() +class TransactionRepository(private val dao: TransactionDao) { + private val handler: Handler init { val thread = HandlerThread("transactions") @@ -14,36 +17,13 @@ class TransactionRepository(val dao: TransactionDao) { handler = Handler(thread.looper) } - fun getTransactionsByType(count: Int, type: TransactionType): LiveData> = + fun getTransactionsByType(count: Int, type: TransactionType): LiveData> = dao.loadMultipleByType(count, type) - fun getTransactions(count: Int): LiveData> = dao.loadMultiple(count) + fun getTransactions(count: Int): LiveData> = dao.loadMultiple(count) -// fun getTransactions(count: Int): LiveData> { -// val data = MutableLiveData>() -// -// handler.post { -// val transactions = ArrayList() -// for (i in 0..count) { -// transactions.add(Transaction( -// i, -// "Transaction $i", -// Date(), -// "Spent some money on something", -// (Math.random() * 100).toFloat(), -// TransactionType.EXPENSE -// )) -// } -// -// uiHandler.post { -// data.value = transactions -// } -// } -// -// return data -// } - fun getTransaction(id: Int): LiveData = dao.load(id) + fun getTransaction(id: Int): LiveData = dao.load(id) fun save(transaction: Transaction) { @@ -62,4 +42,6 @@ class TransactionRepository(val dao: TransactionDao) { fun getCurrentBalance(): LiveData = dao.getBalance() + + fun getCategories(): LiveData> = dao.loadCategories() } \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/TransactionsDatabase.kt b/app/src/main/java/com/wbrawner/budget/data/TransactionsDatabase.kt deleted file mode 100644 index 9df4892..0000000 --- a/app/src/main/java/com/wbrawner/budget/data/TransactionsDatabase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.wbrawner.budget.data - -import android.arch.persistence.room.Database -import android.arch.persistence.room.RoomDatabase -import android.arch.persistence.room.TypeConverters - -@Database(entities = [(Transaction::class)], version = 1, exportSchema = false) -@TypeConverters(DateTypeConverter::class, TransactionTypeTypeConverter::class) -abstract class TransactionsDatabase: RoomDatabase() { - abstract fun dao(): TransactionDao -} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/TypeConverters.kt b/app/src/main/java/com/wbrawner/budget/data/TypeConverters.kt index 8cbcc5b..603d6bf 100644 --- a/app/src/main/java/com/wbrawner/budget/data/TypeConverters.kt +++ b/app/src/main/java/com/wbrawner/budget/data/TypeConverters.kt @@ -1,5 +1,6 @@ package com.wbrawner.budget.data +import android.arch.persistence.room.TypeConverter import java.text.SimpleDateFormat import java.util.* diff --git a/app/src/main/java/com/wbrawner/budget/data/dao/CategoryDao.kt b/app/src/main/java/com/wbrawner/budget/data/dao/CategoryDao.kt new file mode 100644 index 0000000..e1617c0 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/data/dao/CategoryDao.kt @@ -0,0 +1,37 @@ +package com.wbrawner.budget.data.dao + +import android.arch.lifecycle.LiveData +import android.arch.persistence.room.Dao +import android.arch.persistence.room.Delete +import android.arch.persistence.room.Insert +import android.arch.persistence.room.OnConflictStrategy.REPLACE +import android.arch.persistence.room.Query +import com.wbrawner.budget.data.model.Category +import com.wbrawner.budget.data.model.Transaction + +@Dao +interface CategoryDao { + @Insert(onConflict = REPLACE) + fun save(category: Category) + + @Query("SELECT * FROM `Category` WHERE id = :id") + fun load(id: Int): LiveData + + @Query("SELECT * FROM `Category`") + fun loadMultiple(): LiveData> + + @Query("SELECT " + + "(SELECT TOTAL(amount) from `Transaction` WHERE type = 'INCOME' AND categoryId = :categoryId) " + + "- (SELECT TOTAL(amount) from `Transaction` WHERE type = 'EXPENSE' AND categoryId = :categoryId)") + fun getBalanceForCategory(categoryId: Int): LiveData + + @Delete + fun delete(category: Category) + + @Query("DELETE FROM `Category` WHERE id = :id") + fun deleteById(id: Int) + + + @Query("SELECT * FROM `Transaction` WHERE categoryId = :id") + fun getTransactions(id: Int): LiveData> +} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/TransactionDao.kt b/app/src/main/java/com/wbrawner/budget/data/dao/TransactionDao.kt similarity index 64% rename from app/src/main/java/com/wbrawner/budget/data/TransactionDao.kt rename to app/src/main/java/com/wbrawner/budget/data/dao/TransactionDao.kt index 3484722..4f20470 100644 --- a/app/src/main/java/com/wbrawner/budget/data/TransactionDao.kt +++ b/app/src/main/java/com/wbrawner/budget/data/dao/TransactionDao.kt @@ -1,4 +1,4 @@ -package com.wbrawner.budget.data +package com.wbrawner.budget.data.dao import android.arch.lifecycle.LiveData import android.arch.persistence.room.Dao @@ -6,6 +6,10 @@ import android.arch.persistence.room.Delete import android.arch.persistence.room.Insert import android.arch.persistence.room.OnConflictStrategy.REPLACE import android.arch.persistence.room.Query +import com.wbrawner.budget.data.model.Transaction +import com.wbrawner.budget.data.model.TransactionCategory +import com.wbrawner.budget.data.TransactionType +import com.wbrawner.budget.data.model.TransactionWithCategory @Dao interface TransactionDao { @@ -13,13 +17,13 @@ interface TransactionDao { fun save(transaction: Transaction) @Query("SELECT * FROM `Transaction` WHERE id = :id") - fun load(id: Int): LiveData + fun load(id: Int): LiveData @Query("SELECT * FROM `Transaction` LIMIT :count") - fun loadMultiple(count: Int): LiveData> + fun loadMultiple(count: Int): LiveData> @Query("SELECT * FROM `Transaction` WHERE type = :type LIMIT :count") - fun loadMultipleByType(count: Int, type: TransactionType): LiveData> + fun loadMultipleByType(count: Int, type: TransactionType): LiveData> @Query("SELECT (SELECT TOTAL(amount) from `Transaction` WHERE type = 'INCOME') - (SELECT TOTAL(amount) from `Transaction` WHERE type = 'EXPENSE')") fun getBalance(): LiveData @@ -29,4 +33,7 @@ interface TransactionDao { @Query("DELETE FROM `Transaction` WHERE id = :id") fun deleteById(id: Int) + + @Query("SELECT id,name from `Category`") + fun loadCategories(): LiveData> } \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/migrations/MIGRATION_1_2.kt b/app/src/main/java/com/wbrawner/budget/data/migrations/MIGRATION_1_2.kt new file mode 100644 index 0000000..ac73356 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/data/migrations/MIGRATION_1_2.kt @@ -0,0 +1,13 @@ +package com.wbrawner.budget.data.migrations + +import android.arch.persistence.db.SupportSQLiteDatabase +import android.arch.persistence.room.migration.Migration + +class MIGRATION_1_2: Migration(1, 2) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE `Category` (`id` INTEGER, `name` TEXT, `amount` REAL, " + + "`repeat` TEXT, `color` INTEGER PRIMARY KEY (`id`))") + database.execSQL("ALTER TABLE `Transaction` ADD COLUMN categoryId") + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/model/Category.kt b/app/src/main/java/com/wbrawner/budget/data/model/Category.kt new file mode 100644 index 0000000..40cb2f2 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/data/model/Category.kt @@ -0,0 +1,15 @@ +package com.wbrawner.budget.data.model + +import android.arch.persistence.room.Entity +import android.arch.persistence.room.PrimaryKey +import android.support.annotation.ColorInt + +@Entity +class Category( + @PrimaryKey + val id: Int?, + val name: String, + val amount: Double, + val repeat: String?, + @ColorInt val color: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/Transaction.kt b/app/src/main/java/com/wbrawner/budget/data/model/Transaction.kt similarity index 73% rename from app/src/main/java/com/wbrawner/budget/data/Transaction.kt rename to app/src/main/java/com/wbrawner/budget/data/model/Transaction.kt index 688af4d..b62a19a 100644 --- a/app/src/main/java/com/wbrawner/budget/data/Transaction.kt +++ b/app/src/main/java/com/wbrawner/budget/data/model/Transaction.kt @@ -1,7 +1,8 @@ -package com.wbrawner.budget.data +package com.wbrawner.budget.data.model import android.arch.persistence.room.Entity import android.arch.persistence.room.PrimaryKey +import com.wbrawner.budget.data.TransactionType import java.util.* @Entity @@ -12,5 +13,6 @@ class Transaction( val date: Date, val description: String, val amount: Double, + val categoryId: Int?, val type: TransactionType ) \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/model/TransactionCategory.kt b/app/src/main/java/com/wbrawner/budget/data/model/TransactionCategory.kt new file mode 100644 index 0000000..1b5acd5 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/data/model/TransactionCategory.kt @@ -0,0 +1,10 @@ +package com.wbrawner.budget.data.model + +class TransactionCategory( + val id: Int, + val name: String +) { + override fun toString(): String { + return name + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/data/model/TransactionWithCategory.kt b/app/src/main/java/com/wbrawner/budget/data/model/TransactionWithCategory.kt new file mode 100644 index 0000000..14bd314 --- /dev/null +++ b/app/src/main/java/com/wbrawner/budget/data/model/TransactionWithCategory.kt @@ -0,0 +1,14 @@ +package com.wbrawner.budget.data.model + +import android.arch.persistence.room.Embedded +import android.arch.persistence.room.Relation +import com.wbrawner.budget.data.model.Category +import com.wbrawner.budget.data.model.Transaction + +class TransactionWithCategory { + @Embedded + lateinit var transaction: Transaction + + @Relation(parentColumn = "id", entityColumn = "id") + lateinit var categorySet: Set +} \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/budget/overview/OverviewFragment.kt b/app/src/main/java/com/wbrawner/budget/overview/OverviewFragment.kt index e8d2b2e..19f5f3d 100644 --- a/app/src/main/java/com/wbrawner/budget/overview/OverviewFragment.kt +++ b/app/src/main/java/com/wbrawner/budget/overview/OverviewFragment.kt @@ -20,12 +20,7 @@ class OverviewFragment : Fragment() { return } - if (savedInstanceState != null) { - return - } - viewModel = ViewModelProviders.of(activity!!).get(TransactionViewModel::class.java) - } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -64,4 +59,4 @@ class OverviewFragment : Fragment() { const val TAG_FRAGMENT = "overview" const val TITLE_FRAGMENT = R.string.app_name } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/wbrawner/budget/addedittransaction/AddEditTransactionActivity.kt b/app/src/main/java/com/wbrawner/budget/transactions/AddEditTransactionActivity.kt similarity index 65% rename from app/src/main/java/com/wbrawner/budget/addedittransaction/AddEditTransactionActivity.kt rename to app/src/main/java/com/wbrawner/budget/transactions/AddEditTransactionActivity.kt index 99a008e..4ad04b0 100644 --- a/app/src/main/java/com/wbrawner/budget/addedittransaction/AddEditTransactionActivity.kt +++ b/app/src/main/java/com/wbrawner/budget/transactions/AddEditTransactionActivity.kt @@ -1,4 +1,4 @@ -package com.wbrawner.budget.addedittransaction +package com.wbrawner.budget.transactions import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModelProviders @@ -6,10 +6,12 @@ import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.view.Menu import android.view.MenuItem +import android.widget.ArrayAdapter import com.wbrawner.budget.R -import com.wbrawner.budget.data.Transaction +import com.wbrawner.budget.data.model.Transaction +import com.wbrawner.budget.data.model.TransactionCategory import com.wbrawner.budget.data.TransactionType -import com.wbrawner.budget.transactions.TransactionViewModel +import com.wbrawner.budget.data.model.TransactionWithCategory import kotlinx.android.synthetic.main.activity_add_edit_transaction.* import java.util.* @@ -26,6 +28,22 @@ class AddEditTransactionActivity : AppCompatActivity() { setSupportActionBar(action_bar) supportActionBar?.setDisplayHomeAsUpEnabled(true) viewModel = ViewModelProviders.of(this).get(TransactionViewModel::class.java) + viewModel.getCategories() + .observe(this, Observer> { categories -> + val adapter = ArrayAdapter( + this@AddEditTransactionActivity, + android.R.layout.simple_list_item_1 + ) + + adapter.add(TransactionCategory(0, getString(R.string.uncategorized))) + if (categories == null || categories.isEmpty()) { + return@Observer + } + + adapter.addAll(categories) + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + edit_transaction_category.adapter = adapter + }) if (intent?.hasExtra(EXTRA_TYPE) == true) { type = TransactionType.valueOf(intent?.extras?.getString(EXTRA_TYPE, "EXPENSE") ?: "EXPENSE") @@ -37,11 +55,12 @@ class AddEditTransactionActivity : AppCompatActivity() { } viewModel.getTransaction(intent!!.extras!!.getInt(EXTRA_TRANSACTION_ID)) - .observe(this, Observer { transaction -> - if (transaction == null) { + .observe(this, Observer { transactionWithCategory -> + if (transactionWithCategory == null) { menu?.findItem(R.id.action_delete)?.isVisible = false return@Observer } + val transaction = transactionWithCategory.transaction id = transaction.id type = transaction.type setTitle(type.editTitle) @@ -55,6 +74,15 @@ class AddEditTransactionActivity : AppCompatActivity() { val month = field.get(Calendar.MONTH) val day = field.get(Calendar.DAY_OF_MONTH) edit_transaction_date.updateDate(year, month, day) + if (transactionWithCategory.categorySet.isNotEmpty()) { + val category = transactionWithCategory.categorySet.first() + for (i in 0 until edit_transaction_category.adapter.count) { + if (category.id == (edit_transaction_category.adapter.getItem(i) as TransactionCategory).id) { + edit_transaction_category.setSelection(i) + break + } + } + } }) } @@ -81,7 +109,8 @@ class AddEditTransactionActivity : AppCompatActivity() { date = cal.time, description = edit_transaction_description.text.toString(), amount = edit_transaction_amount.text.toString().toDouble(), - type = type + type = type, + categoryId = (edit_transaction_category.selectedItem as TransactionCategory).id )) finish() } diff --git a/app/src/main/java/com/wbrawner/budget/transactions/TransactionAdapter.kt b/app/src/main/java/com/wbrawner/budget/transactions/TransactionAdapter.kt index fe5b416..6c05a20 100644 --- a/app/src/main/java/com/wbrawner/budget/transactions/TransactionAdapter.kt +++ b/app/src/main/java/com/wbrawner/budget/transactions/TransactionAdapter.kt @@ -8,13 +8,15 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import com.wbrawner.budget.R -import com.wbrawner.budget.addedittransaction.AddEditTransactionActivity -import com.wbrawner.budget.addedittransaction.AddEditTransactionActivity.Companion.EXTRA_TRANSACTION_ID -import com.wbrawner.budget.data.Transaction +import com.wbrawner.budget.transactions.AddEditTransactionActivity.Companion.EXTRA_TRANSACTION_ID +import com.wbrawner.budget.data.model.Transaction +import com.wbrawner.budget.data.model.TransactionWithCategory import java.text.SimpleDateFormat -class TransactionAdapter(private val data: List) - : RecyclerView.Adapter() { +class TransactionAdapter() : RecyclerView.Adapter() { + private lateinit var data: List + private lateinit var listType: Class + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.list_item_transaction, parent, false) @@ -24,7 +26,9 @@ class TransactionAdapter(private val data: List) override fun getItemCount(): Int = data.size override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val transaction = data[position] + val transaction: Transaction = + if (listType == TransactionWithCategory::class.java) (data[position] as TransactionWithCategory).transaction + else data[position] as Transaction holder.title.text = transaction.title holder.date.text = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(transaction.date) holder.amount.text = String.format("${'$'}%.02f", transaction.amount) @@ -44,7 +48,16 @@ class TransactionAdapter(private val data: List) } } - inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { + constructor(transactions: List) : this() { + if (transactions.isEmpty()) { + return + } + + listType = transactions.first().javaClass + data = transactions + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val title = itemView.findViewById(R.id.transaction_title) val date = itemView.findViewById(R.id.transaction_date) val amount = itemView.findViewById(R.id.transaction_amount) diff --git a/app/src/main/java/com/wbrawner/budget/transactions/TransactionListActivity.kt b/app/src/main/java/com/wbrawner/budget/transactions/TransactionListActivity.kt index d903aa7..f25d21f 100644 --- a/app/src/main/java/com/wbrawner/budget/transactions/TransactionListActivity.kt +++ b/app/src/main/java/com/wbrawner/budget/transactions/TransactionListActivity.kt @@ -6,6 +6,7 @@ import android.support.text.emoji.EmojiCompat import android.support.text.emoji.bundled.BundledEmojiCompatConfig import android.support.v7.app.AppCompatActivity import com.wbrawner.budget.R +import com.wbrawner.budget.categories.CategoryListFragment import com.wbrawner.budget.data.TransactionType import com.wbrawner.budget.overview.OverviewFragment import kotlinx.android.synthetic.main.activity_transaction_list.* @@ -21,6 +22,7 @@ class TransactionListActivity : AppCompatActivity() { when (item.itemId) { R.id.action_expenses -> updateFragment(TransactionType.EXPENSE) R.id.action_income -> updateFragment(TransactionType.INCOME) + R.id.action_categories -> updateFragment(CategoryListFragment.TAG_FRAGMENT, CategoryListFragment.TITLE_FRAGMENT) else -> updateFragment(OverviewFragment.TAG_FRAGMENT, OverviewFragment.TITLE_FRAGMENT) } @@ -38,13 +40,17 @@ class TransactionListActivity : AppCompatActivity() { var fragment = supportFragmentManager.findFragmentByTag(tag) val ft = supportFragmentManager.beginTransaction() if (fragment == null) { - fragment = if (tag == "overview") OverviewFragment() - else - TransactionListFragment().apply { - arguments = Bundle().apply { - putSerializable(TransactionListFragment.ARG_TYPE, TransactionType.valueOf(tag)) + fragment = when (tag) { + OverviewFragment.TAG_FRAGMENT -> OverviewFragment() + CategoryListFragment.TAG_FRAGMENT -> CategoryListFragment() + else -> { + TransactionListFragment().apply { + arguments = Bundle().apply { + putSerializable(TransactionListFragment.ARG_TYPE, TransactionType.valueOf(tag)) + } } } + } ft.add(R.id.content_container, fragment, tag) } for (fmFragment in supportFragmentManager.fragments) { diff --git a/app/src/main/java/com/wbrawner/budget/transactions/TransactionListFragment.kt b/app/src/main/java/com/wbrawner/budget/transactions/TransactionListFragment.kt index 337fab8..555d6e4 100644 --- a/app/src/main/java/com/wbrawner/budget/transactions/TransactionListFragment.kt +++ b/app/src/main/java/com/wbrawner/budget/transactions/TransactionListFragment.kt @@ -13,10 +13,9 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.wbrawner.budget.R -import com.wbrawner.budget.addedittransaction.AddEditTransactionActivity -import com.wbrawner.budget.addedittransaction.AddEditTransactionActivity.Companion.EXTRA_TYPE -import com.wbrawner.budget.data.Transaction +import com.wbrawner.budget.transactions.AddEditTransactionActivity.Companion.EXTRA_TYPE import com.wbrawner.budget.data.TransactionType +import com.wbrawner.budget.data.model.TransactionWithCategory class TransactionListFragment : Fragment() { lateinit var viewModel: TransactionViewModel @@ -43,7 +42,7 @@ class TransactionListFragment : Fragment() { val fab = view.findViewById(R.id.fab_add_transaction) recyclerView.layoutManager = LinearLayoutManager(activity) viewModel.getTransactionsByType(20, type) - .observe(this, Observer> { data -> + .observe(this, Observer> { data -> val noDataView = view.findViewById(R.id.transaction_list_no_data) if (data == null || data.isEmpty()) { recyclerView.adapter = null diff --git a/app/src/main/java/com/wbrawner/budget/transactions/TransactionViewModel.kt b/app/src/main/java/com/wbrawner/budget/transactions/TransactionViewModel.kt index a3e02be..ea1dcb5 100644 --- a/app/src/main/java/com/wbrawner/budget/transactions/TransactionViewModel.kt +++ b/app/src/main/java/com/wbrawner/budget/transactions/TransactionViewModel.kt @@ -4,22 +4,25 @@ import android.app.Application import android.arch.lifecycle.AndroidViewModel import android.arch.lifecycle.LiveData import com.wbrawner.budget.AllowanceApplication -import com.wbrawner.budget.data.Transaction -import com.wbrawner.budget.data.TransactionRepository -import com.wbrawner.budget.data.TransactionType +import com.wbrawner.budget.data.* +import com.wbrawner.budget.data.model.Transaction +import com.wbrawner.budget.data.model.TransactionCategory +import com.wbrawner.budget.data.model.TransactionWithCategory class TransactionViewModel(application: Application): AndroidViewModel(application) { - private val transactionRepo = TransactionRepository((application as AllowanceApplication).dao) + private val transactionRepo = TransactionRepository((application as AllowanceApplication).transactionDao) - fun getTransaction(id: Int): LiveData = transactionRepo.getTransaction(id) + fun getTransaction(id: Int): LiveData = transactionRepo.getTransaction(id) - fun getTransactions(count: Int): LiveData> = transactionRepo.getTransactions(count) + fun getTransactions(count: Int): LiveData> = transactionRepo.getTransactions(count) - fun getTransactionsByType(count: Int, type: TransactionType): LiveData> + fun getTransactionsByType(count: Int, type: TransactionType): LiveData> = transactionRepo.getTransactionsByType(count, type) fun getCurrentBalance(): LiveData = transactionRepo.getCurrentBalance() + fun getCategories(): LiveData> = transactionRepo.getCategories() + fun saveTransaction(transaction: Transaction) = transactionRepo.save(transaction) fun deleteTransaction(transaction: Transaction) = transactionRepo.delete(transaction) diff --git a/app/src/main/res/drawable/ic_baseline_category_24px.xml b/app/src/main/res/drawable/ic_baseline_category_24px.xml new file mode 100644 index 0000000..ba008f3 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_category_24px.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/activity_add_edit_category.xml b/app/src/main/res/layout/activity_add_edit_category.xml new file mode 100644 index 0000000..3312118 --- /dev/null +++ b/app/src/main/res/layout/activity_add_edit_category.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_add_edit_transaction.xml b/app/src/main/res/layout/activity_add_edit_transaction.xml index 5ca5e08..1f4a08a 100644 --- a/app/src/main/res/layout/activity_add_edit_transaction.xml +++ b/app/src/main/res/layout/activity_add_edit_transaction.xml @@ -88,10 +88,31 @@ + + + + diff --git a/app/src/main/res/layout/list_item_category.xml b/app/src/main/res/layout/list_item_category.xml new file mode 100644 index 0000000..6f2d1c5 --- /dev/null +++ b/app/src/main/res/layout/list_item_category.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/main_navigation.xml b/app/src/main/res/menu/main_navigation.xml index 87a976d..c498d0c 100644 --- a/app/src/main/res/menu/main_navigation.xml +++ b/app/src/main/res/menu/main_navigation.xml @@ -12,4 +12,8 @@ android:id="@+id/action_income" android:icon="@drawable/ic_attach_money_black_24dp" android:title="@string/title_income" /> + \ 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 e72e63c..09ece57 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,4 +19,13 @@ Edit Expense 🌳\nGet to work! Money doesn\'t grow on trees after all 🤔\nAre you sure you haven\'t spent any money? + Categories + Edit Category + Amount + Name + Add Category + Uncategorized + Category + Name is a required field + Amount is a required field diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml index 508068f..320c19b 100644 --- a/app/src/main/res/xml/shortcuts.xml +++ b/app/src/main/res/xml/shortcuts.xml @@ -10,7 +10,7 @@ + android:targetClass="com.wbrawner.budget.transactions.AddEditTransactionActivity"> @@ -25,7 +25,7 @@ + android:targetClass="com.wbrawner.budget.transactions.AddEditTransactionActivity"> diff --git a/build.gradle b/build.gradle index 6a0f417..ff630b8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.41' + ext.kotlin_version = '1.3.0' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f20b0fe..c9fbfeb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip