Initial mobile app implementation
This commit is contained in:
25
app/src/main/java/id/abelbirdnest/mobile/MainActivity.kt
Normal file
25
app/src/main/java/id/abelbirdnest/mobile/MainActivity.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package id.abelbirdnest.mobile
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
import id.abelbirdnest.mobile.ui.AbelbirdnestApp
|
||||
import id.abelbirdnest.mobile.ui.MainViewModel
|
||||
import id.abelbirdnest.mobile.ui.theme.AbelbirdnestTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val viewModel by viewModels<MainViewModel>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
AbelbirdnestTheme {
|
||||
AbelbirdnestApp(viewModel = viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,324 @@
|
||||
package id.abelbirdnest.mobile.data
|
||||
|
||||
import android.content.Context
|
||||
import id.abelbirdnest.mobile.data.toLotDetailData
|
||||
import id.abelbirdnest.mobile.network.ApiFactory
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import com.google.gson.Gson
|
||||
import retrofit2.HttpException
|
||||
|
||||
class MobileRepository(context: Context) {
|
||||
private val api = ApiFactory.create()
|
||||
private val sessionStore = SessionStore(context)
|
||||
private val gson = Gson()
|
||||
|
||||
fun sessionToken(): String? = sessionStore.token()
|
||||
|
||||
suspend fun login(identity: String, password: String): LoginData {
|
||||
val response = api.login(LoginRequest(identity = identity, password = password))
|
||||
sessionStore.save(response.data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
suspend fun loadDashboardBundle(): DashboardBundle = coroutineScope {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
val auth = "Bearer $token"
|
||||
|
||||
val bootstrap = async { api.bootstrap(auth).data }
|
||||
val dashboard = async { api.dashboard(auth).data }
|
||||
|
||||
val bootstrapData = bootstrap.await()
|
||||
val dashboardData = dashboard.await()
|
||||
val lotItems = if (bootstrapData.modules.contains("lots")) {
|
||||
try {
|
||||
api.lots(auth).data
|
||||
} catch (error: HttpException) {
|
||||
if (error.code() == 403) emptyList() else throw error
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
DashboardBundle(
|
||||
user = bootstrapData.user,
|
||||
modules = bootstrapData.modules,
|
||||
summary = bootstrapData.summary,
|
||||
metrics = dashboardData.metrics,
|
||||
criticalLots = dashboardData.criticalLots,
|
||||
recentActivity = dashboardData.recentActivity,
|
||||
gradeDistribution = lotItems
|
||||
.filter { it.availableQty > 0 }
|
||||
.groupBy { it.grade }
|
||||
.mapValues { entry -> entry.value.sumOf { it.availableQty } }
|
||||
.entries
|
||||
.sortedByDescending { it.value }
|
||||
.take(3)
|
||||
.let { topGrades ->
|
||||
val total = topGrades.sumOf { it.value }.takeIf { it > 0 } ?: 1.0
|
||||
topGrades.map { grade ->
|
||||
GradeDistribution(
|
||||
grade = grade.key,
|
||||
quantity = grade.value,
|
||||
percentage = ((grade.value / total) * 100).toInt(),
|
||||
)
|
||||
}
|
||||
},
|
||||
transformationTypes = bootstrapData.transformationTypes,
|
||||
remainderModes = bootstrapData.remainderModes,
|
||||
processingLossModes = bootstrapData.processingLossModes,
|
||||
grades = bootstrapData.grades,
|
||||
warehouses = bootstrapData.warehouses,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun loadLots(): List<LotItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.lots("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadLotDetail(id: String): LotDetailData {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.lotDetail("Bearer $token", id).data.toLotDetailData()
|
||||
}
|
||||
|
||||
suspend fun scanLot(code: String): LotScanResult {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return LotScanResult(
|
||||
scannedCode = code,
|
||||
scannedAtMillis = System.currentTimeMillis(),
|
||||
payload = api.scanLot("Bearer $token", code).data,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun loadAdjustmentReasons(): List<AdjustmentReason> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.adjustmentReasons("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadStockAdjustments(): List<StockAdjustmentListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.stockAdjustments("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun createStockAdjustment(payload: StockAdjustmentCreatePayload): StockAdjustmentListItem {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.createStockAdjustment("Bearer $token", payload).data
|
||||
}
|
||||
|
||||
suspend fun loadPurchases(): List<PurchaseListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.purchases("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadUnits(): List<UnitLookup> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.units("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadEmployees(): List<LookupRecord> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.employees("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun createPurchase(payload: PurchaseCreatePayload): PurchaseDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.createPurchase("Bearer $token", payload).data
|
||||
}
|
||||
|
||||
suspend fun loadPurchaseDetail(id: String): PurchaseDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.purchaseDetail("Bearer $token", id).data
|
||||
}
|
||||
|
||||
suspend fun updatePurchase(id: String, payload: PurchaseCreatePayload): PurchaseDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.updatePurchase("Bearer $token", id, payload).data
|
||||
}
|
||||
|
||||
suspend fun loadPurchaseAnalyses(): List<PurchaseAnalysisListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.purchaseAnalyses("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadPurchaseAnalysisDetail(purchaseId: String): PurchaseAnalysisDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.purchaseAnalysisDetail("Bearer $token", purchaseId).data
|
||||
}
|
||||
|
||||
suspend fun loadPurchaseRealizations(): List<PurchaseRealizationListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.purchaseRealizations("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadPurchaseRealizationDetail(purchaseId: String): PurchaseRealizationDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.purchaseRealizationDetail("Bearer $token", purchaseId).data
|
||||
}
|
||||
|
||||
suspend fun loadRegularSales(): List<RegularSaleListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.regularSales("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadRegularSaleDetail(id: String): RegularSaleDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.regularSaleDetail("Bearer $token", id).data
|
||||
}
|
||||
|
||||
suspend fun closeRegularSale(id: String, payload: RegularSaleClosePayload): RegularSaleDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.closeRegularSale("Bearer $token", id, payload).data
|
||||
}
|
||||
|
||||
suspend fun loadJitSales(): List<JitSaleListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.jitSales("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadJitSaleDetail(id: String): JitSaleDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.jitSaleDetail("Bearer $token", id).data
|
||||
}
|
||||
|
||||
suspend fun closeJitSale(id: String, payload: JitSaleClosePayload): JitSaleDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.closeJitSale("Bearer $token", id, payload).data
|
||||
}
|
||||
|
||||
suspend fun loadConsignmentsBootstrap(): ConsignmentBootstrapData {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.consignmentsBootstrap("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadConsignments(): List<ConsignmentListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.consignments("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadConsignmentDetail(id: String): ConsignmentDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.consignmentDetail("Bearer $token", id).data
|
||||
}
|
||||
|
||||
suspend fun closeConsignmentLine(lineId: String, payload: ConsignmentCloseLinePayload) {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
api.closeConsignmentLine("Bearer $token", lineId, payload)
|
||||
}
|
||||
|
||||
suspend fun loadFundRequestsBootstrap(): FundRequestsBootstrapData {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.fundRequestsBootstrap("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadFundRequests(): List<FundRequestListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.fundRequests("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun createFundRequest(
|
||||
transferType: String,
|
||||
referenceNo: String,
|
||||
agentId: String,
|
||||
agentBankAccountId: String,
|
||||
companyBankAccountId: String,
|
||||
amount: String,
|
||||
transferredAt: String,
|
||||
): FundRequestListItem {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
val plain = "text/plain".toMediaType()
|
||||
val fields = linkedMapOf(
|
||||
"transfer_type" to transferType.toRequestBody(plain),
|
||||
"reference_no" to referenceNo.toRequestBody(plain),
|
||||
"agent_id" to agentId.toRequestBody(plain),
|
||||
"agent_bank_account_id" to agentBankAccountId.toRequestBody(plain),
|
||||
"company_bank_account_id" to companyBankAccountId.toRequestBody(plain),
|
||||
"amount" to amount.toRequestBody(plain),
|
||||
"transferred_at" to transferredAt.toRequestBody(plain),
|
||||
)
|
||||
return api.createFundRequest("Bearer $token", fields).data
|
||||
}
|
||||
|
||||
suspend fun submitPurchase(id: String): PurchaseDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.submitPurchase("Bearer $token", id).data
|
||||
}
|
||||
|
||||
suspend fun cancelPurchase(id: String) {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
api.cancelPurchase("Bearer $token", id)
|
||||
}
|
||||
|
||||
suspend fun loadLotTransformations(): List<LotTransformationListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.lotTransformations("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadLotTransformationDetail(id: String): LotTransformationDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.lotTransformationDetail("Bearer $token", id).data
|
||||
}
|
||||
|
||||
suspend fun createLotTransformation(payload: LotTransformationCreatePayload): LotTransformationDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.createLotTransformation("Bearer $token", payload).data
|
||||
}
|
||||
|
||||
suspend fun loadWashingBootstrap(): WashingBootstrapData {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.washingBootstrap("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadWashings(): List<WashingListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.washings("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun createWashing(payload: WashingCreatePayload): WashingListItem {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
val body = gson.toJson(payload).toRequestBody("text/plain".toMediaType())
|
||||
return api.createWashing("Bearer $token", body).data
|
||||
}
|
||||
|
||||
suspend fun updateWashing(id: String, payload: WashingCreatePayload): WashingListItem {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
val body = gson.toJson(payload).toRequestBody("text/plain".toMediaType())
|
||||
return api.updateWashing("Bearer $token", id, body).data
|
||||
}
|
||||
|
||||
suspend fun completeWashing(id: String, payload: CompleteWashingPayload): WashingListItem {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.completeWashing("Bearer $token", id, payload).data
|
||||
}
|
||||
|
||||
suspend fun loadReceiptsBootstrap(): ReceiptBootstrapData {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.receiptsBootstrap("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadReceipts(): List<ReceiptListItem> {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.receipts("Bearer $token").data
|
||||
}
|
||||
|
||||
suspend fun loadReceiptDetail(id: String): ReceiptDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.receiptDetail("Bearer $token", id).data
|
||||
}
|
||||
|
||||
suspend fun createReceipt(payload: ReceiptCreatePayload): ReceiptDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.createReceipt("Bearer $token", payload).data
|
||||
}
|
||||
|
||||
suspend fun generateReceiptLots(id: String): ReceiptDetail {
|
||||
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||
return api.generateReceiptLots("Bearer $token", id).data
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
sessionStore.clear()
|
||||
}
|
||||
}
|
||||
1152
app/src/main/java/id/abelbirdnest/mobile/data/Models.kt
Normal file
1152
app/src/main/java/id/abelbirdnest/mobile/data/Models.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
||||
package id.abelbirdnest.mobile.data
|
||||
|
||||
import android.content.Context
|
||||
|
||||
class SessionStore(context: Context) {
|
||||
private val prefs = context.getSharedPreferences("abelbirdnest_stock_session", Context.MODE_PRIVATE)
|
||||
|
||||
fun save(loginData: LoginData) {
|
||||
prefs.edit()
|
||||
.putString(KEY_TOKEN, loginData.sessionToken)
|
||||
.putString(KEY_NAME, loginData.user.name)
|
||||
.putString(KEY_ROLE, loginData.user.role)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun token(): String? = prefs.getString(KEY_TOKEN, null)
|
||||
|
||||
fun clear() {
|
||||
prefs.edit().clear().apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_TOKEN = "token"
|
||||
private const val KEY_NAME = "name"
|
||||
private const val KEY_ROLE = "role"
|
||||
}
|
||||
}
|
||||
352
app/src/main/java/id/abelbirdnest/mobile/network/ApiService.kt
Normal file
352
app/src/main/java/id/abelbirdnest/mobile/network/ApiService.kt
Normal file
@ -0,0 +1,352 @@
|
||||
package id.abelbirdnest.mobile.network
|
||||
|
||||
import id.abelbirdnest.mobile.data.ApiEnvelope
|
||||
import id.abelbirdnest.mobile.data.BootstrapData
|
||||
import id.abelbirdnest.mobile.data.CompleteWashingPayload
|
||||
import id.abelbirdnest.mobile.data.ConsignmentBootstrapData
|
||||
import id.abelbirdnest.mobile.data.ConsignmentCloseLinePayload
|
||||
import id.abelbirdnest.mobile.data.ConsignmentDetail
|
||||
import id.abelbirdnest.mobile.data.ConsignmentListItem
|
||||
import id.abelbirdnest.mobile.data.DashboardData
|
||||
import id.abelbirdnest.mobile.data.FundRequestListItem
|
||||
import id.abelbirdnest.mobile.data.FundRequestsBootstrapData
|
||||
import id.abelbirdnest.mobile.data.JitSaleClosePayload
|
||||
import id.abelbirdnest.mobile.data.JitSaleDetail
|
||||
import id.abelbirdnest.mobile.data.JitSaleListItem
|
||||
import id.abelbirdnest.mobile.data.LookupRecord
|
||||
import id.abelbirdnest.mobile.data.LotDetailResponse
|
||||
import id.abelbirdnest.mobile.data.LotTransformationCreatePayload
|
||||
import id.abelbirdnest.mobile.data.LotTransformationDetail
|
||||
import id.abelbirdnest.mobile.data.LotTransformationListItem
|
||||
import id.abelbirdnest.mobile.data.AdjustmentReason
|
||||
import id.abelbirdnest.mobile.data.PurchaseAnalysisDetail
|
||||
import id.abelbirdnest.mobile.data.PurchaseAnalysisListItem
|
||||
import id.abelbirdnest.mobile.data.PurchaseCreatePayload
|
||||
import id.abelbirdnest.mobile.data.PurchaseDetail
|
||||
import id.abelbirdnest.mobile.data.PurchaseListItem
|
||||
import id.abelbirdnest.mobile.data.PurchaseRealizationDetail
|
||||
import id.abelbirdnest.mobile.data.PurchaseRealizationListItem
|
||||
import id.abelbirdnest.mobile.data.RegularSaleClosePayload
|
||||
import id.abelbirdnest.mobile.data.RegularSaleDetail
|
||||
import id.abelbirdnest.mobile.data.RegularSaleListItem
|
||||
import id.abelbirdnest.mobile.data.ReceiptBootstrapData
|
||||
import id.abelbirdnest.mobile.data.StockAdjustmentCreatePayload
|
||||
import id.abelbirdnest.mobile.data.StockAdjustmentListItem
|
||||
import id.abelbirdnest.mobile.data.ReceiptCreatePayload
|
||||
import id.abelbirdnest.mobile.data.ReceiptDetail
|
||||
import id.abelbirdnest.mobile.data.ReceiptListItem
|
||||
import id.abelbirdnest.mobile.data.LoginRequest
|
||||
import id.abelbirdnest.mobile.data.LoginResponse
|
||||
import id.abelbirdnest.mobile.data.LotItem
|
||||
import id.abelbirdnest.mobile.data.LotScanPayload
|
||||
import id.abelbirdnest.mobile.data.UnitLookup
|
||||
import id.abelbirdnest.mobile.data.WashingBootstrapData
|
||||
import id.abelbirdnest.mobile.data.WashingListItem
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.PartMap
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
import retrofit2.http.PUT
|
||||
|
||||
interface ApiService {
|
||||
@POST("auth/login")
|
||||
suspend fun login(
|
||||
@Body request: LoginRequest,
|
||||
): LoginResponse
|
||||
|
||||
@GET("mobile/bootstrap")
|
||||
suspend fun bootstrap(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<BootstrapData>
|
||||
|
||||
@GET("mobile/dashboard")
|
||||
suspend fun dashboard(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Query("locale") locale: String = "id",
|
||||
): ApiEnvelope<DashboardData>
|
||||
|
||||
@GET("mobile/lots")
|
||||
suspend fun lots(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<LotItem>>
|
||||
|
||||
@GET("mobile/lots/{id}")
|
||||
suspend fun lotDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<LotDetailResponse>
|
||||
|
||||
@GET("mobile/lots/scan")
|
||||
suspend fun scanLot(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Query("code") code: String,
|
||||
): ApiEnvelope<LotScanPayload>
|
||||
|
||||
@GET("adjustment-reasons")
|
||||
suspend fun adjustmentReasons(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<AdjustmentReason>>
|
||||
|
||||
@GET("mobile/stock-adjustments")
|
||||
suspend fun stockAdjustments(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<StockAdjustmentListItem>>
|
||||
|
||||
@POST("mobile/stock-adjustments")
|
||||
suspend fun createStockAdjustment(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Body payload: StockAdjustmentCreatePayload,
|
||||
): ApiEnvelope<StockAdjustmentListItem>
|
||||
|
||||
@GET("mobile/purchases")
|
||||
suspend fun purchases(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<PurchaseListItem>>
|
||||
|
||||
@GET("units")
|
||||
suspend fun units(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<UnitLookup>>
|
||||
|
||||
@GET("employees")
|
||||
suspend fun employees(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<LookupRecord>>
|
||||
|
||||
@POST("mobile/purchases")
|
||||
suspend fun createPurchase(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Body payload: PurchaseCreatePayload,
|
||||
): ApiEnvelope<PurchaseDetail>
|
||||
|
||||
@GET("mobile/purchases/{id}")
|
||||
suspend fun purchaseDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<PurchaseDetail>
|
||||
|
||||
@PUT("mobile/purchases/{id}")
|
||||
suspend fun updatePurchase(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
@Body payload: PurchaseCreatePayload,
|
||||
): ApiEnvelope<PurchaseDetail>
|
||||
|
||||
@GET("mobile/purchase-analyses")
|
||||
suspend fun purchaseAnalyses(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<PurchaseAnalysisListItem>>
|
||||
|
||||
@GET("mobile/purchase-analyses/{purchaseId}")
|
||||
suspend fun purchaseAnalysisDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("purchaseId") purchaseId: String,
|
||||
): ApiEnvelope<PurchaseAnalysisDetail>
|
||||
|
||||
@GET("mobile/purchase-realizations")
|
||||
suspend fun purchaseRealizations(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<PurchaseRealizationListItem>>
|
||||
|
||||
@GET("mobile/purchase-realizations/{purchaseId}")
|
||||
suspend fun purchaseRealizationDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("purchaseId") purchaseId: String,
|
||||
): ApiEnvelope<PurchaseRealizationDetail>
|
||||
|
||||
@GET("mobile/sales-regular")
|
||||
suspend fun regularSales(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<RegularSaleListItem>>
|
||||
|
||||
@GET("mobile/sales-regular/{id}")
|
||||
suspend fun regularSaleDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<RegularSaleDetail>
|
||||
|
||||
@POST("mobile/sales-regular/{id}/close")
|
||||
suspend fun closeRegularSale(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
@Body payload: RegularSaleClosePayload,
|
||||
): ApiEnvelope<RegularSaleDetail>
|
||||
|
||||
@GET("mobile/sales-jit")
|
||||
suspend fun jitSales(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<JitSaleListItem>>
|
||||
|
||||
@GET("mobile/sales-jit/{id}")
|
||||
suspend fun jitSaleDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<JitSaleDetail>
|
||||
|
||||
@POST("mobile/sales-jit/{id}/close")
|
||||
suspend fun closeJitSale(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
@Body payload: JitSaleClosePayload,
|
||||
): ApiEnvelope<JitSaleDetail>
|
||||
|
||||
@GET("mobile/consignments/bootstrap")
|
||||
suspend fun consignmentsBootstrap(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<ConsignmentBootstrapData>
|
||||
|
||||
@GET("mobile/consignments")
|
||||
suspend fun consignments(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<ConsignmentListItem>>
|
||||
|
||||
@GET("mobile/consignments/{id}")
|
||||
suspend fun consignmentDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<ConsignmentDetail>
|
||||
|
||||
@POST("consignments/lines/{lineId}/close")
|
||||
suspend fun closeConsignmentLine(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("lineId") lineId: String,
|
||||
@Body payload: ConsignmentCloseLinePayload,
|
||||
): ApiEnvelope<Map<String, Any>>
|
||||
|
||||
@GET("mobile/fund-requests/bootstrap")
|
||||
suspend fun fundRequestsBootstrap(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<FundRequestsBootstrapData>
|
||||
|
||||
@GET("mobile/fund-requests")
|
||||
suspend fun fundRequests(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<FundRequestListItem>>
|
||||
|
||||
@Multipart
|
||||
@POST("mobile/fund-requests")
|
||||
suspend fun createFundRequest(
|
||||
@Header("Authorization") authorization: String,
|
||||
@PartMap fields: Map<String, @JvmSuppressWildcards RequestBody>,
|
||||
): ApiEnvelope<FundRequestListItem>
|
||||
|
||||
@POST("mobile/purchases/{id}/submit")
|
||||
suspend fun submitPurchase(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<PurchaseDetail>
|
||||
|
||||
@POST("mobile/purchases/{id}/cancel")
|
||||
suspend fun cancelPurchase(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<Map<String, Any>>
|
||||
|
||||
@GET("mobile/lot-transformations")
|
||||
suspend fun lotTransformations(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<LotTransformationListItem>>
|
||||
|
||||
@GET("mobile/lot-transformations/{id}")
|
||||
suspend fun lotTransformationDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<LotTransformationDetail>
|
||||
|
||||
@POST("mobile/lot-transformations")
|
||||
suspend fun createLotTransformation(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Body payload: LotTransformationCreatePayload,
|
||||
): ApiEnvelope<LotTransformationDetail>
|
||||
|
||||
@GET("mobile/washing/bootstrap")
|
||||
suspend fun washingBootstrap(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<WashingBootstrapData>
|
||||
|
||||
@GET("mobile/washing")
|
||||
suspend fun washings(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<WashingListItem>>
|
||||
|
||||
@Multipart
|
||||
@POST("mobile/washing")
|
||||
suspend fun createWashing(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Part("payload") payload: RequestBody,
|
||||
): ApiEnvelope<WashingListItem>
|
||||
|
||||
@Multipart
|
||||
@PUT("mobile/washing/{id}")
|
||||
suspend fun updateWashing(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
@Part("payload") payload: RequestBody,
|
||||
): ApiEnvelope<WashingListItem>
|
||||
|
||||
@POST("mobile/washing/{id}/complete")
|
||||
suspend fun completeWashing(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
@Body payload: CompleteWashingPayload,
|
||||
): ApiEnvelope<WashingListItem>
|
||||
|
||||
@GET("mobile/receipts/bootstrap")
|
||||
suspend fun receiptsBootstrap(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<ReceiptBootstrapData>
|
||||
|
||||
@GET("mobile/receipts")
|
||||
suspend fun receipts(
|
||||
@Header("Authorization") authorization: String,
|
||||
): ApiEnvelope<List<ReceiptListItem>>
|
||||
|
||||
@POST("mobile/receipts")
|
||||
suspend fun createReceipt(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Body payload: ReceiptCreatePayload,
|
||||
): ApiEnvelope<ReceiptDetail>
|
||||
|
||||
@GET("mobile/receipts/{id}")
|
||||
suspend fun receiptDetail(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<ReceiptDetail>
|
||||
|
||||
@POST("mobile/receipts/{id}/generate-lots")
|
||||
suspend fun generateReceiptLots(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("id") id: String,
|
||||
): ApiEnvelope<ReceiptDetail>
|
||||
}
|
||||
|
||||
object ApiFactory {
|
||||
private const val BASE_URL = "https://abelbirdnest.id/api/v1/"
|
||||
|
||||
fun create(): ApiService {
|
||||
val logger = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BASIC
|
||||
}
|
||||
|
||||
val client = OkHttpClient.Builder()
|
||||
.addInterceptor(logger)
|
||||
.build()
|
||||
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create(ApiService::class.java)
|
||||
}
|
||||
}
|
||||
7667
app/src/main/java/id/abelbirdnest/mobile/ui/AbelbirdnestApp.kt
Normal file
7667
app/src/main/java/id/abelbirdnest/mobile/ui/AbelbirdnestApp.kt
Normal file
File diff suppressed because it is too large
Load Diff
3231
app/src/main/java/id/abelbirdnest/mobile/ui/MainViewModel.kt
Normal file
3231
app/src/main/java/id/abelbirdnest/mobile/ui/MainViewModel.kt
Normal file
File diff suppressed because it is too large
Load Diff
21
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Color.kt
Normal file
21
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Color.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package id.abelbirdnest.mobile.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Background = Color(0xFFF8FAFA)
|
||||
val Surface = Color(0xFFF8FAFA)
|
||||
val SurfaceContainerLowest = Color(0xFFFFFFFF)
|
||||
val SurfaceContainerHigh = Color(0xFFE6E8E9)
|
||||
val SurfaceContainer = Color(0xFFECEEEE)
|
||||
val OnSurface = Color(0xFF191C1D)
|
||||
val OnSurfaceVariant = Color(0xFF3F484A)
|
||||
val Outline = Color(0xFF6F797A)
|
||||
val OutlineVariant = Color(0xFFBFC8CA)
|
||||
val Primary = Color(0xFF00454C)
|
||||
val PrimaryContainer = Color(0xFF0D5E67)
|
||||
val OnPrimary = Color(0xFFFFFFFF)
|
||||
val OnPrimaryContainer = Color(0xFF92D5DF)
|
||||
val Secondary = Color(0xFF4E6073)
|
||||
val SecondaryContainer = Color(0xFFCFE2F9)
|
||||
val Tertiary = Color(0xFF60320F)
|
||||
val Error = Color(0xFFBA1A1A)
|
||||
87
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Theme.kt
Normal file
87
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Theme.kt
Normal file
@ -0,0 +1,87 @@
|
||||
package id.abelbirdnest.mobile.ui.theme
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
private val AppColorScheme: ColorScheme = lightColorScheme(
|
||||
primary = Primary,
|
||||
onPrimary = OnPrimary,
|
||||
primaryContainer = PrimaryContainer,
|
||||
onPrimaryContainer = OnPrimaryContainer,
|
||||
secondary = Secondary,
|
||||
secondaryContainer = SecondaryContainer,
|
||||
tertiary = Tertiary,
|
||||
error = Error,
|
||||
background = Background,
|
||||
surface = Surface,
|
||||
surfaceContainer = SurfaceContainer,
|
||||
surfaceContainerHigh = SurfaceContainerHigh,
|
||||
surfaceContainerLowest = SurfaceContainerLowest,
|
||||
onSurface = OnSurface,
|
||||
onSurfaceVariant = OnSurfaceVariant,
|
||||
outline = Outline,
|
||||
outlineVariant = OutlineVariant,
|
||||
)
|
||||
|
||||
private val AppTypography = Typography(
|
||||
headlineMedium = TextStyle(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 28.sp,
|
||||
lineHeight = 32.sp,
|
||||
),
|
||||
headlineSmall = TextStyle(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 24.sp,
|
||||
lineHeight = 30.sp,
|
||||
),
|
||||
titleMedium = TextStyle(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 22.sp,
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
),
|
||||
bodySmall = TextStyle(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 13.sp,
|
||||
lineHeight = 18.sp,
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.6.sp,
|
||||
),
|
||||
displaySmall = TextStyle(
|
||||
fontFamily = FontFamily.SansSerif,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 34.sp,
|
||||
lineHeight = 40.sp,
|
||||
),
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun AbelbirdnestTheme(content: @Composable () -> Unit) {
|
||||
MaterialTheme(
|
||||
colorScheme = AppColorScheme,
|
||||
typography = AppTypography,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
package id.abelbirdnest.mobile.ui.theme
|
||||
|
||||
// Typography is defined in Theme.kt.
|
||||
Reference in New Issue
Block a user