Skip to content
Snippets Groups Projects
Unverified Commit a81972aa authored by Frankie Huang's avatar Frankie Huang Committed by GitHub
Browse files

Release Version 1

Release Version 1
parents 02ff5684 509d2e82
No related merge requests found
Showing
with 534 additions and 1 deletion
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
env
# Default ignored files
/shelf/
/workspace.xml
# IF3210-2024-Android-AMW
\ No newline at end of file
# 👨🏽 Bondoman
Bandung Bondowoso merupakan seorang kepala proyek pembangunan seribu candi. Proyek tersebut dia dapatkan dari Roro yang tidak memahami segitiga manajemen proyek, yaitu budget, deadline, dan quality. Proyek ini memiliki budget yang sedikit, deadline yang hanya satu malam, dan Roro meminta kualitas candi yang bagus.
Bondowoso merasa pusing ketika mengerjakan proyek seribu candi dalam satu malam ini. Dalam pengerjaannya, terjadi banyak sekali transaksi pembelian bahan baku. Tentu saja dia harus berhati-hati dalam melakukan perhitungan biaya, sebab jika tidak seusai dengan RAB (Rancangan Anggaran Biaya) semula, Roro tidak ingin membayarnya.
Awalnya, Bondowoso menuliskan segala transaksi jual beli bahan baku di atas prasasti berbentuk batu. Namun, dengan skala proyek yang sangat besar ini, ia kewalahan dan meminta bantuan kalian, para jin yang saat ini berkuliah di Institut Jin semester 6, untuk membuat aplikasi manajemen keuangan. Ia percaya kalian bukanlah jin yang pandir sehingga dapat menyelesaikan aplikasi tersebut.
Saat ini, Bondowoso menginginkan aplikasi tersebut dapat berjalan di gawai yang ia miliki. Karena ia tidak suka buah apel, ia meminta aplikasi dibuat untuk sistem operasi Android. Bantulan Bondowoso memanajemen uangnya agar pembangunan candi berjalan lancar!
## ⚡ Main Features
| Feature | Screenshot |
|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| Login | <img src="screenshot/login.png" alt="drawing" width="200"/> |
| Transaction List | <img src="screenshot/daftar_transaksi.png" alt="drawing" width="200"/> |
| Add Transaction | <img src="screenshot/penambahan_transaksi.png" alt="drawing" width="200"/> |
| Update Transaction | <img src="screenshot/pembaruan_transaksi.png" alt="drawing" width="200"/> |
| Scan | <img src="screenshot/scan.png" alt="drawing" width="200"/> |
| Scan Result | <img src="screenshot/hasil_scan.png" alt="drawing" width="200"/> |
| Twibbon | <img src="screenshot/twibbon.png" alt="drawing" width="200"/> |
| Graph | <img src="screenshot/graph_vertical.png" alt="drawing" width="200"/> <img src="screenshot/graph_horizontal.png" alt="drawing" height="200"/> |
| Settings | <img src="screenshot/pengaturan.png" alt="drawing" width="200"/> |
| Network Sensing | <img src="screenshot/pendeteksi_jaringan.png" alt="drawing" width="200"/> |
## 🕹️ Library
* room
* retrofit2
* Moshi
* dotenv
* cameraX
* okhttp3
* Apache POI
## 💻 Developers
| Name | Features | Exploration hours | Working Hours |
|---------------------------------------------------|----------------------------------------------------------------------------------|-------------------|---------------|
| [Yudi Kurniawan](https://github.com/frankiehuangg)| Login | | |
| [Wilson Tansil](https://github.com/Tansil011019) | Login; Logout; JWT Service; Graph; Network Sensing; Twibbon | 5 | 20 |
| [Farizki Kurniawan](https://github.com/farizkik) | Header and Navbar; Transaction CRUD; Transaction List; Broadcast Receiver | 5 | 20 |
| [Frankie Huang](https://github.com/frankiehuangg) | Note Scanning; Export Transaction to Spreadsheets; Send Transaction with Gmail | 8 | 8 |
/build
\ No newline at end of file
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
id("androidx.navigation.safeargs.kotlin")
}
android {
namespace = "com.example.bondoman"
compileSdk = 34
defaultConfig {
applicationId = "com.example.bondoman"
minSdk = 29
targetSdk = 34
versionCode = 1
versionName = "1.0"
multiDexEnabled = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.8.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.navigation:navigation-fragment-ktx:2.5.3")
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
implementation("androidx.navigation:navigation-ui-ktx:2.5.3")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
implementation("androidx.legacy:legacy-support-v4:1.0.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
implementation ("androidx.room:room-runtime:2.5.0")
annotationProcessor("androidx.room:room-compiler:2.5.0")
// To use Kotlin annotation processing tool (kapt)
kapt ("androidx.room:room-compiler:2.5.0")
implementation ("androidx.room:room-ktx:2.5.0")
// To use Kotlin Symbol Processing (KSP)
// ksp "androidx.room:room-compiler:$room_version"
// optional - RxJava2 support for Room
implementation ("androidx.room:room-rxjava2:2.5.0")
implementation ("androidx.room:room-rxjava3:2.5.0")
implementation ("androidx.room:room-guava:2.5.0")
testImplementation ("androidx.room:room-testing:2.5.0")
implementation ("androidx.room:room-paging:2.5.0")
implementation ("com.github.AnyChart:AnyChart-Android:1.1.5")
implementation ("androidx.multidex:multidex:2.0.1")
// To use Retrofit
implementation ("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-moshi:2.9.0")
// To use moshi
implementation ("com.squareup.moshi:moshi:1.15.0")
implementation ("com.squareup.moshi:moshi-kotlin:1.15.0")
implementation ("com.squareup.moshi:moshi-kotlin-codegen:1.15.0")
// To use dotenv
implementation ("io.github.cdimascio:dotenv-kotlin:6.4.1")
// Use camera
implementation("com.google.guava:guava:31.0.1-android")
val cameraXVersion = "1.3.2"
implementation("androidx.camera:camera-core:${cameraXVersion}")
implementation("androidx.camera:camera-camera2:${cameraXVersion}")
implementation("androidx.camera:camera-lifecycle:${cameraXVersion}")
implementation("androidx.camera:camera-video:${cameraXVersion}")
implementation("androidx.camera:camera-view:${cameraXVersion}")
implementation("androidx.camera:camera-extensions:${cameraXVersion}")
// Use okhttp3
implementation("com.squareup.okhttp3:okhttp:4.7.2")
// Use Apache POI
implementation("com.github.SUPERCILEX.poi-android:poi:3.17")
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.example.bondoman
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.bondoman", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BondoMan"
tools:targetApi="31">
<activity
android:name=".ui.login.LoginActivity"
android:exported="false"
android:label="@string/login">
</activity>
<receiver
android:name=".receiver.MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.bondoman.action" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:enabled="true"
android:exported="true"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".service.auth.TokenExpService" />
<provider
android:authorities="com.example.counter"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
/>
</provider>
</application>
</manifest>
\ No newline at end of file
BASE_URL = "https://pbd-backend-2024.vercel.app/"
\ No newline at end of file
package com.example.bondoman
import android.content.BroadcastReceiver
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import com.example.bondoman.databinding.ActivityMainBinding
import com.example.bondoman.network.ConnectivityObserver
import com.example.bondoman.network.NetworkConnectivityObserver
import com.example.bondoman.receiver.MyBroadcastListener
import com.example.bondoman.share_preference.PreferenceManager
import com.example.bondoman.ui.login.LoginActivity
import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity(), MyBroadcastListener {
private lateinit var binding: ActivityMainBinding
private lateinit var receiver: BroadcastReceiver
private lateinit var connectivityObserver: ConnectivityObserver
private lateinit var preferenceManager: PreferenceManager
private var alertDialog: AlertDialog? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
connectivityObserver = NetworkConnectivityObserver(applicationContext)
preferenceManager = PreferenceManager(this)
if (preferenceManager.getToken().isNullOrEmpty()) {
startActivity(Intent(this, LoginActivity::class.java))
finish()
}
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home, R.id.navigation_transactions, R.id.navigation_graph, R.id.navigation_settings, R.id.transactionFragment, R.id.resultFragment, R.id.navigation_twibbon
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
override fun onStart() {
super.onStart()
lifecycleScope.launch {
observeConnectivity()
}
}
private fun observeConnectivity() {
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
connectivityObserver.observe().collect { status ->
if (status == ConnectivityObserver.Status.Unavailable || status == ConnectivityObserver.Status.Lost) {
showNoInternetPopUp()
} else {
hideNoInternetPopup()
}
}
}
}
private fun showNoInternetPopUp() {
if (!isFinishing && alertDialog == null) {
alertDialog = AlertDialog.Builder(this)
.setTitle("No Internet Connection")
.setMessage("Please check your internet connection and try again.")
.setCancelable(false)
.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
}
.show()
}
}
private fun hideNoInternetPopup() {
alertDialog?.dismiss()
alertDialog = null
}
override fun onBroadcastReceived(value: String?) {
Log.d("adsadas", "asdasd")
}
}
\ No newline at end of file
package com.example.bondoman.api
import com.example.bondoman.api.auth.login.LoginService
import com.example.bondoman.api.auth.token.TokenService
import com.example.bondoman.api.scan.UploadService
import com.example.bondoman.common.Constant
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
object APIClient {
private val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
private val retrofit: Retrofit by lazy { Retrofit.Builder().baseUrl(Constant.BASE_URL).addConverterFactory(
MoshiConverterFactory.create(moshi)).build() }
val loginService: LoginService by lazy {
retrofit.create(LoginService::class.java)
}
val tokenService: TokenService by lazy {
retrofit.create(TokenService::class.java)
}
val uploadService: UploadService by lazy {
retrofit.create(UploadService::class.java)
}
}
\ No newline at end of file
package com.example.bondoman.api.auth.login
import com.example.bondoman.api.auth.login.dto.LoginRequest
import com.example.bondoman.api.auth.login.dto.LoginResponse
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
interface LoginService {
@POST("api/auth/login")
fun login(@Body req: LoginRequest): Call<LoginResponse>
}
\ No newline at end of file
package com.example.bondoman.api.auth.login.dto
import com.squareup.moshi.Json
data class LoginRequest(
@Json(name= "email")
val email: String,
@Json(name= "password")
val password: String
)
package com.example.bondoman.api.auth.login.dto
import com.squareup.moshi.Json
data class LoginResponse(
@Json(name="token")
val token: String
)
package com.example.bondoman.api.auth.token
import com.example.bondoman.api.auth.token.dto.TokenResponse
import retrofit2.Call
import retrofit2.http.Header
import retrofit2.http.POST
interface TokenService {
@POST("api/auth/token")
fun getToken (
@Header("Authorization") token: String?
) : Call<TokenResponse>
}
\ No newline at end of file
package com.example.bondoman.api.auth.token.dto
import com.squareup.moshi.Json
data class TokenResponse(
@Json(name = "nim")
val nim: String,
@Json(name="iat")
val iat: Long,
@Json(name="exp")
val exp: Long
)
package com.example.bondoman.api.scan
import com.example.bondoman.api.scan.dto.UploadResponse
import okhttp3.MultipartBody
import retrofit2.Call
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
interface UploadService {
@Multipart
@POST("api/bill/upload")
fun upload(
@Header("Authorization") token: String,
@Part file: MultipartBody.Part
): Call<UploadResponse>
}
\ No newline at end of file
package com.example.bondoman.api.scan.dto
import com.example.bondoman.core.data.Items
import com.squareup.moshi.Json
data class UploadResponse(
@Json(name = "items")
val items: Items
)
\ No newline at end of file
package com.example.bondoman.common
import io.github.cdimascio.dotenv.dotenv
object Constant {
lateinit var BASE_URL: String
init {
try {
val dotenv = dotenv {
directory = "/assets"
filename = "env"
}
BASE_URL = dotenv["BASE_URL"] ?: ""
} catch (e: Exception) {
e.printStackTrace()
BASE_URL = ""
}
}
}
\ No newline at end of file
package com.example.bondoman.common.response
sealed class ResponseContract {
data class Success<T>(val response: T) : ResponseContract()
data class Error(val response: String) : ResponseContract()
}
\ No newline at end of file
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment