Bondoman is a user-friendly money management app designed specifically for Android devices. Bondoman helps you take control of your finances effortlessly by providing essential tools to manage your transactions efficiently.
Bondoman is compatible with Android devices running on API 29 (Android 10) up to API 34 (Android 14)
Bondoman Features:
- Login: Securely log in to your Bondoman account to access all features.
- Home Dashboard: Get a comprehensive overview of your finances at a glance, including your budget summaries and recent transactions.
- Transaction List: View list of your transactions, categorized by date.
- Add and Edit Transactions: Easily add new transactions and edit existing ones.
- Scan Receipts: Capture receipts directly from your device's camera or select from gallery to add new transaction.
- Save Transaction List to Excel: Export your transaction data to Excel format for further analysis or record-keeping.
- Send Transaction List File to Email: Share your transaction files via email with ease, allowing for seamless collaboration or backup.
- Twibbon Integration: Add a personal touch to your transactions by overlaying Twibbon frames, making them more visually appealing and shareable on social media.
- Randomize Transaction: Adding new transaction by randomization.
- AndroidX Core KTX:
androidx.core:core-ktx:1.7.0
- AndroidX AppCompat:
androidx.appcompat:appcompat:1.3.0
- Material Components for Android:
com.google.android.material:material:1.3.0
- AndroidX ConstraintLayout:
androidx.constraintlayout:constraintlayout:2.1.4
- AndroidX Room (Runtime, Common, KTX):
androidx.room:room-runtime:2.5.0
androidx.room:room-common:2.5.0
androidx.room:room-ktx:2.5.0
- CameraView:
androidx.camera:camera-view:1.3.2
- Google AR Sceneform (Filament):
com.google.ar.sceneform:filament-android:1.17.1
- AndroidX Lifecycle (LiveData, ViewModel):
androidx.lifecycle:lifecycle-livedata-ktx:2.4.0
androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0
- Retrofit:
com.squareup.retrofit2:retrofit:2.9.0
- Retrofit Gson Converter:
com.squareup.retrofit2:converter-gson:2.9.0
- OkHttp Logging Interceptor:
com.squareup.okhttp3:logging-interceptor:4.9.3
- MPAndroidChart:
com.github.PhilJay:MPAndroidChart:v3.1.0
- AndroidX Preference:
androidx.preference:preference-ktx:1.2.0
- AndroidX DataStore:
androidx.datastore:datastore-core:1.0.0
- Google Places SDK:
com.google.android.libraries.places:places:3.3.0
- Apache POI (Java Excel API):
org.apache.poi:poi:5.2.2
org.apache.poi:poi-ooxml:5.2.2
- JUnit:
junit:junit:4.13.2
- AndroidX Test (JUnit):
androidx.test.ext:junit:1.1.3
- Espresso Core:
androidx.test.espresso:espresso-core:3.4.0
- CameraX (Camera Camera2, Camera Lifecycle):
androidx.camera:camera-camera2:1.4.0-alpha04
androidx.camera:camera-lifecycle:1.4.0-alpha04
- Validasi input diimplementasikan pada form login untuk memastikan hanya input yang valid yang diterima.
- Aturan validasi meliputi pemeriksaan untuk kolom yang kosong, format email yang benar, dan format password yang benar untuk menghindari injection.
// Username validation check
private fun isUserNameValid(username: String): Boolean {
val maxLength = 50
val expectedEmailPattern = "^\\w+@std\\.stei\\.itb\\.ac\\.id$".toRegex()
return expectedEmailPattern.matches(username) && username.length <= maxLength
}
// Password validation check
private fun isPasswordValid(password: String): Boolean {
val minLength = 6
val maxLength = 40
return password.length in minLength..maxLength
}
- Validasi input diberlakukan baik untuk menambahkan maupun mengedit transaksi.
- Kolom seperti judul and nominal divalidasi untuk mencegah injection.
- Pesan validasi yang tepat ditampilkan kepada pengguna untuk memandu mereka dalam memberikan input yang benar.
private fun validateTitle(title: String): Boolean {
val maxLength = 50
return title.isNotEmpty() && title.length <= maxLength && !containsSpecialCharacters(title)
}
private fun validateAmount(amountText: String): Boolean {
val amount = amountText.toIntOrNull() ?: 0
val minAmount = 1
val maxAmount = 100000000000
return amount in minAmount..maxAmount
}
- Aplikasi mengelola kunci API secara aman, termasuk kunci API Google Maps.
- Kunci API disimpan dengan aman dalam file
apikey.properties
dan diakses melalui kelasBuildConfig
. - Pendekatan ini memastikan bahwa informasi sensitif seperti kunci API tidak terpapar langsung dalam kode sumber.
// apikey.properties
API_KEY=AIzaSyBuyaXa4Ism_7xVMqZNLeTCmChMEsX8gKY
ENCRYPTION_KEY=V6B9D+GQITf/WKjSxMcSjQ==
// Load API key from apikey.properties file
val apikeyPropsFile = rootProject.file("apikey.properties")
if (apikeyPropsFile.exists()) {
val apikeyProps = Properties()
apikeyProps.load(apikeyPropsFile.inputStream())
// Access API key
buildConfigField("String", "API_KEY", "\"${apikeyProps.getProperty("API_KEY")}\"")
buildConfigField("String", "ENCRYPTION_KEY", "\"${apikeyProps.getProperty("ENCRYPTION_KEY")}\"")
} else {
// Set API key to empty string if apikey.properties doesn't exist
buildConfigField("String", "API_KEY", "\"\"")
buildConfigField("String", "ENCRYPTION_KEY", "\"\"")
}
// Initialize Places SDK
Places.initialize(applicationContext, BuildConfig.API_KEY)
- Token JWT, yang digunakan untuk tujuan otentikasi, disimpan dengan aman dengan mengenkripsinya.
- Enkripsi memastikan bahwa bahkan jika token diakses oleh pihak yang tidak berwenang, mereka tetap tidak terbaca dan tidak dapat digunakan.
// Login
val result = loginRepository.login(username, password)
if (result is Result.Success) {
val token = result.data
val encryptedToken = encryptToken(token)
val editor: SharedPreferences.Editor = sharedPreferences.edit()
editor.putString("username", username)
editor.putString("token", encryptedToken)
editor.apply()
_loginResult.value = LoginResult(success = LoggedInUserView(displayName = username))
} else if (result is Result.Error) {
_loginResult.value = LoginResult(error = R.string.login_failed)
}
// Encryption
private fun encryptToken(token: String): String? {
try {
val keySpec = SecretKeySpec(ENCRYPTION_KEY.toByteArray(), "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
val initVectorBytes = Base64.decode(sharedPreferences.getString("init_vector", ""), Base64.DEFAULT)
val ivParameterSpec = IvParameterSpec(initVectorBytes)
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec)
val encryptedBytes = cipher.doFinal(token.toByteArray())
return Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
// Decrypt token using AES in CBC mode
private fun decryptToken(encryptedToken: String): String? {
try {
val keySpec = SecretKeySpec(ENCRYPTION_KEY.toByteArray(), "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
val initVectorBytes = Base64.decode(sharedPreferences.getString("init_vector", ""), Base64.DEFAULT)
val ivParameterSpec = IvParameterSpec(initVectorBytes)
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec)
val decryptedBytes = cipher.doFinal(Base64.decode(encryptedToken, Base64.DEFAULT))
return String(decryptedBytes)
} catch (e: Exception) {
e.printStackTrace()
}
return null
}
Notes: Suggestions have been fixed and can be seen on screenshot section
Other suggestions contain suggestions that can not be fixed, such as suggestions for Google plugins and navigation bar
NIM | Full Name | Responsibilities | Working Hours |
---|---|---|---|
13521059 | Arleen Chrysantha Gunardi | Login Save and Send Transaction list Transaction Accessibility Testing Intro Settings |
8 hours 8 hours 4 hours 4 hours 4 hours 4 hours |
13521124 | Michael Jonathan Halim | Header dan Navbar Logout Transactions Background Service Network Sensing Broadcast Receiver OWASP |
4 hours 4 hours 12 hours 4 hours 4 hours 4 hours 8 hours |
13521142 | Enrique Alifio Ditya | Transactions Scan Graf Twibbon Home Save and Send Transaction list |
4 hours 8 hours 8 hours 4 hours 4 hours 4 hours |
NIM | Full Name | Total Working Hours |
---|---|---|
13521059 | Arleen Chrysantha Gunardi | 32 hours |
13521124 | Michael Jonathan Halim | 40 hours |
13521142 | Enrique Alifio Ditya | 32 hours |