NetPOS ViewModels follow Android Architecture Components pattern, managing UI-related data and business logic. All ViewModels extend androidx.lifecycle.ViewModel and use LiveData for reactive state updates.
Handles user authentication, login, and password reset.
viewmodels/AuthViewModel.kt:26
class AuthViewModel : ViewModel() { private val disposables = CompositeDisposable() var stormApiService: StormApiService? = null val authInProgress = MutableLiveData(false) val passwordResetInProgress = MutableLiveData(false) val usernameLiveData = MutableLiveData("") val passwordLiveData = MutableLiveData("") private val _message = MutableLiveData<Event<String>>() val message: LiveData<Event<String>> get() = _message private val _authDone = MutableLiveData<Event<Boolean>>() val authDone: LiveData<Event<Boolean>> get() = _authDone}
AuthViewModel Methods
login()
Standard email/password authentication:
viewmodels/AuthViewModel.kt:51
fun login() { val username = usernameLiveData.value val password = passwordLiveData.value if (username.isNullOrEmpty() || password.isNullOrEmpty()) { _message.value = Event("All fields are required") return } if (!Patterns.EMAIL_ADDRESS.matcher(username).matches()) { _message.value = Event("Please enter a valid email") return } auth(username, password)}
login(deviceId: String)
EasyPOS authentication with device binding:
viewmodels/AuthViewModel.kt:65
fun login(deviceId: String) { val credentials = JsonObject().apply { addProperty("username", username) addProperty("password", password) addProperty("deviceId", deviceId) } stormApiService!!.userToken(credentials) .flatMap { response -> val userToken = response.token Prefs.putString(PREF_USER_TOKEN, userToken) parseUserFromJWT(userToken) } .subscribe { res, error -> // Handle authentication result }}
resetPassword()
Password reset flow:
viewmodels/AuthViewModel.kt:378
fun resetPassword() { val username = usernameLiveData.value if (username.isNullOrEmpty()) { _message.value = Event("Please enter your email address") return } val payload = JsonObject().apply { addProperty("username", username) } stormApiService!!.passwordReset(payload) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { response, error -> if (response?.code() == 200) { _message.value = Event("Password reset email sent") } }}
Manages card transactions, refunds, and receipt printing.
viewmodels/TransactionsViewModel.kt:28
class TransactionsViewModel(private val appDatabase: AppDatabase) : ViewModel() { var cardData: CardData? = null private val compositeDisposable = CompositeDisposable() val lastTransactionResponse = MutableLiveData<TransactionResponse>() val inProgress = MutableLiveData(false) private val _message = MutableLiveData<Event<String>>() val message: LiveData<Event<String>> get() = _message private val _shouldRefreshNibssKeys = MutableLiveData<Event<Boolean>>() val shouldRefreshNibssKeys: LiveData<Event<Boolean>> get() = _shouldRefreshNibssKeys val pagedTransaction: LiveData<PagedList<TransactionResponse>>}
Handles QR code generation for contactless payments (MasterPass, NIBSS, Zenith).
viewmodels/QRViewModel.kt:28
open class QRViewModel( private val masterPassQRService: MasterPassQRService, private val nibssQRService: NibssQRService, private val zenithQRService: ZenithQrService, private val blueCodeService: BlueCodeService) : ViewModel() { private var retryAttempts = 1 var stillHasRetryAttempts = true val disposable = CompositeDisposable() private val _nibssQRBitmap = MutableLiveData<Event<Bitmap>>() val nibssQRBitmap: LiveData<Event<Bitmap>> get() = _nibssQRBitmap val message = MediatorLiveData<Event<String>>()}
QR Generation Methods
MasterPass QR
viewmodels/QRViewModel.kt:112
fun getMasterPassQr(amount: Double) { val qrRequestBody = JsonObject().apply { val user = Singletons.getCurrentlyLoggedInUser()!! addProperty("amount", amount.toString()) addProperty("order_id", UUID.randomUUID().toString()) addProperty("merchant_id", user.netplus_id) addProperty("currency_code", "NGN") addProperty("business_name", user.business_name) addProperty("merchant_city", "Lagos") } masterPassQRService.getStaticQr(qrRequestBody) .subscribeOn(Schedulers.io()) .flatMap { response -> val bmp = BitmapFactory.decodeStream(response.byteStream()) if (bmp != null) Single.just(bmp) else throw NullPointerException("Bitmap is null") } .subscribe { bitmap, error -> bitmap?.let { _masterPassQrBitmap.value = Event(it) } }}
NIBSS QR with Auto-Query
viewmodels/QRViewModel.kt:155
fun getNibssQR(amount: Double) { val orderNumber = generateOrderNumber() lastNibssOrderNumber.value = orderNumber val jsonObject = JsonObject().apply { addProperty("amount", amount.toString()) addProperty("order_no", orderNumber) } nibssQRService.getQr(jsonObject) .flatMap { response -> if (response.returnCode != "Success") { throw Exception("Could not fetch QR code") } Single.just(encodeAsBitmap(response.codeUrl, 150, 150)) } .subscribe { bitmap, error -> bitmap?.let { _nibssQRBitmap.value = Event(it) runHandler() // Start auto-query } }}private fun runHandler() { if (retryAttempts > 15) { stillHasRetryAttempts = false message.value = Event("Too many attempts without response") return } Handler(Looper.getMainLooper()).postDelayed({ queryTransaction() }, 4000)}
Zenith QR with Merchant Registration
viewmodels/QRViewModel.kt:235
fun getZenithQR(type: String, amount: Double) { val req = if (amount == 0.0) zenithQRService.getZenithQr(type) else zenithQRService.getDynamicQr(type, amount.toInt().toString()) req.subscribeOn(Schedulers.io()) .flatMap { response -> val bitmap = response.qrCode.decodeBase64ToBitmap() if (bitmap != null) Single.just(bitmap) else throw NullPointerException("Bitmap is null") } .subscribe { bitmap, error -> bitmap?.let { _zenithQr.value = Event(it) } error?.let { val responseBody = it.getResponseBody() if (it.isHttpStatusCode(404) && responseBody.contains("Merchant not registered")) { _createZenithMerchant.value = Event(type) } } }}fun registerZenithMerchant() { val payload = createZenithMerchantPayload.value!! zenithQRService.createZenithQRMerchant(payload) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { response, error -> response?.let { _zenithQrRegistrationDone.value = Event(true) } }}
class TransactionsViewModelFactory( private val appDatabase: AppDatabase) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(TransactionsViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return TransactionsViewModel(appDatabase) as T } throw IllegalArgumentException("Unknown ViewModel class") }}
data class Event<out T>(private val content: T) { private var hasBeenHandled = false fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } } fun peekContent(): T = content}
Event wrapper prevents re-emission of single-use events on configuration changes.