├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── acme │ │ └── tipcalculator │ │ └── TipCalculatorActivityTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── acme │ │ │ └── tipcalculator │ │ │ ├── model │ │ │ ├── Calculator.kt │ │ │ ├── TipCalculation.kt │ │ │ └── TipCalculationRepository.kt │ │ │ ├── view │ │ │ ├── LoadDialogFragment.kt │ │ │ ├── SaveDialogFragment.kt │ │ │ ├── TipCalculatorActivity.kt │ │ │ └── TipSummaryAdapter.kt │ │ │ └── viewmodel │ │ │ ├── CalculatorViewModel.kt │ │ │ ├── ObservableViewModel.kt │ │ │ └── TipCalculationSummaryItem.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_money_white_24dp.xml │ │ ├── layout │ │ ├── activity_tip_calculator.xml │ │ ├── content_tip_calculator.xml │ │ ├── saved_tip_calculations_list.xml │ │ └── saved_tip_calculations_list_item.xml │ │ ├── menu │ │ └── menu_tip_calculator.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ ├── java │ └── com │ │ └── acme │ │ └── tipcalculator │ │ ├── model │ │ ├── CalculatorTest.kt │ │ └── TipCalculationRepositoryTest.kt │ │ └── viewmodel │ │ └── CalculatorViewModelTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | /local.properties 5 | /.idea/vcs.xml 6 | /.idea/workspace.xml 7 | /.idea/libraries 8 | .DS_Store 9 | /build 10 | /captures 11 | .externalNativeBuild 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion compile_sdk 8 | defaultConfig { 9 | applicationId app_id 10 | minSdkVersion min_sdk 11 | targetSdkVersion target_sdk 12 | versionCode version_code 13 | versionName version_name 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | dataBinding { 24 | enabled = true 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | 31 | // Kotlin 32 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 33 | 34 | // Support 35 | implementation "com.android.support:appcompat-v7:$support_library" 36 | implementation "com.android.support.constraint:constraint-layout:$constraint_layout" 37 | implementation "com.android.support:design:$support_library" 38 | implementation "com.android.support:support-v4:$support_library" 39 | kapt "com.android.databinding:compiler:$android_plugin" 40 | implementation "android.arch.lifecycle:extensions:$arch_comp" 41 | 42 | // Test 43 | testImplementation "junit:junit:$junit" 44 | testImplementation "org.mockito:mockito-core:$mockito" 45 | testImplementation "android.arch.core:core-testing:$arch_comp" 46 | 47 | androidTestImplementation "com.android.support.test:runner:$android_junit_runner" 48 | androidTestImplementation "com.android.support.test.espresso:espresso-core:$espresso" 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/acme/tipcalculator/TipCalculatorActivityTest.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.espresso.Espresso.onView 5 | import android.support.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu 6 | import android.support.test.espresso.action.ViewActions.click 7 | import android.support.test.espresso.action.ViewActions.replaceText 8 | import android.support.test.espresso.assertion.ViewAssertions.matches 9 | import android.support.test.espresso.matcher.ViewMatchers.* 10 | import android.support.test.rule.ActivityTestRule 11 | import com.acme.tipcalculator.view.TipCalculatorActivity 12 | import org.junit.Rule 13 | import org.junit.Test 14 | 15 | class TipCalculatorActivityTest { 16 | 17 | @get:Rule var activityTestRule = ActivityTestRule(TipCalculatorActivity::class.java) 18 | 19 | @Test 20 | fun testTipCalculator() { 21 | 22 | // Calculate Tip 23 | enter(checkAmount = 15.99, tipPercent = 15) 24 | calculateTip() 25 | assertOutput(name = "", checkAmount = "$15.99", tipAmount = "$2.40", total = "$18.39") 26 | 27 | // Save Tip 28 | saveTip(name = "BBQ Max") 29 | assertOutput(name = "BBQ Max", checkAmount = "$15.99", tipAmount = "$2.40", total = "$18.39") 30 | 31 | // Clear Outputs 32 | clearOutputs() 33 | assertOutput(name = "", checkAmount = "$0.00", tipAmount = "$0.00", total = "$0.00") 34 | 35 | // Load Tip 36 | loadTip(name = "BBQ Max") 37 | assertOutput(name = "BBQ Max", checkAmount = "$15.99", tipAmount = "$2.40", total = "$18.39") 38 | 39 | } 40 | 41 | private fun enter(checkAmount: Double, tipPercent: Int) { 42 | onView(withId(R.id.input_check_amount)).perform(replaceText(checkAmount.toString())) 43 | onView(withId(R.id.input_tip_percentage)).perform(replaceText(tipPercent.toString())) 44 | } 45 | 46 | private fun calculateTip() { 47 | onView(withId(R.id.calculate_fab)).perform(click()) 48 | } 49 | 50 | private fun assertOutput(name: String, checkAmount: String, tipAmount: String, total: String) { 51 | onView(withId(R.id.bill_amount)).check(matches(withText(checkAmount))) 52 | onView(withId(R.id.tip_dollar_amount)).check(matches(withText(tipAmount))) 53 | onView(withId(R.id.total_dollar_amount)).check(matches(withText(total))) 54 | onView(withId(R.id.calculation_name)).check(matches((withText(name)))) 55 | } 56 | 57 | private fun clearOutputs() { 58 | enter(checkAmount = 0.0, tipPercent = 0) 59 | calculateTip() 60 | } 61 | 62 | private fun saveTip(name: String) { 63 | 64 | openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getContext()) 65 | 66 | onView(withText(R.string.action_save)).perform(click()) 67 | 68 | onView(withHint(R.string.save_hint)).perform(replaceText(name)) 69 | 70 | onView(withText(R.string.action_save)).perform(click()) 71 | 72 | } 73 | 74 | private fun loadTip(name: String) { 75 | 76 | openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getContext()) 77 | 78 | onView(withText(R.string.action_load)).perform(click()) 79 | 80 | onView(withText(name)).perform(click()) 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/model/Calculator.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.model 2 | 3 | import android.arch.lifecycle.LiveData 4 | import java.math.RoundingMode 5 | 6 | class Calculator(val repository: TipCalculationRepository = TipCalculationRepository()) { 7 | 8 | fun calculateTip(checkAmount: Double, tipPct: Int) : TipCalculation { 9 | 10 | val tipAmount = (checkAmount * (tipPct.toDouble() / 100.0)) 11 | .toBigDecimal() 12 | .setScale(2, RoundingMode.HALF_UP) 13 | .toDouble() 14 | 15 | val grandTotal = checkAmount + tipAmount 16 | 17 | return TipCalculation( 18 | checkAmount = checkAmount, 19 | tipPct = tipPct, 20 | tipAmount = tipAmount, 21 | grandTotal = grandTotal 22 | ) 23 | } 24 | 25 | fun saveTipCalculation(tc: TipCalculation) { 26 | repository.saveTipCalculation(tc) 27 | } 28 | 29 | fun loadTipCalculationByLocationName(locationName: String) : TipCalculation? { 30 | return repository.loadTipCalculationByName(locationName) 31 | } 32 | 33 | fun loadSavedTipCalculations() : LiveData> { 34 | return repository.loadSavedTipCalculations() 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/model/TipCalculation.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.model 2 | 3 | data class TipCalculation( 4 | val locationName: String = "", 5 | val tipPct: Int = 0, 6 | val checkAmount: Double = 0.0, 7 | val tipAmount: Double = 0.0, 8 | val grandTotal: Double = 0.0) -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/model/TipCalculationRepository.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.model 2 | 3 | import android.arch.lifecycle.LiveData 4 | import android.arch.lifecycle.MutableLiveData 5 | 6 | class TipCalculationRepository { 7 | 8 | private val savedTips = mutableMapOf() 9 | 10 | fun saveTipCalculation(tc: TipCalculation) { 11 | savedTips[tc.locationName] = tc 12 | } 13 | 14 | fun loadTipCalculationByName(locationName: String) : TipCalculation? { 15 | return savedTips[locationName] 16 | } 17 | 18 | fun loadSavedTipCalculations() : LiveData> { 19 | val liveData = MutableLiveData>() 20 | liveData.value = savedTips.values.toList() 21 | return liveData 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/view/LoadDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.view 2 | 3 | import android.app.Dialog 4 | import android.arch.lifecycle.Observer 5 | import android.arch.lifecycle.ViewModelProviders 6 | import android.content.Context 7 | import android.os.Bundle 8 | import android.support.v4.app.DialogFragment 9 | import android.support.v7.app.AlertDialog 10 | import android.support.v7.widget.DividerItemDecoration 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import com.acme.tipcalculator.R 14 | import com.acme.tipcalculator.viewmodel.CalculatorViewModel 15 | import kotlinx.android.synthetic.main.saved_tip_calculations_list.view.* 16 | 17 | 18 | class LoadDialogFragment : DialogFragment() { 19 | 20 | interface Callback { 21 | fun onTipSelected(name: String) 22 | } 23 | 24 | private var loadTipCallback: Callback? = null 25 | 26 | override fun onAttach(context: Context?) { 27 | super.onAttach(context) 28 | loadTipCallback = context as? Callback 29 | } 30 | 31 | override fun onDetach() { 32 | super.onDetach() 33 | loadTipCallback = null 34 | } 35 | 36 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 37 | 38 | val dialog = context?.let { ctx -> 39 | 40 | AlertDialog.Builder(ctx) 41 | .setView(createView(ctx)) 42 | .setNegativeButton(R.string.action_cancel, null) 43 | .create() 44 | 45 | } 46 | 47 | return dialog!! 48 | } 49 | 50 | private fun createView(ctx: Context) : View { 51 | 52 | val rv = LayoutInflater 53 | .from(ctx) 54 | .inflate(R.layout.saved_tip_calculations_list, null) 55 | .recycler_saved_calculations 56 | 57 | rv.setHasFixedSize(true) 58 | rv.addItemDecoration(DividerItemDecoration(ctx, DividerItemDecoration.VERTICAL)) 59 | 60 | val adapter = TipSummaryAdapter { 61 | loadTipCallback?.onTipSelected(it.locationName) 62 | dismiss() 63 | } 64 | rv.adapter = adapter 65 | 66 | val vm = ViewModelProviders.of(activity!!).get(CalculatorViewModel::class.java) 67 | 68 | vm.loadSavedTipCalculationSummaries().observe(this, Observer { 69 | if(it != null) { 70 | adapter.updateList(it) 71 | } 72 | }) 73 | 74 | return rv 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/view/SaveDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.view 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.support.v4.app.DialogFragment 7 | import android.support.v7.app.AlertDialog 8 | import android.util.Log 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import android.widget.EditText 13 | import com.acme.tipcalculator.R 14 | 15 | class SaveDialogFragment : DialogFragment() { 16 | 17 | val TAG = "SaveDialogFragment" 18 | interface Callback { 19 | fun onSaveTip(name: String) 20 | } 21 | 22 | private var saveTipCallback: SaveDialogFragment.Callback? = null 23 | 24 | override fun onAttach(context: Context?) { 25 | super.onAttach(context) 26 | saveTipCallback = context as? Callback 27 | } 28 | 29 | override fun onDetach() { 30 | super.onDetach() 31 | saveTipCallback = null 32 | } 33 | 34 | override fun onCreate(savedInstanceState: Bundle?) { 35 | super.onCreate(savedInstanceState) 36 | Log.d(TAG,"OnCreate") 37 | } 38 | 39 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 40 | Log.d(TAG,"OnCreateView") 41 | return super.onCreateView(inflater, container, savedInstanceState) 42 | } 43 | 44 | override fun onDestroy() { 45 | Log.d(TAG,"OnDestroy") 46 | super.onDestroy() 47 | } 48 | 49 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 50 | Log.d(TAG, "OnCreateDialog") 51 | 52 | val saveDialog = context?.let { ctx -> 53 | 54 | val editText = EditText(ctx) 55 | editText.id = viewId 56 | editText.hint = getString(R.string.save_hint) 57 | 58 | AlertDialog.Builder(ctx) 59 | .setView(editText) 60 | .setNegativeButton(R.string.action_cancel, null) 61 | .setPositiveButton(R.string.action_save, {_,_ -> onSave(editText)}) 62 | .create() 63 | 64 | } 65 | 66 | return saveDialog!! 67 | 68 | } 69 | 70 | private fun onSave(editText: EditText) { 71 | val text = editText.text.toString() 72 | if(text.isNotEmpty()) { 73 | saveTipCallback?.onSaveTip(text) 74 | } 75 | } 76 | 77 | companion object { 78 | val viewId = View.generateViewId() 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/view/TipCalculatorActivity.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.view 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.databinding.DataBindingUtil 5 | import android.os.Bundle 6 | import android.support.design.widget.Snackbar 7 | import android.support.v7.app.AppCompatActivity 8 | import android.view.Menu 9 | import android.view.MenuItem 10 | import com.acme.tipcalculator.R 11 | import com.acme.tipcalculator.databinding.ActivityTipCalculatorBinding 12 | import com.acme.tipcalculator.viewmodel.CalculatorViewModel 13 | 14 | class TipCalculatorActivity : AppCompatActivity(), SaveDialogFragment.Callback, LoadDialogFragment.Callback { 15 | 16 | lateinit var binding: ActivityTipCalculatorBinding 17 | 18 | override fun onSaveTip(name: String) { 19 | binding.vm?.saveCurrentTip(name) 20 | Snackbar.make(binding.root, "Saved $name", Snackbar.LENGTH_SHORT).show() 21 | } 22 | 23 | override fun onTipSelected(name: String) { 24 | binding.vm?.loadTipCalculation(name) 25 | Snackbar.make(binding.root, "Loaded $name", Snackbar.LENGTH_SHORT).show() 26 | } 27 | 28 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 29 | menuInflater.inflate(R.menu.menu_tip_calculator, menu) 30 | return true 31 | } 32 | 33 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 34 | return when(item.itemId) { 35 | R.id.action_save -> { 36 | showSaveDialog() 37 | true 38 | } 39 | R.id.action_load -> { 40 | showLoadDialog() 41 | true 42 | } 43 | else -> super.onOptionsItemSelected(item) 44 | } 45 | } 46 | 47 | private fun showLoadDialog() { 48 | val loadFragment = LoadDialogFragment() 49 | loadFragment.show(supportFragmentManager, "LoadDialog") 50 | } 51 | 52 | private fun showSaveDialog() { 53 | val saveFragment = SaveDialogFragment() 54 | saveFragment.show(supportFragmentManager, "SaveDialog") 55 | } 56 | 57 | override fun onCreate(savedInstanceState: Bundle?) { 58 | super.onCreate(savedInstanceState) 59 | binding = DataBindingUtil.setContentView(this, R.layout.activity_tip_calculator) 60 | binding.vm = ViewModelProviders.of(this).get(CalculatorViewModel::class.java) 61 | setSupportActionBar(binding.toolbar) 62 | } 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/view/TipSummaryAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.view 2 | 3 | import android.databinding.DataBindingUtil 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.LayoutInflater 6 | import android.view.ViewGroup 7 | import com.acme.tipcalculator.R 8 | import com.acme.tipcalculator.databinding.SavedTipCalculationsListItemBinding 9 | import com.acme.tipcalculator.viewmodel.TipCalculationSummaryItem 10 | 11 | class TipSummaryAdapter(val onItemSelected: (item: TipCalculationSummaryItem) -> Unit) 12 | : RecyclerView.Adapter() { 13 | 14 | private val tipCalculationSummaries = mutableListOf() 15 | 16 | fun updateList(updates: List) { 17 | tipCalculationSummaries.clear() 18 | tipCalculationSummaries.addAll(updates) 19 | notifyDataSetChanged() 20 | } 21 | 22 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TipSummaryViewHolder { 23 | val inflater = LayoutInflater.from(parent.context) 24 | 25 | val binding = DataBindingUtil.inflate( 26 | inflater, R.layout.saved_tip_calculations_list_item, parent, false) 27 | 28 | return TipSummaryViewHolder(binding) 29 | } 30 | 31 | override fun getItemCount(): Int { 32 | return tipCalculationSummaries.size 33 | } 34 | 35 | override fun onBindViewHolder(holder: TipSummaryViewHolder, position: Int) { 36 | holder.bind(tipCalculationSummaries[position]) 37 | } 38 | 39 | inner class TipSummaryViewHolder(val binding: SavedTipCalculationsListItemBinding) 40 | : RecyclerView.ViewHolder(binding.root) { 41 | 42 | fun bind(item: TipCalculationSummaryItem) { 43 | binding.item = item 44 | binding.root.setOnClickListener { onItemSelected(item) } 45 | binding.executePendingBindings() 46 | } 47 | 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/viewmodel/CalculatorViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.viewmodel 2 | 3 | import android.app.Application 4 | import android.arch.lifecycle.LiveData 5 | import android.arch.lifecycle.Transformations 6 | import com.acme.tipcalculator.R 7 | import com.acme.tipcalculator.model.Calculator 8 | import com.acme.tipcalculator.model.TipCalculation 9 | 10 | class CalculatorViewModel @JvmOverloads constructor( 11 | app: Application, val calculator: Calculator = Calculator()) : ObservableViewModel(app) { 12 | 13 | private var lastTipCalculated = TipCalculation() 14 | 15 | var inputCheckAmount = "" 16 | 17 | var inputTipPercentage = "" 18 | 19 | val outputCheckAmount get() = getApplication().getString(R.string.dollar_amount, lastTipCalculated.checkAmount) 20 | val outputTipAmount get() = getApplication().getString(R.string.dollar_amount, lastTipCalculated.tipAmount) 21 | val outputTotalDollarAmount get() = getApplication().getString(R.string.dollar_amount, lastTipCalculated.grandTotal) 22 | val locationName get() = lastTipCalculated.locationName 23 | 24 | init { 25 | updateOutputs(TipCalculation()) 26 | } 27 | 28 | private fun updateOutputs(tc: TipCalculation) { 29 | lastTipCalculated = tc 30 | notifyChange() 31 | } 32 | 33 | fun saveCurrentTip(name: String) { 34 | val tipToSave = lastTipCalculated.copy(locationName = name) 35 | calculator.saveTipCalculation(tipToSave) 36 | updateOutputs(tipToSave) 37 | } 38 | 39 | fun loadSavedTipCalculationSummaries() : LiveData> { 40 | return Transformations.map(calculator.loadSavedTipCalculations(), { tipCalculationObjects -> 41 | tipCalculationObjects.map { 42 | TipCalculationSummaryItem(it.locationName, 43 | getApplication().getString(R.string.dollar_amount, it.grandTotal)) 44 | } 45 | }) 46 | } 47 | 48 | fun loadTipCalculation(name: String) { 49 | 50 | val tc = calculator.loadTipCalculationByLocationName(name) 51 | 52 | if (tc != null) { 53 | inputCheckAmount = tc.checkAmount.toString() 54 | inputTipPercentage = tc.tipPct.toString() 55 | 56 | updateOutputs(tc) 57 | notifyChange() 58 | } 59 | } 60 | 61 | 62 | fun calculateTip() { 63 | 64 | val checkAmount = inputCheckAmount.toDoubleOrNull() 65 | val tipPct = inputTipPercentage.toIntOrNull() 66 | 67 | if(checkAmount != null && tipPct != null) { 68 | updateOutputs(calculator.calculateTip(checkAmount, tipPct)) 69 | } 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/viewmodel/ObservableViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.viewmodel 2 | 3 | import android.app.Application 4 | import android.arch.lifecycle.AndroidViewModel 5 | import android.databinding.Observable 6 | import android.databinding.PropertyChangeRegistry 7 | import com.acme.tipcalculator.BR 8 | 9 | abstract class ObservableViewModel(app: Application) : AndroidViewModel(app), Observable { 10 | 11 | @delegate:Transient 12 | private val mCallbacks: PropertyChangeRegistry by lazy { PropertyChangeRegistry() } 13 | 14 | override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) { 15 | mCallbacks.add(callback) 16 | } 17 | 18 | override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) { 19 | mCallbacks.remove(callback) 20 | } 21 | 22 | fun notifyChange() { 23 | mCallbacks.notifyChange(this, BR._all) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/acme/tipcalculator/viewmodel/TipCalculationSummaryItem.kt: -------------------------------------------------------------------------------- 1 | package com.acme.tipcalculator.viewmodel 2 | 3 | data class TipCalculationSummaryItem(val locationName: String, val totalDollarAmount: String) -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_money_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_tip_calculator.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 21 | 22 | 28 | 29 | 30 | 31 | 35 | 36 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_tip_calculator.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 20 | 21 | 28 | 29 | 39 | 40 | 41 | 42 | 43 | 50 | 51 | 61 | 62 | 63 | 64 | 76 | 77 | 82 | 83 | 91 | 92 | 99 | 100 | 101 | 102 | 103 | 108 | 109 | 117 | 118 | 125 | 126 | 127 | 128 | 129 | 134 | 135 | 143 | 144 | 151 | 152 | 153 | 154 | 155 | 156 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/saved_tip_calculations_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/saved_tip_calculations_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 22 | 23 | 33 | 34 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_tip_calculator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericmaxwell2003/MvvmTipCalculator/054064b7f8e79445fbd2c8903e7f6f5c3702fee2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Tip Calculator 3 | 4 | Check 5 | Tip Percentage 6 | Total 7 | 8 | Total 9 | Tip 10 | Check 11 | 12 | $%1$.02f 13 | 14 | Enter Location 15 | Save 16 | Cancel 17 | 18 | Load 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 16 |