Initial mobile app implementation

This commit is contained in:
2026-05-21 23:36:13 +07:00
commit 6edd98a268
28 changed files with 14407 additions and 0 deletions

View 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)
}
}
}
}

View File

@ -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()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View 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)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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)

View 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,
)
}

View File

@ -0,0 +1,3 @@
package id.abelbirdnest.mobile.ui.theme
// Typography is defined in Theme.kt.