├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── org │ │ └── akanework │ │ └── riviere │ │ ├── Riviere.kt │ │ ├── logic │ │ ├── Extensions.kt │ │ └── utils │ │ │ └── DecimalDigitsInputFilter.kt │ │ └── ui │ │ ├── MainActivity.kt │ │ ├── adapters │ │ ├── HomeChipAdapter.kt │ │ └── HomePresetAdapter.kt │ │ ├── data │ │ └── HolderTypes.kt │ │ └── fragments │ │ ├── BaseFragment.kt │ │ └── HomeFragment.kt │ └── res │ ├── drawable │ ├── bg_chip_background.xml │ ├── ic_add.xml │ ├── ic_arrow_downward.xml │ ├── ic_category.xml │ ├── ic_category_filled.xml │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ ├── ic_local_cafe.xml │ ├── ic_remove.xml │ ├── ic_star.xml │ ├── ic_star_alt.xml │ ├── minus_to_plus.xml │ └── plus_to_minus.xml │ ├── font │ └── plexsans.ttf │ ├── layout │ ├── activity_main.xml │ ├── bottom_sheet.xml │ ├── chip_card_add.xml │ ├── chip_card_layout.xml │ ├── chip_card_layout_alt.xml │ ├── detail_item.xml │ ├── fragment_home.xml │ ├── fragment_home_upper.xml │ ├── preset_card_add.xml │ ├── preset_card_layout.xml │ └── total_header.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-night │ ├── bools.xml │ ├── colors.xml │ └── colors_md.xml │ ├── values-v23 │ └── themes.xml │ ├── values-v27 │ └── themes.xml │ ├── values-v29 │ └── themes.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values │ ├── attrs.xml │ ├── bools.xml │ ├── colors.xml │ ├── colors_md.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ ├── theme_overlays.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Rivière -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | /debug 4 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "org.akanework.riviere" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "org.akanework.riviere" 12 | minSdk = 21 13 | targetSdk = 34 14 | versionCode = 1 15 | versionName = "1.0" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = true 21 | isShrinkResources = true 22 | proguardFiles( 23 | getDefaultProguardFile("proguard-android-optimize.txt"), 24 | "proguard-rules.pro" 25 | ) 26 | } 27 | } 28 | compileOptions { 29 | sourceCompatibility = JavaVersion.VERSION_1_8 30 | targetCompatibility = JavaVersion.VERSION_1_8 31 | } 32 | kotlinOptions { 33 | jvmTarget = "1.8" 34 | } 35 | } 36 | 37 | dependencies { 38 | 39 | implementation("androidx.core:core-ktx:1.12.0") 40 | implementation("androidx.appcompat:appcompat:1.6.1") 41 | implementation("com.google.android.material:material:1.11.0") 42 | implementation("androidx.constraintlayout:constraintlayout:2.1.4") 43 | implementation("androidx.preference:preference-ktx:1.2.1") 44 | 45 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/Riviere.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere 2 | 3 | import android.app.Application 4 | import com.google.android.material.color.DynamicColors 5 | 6 | class Riviere : Application() { 7 | override fun onCreate() { 8 | super.onCreate() 9 | DynamicColors.applyToActivitiesIfAvailable(this) 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/logic/Extensions.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere.logic 2 | 3 | import android.content.res.Resources.getSystem 4 | 5 | val Int.dp: Int get() = (this.toFloat().dp).toInt() 6 | 7 | val Int.px: Int get() = (this.toFloat().px).toInt() 8 | 9 | val Float.dp: Float get() = this / getSystem().displayMetrics.density 10 | 11 | val Float.px: Float get() = this * getSystem().displayMetrics.density -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/logic/utils/DecimalDigitsInputFilter.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere.logic.utils 2 | 3 | import android.text.InputFilter 4 | import android.text.Spanned 5 | 6 | /** 7 | * Input filter that limits the number of decimal digits that are allowed to be 8 | * entered. 9 | */ 10 | class DecimalDigitsInputFilter(private val decimalDigits: Int) : 11 | InputFilter { 12 | 13 | override fun filter( 14 | source: CharSequence, 15 | start: Int, 16 | end: Int, 17 | dest: Spanned, 18 | dstart: Int, 19 | dend: Int 20 | ): String? { 21 | var dotPos = -1 22 | val len = dest.length 23 | for (i in 0 until len) { 24 | val c = dest[i] 25 | if (c == '.' || c == ',') { 26 | dotPos = i 27 | break 28 | } 29 | } 30 | 31 | if (dotPos >= 0) { 32 | 33 | // protects against many dots 34 | if (source == "." || source == ",") { 35 | return "" 36 | } 37 | // if the text is entered before the dot 38 | if (dend <= dotPos) { 39 | return null 40 | } 41 | if (len - dotPos > decimalDigits) { 42 | return "" 43 | } 44 | } 45 | return null 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere.ui 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import androidx.activity.enableEdgeToEdge 6 | import androidx.core.view.WindowCompat 7 | import org.akanework.riviere.R 8 | 9 | class MainActivity : AppCompatActivity() { 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | WindowCompat.setDecorFitsSystemWindows(window, false) 13 | setContentView(R.layout.activity_main) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/ui/adapters/HomeChipAdapter.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere.ui.adapters 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import androidx.core.content.ContextCompat 10 | import androidx.recyclerview.widget.RecyclerView 11 | import org.akanework.riviere.R 12 | import org.akanework.riviere.ui.data.HolderTypes 13 | 14 | class HomeChipAdapter ( 15 | private val cardData: MutableList, 16 | private val context: Context 17 | ) : RecyclerView.Adapter () { 18 | 19 | // private val prefs = PreferenceManager.getDefaultSharedPreferences(context) 20 | 21 | inner class ViewHolder(view: View) : 22 | RecyclerView.ViewHolder(view) { 23 | val desc: TextView = view.findViewById(R.id.desc) 24 | val icon: ImageView = view.findViewById(R.id.icon) 25 | } 26 | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 28 | return ViewHolder( 29 | LayoutInflater.from(parent.context) 30 | .inflate( 31 | when (viewType) { 32 | 0 -> R.layout.chip_card_layout 33 | 1 -> R.layout.chip_card_layout_alt 34 | 2 -> R.layout.chip_card_add 35 | else -> throw IllegalArgumentException() 36 | }, 37 | parent, 38 | false 39 | ) 40 | ) 41 | } 42 | 43 | override fun getItemViewType(position: Int): Int = 44 | if (!cardData[position].isBlock && position == 0) { 45 | 0 46 | } else if (!cardData[position].isBlock) { 47 | 1 48 | } else { 49 | 2 50 | } 51 | 52 | override fun getItemCount(): Int = cardData.size 53 | 54 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 55 | if (holder.itemViewType != 2) { 56 | holder.desc.text = cardData[position].desc ?: "餐厅" 57 | holder.icon.setImageDrawable( 58 | ContextCompat.getDrawable( 59 | context, 60 | cardData[position].icon ?: R.drawable.ic_local_cafe 61 | ) 62 | ) 63 | } 64 | } 65 | 66 | /* 67 | 68 | fun updateList(prefs: SharedPreferences) { 69 | val dumpList = StoreUtils.dumpExpenseList(prefs).toMutableList() 70 | if (dumpList.isEmpty()) { 71 | val initializeHorizontalCard = HolderTypes.HorizontalCardData( 72 | context.getString(R.string.quick_start), 73 | context.getString(R.string.add_your_expense_type), 74 | context.getString(R.string.first_expense_type), 75 | R.drawable.ic_add, 76 | true 77 | ) 78 | dumpList.add(initializeHorizontalCard) 79 | } 80 | val diffResult = DiffUtil.calculateDiff(DiffCallback(cardData, dumpList)) 81 | cardData.clear() 82 | cardData.addAll(dumpList) 83 | diffResult.dispatchUpdatesTo(this) 84 | } 85 | 86 | private class DiffCallback( 87 | private val oldList: MutableList, 88 | private val newList: MutableList, 89 | ) : DiffUtil.Callback() { 90 | override fun getOldListSize() = oldList.size 91 | 92 | override fun getNewListSize() = newList.size 93 | 94 | override fun areItemsTheSame( 95 | oldItemPosition: Int, 96 | newItemPosition: Int, 97 | ) = oldList[oldItemPosition] == newList[newItemPosition] 98 | 99 | override fun areContentsTheSame( 100 | oldItemPosition: Int, 101 | newItemPosition: Int, 102 | ) = oldList[oldItemPosition] == newList[newItemPosition] 103 | } 104 | 105 | */ 106 | } -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/ui/adapters/HomePresetAdapter.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere.ui.adapters 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.ImageView 8 | import android.widget.TextView 9 | import androidx.core.content.ContextCompat 10 | import androidx.recyclerview.widget.RecyclerView 11 | import org.akanework.riviere.R 12 | import org.akanework.riviere.ui.data.HolderTypes 13 | 14 | class HomePresetAdapter ( 15 | private val cardData: MutableList, 16 | private val context: Context 17 | ) : RecyclerView.Adapter () { 18 | 19 | // private val prefs = PreferenceManager.getDefaultSharedPreferences(context) 20 | 21 | inner class ViewHolder(view: View) : 22 | RecyclerView.ViewHolder(view) { 23 | val desc: TextView = view.findViewById(R.id.desc) 24 | val icon: ImageView = view.findViewById(R.id.indicator) 25 | val value: TextView = view.findViewById(R.id.value) 26 | } 27 | 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 29 | return ViewHolder( 30 | LayoutInflater.from(parent.context) 31 | .inflate( 32 | when (viewType) { 33 | 0 -> R.layout.preset_card_layout 34 | 1 -> R.layout.preset_card_add 35 | 2 -> R.layout.preset_card_layout 36 | else -> throw IllegalArgumentException() 37 | }, 38 | parent, 39 | false 40 | ) 41 | ) 42 | } 43 | 44 | override fun getItemViewType(position: Int): Int { 45 | return if (position == 0 && !cardData[position].isBlock) { 46 | 0 47 | } else if (position > 0 && !cardData[position].isBlock) { 48 | 2 49 | } else { 50 | 1 51 | } 52 | } 53 | 54 | override fun getItemCount(): Int = cardData.size 55 | 56 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 57 | holder.value.text = (cardData[position].defVal ?: "").toString() 58 | holder.desc.text = cardData[position].desc ?: "" 59 | holder.icon.setImageDrawable(ContextCompat.getDrawable( 60 | context, 61 | cardData[position].icon ?: R.drawable.ic_local_cafe 62 | )) 63 | } 64 | 65 | /* 66 | 67 | fun updateList(prefs: SharedPreferences) { 68 | val dumpList = StoreUtils.dumpExpenseList(prefs).toMutableList() 69 | if (dumpList.isEmpty()) { 70 | val initializeHorizontalCard = HolderTypes.HorizontalCardData( 71 | context.getString(R.string.quick_start), 72 | context.getString(R.string.add_your_expense_type), 73 | context.getString(R.string.first_expense_type), 74 | R.drawable.ic_add, 75 | true 76 | ) 77 | dumpList.add(initializeHorizontalCard) 78 | } 79 | val diffResult = DiffUtil.calculateDiff(DiffCallback(cardData, dumpList)) 80 | cardData.clear() 81 | cardData.addAll(dumpList) 82 | diffResult.dispatchUpdatesTo(this) 83 | } 84 | 85 | private class DiffCallback( 86 | private val oldList: MutableList, 87 | private val newList: MutableList, 88 | ) : DiffUtil.Callback() { 89 | override fun getOldListSize() = oldList.size 90 | 91 | override fun getNewListSize() = newList.size 92 | 93 | override fun areItemsTheSame( 94 | oldItemPosition: Int, 95 | newItemPosition: Int, 96 | ) = oldList[oldItemPosition] == newList[newItemPosition] 97 | 98 | override fun areContentsTheSame( 99 | oldItemPosition: Int, 100 | newItemPosition: Int, 101 | ) = oldList[oldItemPosition] == newList[newItemPosition] 102 | } 103 | 104 | */ 105 | } -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/ui/data/HolderTypes.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere.ui.data 2 | 3 | object HolderTypes { 4 | data class PresetType ( 5 | val icon: Int? = null, 6 | val desc: String? = null, 7 | val defVal: Float? = null, 8 | val currencyType: Char? = null, 9 | val isBlock: Boolean = false 10 | ) 11 | 12 | data class ChipType ( 13 | val icon: Int? = null, 14 | val desc: String? = null, 15 | val isBlock: Boolean = false 16 | ) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/ui/fragments/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023 Akane Foundation 3 | * 4 | * Gramophone is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * Gramophone is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | package org.akanework.riviere.ui.fragments 19 | 20 | import android.os.Bundle 21 | import androidx.fragment.app.Fragment 22 | import com.google.android.material.transition.MaterialSharedAxis 23 | 24 | abstract class BaseFragment : Fragment() { 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | // Enable material transitions. 29 | enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true) 30 | returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false) 31 | exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ true) 32 | reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, /* forward= */ false) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/org/akanework/riviere/ui/fragments/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package org.akanework.riviere.ui.fragments 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.SharedPreferences 5 | import android.content.res.ColorStateList 6 | import android.graphics.drawable.AnimatedVectorDrawable 7 | import android.os.Bundle 8 | import android.text.InputFilter 9 | import android.util.DisplayMetrics 10 | import android.util.Log 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import android.widget.EditText 15 | import android.widget.FrameLayout 16 | import android.widget.TextView 17 | import androidx.appcompat.content.res.AppCompatResources 18 | import androidx.core.content.ContextCompat 19 | import androidx.preference.PreferenceManager 20 | import androidx.recyclerview.widget.LinearLayoutManager 21 | import androidx.recyclerview.widget.RecyclerView 22 | import com.google.android.material.bottomsheet.BottomSheetBehavior 23 | import com.google.android.material.button.MaterialButton 24 | import com.google.android.material.card.MaterialCardView 25 | import com.google.android.material.color.MaterialColors 26 | import com.google.android.material.slider.Slider 27 | import org.akanework.riviere.R 28 | import org.akanework.riviere.logic.px 29 | import org.akanework.riviere.logic.utils.DecimalDigitsInputFilter 30 | import org.akanework.riviere.ui.adapters.HomeChipAdapter 31 | import org.akanework.riviere.ui.adapters.HomePresetAdapter 32 | import org.akanework.riviere.ui.data.HolderTypes 33 | 34 | 35 | class HomeFragment : BaseFragment() { 36 | 37 | companion object { 38 | const val ACTION_BUTTON_ANIMATION_DURATION: Long = 300 39 | } 40 | 41 | private lateinit var prefs: SharedPreferences 42 | 43 | private lateinit var formatSwitchButton: MaterialButton 44 | private lateinit var targetExpenseEditText: EditText 45 | 46 | private var colorPlusPrimaryContainer: Int = -1 47 | private var colorPlusOnPrimaryContainer: Int = -1 48 | private var colorMinusPrimaryContainer: Int = -1 49 | private var colorMinusOnPrimaryContainer: Int = -1 50 | 51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 52 | super.onViewCreated(view, savedInstanceState) 53 | view.setBackgroundColor( 54 | MaterialColors.getColor(view, com.google.android.material.R.attr.colorSurfaceContainer) 55 | ) 56 | } 57 | 58 | @Suppress("DEPRECATION") 59 | override fun onCreateView( 60 | inflater: LayoutInflater, 61 | container: ViewGroup?, 62 | savedInstanceState: Bundle? 63 | ): View? { 64 | Log.d("TAG", "HOMEFRAGMENT ONCREATE") 65 | 66 | prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()) 67 | 68 | val rootView = inflater.inflate(R.layout.fragment_home, container, false) 69 | 70 | // Upper part 71 | val presetRecyclerView: RecyclerView = rootView.findViewById(R.id.preset_recyclerview) 72 | val chipRecyclerView: RecyclerView = rootView.findViewById(R.id.chips_recyclerview) 73 | targetExpenseEditText = rootView.findViewById(R.id.target_expense) 74 | 75 | formatSwitchButton = rootView.findViewById(R.id.plus_minus_switch) 76 | 77 | // Bottom part 78 | val bottomSheet: FrameLayout = rootView.findViewById(R.id.bottom_sheet) 79 | val standardBottomSheetBehavior = BottomSheetBehavior.from(bottomSheet) 80 | val bottomSheetHeaderCard: MaterialCardView = bottomSheet.findViewById(R.id.bottom_header) 81 | val expenseTitleTextView: TextView = bottomSheet.findViewById(R.id.expense_title) 82 | val currencyIcon: TextView = bottomSheet.findViewById(R.id.currency) 83 | val roundCounterTextView: TextView = bottomSheet.findViewById(R.id.round) 84 | val decimalCounterTextView: TextView = bottomSheet.findViewById(R.id.decimal) 85 | val incomeTitleTextView: TextView = bottomSheet.findViewById(R.id.income_title) 86 | val incomeTextView: TextView = bottomSheet.findViewById(R.id.income) 87 | val budgetTitleTextView: TextView = bottomSheet.findViewById(R.id.budget_title) 88 | val budgetTextView: TextView = bottomSheet.findViewById(R.id.budget) 89 | val budgetSlider: Slider = bottomSheet.findViewById(R.id.budget_slider) 90 | 91 | // Get colors 92 | val colorPositiveBottomHeaderPrimaryContainer = 93 | MaterialColors.harmonizeWithPrimary( 94 | requireContext(), 95 | ContextCompat.getColor( 96 | requireContext(), 97 | R.color.bottom_sheet_colorPrimaryContainer) 98 | ) 99 | val colorPositiveBottomHeaderOnPrimaryContainer = 100 | MaterialColors.harmonizeWithPrimary( 101 | requireContext(), 102 | ContextCompat.getColor( 103 | requireContext(), 104 | R.color.bottom_sheet_colorOnPrimaryContainer) 105 | ) 106 | val colorPositiveBottomHeaderPrimaryInverse = 107 | MaterialColors.harmonizeWithPrimary( 108 | requireContext(), 109 | ContextCompat.getColor( 110 | requireContext(), 111 | R.color.bottom_sheet_colorPrimaryInverse) 112 | ) 113 | colorPlusPrimaryContainer = 114 | MaterialColors.getColor( 115 | requireContext(), 116 | com.google.android.material.R.attr.colorPrimary, 117 | -1 118 | ) 119 | colorPlusOnPrimaryContainer = 120 | MaterialColors.getColor( 121 | requireContext(), 122 | com.google.android.material.R.attr.colorOnPrimary, 123 | -1 124 | ) 125 | colorMinusPrimaryContainer = 126 | MaterialColors.getColor( 127 | requireContext(), 128 | com.google.android.material.R.attr.colorTertiary, 129 | -1 130 | ) 131 | colorMinusOnPrimaryContainer = 132 | MaterialColors.getColor( 133 | requireContext(), 134 | com.google.android.material.R.attr.colorOnTertiary, 135 | -1 136 | ) 137 | 138 | setUpSwitchButton() 139 | setupUpperPart() 140 | 141 | // Dispatch colors 142 | bottomSheetHeaderCard.setCardBackgroundColor( 143 | colorPositiveBottomHeaderPrimaryContainer 144 | ) 145 | expenseTitleTextView.setTextColor( 146 | colorPositiveBottomHeaderOnPrimaryContainer 147 | ) 148 | currencyIcon.setTextColor( 149 | colorPositiveBottomHeaderOnPrimaryContainer 150 | ) 151 | roundCounterTextView.setTextColor( 152 | colorPositiveBottomHeaderOnPrimaryContainer 153 | ) 154 | decimalCounterTextView.setTextColor( 155 | colorPositiveBottomHeaderOnPrimaryContainer 156 | ) 157 | incomeTitleTextView.setTextColor( 158 | colorPositiveBottomHeaderOnPrimaryContainer 159 | ) 160 | incomeTextView.setTextColor( 161 | colorPositiveBottomHeaderOnPrimaryContainer 162 | ) 163 | budgetTitleTextView.setTextColor( 164 | colorPositiveBottomHeaderOnPrimaryContainer 165 | ) 166 | budgetTextView.setTextColor( 167 | colorPositiveBottomHeaderOnPrimaryContainer 168 | ) 169 | budgetSlider.trackActiveTintList = 170 | ColorStateList.valueOf(colorPositiveBottomHeaderOnPrimaryContainer) 171 | budgetSlider.trackInactiveTintList = 172 | ColorStateList.valueOf(colorPositiveBottomHeaderPrimaryInverse) 173 | 174 | val displayMetrics = DisplayMetrics() 175 | 176 | requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) 177 | 178 | val halfScreenHeight = displayMetrics.heightPixels - 324.px 179 | 180 | standardBottomSheetBehavior.peekHeight = halfScreenHeight 181 | 182 | presetRecyclerView.adapter = HomePresetAdapter( 183 | mutableListOf( 184 | HolderTypes.PresetType( 185 | null, 186 | "星巴克", 187 | -50f, 188 | isBlock = false 189 | ), 190 | HolderTypes.PresetType( 191 | isBlock = true 192 | ) 193 | ), 194 | requireContext() 195 | ) 196 | presetRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) 197 | chipRecyclerView.adapter = HomeChipAdapter( 198 | mutableListOf( 199 | HolderTypes.ChipType( 200 | R.drawable.ic_category_filled, 201 | "默认" 202 | ), 203 | HolderTypes.ChipType( 204 | 205 | ), 206 | HolderTypes.ChipType( 207 | isBlock = true 208 | ) 209 | ), 210 | requireContext() 211 | ) 212 | chipRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) 213 | 214 | return rootView 215 | } 216 | 217 | private fun setUpSwitchButton() { 218 | val formatSwitchButtonIsPlus = prefs.getBoolean("switch_button_state", true) 219 | setButtonStatus(formatSwitchButtonIsPlus, false) 220 | formatSwitchButton.setOnClickListener { 221 | val status = prefs.getBoolean("switch_button_state", true) 222 | setButtonStatus( 223 | !status, true 224 | ) 225 | prefs.edit() 226 | .putBoolean("switch_button_state", !status) 227 | .apply() 228 | } 229 | } 230 | 231 | private fun setButtonStatus(isPlus: Boolean, withAnimation: Boolean) { 232 | if (!withAnimation) { 233 | formatSwitchButton.iconTint = 234 | ColorStateList.valueOf( 235 | if (isPlus) colorPlusOnPrimaryContainer else colorMinusOnPrimaryContainer 236 | ) 237 | formatSwitchButton.backgroundTintList = 238 | ColorStateList.valueOf( 239 | if (isPlus) colorPlusPrimaryContainer else colorMinusPrimaryContainer 240 | ) 241 | formatSwitchButton.icon = 242 | AppCompatResources.getDrawable( 243 | requireContext(), 244 | if (isPlus) R.drawable.minus_to_plus else R.drawable.plus_to_minus 245 | ) 246 | } else { 247 | val backgroundAnimator = ValueAnimator.ofArgb( 248 | if (isPlus) colorMinusPrimaryContainer else colorPlusPrimaryContainer, 249 | if (isPlus) colorPlusPrimaryContainer else colorMinusPrimaryContainer 250 | ) 251 | val iconAnimator = ValueAnimator.ofArgb( 252 | if (isPlus) colorMinusOnPrimaryContainer else colorPlusOnPrimaryContainer, 253 | if (isPlus) colorPlusOnPrimaryContainer else colorMinusOnPrimaryContainer 254 | ) 255 | backgroundAnimator.apply { 256 | addUpdateListener { 257 | val color = it.animatedValue as Int 258 | formatSwitchButton.backgroundTintList = 259 | ColorStateList.valueOf(color) 260 | } 261 | duration = ACTION_BUTTON_ANIMATION_DURATION 262 | } 263 | iconAnimator.apply { 264 | addUpdateListener { 265 | val color = it.animatedValue as Int 266 | formatSwitchButton.iconTint = 267 | ColorStateList.valueOf(color) 268 | } 269 | duration = ACTION_BUTTON_ANIMATION_DURATION 270 | } 271 | formatSwitchButton.icon = 272 | AppCompatResources.getDrawable( 273 | requireContext(), 274 | if (isPlus) R.drawable.minus_to_plus else R.drawable.plus_to_minus 275 | ) 276 | backgroundAnimator.start() 277 | iconAnimator.start() 278 | (formatSwitchButton.icon as AnimatedVectorDrawable).start() 279 | } 280 | } 281 | 282 | private fun setupUpperPart() { 283 | targetExpenseEditText.setFilters( 284 | arrayOf( 285 | DecimalDigitsInputFilter(2) 286 | ) 287 | ) 288 | } 289 | 290 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_chip_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_downward.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_category.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_category_filled.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_local_cafe.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_remove.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star_alt.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/minus_to_plus.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/plus_to_minus.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/font/plexsans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/font/plexsans.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 33 | 34 | 37 | 38 | 50 | 51 | 63 | 64 | 76 | 77 | 89 | 90 | 101 | 102 | 113 | 114 | 125 | 126 | 137 | 138 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /app/src/main/res/layout/chip_card_add.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 24 | 25 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/chip_card_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 21 | 22 | 32 | 33 | 41 | 42 | 43 | 44 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/chip_card_layout_alt.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 21 | 22 | 32 | 33 | 41 | 42 | 43 | 44 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/detail_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 21 | 22 | 28 | 29 | 30 | 31 | 36 | 37 | 45 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 63 | 64 | 72 | 73 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home_upper.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 30 | 31 | 46 | 47 | 60 | 61 | 78 | 79 | 90 | 91 | 104 | 105 | 119 | 120 | 136 | 137 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preset_card_add.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 23 | 24 | 29 | 30 | 33 | 34 | 42 | 43 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preset_card_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 27 | 28 | 35 | 36 | 37 | 38 | 48 | 49 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/total_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 21 | 22 | 30 | 31 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AkaneTan/Riviere/32dfe5cde01fee49949a45a17399026af09ef5e6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #ace2aa 5 | #19481f 6 | #39693c 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors_md.xml: -------------------------------------------------------------------------------- 1 | 2 | #9ED49D 3 | #053912 4 | #205026 5 | #BAF0B7 6 | #B9CCB4 7 | #243424 8 | #3A4B39 9 | #D5E8D0 10 | #A1CED5 11 | #00363C 12 | #1F4D53 13 | #BCEBF2 14 | #FFB4AB 15 | #690005 16 | #93000A 17 | #FFDAD6 18 | #101410 19 | #E0E4DB 20 | #101410 21 | #E0E4DB 22 | #424940 23 | #C2C9BD 24 | #8C9388 25 | #424940 26 | #000000 27 | #E0E4DB 28 | #2D322C 29 | #38693C 30 | #BAF0B7 31 | #002106 32 | #9ED49D 33 | #205026 34 | #D5E8D0 35 | #101F10 36 | #B9CCB4 37 | #3A4B39 38 | #BCEBF2 39 | #001F23 40 | #A1CED5 41 | #1F4D53 42 | #101410 43 | #363A34 44 | #0B0F0B 45 | #181D18 46 | #1C211B 47 | #272B26 48 | #313630 49 | #A2D8A1 50 | #001B04 51 | #6A9D6A 52 | #000000 53 | #BDD0B9 54 | #0A1A0B 55 | #839680 56 | #000000 57 | #A5D3DA 58 | #001A1D 59 | #6C989F 60 | #000000 61 | #FFBAB1 62 | #370001 63 | #FF5449 64 | #000000 65 | #101410 66 | #E0E4DB 67 | #101410 68 | #F8FCF3 69 | #424940 70 | #C6CDC1 71 | #9EA59A 72 | #7E857B 73 | #000000 74 | #E0E4DB 75 | #272B26 76 | #215227 77 | #BAF0B7 78 | #001603 79 | #9ED49D 80 | #0C3F17 81 | #D5E8D0 82 | #061407 83 | #B9CCB4 84 | #2A3A29 85 | #BCEBF2 86 | #001417 87 | #A1CED5 88 | #083C42 89 | #101410 90 | #363A34 91 | #0B0F0B 92 | #181D18 93 | #1C211B 94 | #272B26 95 | #313630 96 | #F0FFEB 97 | #000000 98 | #A2D8A1 99 | #000000 100 | #F0FFEB 101 | #000000 102 | #BDD0B9 103 | #000000 104 | #F1FDFF 105 | #000000 106 | #A5D3DA 107 | #000000 108 | #FFF9F9 109 | #000000 110 | #FFBAB1 111 | #000000 112 | #101410 113 | #E0E4DB 114 | #101410 115 | #FFFFFF 116 | #424940 117 | #F6FDF1 118 | #C6CDC1 119 | #C6CDC1 120 | #000000 121 | #E0E4DB 122 | #000000 123 | #00320D 124 | #BEF5BB 125 | #000000 126 | #A2D8A1 127 | #001B04 128 | #D9ECD4 129 | #000000 130 | #BDD0B9 131 | #0A1A0B 132 | #C1EFF6 133 | #000000 134 | #A5D3DA 135 | #001A1D 136 | #101410 137 | #363A34 138 | #0B0F0B 139 | #181D18 140 | #1C211B 141 | #272B26 142 | #313630 143 | #83D3E3 144 | #00363E 145 | #004E59 146 | #A1EFFF 147 | #FDB975 148 | #4A2800 149 | #693C00 150 | #FFDCBE 151 | #87D7E7 152 | #001A1E 153 | #4A9CAB 154 | #000000 155 | #FFBE7D 156 | #251200 157 | #C08446 158 | #000000 159 | #F2FCFF 160 | #000000 161 | #87D7E7 162 | #000000 163 | #FFFAF8 164 | #000000 165 | #FFBE7D 166 | #000000 167 | 168 | -------------------------------------------------------------------------------- /app/src/main/res/values-v23/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 8 | 9 | 8 | 9 | 58 | 114 | 115 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 62 | 65 | 66 | 69 | 70 |