Update database structure to match server

This commit is contained in:
William Brawner 2018-11-01 19:12:03 -06:00
parent 85a91d0af0
commit f3e456ac28
35 changed files with 474 additions and 209 deletions

View file

@ -1,9 +1,6 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
@ -50,16 +47,17 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2' implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:27.1.1' implementation 'com.android.support:design:27.1.1'
implementation "com.android.support:support-emoji-bundled:27.1.1" implementation "com.android.support:support-emoji-bundled:27.1.1"
implementation 'com.github.BlacKCaT27:CurrencyEditText:2.0.2'
def room_version = "1.1.1" def room_version = "1.1.1"
def lifecycle_version = "1.1.1" def lifecycle_version = "1.1.1"
// ViewModel and LiveData // ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version" implementation "android.arch.lifecycle:extensions:$lifecycle_version"
implementation "android.arch.persistence.room:runtime:$room_version" implementation "android.arch.persistence.room:runtime:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version" kapt "android.arch.persistence.room:compiler:$room_version"
testImplementation "android.arch.persistence.room:testing:$room_version" androidTestImplementation "android.arch.persistence.room:testing:$room_version"
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
// ACRA Crash reporting // ACRA Crash reporting

View file

@ -0,0 +1,63 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "da32c301dc558cb73099ff325bb88fb7",
"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, `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": "type",
"columnName": "type",
"affinity": "TEXT",
"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, \"da32c301dc558cb73099ff325bb88fb7\")"
]
}
}

View file

@ -0,0 +1,125 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "68f2dbe4273bb745930a348dc280ae96",
"entities": [
{
"tableName": "Transaction",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `remoteId` TEXT, `name` TEXT NOT NULL, `date` TEXT NOT NULL, `description` TEXT NOT NULL, `amount` INTEGER NOT NULL, `categoryId` INTEGER, `isExpense` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"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": "INTEGER",
"notNull": true
},
{
"fieldPath": "categoryId",
"columnName": "categoryId",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "isExpense",
"columnName": "isExpense",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "Category",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `remoteId` TEXT, `name` TEXT NOT NULL, `amount` INTEGER NOT NULL, `repeat` TEXT, `color` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "remoteId",
"columnName": "remoteId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "amount",
"columnName": "amount",
"affinity": "INTEGER",
"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, \"68f2dbe4273bb745930a348dc280ae96\")"
]
}
}

View file

@ -0,0 +1,109 @@
package com.wbrawner.budget;
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
import android.arch.persistence.room.testing.MigrationTestHelper;
import android.database.Cursor;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import com.wbrawner.budget.data.BudgetDatabase;
import com.wbrawner.budget.data.migrations.MIGRATION_1_2;
import com.wbrawner.budget.data.migrations.MIGRATION_2_3;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import static junit.framework.Assert.assertNull;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
@RunWith(AndroidJUnit4.class)
public class MigrationTests {
private static final String TEST_DB = "migration-test";
@Rule
public MigrationTestHelper migrationHelper;
public MigrationTests() {
this.migrationHelper = new MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
BudgetDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory()
);
}
@Test
public void migrate1to2() throws IOException {
SupportSQLiteDatabase db = migrationHelper.createDatabase(TEST_DB, 1);
db.execSQL("INSERT INTO `Transaction` (id,title,date,description,amount,type) " +
"VALUES (1,'An expense','2018-10-31','Spent some money',12.34,'EXPENSE')");
db.close();
db = migrationHelper.runMigrationsAndValidate(
TEST_DB,
2,
true,
new MIGRATION_1_2()
);
Cursor cursor = db.query("SELECT * FROM 'Transaction'");
assertEquals(1, cursor.getCount());
cursor.moveToFirst();
assertEquals(1, cursor.getInt(cursor.getColumnIndex("id")));
assertEquals("An expense", cursor.getString(cursor.getColumnIndex("title")));
assertEquals("2018-10-31", cursor.getString(cursor.getColumnIndex("date")));
assertEquals("Spent some money", cursor.getString(cursor.getColumnIndex("description")));
assertEquals(12.34, cursor.getDouble(cursor.getColumnIndex("amount")));
assertEquals(0, cursor.getInt(cursor.getColumnIndex("categoryId")));
assertEquals("EXPENSE", cursor.getString(cursor.getColumnIndex("type")));
}
@Test
public void migrate2to3() throws IOException {
SupportSQLiteDatabase db = migrationHelper.createDatabase(TEST_DB, 2);
db.execSQL("INSERT INTO `Transaction` (id,title,date,description,amount,categoryId,type) " +
"VALUES (1,'An expense','2018-10-31','Spent some money',12.34,1,'EXPENSE')");
db.execSQL("INSERT INTO `Transaction` (id,title,date,description,amount,categoryId,type) " +
"VALUES (2,'Some income','2018-01-02','Made some money',42.65,0,'INCOME')");
db.execSQL("INSERT INTO `Category` (id,name,amount,repeat,color) " +
"VALUES (1,'Groceries',1234.56,'monthly',987)");
db.close();
db = migrationHelper.runMigrationsAndValidate(
TEST_DB,
3,
true,
new MIGRATION_2_3()
);
Cursor cursor = db.query("SELECT * FROM 'Transaction'");
assertEquals(2, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals(1, cursor.getInt(cursor.getColumnIndex("id")));
assertNull(cursor.getString(cursor.getColumnIndex("remoteId")));
assertEquals("An expense", cursor.getString(cursor.getColumnIndex("name")));
assertEquals("2018-10-31", cursor.getString(cursor.getColumnIndex("date")));
assertEquals("Spent some money", cursor.getString(cursor.getColumnIndex("description")));
assertEquals(1234, cursor.getInt(cursor.getColumnIndex("amount")));
assertEquals(1, cursor.getInt(cursor.getColumnIndex("categoryId")));
assertEquals(1, cursor.getInt(cursor.getColumnIndex("isExpense")));
assertTrue(cursor.moveToNext());
assertEquals(2, cursor.getInt(cursor.getColumnIndex("id")));
assertNull(cursor.getString(cursor.getColumnIndex("remoteId")));
assertEquals("Some income", cursor.getString(cursor.getColumnIndex("name")));
assertEquals("2018-01-02", cursor.getString(cursor.getColumnIndex("date")));
assertEquals("Made some money", cursor.getString(cursor.getColumnIndex("description")));
assertEquals(4265, cursor.getInt(cursor.getColumnIndex("amount")));
assertEquals(0, cursor.getInt(cursor.getColumnIndex("categoryId")));
assertEquals(0, cursor.getInt(cursor.getColumnIndex("isExpense")));
cursor = db.query("SELECT * FROM 'Category'");
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals(1, cursor.getInt(cursor.getColumnIndex("id")));
assertNull(cursor.getString(cursor.getColumnIndex("remoteId")));
assertEquals("Groceries", cursor.getString(cursor.getColumnIndex("name")));
assertEquals(123456, cursor.getInt(cursor.getColumnIndex("amount")));
assertEquals("monthly", cursor.getString(cursor.getColumnIndex("repeat")));
assertEquals(987, cursor.getInt(cursor.getColumnIndex("color")));
}
}

View file

@ -10,7 +10,7 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name="com.wbrawner.budget.transactions.TransactionListActivity"> <activity android:name="com.wbrawner.budget.ui.MainActivity">
<intent-filter android:label="@string/app_name_short"> <intent-filter android:label="@string/app_name_short">
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -20,8 +20,8 @@
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
</activity> </activity>
<activity android:name="com.wbrawner.budget.transactions.AddEditTransactionActivity" /> <activity android:name="com.wbrawner.budget.ui.transactions.AddEditTransactionActivity" />
<activity android:name="com.wbrawner.budget.categories.AddEditCategoryActivity" /> <activity android:name="com.wbrawner.budget.ui.categories.AddEditCategoryActivity" />
</application> </application>
</manifest> </manifest>

View file

@ -7,6 +7,7 @@ import com.wbrawner.budget.data.dao.TransactionDao
import com.wbrawner.budget.data.BudgetDatabase import com.wbrawner.budget.data.BudgetDatabase
import com.wbrawner.budget.data.dao.CategoryDao import com.wbrawner.budget.data.dao.CategoryDao
import com.wbrawner.budget.data.migrations.MIGRATION_1_2 import com.wbrawner.budget.data.migrations.MIGRATION_1_2
import com.wbrawner.budget.data.migrations.MIGRATION_2_3
import org.acra.ACRA import org.acra.ACRA
import org.acra.annotation.AcraCore import org.acra.annotation.AcraCore
import org.acra.annotation.AcraHttpSender import org.acra.annotation.AcraHttpSender
@ -33,6 +34,7 @@ class AllowanceApplication: Application() {
database = Room.databaseBuilder(applicationContext, BudgetDatabase::class.java, "transactions") database = Room.databaseBuilder(applicationContext, BudgetDatabase::class.java, "transactions")
.addMigrations(MIGRATION_1_2()) .addMigrations(MIGRATION_1_2())
.addMigrations(MIGRATION_2_3())
.build() .build()
transactionDao = database.transactionDao() transactionDao = database.transactionDao()
categoryDao = database.categoryDao() categoryDao = database.categoryDao()

View file

@ -8,8 +8,8 @@ import com.wbrawner.budget.data.dao.TransactionDao
import com.wbrawner.budget.data.model.Category import com.wbrawner.budget.data.model.Category
import com.wbrawner.budget.data.model.Transaction import com.wbrawner.budget.data.model.Transaction
@Database(entities = [(Transaction::class), (Category::class)], version = 2) @Database(entities = [(Transaction::class), (Category::class)], version = 3)
@TypeConverters(DateTypeConverter::class, TransactionTypeTypeConverter::class) @TypeConverters(DateTypeConverter::class)
abstract class BudgetDatabase: RoomDatabase() { abstract class BudgetDatabase: RoomDatabase() {
abstract fun transactionDao(): TransactionDao abstract fun transactionDao(): TransactionDao
abstract fun categoryDao(): CategoryDao abstract fun categoryDao(): CategoryDao

View file

@ -37,7 +37,7 @@ class CategoryRepository(private val dao: CategoryDao) {
} }
fun getCurrentBalance(id: Int): LiveData<Double> = dao.getBalanceForCategory(id) fun getCurrentBalance(id: Int): LiveData<Int> = dao.getBalanceForCategory(id)
fun getTransactions(id: Int): LiveData<List<Transaction>> = dao.getTransactions(id) fun getTransactions(id: Int): LiveData<List<Transaction>> = dao.getTransactions(id)

View file

@ -17,8 +17,8 @@ class TransactionRepository(private val dao: TransactionDao) {
handler = Handler(thread.looper) handler = Handler(thread.looper)
} }
fun getTransactionsByType(count: Int, type: TransactionType): LiveData<List<TransactionWithCategory>> = // fun getTransactionsByType(count: Int, type: TransactionType): LiveData<List<TransactionWithCategory>> =
dao.loadMultipleByType(count, type) // dao.loadMultipleByType(count, type)
fun getTransactions(count: Int): LiveData<List<TransactionWithCategory>> = dao.loadMultiple(count) fun getTransactions(count: Int): LiveData<List<TransactionWithCategory>> = dao.loadMultiple(count)
@ -41,7 +41,7 @@ class TransactionRepository(private val dao: TransactionDao) {
} }
fun getCurrentBalance(): LiveData<Double> = dao.getBalance() fun getCurrentBalance(): LiveData<Int> = dao.getBalance()
fun getCategories(): LiveData<List<TransactionCategory>> = dao.loadCategories() fun getCategories(): LiveData<List<TransactionCategory>> = dao.loadCategories()
} }

View file

@ -1,29 +0,0 @@
package com.wbrawner.budget.data
import android.support.annotation.ColorRes
import android.support.annotation.StringRes
import com.wbrawner.budget.R
import java.io.Serializable
enum class TransactionType(
@StringRes val title: Int,
@StringRes val addTitle: Int,
@StringRes val editTitle: Int,
@StringRes val noDataText: Int,
@ColorRes val textColor: Int
) : Serializable {
INCOME(
R.string.title_income,
R.string.title_add_income,
R.string.title_edit_income,
R.string.income_no_data,
R.color.colorTextGreen
),
EXPENSE(
R.string.title_expenses,
R.string.title_add_expense,
R.string.title_edit_expense,
R.string.expenses_no_data,
R.color.colorTextRed
);
}

View file

@ -4,14 +4,6 @@ import android.arch.persistence.room.TypeConverter
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class TransactionTypeTypeConverter {
@android.arch.persistence.room.TypeConverter
fun toTransactionType(value: String): TransactionType? = TransactionType.valueOf(value)
@android.arch.persistence.room.TypeConverter
fun toString(type: TransactionType?): String? = type?.name
}
class DateTypeConverter { class DateTypeConverter {
@android.arch.persistence.room.TypeConverter @android.arch.persistence.room.TypeConverter
fun toDate(value: String): Date? = dateFormat.parse(value) fun toDate(value: String): Date? = dateFormat.parse(value)

View file

@ -21,9 +21,9 @@ interface CategoryDao {
fun loadMultiple(): LiveData<List<Category>> fun loadMultiple(): LiveData<List<Category>>
@Query("SELECT " + @Query("SELECT " +
"(SELECT TOTAL(amount) from `Transaction` WHERE type = 'INCOME' AND categoryId = :categoryId) " + "(SELECT TOTAL(amount) from `Transaction` WHERE isExpense = 0 AND categoryId = :categoryId) " +
"- (SELECT TOTAL(amount) from `Transaction` WHERE type = 'EXPENSE' AND categoryId = :categoryId)") "- (SELECT TOTAL(amount) from `Transaction` WHERE isExpense = 1 AND categoryId = :categoryId)")
fun getBalanceForCategory(categoryId: Int): LiveData<Double> fun getBalanceForCategory(categoryId: Int): LiveData<Int>
@Delete @Delete
fun delete(category: Category) fun delete(category: Category)

View file

@ -8,7 +8,6 @@ import android.arch.persistence.room.OnConflictStrategy.REPLACE
import android.arch.persistence.room.Query import android.arch.persistence.room.Query
import com.wbrawner.budget.data.model.Transaction import com.wbrawner.budget.data.model.Transaction
import com.wbrawner.budget.data.model.TransactionCategory import com.wbrawner.budget.data.model.TransactionCategory
import com.wbrawner.budget.data.TransactionType
import com.wbrawner.budget.data.model.TransactionWithCategory import com.wbrawner.budget.data.model.TransactionWithCategory
@Dao @Dao
@ -22,11 +21,8 @@ interface TransactionDao {
@Query("SELECT * FROM `Transaction` LIMIT :count") @Query("SELECT * FROM `Transaction` LIMIT :count")
fun loadMultiple(count: Int): LiveData<List<TransactionWithCategory>> fun loadMultiple(count: Int): LiveData<List<TransactionWithCategory>>
@Query("SELECT * FROM `Transaction` WHERE type = :type LIMIT :count") @Query("SELECT (SELECT TOTAL(amount) from `Transaction` WHERE isExpense = 0) - (SELECT TOTAL(amount) from `Transaction` WHERE isExpense = 1)")
fun loadMultipleByType(count: Int, type: TransactionType): LiveData<List<TransactionWithCategory>> fun getBalance(): LiveData<Int>
@Query("SELECT (SELECT TOTAL(amount) from `Transaction` WHERE type = 'INCOME') - (SELECT TOTAL(amount) from `Transaction` WHERE type = 'EXPENSE')")
fun getBalance(): LiveData<Double>
@Delete @Delete
fun delete(transaction: Transaction) fun delete(transaction: Transaction)

View file

@ -5,9 +5,8 @@ import android.arch.persistence.room.migration.Migration
class MIGRATION_1_2: Migration(1, 2) { class MIGRATION_1_2: Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) { override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE `Category` (`id` INTEGER, `name` TEXT, `amount` REAL, " + database.execSQL("CREATE TABLE `Category` (`id` INTEGER, `name` TEXT NOT NULL, `amount` REAL NOT NULL, `repeat` TEXT, `color` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (`id`))")
"`repeat` TEXT, `color` INTEGER PRIMARY KEY (`id`))") database.execSQL("ALTER TABLE `Transaction` ADD COLUMN categoryId INTEGER")
database.execSQL("ALTER TABLE `Transaction` ADD COLUMN categoryId")
} }
} }

View file

@ -0,0 +1,20 @@
package com.wbrawner.budget.data.migrations
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.migration.Migration
class MIGRATION_2_3: Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `Transaction` RENAME TO `TransactionOld`")
database.execSQL("CREATE TABLE `Transaction` (`id` INTEGER,`remoteId` TEXT,`name` TEXT NOT NULL,`date` TEXT NOT NULL, `description` TEXT NOT NULL, `amount` INTEGER NOT NULL,`isExpense` INTEGER NOT NULL DEFAULT 1,`categoryId` INTEGER,PRIMARY KEY (`id`))")
database.execSQL("INSERT INTO `Transaction` ( id, name, date, description, amount, categoryId ) SELECT id,title,date,description,amount * 100,categoryId FROM `TransactionOld`")
database.execSQL("UPDATE `Transaction` SET isExpense = 1 WHERE id IN (SELECT id FROM `TransactionOld` WHERE `TransactionOld`.type = 'EXPENSE')")
database.execSQL("UPDATE `Transaction` SET isExpense = 0 WHERE id IN (SELECT id FROM `TransactionOld` WHERE `TransactionOld`.type = 'INCOME')")
database.execSQL("DROP TABLE `TransactionOld`")
database.execSQL("ALTER TABLE `Category` RENAME TO `CategoryOld`")
database.execSQL("CREATE TABLE `Category` (`id` INTEGER,`remoteId` TEXT,`name` TEXT NOT NULL, `amount` INTEGER NOT NULL, `repeat` TEXT, `color` INTEGER NOT NULL DEFAULT 0,PRIMARY KEY (`id`))")
database.execSQL("INSERT INTO `Category` ( id, name, amount, repeat, color ) SELECT id,name,amount * 100,repeat,color FROM `CategoryOld`")
database.execSQL("DROP TABLE `CategoryOld`")
}
}

View file

@ -8,8 +8,9 @@ import android.support.annotation.ColorInt
class Category( class Category(
@PrimaryKey @PrimaryKey
val id: Int?, val id: Int?,
val remoteId: String?,
val name: String, val name: String,
val amount: Double, val amount: Int,
val repeat: String?, val repeat: String?,
@ColorInt val color: Int @ColorInt val color: Int
) )

View file

@ -2,17 +2,17 @@ package com.wbrawner.budget.data.model
import android.arch.persistence.room.Entity import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey import android.arch.persistence.room.PrimaryKey
import com.wbrawner.budget.data.TransactionType
import java.util.* import java.util.*
@Entity @Entity
class Transaction( class Transaction(
@PrimaryKey @PrimaryKey
val id: Int?, val id: Int?,
val title: String, val remoteId: String?,
val name: String,
val date: Date, val date: Date,
val description: String, val description: String,
val amount: Double, val amount: Int,
val categoryId: Int?, val categoryId: Int?,
val type: TransactionType val isExpense: Boolean
) )

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.transactions package com.wbrawner.budget.ui
import android.os.Bundle import android.os.Bundle
import android.support.annotation.StringRes import android.support.annotation.StringRes
@ -6,12 +6,12 @@ import android.support.text.emoji.EmojiCompat
import android.support.text.emoji.bundled.BundledEmojiCompatConfig import android.support.text.emoji.bundled.BundledEmojiCompatConfig
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import com.wbrawner.budget.R import com.wbrawner.budget.R
import com.wbrawner.budget.categories.CategoryListFragment import com.wbrawner.budget.ui.categories.CategoryListFragment
import com.wbrawner.budget.data.TransactionType import com.wbrawner.budget.ui.overview.OverviewFragment
import com.wbrawner.budget.overview.OverviewFragment import com.wbrawner.budget.ui.transactions.TransactionListFragment
import kotlinx.android.synthetic.main.activity_transaction_list.* import kotlinx.android.synthetic.main.activity_transaction_list.*
class TransactionListActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
EmojiCompat.init(BundledEmojiCompatConfig(this)) EmojiCompat.init(BundledEmojiCompatConfig(this))
@ -20,9 +20,14 @@ class TransactionListActivity : AppCompatActivity() {
menu_main.setOnNavigationItemSelectedListener { item -> menu_main.setOnNavigationItemSelectedListener { item ->
when (item.itemId) { when (item.itemId) {
R.id.action_expenses -> updateFragment(TransactionType.EXPENSE) R.id.action_transactions -> updateFragment(
R.id.action_income -> updateFragment(TransactionType.INCOME) TransactionListFragment.TAG_FRAGMENT,
R.id.action_categories -> updateFragment(CategoryListFragment.TAG_FRAGMENT, CategoryListFragment.TITLE_FRAGMENT) TransactionListFragment.TITLE_FRAGMENT
)
R.id.action_categories -> updateFragment(
CategoryListFragment.TAG_FRAGMENT,
CategoryListFragment.TITLE_FRAGMENT
)
else -> else ->
updateFragment(OverviewFragment.TAG_FRAGMENT, OverviewFragment.TITLE_FRAGMENT) updateFragment(OverviewFragment.TAG_FRAGMENT, OverviewFragment.TITLE_FRAGMENT)
} }
@ -31,10 +36,6 @@ class TransactionListActivity : AppCompatActivity() {
menu_main.selectedItemId = R.id.action_overview menu_main.selectedItemId = R.id.action_overview
} }
private fun updateFragment(type: TransactionType) {
updateFragment(type.name, type.title)
}
private fun updateFragment(tag: String, @StringRes title: Int) { private fun updateFragment(tag: String, @StringRes title: Int) {
setTitle(title) setTitle(title)
var fragment = supportFragmentManager.findFragmentByTag(tag) var fragment = supportFragmentManager.findFragmentByTag(tag)
@ -44,11 +45,7 @@ class TransactionListActivity : AppCompatActivity() {
OverviewFragment.TAG_FRAGMENT -> OverviewFragment() OverviewFragment.TAG_FRAGMENT -> OverviewFragment()
CategoryListFragment.TAG_FRAGMENT -> CategoryListFragment() CategoryListFragment.TAG_FRAGMENT -> CategoryListFragment()
else -> { else -> {
TransactionListFragment().apply { TransactionListFragment()
arguments = Bundle().apply {
putSerializable(TransactionListFragment.ARG_TYPE, TransactionType.valueOf(tag))
}
}
} }
} }
ft.add(R.id.content_container, fragment, tag) ft.add(R.id.content_container, fragment, tag)

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.categories package com.wbrawner.budget.ui.categories
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders import android.arch.lifecycle.ViewModelProviders
@ -7,6 +7,7 @@ import android.os.Bundle
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import com.wbrawner.budget.R import com.wbrawner.budget.R
import com.wbrawner.budget.data.model.Category import com.wbrawner.budget.data.model.Category
import kotlinx.android.synthetic.main.activity_add_edit_category.* import kotlinx.android.synthetic.main.activity_add_edit_category.*
@ -28,6 +29,7 @@ class AddEditCategoryActivity : AppCompatActivity() {
return return
} }
viewModel.getCategory(intent!!.extras!!.getInt(EXTRA_CATEGORY_ID)) viewModel.getCategory(intent!!.extras!!.getInt(EXTRA_CATEGORY_ID))
.observe(this, Observer<Category> { category -> .observe(this, Observer<Category> { category ->
if (category == null) { if (category == null) {
@ -38,7 +40,7 @@ class AddEditCategoryActivity : AppCompatActivity() {
setTitle(R.string.title_edit_category) setTitle(R.string.title_edit_category)
menu?.findItem(R.id.action_delete)?.isVisible = true menu?.findItem(R.id.action_delete)?.isVisible = true
edit_category_name.setText(category.name) edit_category_name.setText(category.name)
edit_category_amount.setText(String.format("%.02f", category.amount)) edit_category_amount.setText(String.format("%.02f", category.amount / 100.0f))
}) })
} }
@ -58,8 +60,9 @@ class AddEditCategoryActivity : AppCompatActivity() {
if (!validateFields()) return true if (!validateFields()) return true
viewModel.saveCategory(Category( viewModel.saveCategory(Category(
id = id, id = id,
remoteId = null,
name = edit_category_name.text.toString(), name = edit_category_name.text.toString(),
amount = edit_category_amount.text.toString().toDouble(), amount = edit_category_amount.rawValue.toInt(),
color = Color.parseColor("#FF0000"), color = Color.parseColor("#FF0000"),
repeat = "never" repeat = "never"
)) ))

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.categories package com.wbrawner.budget.ui.categories
import android.arch.lifecycle.LifecycleOwner import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
@ -11,7 +11,7 @@ import android.view.ViewGroup
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import com.wbrawner.budget.R import com.wbrawner.budget.R
import com.wbrawner.budget.categories.AddEditCategoryActivity.Companion.EXTRA_CATEGORY_ID import com.wbrawner.budget.ui.categories.AddEditCategoryActivity.Companion.EXTRA_CATEGORY_ID
import com.wbrawner.budget.data.model.Category import com.wbrawner.budget.data.model.Category
class CategoryAdapter( class CategoryAdapter(
@ -30,18 +30,22 @@ class CategoryAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val category = data[position] val category = data[position]
holder.title?.text = category.name holder.title?.text = category.name
holder.amount?.text = String.format("${'$'}%.02f", category.amount) holder.amount?.text = String.format("${'$'}%.02f", category.amount / 100.0f)
viewModel.getCurrentBalance(category.id!!) viewModel.getCurrentBalance(category.id!!)
.observe(lifecycleOwner, Observer<Double> { balance -> .observe(lifecycleOwner, Observer<Int> { balance ->
holder.progress?.isIndeterminate = false holder.progress?.isIndeterminate = false
if (balance == null) { if (balance == null) {
holder.progress?.progress = 0 holder.progress?.progress = 0
} else { } else {
holder.progress?.max = category.amount.toInt() holder.progress?.max = category.amount
holder.progress?.setProgress( holder.progress?.setProgress(
Math.abs(balance).toInt(), -1 * balance,
true true
) )
holder.amount?.text = holder.itemView.context.getString(
R.string.balance_remaning,
(category.amount + balance) / 100.0f
)
} }
}) })
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.categories package com.wbrawner.budget.ui.categories
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders import android.arch.lifecycle.ViewModelProviders
@ -41,7 +41,7 @@ class CategoryListFragment : Fragment() {
val noDataView = view.findViewById<EmojiTextView>(R.id.transaction_list_no_data) val noDataView = view.findViewById<EmojiTextView>(R.id.transaction_list_no_data)
if (data == null || data.isEmpty()) { if (data == null || data.isEmpty()) {
recyclerView.adapter = null recyclerView.adapter = null
noDataView?.setText("No data FIX ME") noDataView?.setText(R.string.categories_no_data)
recyclerView?.visibility = View.GONE recyclerView?.visibility = View.GONE
noDataView?.visibility = View.VISIBLE noDataView?.visibility = View.VISIBLE
} else { } else {

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.categories package com.wbrawner.budget.ui.categories
import android.app.Application import android.app.Application
import android.arch.lifecycle.AndroidViewModel import android.arch.lifecycle.AndroidViewModel

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.overview package com.wbrawner.budget.ui.overview
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders import android.arch.lifecycle.ViewModelProviders
@ -9,7 +9,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import com.wbrawner.budget.R import com.wbrawner.budget.R
import com.wbrawner.budget.transactions.TransactionViewModel import com.wbrawner.budget.ui.transactions.TransactionViewModel
class OverviewFragment : Fragment() { class OverviewFragment : Fragment() {
lateinit var viewModel: TransactionViewModel lateinit var viewModel: TransactionViewModel
@ -32,15 +32,15 @@ class OverviewFragment : Fragment() {
} }
viewModel.getCurrentBalance().observe(this, Observer { balance -> viewModel.getCurrentBalance().observe(this, Observer { balance ->
val safeBalance: Double = balance?: 0.0 val safeBalance: Int = balance?: 0
val balanceView = view.findViewById<TextView>(R.id.overview_current_balance) val balanceView = view.findViewById<TextView>(R.id.overview_current_balance)
val color = when { val color = when {
safeBalance > 0.0 -> R.color.colorTextGreen safeBalance > 0 -> R.color.colorTextGreen
safeBalance == 0.0 -> R.color.colorTextPrimary safeBalance == 0 -> R.color.colorTextPrimary
else -> R.color.colorTextRed else -> R.color.colorTextRed
} }
balanceView.setTextColor(resources.getColor(color, activity!!.theme)) balanceView.setTextColor(resources.getColor(color, activity!!.theme))
balanceView.text = String.format("${'$'}%.02f", safeBalance) balanceView.text = String.format("${'$'}%.02f", safeBalance / 100.0f)
}) })
showData(view, true) showData(view, true)
}) })

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.transactions package com.wbrawner.budget.ui.transactions
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders import android.arch.lifecycle.ViewModelProviders
@ -10,14 +10,12 @@ import android.widget.ArrayAdapter
import com.wbrawner.budget.R import com.wbrawner.budget.R
import com.wbrawner.budget.data.model.Transaction import com.wbrawner.budget.data.model.Transaction
import com.wbrawner.budget.data.model.TransactionCategory import com.wbrawner.budget.data.model.TransactionCategory
import com.wbrawner.budget.data.TransactionType
import com.wbrawner.budget.data.model.TransactionWithCategory import com.wbrawner.budget.data.model.TransactionWithCategory
import kotlinx.android.synthetic.main.activity_add_edit_transaction.* import kotlinx.android.synthetic.main.activity_add_edit_transaction.*
import java.util.* import java.util.*
class AddEditTransactionActivity : AppCompatActivity() { class AddEditTransactionActivity : AppCompatActivity() {
lateinit var viewModel: TransactionViewModel lateinit var viewModel: TransactionViewModel
lateinit var type: TransactionType
var id: Int? = null var id: Int? = null
var date: Date = Date() var date: Date = Date()
var menu: Menu? = null var menu: Menu? = null
@ -27,6 +25,8 @@ class AddEditTransactionActivity : AppCompatActivity() {
setContentView(R.layout.activity_add_edit_transaction) setContentView(R.layout.activity_add_edit_transaction)
setSupportActionBar(action_bar) setSupportActionBar(action_bar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
setTitle(R.string.title_add_transaction)
edit_transaction_type_expense.isChecked = true
viewModel = ViewModelProviders.of(this).get(TransactionViewModel::class.java) viewModel = ViewModelProviders.of(this).get(TransactionViewModel::class.java)
viewModel.getCategories() viewModel.getCategories()
.observe(this, Observer<List<TransactionCategory>> { categories -> .observe(this, Observer<List<TransactionCategory>> { categories ->
@ -44,13 +44,8 @@ class AddEditTransactionActivity : AppCompatActivity() {
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
edit_transaction_category.adapter = adapter edit_transaction_category.adapter = adapter
}) })
if (intent?.hasExtra(EXTRA_TYPE) == true) {
type = TransactionType.valueOf(intent?.extras?.getString(EXTRA_TYPE, "EXPENSE") if (intent?.hasExtra(EXTRA_TRANSACTION_ID) != true) {
?: "EXPENSE")
setTitle(type.addTitle)
return
} else if (intent?.hasExtra(EXTRA_TRANSACTION_ID) != true) {
finish()
return return
} }
@ -62,12 +57,15 @@ class AddEditTransactionActivity : AppCompatActivity() {
} }
val transaction = transactionWithCategory.transaction val transaction = transactionWithCategory.transaction
id = transaction.id id = transaction.id
type = transaction.type
setTitle(type.editTitle)
menu?.findItem(R.id.action_delete)?.isVisible = true menu?.findItem(R.id.action_delete)?.isVisible = true
edit_transaction_title.setText(transaction.title) edit_transaction_title.setText(transaction.name)
edit_transaction_description.setText(transaction.description) edit_transaction_description.setText(transaction.description)
edit_transaction_amount.setText(String.format("%.02f", transaction.amount)) edit_transaction_amount.setText(String.format("%.02f", transaction.amount / 100.0f))
if (transaction.isExpense) {
edit_transaction_type_expense.isChecked = true
} else {
edit_transaction_type_income.isChecked = true
}
val field = Calendar.getInstance() val field = Calendar.getInstance()
field.time = transaction.date field.time = transaction.date
val year = field.get(Calendar.YEAR) val year = field.get(Calendar.YEAR)
@ -105,12 +103,13 @@ class AddEditTransactionActivity : AppCompatActivity() {
viewModel.saveTransaction(Transaction( viewModel.saveTransaction(Transaction(
id = id, id = id,
title = edit_transaction_title.text.toString(), name = edit_transaction_title.text.toString(),
date = cal.time, date = cal.time,
description = edit_transaction_description.text.toString(), description = edit_transaction_description.text.toString(),
amount = edit_transaction_amount.text.toString().toDouble(), amount = edit_transaction_amount.rawValue.toInt(),
type = type, isExpense = edit_transaction_type_expense.isChecked,
categoryId = (edit_transaction_category.selectedItem as TransactionCategory).id categoryId = (edit_transaction_category.selectedItem as TransactionCategory).id,
remoteId = null
)) ))
finish() finish()
} }
@ -124,7 +123,6 @@ class AddEditTransactionActivity : AppCompatActivity() {
companion object { companion object {
const val EXTRA_TYPE = "EXTRA_TRANSACTION_TYPE"
const val EXTRA_TRANSACTION_ID = "EXTRA_TRANSACTION_ID" const val EXTRA_TRANSACTION_ID = "EXTRA_TRANSACTION_ID"
} }
} }

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.transactions package com.wbrawner.budget.ui.transactions
import android.content.Intent import android.content.Intent
import android.support.v4.content.ContextCompat.startActivity import android.support.v4.content.ContextCompat.startActivity
@ -8,7 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import com.wbrawner.budget.R import com.wbrawner.budget.R
import com.wbrawner.budget.transactions.AddEditTransactionActivity.Companion.EXTRA_TRANSACTION_ID import com.wbrawner.budget.ui.transactions.AddEditTransactionActivity.Companion.EXTRA_TRANSACTION_ID
import com.wbrawner.budget.data.model.Transaction import com.wbrawner.budget.data.model.Transaction
import com.wbrawner.budget.data.model.TransactionWithCategory import com.wbrawner.budget.data.model.TransactionWithCategory
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
@ -29,13 +29,12 @@ class TransactionAdapter() : RecyclerView.Adapter<TransactionAdapter.ViewHolder>
val transaction: Transaction = val transaction: Transaction =
if (listType == TransactionWithCategory::class.java) (data[position] as TransactionWithCategory).transaction if (listType == TransactionWithCategory::class.java) (data[position] as TransactionWithCategory).transaction
else data[position] as Transaction else data[position] as Transaction
holder.title.text = transaction.title holder.title.text = transaction.name
holder.date.text = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(transaction.date) holder.date.text = SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT).format(transaction.date)
holder.amount.text = String.format("${'$'}%.02f", transaction.amount) holder.amount.text = String.format("${'$'}%.02f", transaction.amount / 100.0f)
val context = holder.itemView.context val context = holder.itemView.context
holder.amount.setTextColor( val color = if (transaction.isExpense) R.color.colorTextRed else R.color.colorTextGreen
context.resources.getColor(transaction.type.textColor, context.theme) holder.amount.setTextColor(context.resources.getColor(color, context.theme))
)
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
startActivity( startActivity(
it.context.applicationContext, it.context.applicationContext,

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.transactions package com.wbrawner.budget.ui.transactions
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders import android.arch.lifecycle.ViewModelProviders
@ -13,13 +13,10 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.wbrawner.budget.R import com.wbrawner.budget.R
import com.wbrawner.budget.transactions.AddEditTransactionActivity.Companion.EXTRA_TYPE
import com.wbrawner.budget.data.TransactionType
import com.wbrawner.budget.data.model.TransactionWithCategory import com.wbrawner.budget.data.model.TransactionWithCategory
class TransactionListFragment : Fragment() { class TransactionListFragment : Fragment() {
lateinit var viewModel: TransactionViewModel lateinit var viewModel: TransactionViewModel
lateinit var type: TransactionType
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -31,8 +28,6 @@ class TransactionListFragment : Fragment() {
return return
} }
type = arguments?.getSerializable(ARG_TYPE) as? TransactionType ?: TransactionType.EXPENSE
viewModel = ViewModelProviders.of(activity!!).get(TransactionViewModel::class.java) viewModel = ViewModelProviders.of(activity!!).get(TransactionViewModel::class.java)
} }
@ -41,12 +36,12 @@ class TransactionListFragment : Fragment() {
val recyclerView = view.findViewById<RecyclerView>(R.id.list_transactions) val recyclerView = view.findViewById<RecyclerView>(R.id.list_transactions)
val fab = view.findViewById<FloatingActionButton>(R.id.fab_add_transaction) val fab = view.findViewById<FloatingActionButton>(R.id.fab_add_transaction)
recyclerView.layoutManager = LinearLayoutManager(activity) recyclerView.layoutManager = LinearLayoutManager(activity)
viewModel.getTransactionsByType(20, type) viewModel.getTransactions(20)
.observe(this, Observer<List<TransactionWithCategory>> { data -> .observe(this, Observer<List<TransactionWithCategory>> { data ->
val noDataView = view.findViewById<EmojiTextView>(R.id.transaction_list_no_data) val noDataView = view.findViewById<EmojiTextView>(R.id.transaction_list_no_data)
if (data == null || data.isEmpty()) { if (data == null || data.isEmpty()) {
recyclerView.adapter = null recyclerView.adapter = null
noDataView?.setText(type.noDataText) noDataView?.setText(R.string.transactions_no_data)
recyclerView?.visibility = View.GONE recyclerView?.visibility = View.GONE
noDataView?.visibility = View.VISIBLE noDataView?.visibility = View.VISIBLE
} else { } else {
@ -62,9 +57,7 @@ class TransactionListFragment : Fragment() {
}) })
fab.setOnClickListener { fab.setOnClickListener {
startActivity( startActivity(
Intent(activity, AddEditTransactionActivity::class.java).apply { Intent(activity, AddEditTransactionActivity::class.java)
this.putExtra(EXTRA_TYPE, this@TransactionListFragment.type.name)
}
) )
} }
@ -72,6 +65,7 @@ class TransactionListFragment : Fragment() {
} }
companion object { companion object {
const val ARG_TYPE = "TRANSACTION_TYPE" const val TAG_FRAGMENT = "transactions"
const val TITLE_FRAGMENT = R.string.title_transactions
} }
} }

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.transactions package com.wbrawner.budget.ui.transactions
import android.app.Application import android.app.Application
import android.arch.lifecycle.AndroidViewModel import android.arch.lifecycle.AndroidViewModel
@ -16,10 +16,10 @@ class TransactionViewModel(application: Application): AndroidViewModel(applicati
fun getTransactions(count: Int): LiveData<List<TransactionWithCategory>> = transactionRepo.getTransactions(count) fun getTransactions(count: Int): LiveData<List<TransactionWithCategory>> = transactionRepo.getTransactions(count)
fun getTransactionsByType(count: Int, type: TransactionType): LiveData<List<TransactionWithCategory>> // fun getTransactionsByType(count: Int, type: TransactionType): LiveData<List<TransactionWithCategory>>
= transactionRepo.getTransactionsByType(count, type) // = transactionRepo.getTransactionsByType(count, type)
fun getCurrentBalance(): LiveData<Double> = transactionRepo.getCurrentBalance() fun getCurrentBalance(): LiveData<Int> = transactionRepo.getCurrentBalance()
fun getCategories(): LiveData<List<TransactionCategory>> = transactionRepo.getCategories() fun getCategories(): LiveData<List<TransactionCategory>> = transactionRepo.getCategories()

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -51,10 +52,12 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container_edit_category_name"> app:layout_constraintTop_toBottomOf="@+id/container_edit_category_name">
<android.support.design.widget.TextInputEditText <com.blackcat.currencyedittext.CurrencyEditText
android:id="@+id/edit_category_amount" android:id="@+id/edit_category_amount"
style="@style/AppTheme.EditText" style="@style/AppTheme.EditText"
android:inputType="numberDecimal" /> android:text="0.00"
android:inputType="number"
tools:ignore="HardcodedText" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<!--<TextView--> <!--<TextView-->

View file

@ -31,7 +31,6 @@
android:id="@+id/container_edit_transaction_title" android:id="@+id/container_edit_transaction_title"
style="@style/AppTheme.EditText.Container" style="@style/AppTheme.EditText.Container"
android:hint="@string/prompt_transaction_title" android:hint="@string/prompt_transaction_title"
app:layout_constraintBottom_toTopOf="@+id/container_edit_transaction_description"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@ -46,7 +45,6 @@
android:id="@+id/container_edit_transaction_description" android:id="@+id/container_edit_transaction_description"
style="@style/AppTheme.EditText.Container" style="@style/AppTheme.EditText.Container"
android:hint="@string/prompt_transaction_description" android:hint="@string/prompt_transaction_description"
app:layout_constraintBottom_toTopOf="@+id/container_edit_transaction_amount"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_title"> app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_title">
@ -62,17 +60,36 @@
android:id="@+id/container_edit_transaction_amount" android:id="@+id/container_edit_transaction_amount"
style="@style/AppTheme.EditText.Container" style="@style/AppTheme.EditText.Container"
android:hint="@string/prompt_transaction_amount" android:hint="@string/prompt_transaction_amount"
app:layout_constraintBottom_toTopOf="@+id/container_edit_transaction_date"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_description"> app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_description">
<android.support.design.widget.TextInputEditText <com.blackcat.currencyedittext.CurrencyEditText
android:id="@+id/edit_transaction_amount" android:id="@+id/edit_transaction_amount"
style="@style/AppTheme.EditText" style="@style/AppTheme.EditText"
android:inputType="numberDecimal" /> android:inputType="numberDecimal" />
</android.support.design.widget.TextInputLayout> </android.support.design.widget.TextInputLayout>
<RadioGroup
android:id="@+id/container_edit_transaction_type"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_amount">
<RadioButton
android:id="@+id/edit_transaction_type_expense"
android:text="@string/type_expense"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RadioButton
android:id="@+id/edit_transaction_type_income"
android:text="@string/type_income"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RadioGroup>
<TextView <TextView
android:id="@+id/container_edit_transaction_date" android:id="@+id/container_edit_transaction_date"
style="@style/AppTheme.EditText.Hint" style="@style/AppTheme.EditText.Hint"
@ -80,15 +97,13 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:text="@string/prompt_transaction_date" android:text="@string/prompt_transaction_date"
app:layout_constraintBottom_toTopOf="@+id/edit_transaction_date"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_amount" /> app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_type" />
<DatePicker <DatePicker
android:id="@+id/edit_transaction_date" android:id="@+id/edit_transaction_date"
style="@style/AppTheme.DatePicker" style="@style/AppTheme.DatePicker"
app:layout_constraintBottom_toTopOf="@+id/container_edit_transaction_category"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_date" /> app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_date" />
@ -100,7 +115,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:text="@string/prompt_transaction_category" android:text="@string/prompt_transaction_category"
app:layout_constraintBottom_toTopOf="@+id/edit_transaction_category"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/edit_transaction_date" /> app:layout_constraintTop_toBottomOf="@+id/edit_transaction_date" />
@ -109,7 +123,6 @@
android:id="@+id/edit_transaction_category" android:id="@+id/edit_transaction_category"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_category" /> app:layout_constraintTop_toBottomOf="@+id/container_edit_transaction_category" />

View file

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.wbrawner.budget.transactions.TransactionListActivity"> tools:context=".ui.MainActivity">
<android.support.v7.widget.Toolbar <android.support.v7.widget.Toolbar
android:id="@+id/action_bar" android:id="@+id/action_bar"

View file

@ -22,7 +22,7 @@
android:id="@+id/category_amount" android:id="@+id/category_amount"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="18sp" style="?android:attr/textAppearanceSmall"
app:layout_constraintHorizontal_chainStyle="spread_inside" app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintBottom_toTopOf="@id/barrier" app:layout_constraintBottom_toTopOf="@id/barrier"
app:layout_constraintStart_toEndOf="@+id/category_title" app:layout_constraintStart_toEndOf="@+id/category_title"

View file

@ -5,13 +5,9 @@
android:icon="@drawable/ic_baseline_dashboard_24dp" android:icon="@drawable/ic_baseline_dashboard_24dp"
android:title="@string/title_dashboard" /> android:title="@string/title_dashboard" />
<item <item
android:id="@+id/action_expenses" android:id="@+id/action_transactions"
android:icon="@drawable/ic_money_off_black_24dp"
android:title="@string/title_expenses" />
<item
android:id="@+id/action_income"
android:icon="@drawable/ic_attach_money_black_24dp" android:icon="@drawable/ic_attach_money_black_24dp"
android:title="@string/title_income" /> android:title="@string/title_transactions" />
<item <item
android:id="@+id/action_categories" android:id="@+id/action_categories"
android:icon="@drawable/ic_baseline_category_24px" android:icon="@drawable/ic_baseline_category_24px"

View file

@ -2,23 +2,20 @@
<string name="app_name">My Allowance</string> <string name="app_name">My Allowance</string>
<string name="app_name_short">Allowance</string> <string name="app_name_short">Allowance</string>
<string name="prompt_transaction_title">Title</string> <string name="prompt_transaction_title">Title</string>
<string name="title_add_income">Add Income</string>
<string name="title_add_expense">Add Expense</string>
<string name="title_add_transaction">Add Transaction</string> <string name="title_add_transaction">Add Transaction</string>
<string name="prompt_transaction_description">Description</string> <string name="prompt_transaction_description">Description</string>
<string name="prompt_transaction_date">Date</string> <string name="prompt_transaction_date">Date</string>
<string name="prompt_transaction_amount">Amount</string> <string name="prompt_transaction_amount">Amount</string>
<string name="title_expenses">Expenses</string> <string name="type_expense">Expense</string>
<string name="title_income">Income</string> <string name="type_income">Income</string>
<string name="title_dashboard">Dashboard</string> <string name="title_dashboard">Dashboard</string>
<string name="label_current_balance">Current Balance</string> <string name="label_current_balance">Current Balance</string>
<string name="overview_no_data">&#x1F4C8;\nAdd some transactions to see an overview of your spending here</string> <string name="overview_no_data">&#x1F4C8;\nAdd some transactions to see an overview of your spending here</string>
<string name="action_save">Save</string> <string name="action_save">Save</string>
<string name="action_delete">Delete</string> <string name="action_delete">Delete</string>
<string name="title_edit_income">Edit Income</string> <string name="title_edit_transaction">Edit Transaction</string>
<string name="title_edit_expense">Edit Expense</string> <string name="categories_no_data">&#x1F333;\nGet to work! Money doesn\'t grow on trees after all</string>
<string name="income_no_data">&#x1F333;\nGet to work! Money doesn\'t grow on trees after all</string> <string name="transactions_no_data">&#x1F914;\nAre you sure you haven\'t spent any money?</string>
<string name="expenses_no_data">&#x1F914;\nAre you sure you haven\'t spent any money?</string>
<string name="title_categories">Categories</string> <string name="title_categories">Categories</string>
<string name="title_edit_category">Edit Category</string> <string name="title_edit_category">Edit Category</string>
<string name="prompt_category_amount">Amount</string> <string name="prompt_category_amount">Amount</string>
@ -28,4 +25,6 @@
<string name="prompt_transaction_category">Category</string> <string name="prompt_transaction_category">Category</string>
<string name="required_field_name">Name is a required field</string> <string name="required_field_name">Name is a required field</string>
<string name="required_field_amount">Amount is a required field</string> <string name="required_field_amount">Amount is a required field</string>
<string name="title_transactions">Transactions</string>
<string name="balance_remaning">\$%1$.02f remaining</string>
</resources> </resources>

View file

@ -2,33 +2,15 @@
<shortcuts xmlns:tools="http://schemas.android.com/tools" <shortcuts xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut <shortcut
android:shortcutId="expense" android:shortcutId="transaction"
android:enabled="true"
android:icon="@drawable/ic_shortcut_money_off"
android:shortcutShortLabel="@string/title_add_expense"
tools:ignore="UnusedAttribute">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="com.wbrawner.budget"
android:targetClass="com.wbrawner.budget.transactions.AddEditTransactionActivity">
<extra
android:name="EXTRA_TRANSACTION_TYPE"
android:value="EXPENSE" />
</intent>
</shortcut>
<shortcut
android:shortcutId="income"
android:enabled="true" android:enabled="true"
android:icon="@drawable/ic_shortcut_attach_money" android:icon="@drawable/ic_shortcut_attach_money"
android:shortcutShortLabel="@string/title_add_income" android:shortcutShortLabel="@string/title_add_transaction"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:targetPackage="com.wbrawner.budget" android:targetPackage="com.wbrawner.budget"
android:targetClass="com.wbrawner.budget.transactions.AddEditTransactionActivity"> android:targetClass="com.wbrawner.budget.ui.transactions.AddEditTransactionActivity">
<extra
android:name="EXTRA_TRANSACTION_TYPE"
android:value="INCOME" />
</intent> </intent>
</shortcut> </shortcut>
</shortcuts> </shortcuts>

View file

@ -19,6 +19,7 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url "https://jitpack.io" }
} }
} }