diff --git a/accesscontainer/build.gradle b/accesscontainer/build.gradle index 71000eee..e42cb5e2 100644 --- a/accesscontainer/build.gradle +++ b/accesscontainer/build.gradle @@ -1,64 +1,38 @@ apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' +apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'maven-publish' +apply from: '../properties.gradle' -// Because the components are created only during the afterEvaluate phase, you must -// configure your publications using the afterEvaluate() lifecycle method. -afterEvaluate { - task sourceJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - classifier "sources" - } - publishing { - publications { - // Creates a Maven publication called "release". - release(MavenPublication) { - // Applies the component for the release build variant. - from components.release - - // You can then customize attributes of the publication as shown below. - groupId = 'by.esas.tools' - artifactId = 'accesscontainer' - version = "$accesscontainer_lib_version" - } - // Creates a Maven publication called “debug”. - /*debug(MavenPublication) { - // Applies the component for the debug build variant. - from components.debug - - groupId = 'com.example.MyLibrary' - artifactId = 'final-debug' - version = '1.0' - }*/ - - } - repositories { - maven { - // change to point to your repo, e.g. http://my.org/repo - url = 'https://github.com/PManenok/BasicTools' - /*credentials { - username PManenok - password BlackCurrant$25 - }*/ - } - } - } -} android { - compileSdkVersion 30 + namespace = "${package_name}.${project.name}" + + compileSdkVersion compile_sdk_version defaultConfig { - minSdkVersion 16 + minSdkVersion min_sdk_version } } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.core:core-ktx:$corektx_version" //Module - implementation project(path: ':logger') + api project(path: ':logger') +} + +afterEvaluate { + publishing { + publications { + // Creates a Maven publication called "release". + release(MavenPublication) { + from components.release + groupId = "${package_name}" + artifactId = "${project.name}" + version = "$accesscontainer_lib_version" + } + } + } } diff --git a/accesscontainer/src/main/AndroidManifest.xml b/accesscontainer/src/main/AndroidManifest.xml deleted file mode 100644 index 79149913..00000000 --- a/accesscontainer/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/ContainerRequest.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/ContainerRequest.kt index 5fbf1835..3420ee76 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/ContainerRequest.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/ContainerRequest.kt @@ -8,7 +8,7 @@ package by.esas.tools.accesscontainer import by.esas.tools.logger.BaseErrorModel -class ContainerRequest, M : BaseErrorModel> { +class ContainerRequest { private var onComplete: ((T) -> Unit)? = null private var onError: ((M) -> Unit)? = null private var onCancel: (() -> Unit)? = null diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/IRefreshContainer.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/IRefreshContainer.kt index 0bfc71a2..40e2f28f 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/IRefreshContainer.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/IRefreshContainer.kt @@ -11,7 +11,7 @@ import by.esas.tools.accesscontainer.entity.Token import by.esas.tools.accesscontainer.support.IContainerCancellationCallback import by.esas.tools.logger.BaseErrorModel -interface IRefreshContainer, M : BaseErrorModel> { +interface IRefreshContainer { fun setToken(token: Token) fun getToken(): String fun getRefresh(): String @@ -25,26 +25,26 @@ interface IRefreshContainer, M : BaseErrorModel> { * This function can refresh token without secret key * Access token would be refreshed anyway **/ - fun refresh(onComplete: (String?) -> Unit, onError: (BaseErrorModel) -> Unit, onCancel: () -> Unit) + fun refresh(onComplete: (String?) -> Unit, onError: (BaseErrorModel) -> Unit, onCancel: () -> Unit) /** * This function would always invoke dialogs with secrets if it can, because it checks if user can access secret * can refresh token or not * @see refreshExplicitly **/ - fun checkAccess(refreshExplicitly: Boolean = false, response: ContainerRequest.() -> Unit) + fun checkAccess(refreshExplicitly: Boolean = false, response: ContainerRequest.() -> Unit) /** * This function can save refresh token, it invokes only encryption dialogs * Should be used only when no secret exist, because it will override old secrets **/ - fun saveRefresh(refreshToken: String, response: ContainerRequest.() -> Unit) + fun saveRefresh(refreshToken: String, response: ContainerRequest.() -> Unit) /** * This function can save refresh token, it invokes only encryption dialogs * Should be used only when no secret exist, because it will override old secrets **/ - fun getSecret(response: ContainerRequest.() -> Unit) + fun getSecret(response: ContainerRequest.() -> Unit) fun setUserId(userId: String) diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/Refresher.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/Refresher.kt index 37edf965..3ecd4ca0 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/Refresher.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/Refresher.kt @@ -26,19 +26,20 @@ import by.esas.tools.accesscontainer.support.ITypeManager import by.esas.tools.accesscontainer.support.supporter.Supporter import by.esas.tools.logger.BaseErrorModel import by.esas.tools.logger.ILogger +import by.esas.tools.logger.ILogger.Companion.CATEGORY_ERROR import java.lang.ref.WeakReference import javax.crypto.Cipher import javax.crypto.SecretKey -open class Refresher, M : BaseErrorModel>( - protected val logger: ILogger, - protected val executor: IContainerExecutor, +open class Refresher( + protected val logger: ILogger, + protected val executor: IContainerExecutor, protected val typeManager: ITypeManager, protected val userInfo: BiometricUserInfo, - protected val mapper: IErrorMapper, + protected val mapper: IErrorMapper, protected val dialogProvider: DialogProvider, - protected val supporter: Supporter -) : IRefreshContainer { + protected val supporter: Supporter +) : IRefreshContainer { val TAG: String = Refresher::class.java.simpleName protected var accessToken: String = "" protected var refreshToken: String = "" @@ -47,7 +48,7 @@ open class Refresher, M : BaseErrorModel>( protected val defaultCancellationCallback: IContainerCancellationCallback = object : IContainerCancellationCallback { override fun onCancel() { - logger.log("defaultCancellationCallback onCancel does nothing") + logger.logInfo("defaultCancellationCallback onCancel does nothing") //does nothing. should be set before refresh usage } } @@ -58,10 +59,10 @@ open class Refresher, M : BaseErrorModel>( WeakReference(null) // fragment manager for simple dialogs protected var activity: WeakReference = WeakReference(null) // activity for biometric dialog - protected var result: ContainerRequest = - ContainerRequest() // result instance which will get result when it is finished - protected var secretResult: ContainerRequest = - ContainerRequest() // result instance which will get result when it is finished + protected var result: ContainerRequest = + ContainerRequest() // result instance which will get result when it is finished + protected var secretResult: ContainerRequest = + ContainerRequest() // result instance which will get result when it is finished protected var userIdValue: String = "" // user ID sets from getSecrets or with setUserId method protected var isBiometricAvailable: Boolean = false // shows if biometric is available @@ -82,10 +83,10 @@ open class Refresher, M : BaseErrorModel>( protected val types: MutableSet = mutableSetOf()// shows which secret types user has - protected var refresherErrorStatusToCheck: E? = null + protected var refresherErrorStatusToCheck: String? = null protected var checkRefreshError: Boolean = false protected var useExtraCheck: Boolean = false - protected var extraRefreshCheck: (BaseErrorModel) -> Unit = {} + protected var extraRefreshCheck: (BaseErrorModel) -> Unit = {} init { logger.setTag(TAG) @@ -96,12 +97,12 @@ open class Refresher, M : BaseErrorModel>( } override fun getToken(): String { - logger.log("getToken()") + logger.logOrder("getToken()") return accessToken } override fun setToken(token: Token) { - logger.log("setToken() refreshToken isNullOrBlank = ${token.refreshToken.isNullOrBlank()}") + logger.logOrder("setToken() refreshToken isNullOrBlank = ${token.refreshToken.isNullOrBlank()}") accessToken = token.accessToken refreshToken = token.refreshToken ?: "" } @@ -114,57 +115,57 @@ open class Refresher, M : BaseErrorModel>( * Запуск запроса на восстановление маркера доступа с последующим обновлением * данных контейнера и выполнением переданного блока кода */ - override fun refresh(onComplete: (String?) -> Unit, onError: (BaseErrorModel) -> Unit, onCancel: () -> Unit) { - logger.log("refresh") + override fun refresh(onComplete: (String?) -> Unit, onError: (BaseErrorModel) -> Unit, onCancel: () -> Unit) { + logger.logOrder("refresh") this.refreshExplicitly = true - result = ContainerRequest() + result = ContainerRequest() .apply { onComplete { tokenResponse -> - logger.log("refresh result.onComplete token != null = ${tokenResponse.token != null}") + logger.logOrder("refresh result.onComplete token != null = ${tokenResponse.token != null}") tokenResponse.token?.let { setToken(it) } onComplete(tokenResponse.token?.accessToken) } onError { - logger.log("refresh result.onError") + logger.logOrder("refresh result.onError") logger.logError(it) onError(it) } onCancel { - logger.log("refresh result.onCancel") + logger.logOrder("refresh result.onCancel") onCancel() } } - logger.log("refresh needCheck = false") + logger.logDebug("refresh needCheck = false") needCheck = false needSecret = false if (refreshToken.isNotEmpty()) { - logger.log("refresh refreshToken isNotEmpty") + logger.logDebug("refresh refreshToken isNotEmpty") refreshAccess(refreshToken) } else { - logger.log("refresh refreshToken isEmpty") + logger.logDebug("refresh refreshToken isEmpty") userIdValue = userInfo.getCurrentUser() - logger.log("refresh userId = $userIdValue") + logger.logUserInfo("refresh userId = $userIdValue") getSecrets() } } override fun onCancel() { - logger.log("onCancel") + logger.logOrder("onCancel") containerCancellationCallback.onCancel() } - override fun checkAccess(refreshExplicitly: Boolean, response: ContainerRequest.() -> Unit) { - logger.log("checkAccess refreshExplicitly = $refreshExplicitly") + override fun checkAccess(refreshExplicitly: Boolean, response: ContainerRequest.() -> Unit) { + logger.logOrder("checkAccess refreshExplicitly = $refreshExplicitly") this.refreshExplicitly = refreshExplicitly - val result = ContainerRequest().apply(response) + val result = ContainerRequest().apply(response) - this.result = ContainerRequest() + this.result = ContainerRequest() .apply { onComplete { tokenResponse -> - logger.log("checkAccess result.onComplete hasAccess = ${tokenResponse.hasAccess}") + logger.logOrder("checkAccess result.onComplete hasAccess = ${tokenResponse.hasAccess}") if (tokenResponse.hasAccess) { - logger.log("checkAccess result.onComplete token != null = ${tokenResponse.token != null}") + logger.logDebug("checkAccess result.onComplete token != null = ${tokenResponse.token != null}") tokenResponse.token?.let { token -> setToken(token) } @@ -172,52 +173,52 @@ open class Refresher, M : BaseErrorModel>( } else result() } onError { - logger.log("checkAccess result.onError") + logger.logOrder("checkAccess result.onError") logger.logError(it) result(it) } onCancel { - logger.log("checkAccess result.onCancel") + logger.logOrder("checkAccess result.onCancel") result() } } - logger.log("checkAccess needCheck = true") + logger.logDebug("checkAccess needCheck = true needSecret = false") needCheck = true needSecret = false userIdValue = userInfo.getCurrentUser() - logger.log("checkAccess userId = $userIdValue") + logger.logUserInfo("checkAccess userId = $userIdValue") getSecrets() } - override fun getSecret(response: ContainerRequest.() -> Unit) { - logger.log("getSecret") - val result = ContainerRequest().apply(response) + override fun getSecret(response: ContainerRequest.() -> Unit) { + logger.logOrder("getSecret") + val result = ContainerRequest().apply(response) this.secretResult = result - logger.log("getSecret needSecret = true, needCheck = false") + logger.logDebug("getSecret needSecret = true, needCheck = false") needSecret = true needCheck = false userIdValue = userInfo.getCurrentUser() - logger.log("getSecret userId = $userIdValue") + logger.logUserInfo("getSecret userId = $userIdValue") getSecrets() } - override fun saveRefresh(refreshToken: String, response: ContainerRequest.() -> Unit) { - val result = ContainerRequest().apply(response) + override fun saveRefresh(refreshToken: String, response: ContainerRequest.() -> Unit) { + val result = ContainerRequest().apply(response) - this.result = ContainerRequest() + this.result = ContainerRequest() .apply { onComplete { tokenResponse -> - logger.log("saveRefresh result.onComplete") + logger.logOrder("saveRefresh result.onComplete") result(accessToken) } onError { - logger.log("saveRefresh result.onError") + logger.logOrder("saveRefresh result.onError") logger.logError(it) result(it) } onCancel { - logger.log("saveRefresh result.onCancel") + logger.logOrder("saveRefresh result.onCancel") result() } } @@ -232,16 +233,16 @@ open class Refresher, M : BaseErrorModel>( /*############################ Simple refresh token #########################################*/ protected open fun refreshAccess(refreshToken: String) { - logger.log("refreshAccess") + logger.logOrder("refreshAccess") executor.refreshAccessUC(refreshToken) { onComplete { token -> - logger.log("refreshAccess onComplete it != null ${token != null}") + logger.logOrder("refreshAccess onComplete it != null ${token != null}") if (token != null) { result(RefreshResult(token = token)) } } onError { error -> - logger.log("refreshAccess onError error = $error") + logger.logOrder("refreshAccess onError error = $error") if (checkRefreshError) { checkRefreshError(error) } else { @@ -252,6 +253,7 @@ open class Refresher, M : BaseErrorModel>( } protected open fun checkRefreshError(error: M) { + logger.logOrder("checkRefreshError") if (useExtraCheck) { extraRefreshCheck(error) } else { @@ -268,11 +270,11 @@ open class Refresher, M : BaseErrorModel>( /*############################# Refresh token with dialogs ##################################*/ protected open fun getSecrets() { - logger.log("getSecrets") + logger.logOrder("getSecrets") types.clear() executor.getSecretsUC { onComplete { - logger.log("getSecrets onComplete types not empty = ${it.isNotEmpty()}") + logger.logOrder("getSecrets onComplete types not empty = ${it.isNotEmpty()}") if (it.isNotEmpty()) { doWhenHasSecrets(it) } else { @@ -283,7 +285,7 @@ open class Refresher, M : BaseErrorModel>( } } onError { throwable -> - logger.log("getSecrets onError") + logger.logOrder("getSecrets onError") result(throwable) } } @@ -291,9 +293,9 @@ open class Refresher, M : BaseErrorModel>( protected open fun doWhenHasSecrets(list: List) { types.addAll(list) - logger.log("getSecrets onComplete onlyOnePresent = ${list.size == 1}") + logger.logOrder("getSecrets onComplete onlyOnePresent = ${list.size == 1}") val preferred = typeManager.getPreferredType() - logger.log("getSecrets onComplete preferred = $preferred") + logger.logDebug("getSecrets onComplete preferred = $preferred") if (preferred == AuthType.NONE || types.contains(preferred)) { when (preferred) { AuthType.PIN_AUTH -> { @@ -322,46 +324,46 @@ open class Refresher, M : BaseErrorModel>( /*############################# Check access with dialogs ###################################*/ protected open fun checkSecret(pinKey: SecretKey? = null, cipher: Cipher? = null) { - logger.log("checkSecret pinKey!=null ${pinKey != null}, cipher!=null ${cipher != null}") + logger.logOrder("checkSecret pinKey!=null ${pinKey != null}, cipher!=null ${cipher != null}") executor.checkSecretUC(pinKey, cipher) { onComplete { - logger.log("checkSecret onComplete ${it.first}") + logger.logOrder("checkSecret onComplete ${it.first}") if (it.first) { when { needSecret -> secretResult(it.second) refreshExplicitly -> { - logger.log("checkSecret onComplete refreshToken != null") + logger.logDebug("checkSecret onComplete refreshToken != null") refreshWithSecret(it.second) } else -> { - logger.log("checkSecret onComplete return") + logger.logDebug("checkSecret onComplete return") result(RefreshResult()) } } } else { - logger.log("checkSecret onComplete failed check return error SECRET_CHECK_NOT_MATCH") + logger.logDebug("checkSecret onComplete failed check return error SECRET_CHECK_NOT_MATCH") val error = supporter.util.createErrorModel(0, ErrorStatusEnum.SECRET_CHECK_NOT_MATCH.name) if (needSecret) secretResult(error) else result(error) } } onError { error -> - logger.log("checkSecret onError") + logger.logOrder("checkSecret onError") result(error) } } } protected open fun refreshWithSecret(secret: String) { - logger.log("refreshWithSecret") + logger.logOrder("refreshWithSecret") executor.refreshWithSecretUC(secret) { onComplete { token -> - logger.log("refreshWithSecret onComplete") + logger.logOrder("refreshWithSecret onComplete") result(RefreshResult(token)) } onError { error -> - logger.log("refreshWithSecret onError") + logger.logOrder("refreshWithSecret onError") if (checkRefreshError) { checkRefreshError(error) } else { @@ -375,13 +377,13 @@ open class Refresher, M : BaseErrorModel>( /*############################### Authentication with password ##############################*/ protected open fun authenticate(password: String, recreate: Boolean, noSecrets: Boolean) { - logger.log("authenticate password not blank = ${password.isNotBlank()}; recreate = $recreate; noSecrets = $noSecrets") + logger.logOrder("authenticate password not blank = ${password.isNotBlank()}; recreate = $recreate; noSecrets = $noSecrets") if (password.isNotBlank()) { executor.authorizationInSystemUC(password) { onComplete { token -> - logger.log("authenticate onComplete it.refreshToken != null ${token.refreshToken != null}") + logger.logOrder("authenticate onComplete it.refreshToken != null ${token.refreshToken != null}") if (token.refreshToken != null) { - logger.log("authenticate onComplete isBiometricAvailable $isBiometricAvailable") + logger.logDebug("authenticate onComplete isBiometricAvailable $isBiometricAvailable") if (recreate) { if (isBiometricAvailable) { @@ -393,30 +395,30 @@ open class Refresher, M : BaseErrorModel>( result(RefreshResult(token)) } } else { - logger.log("authenticate onComplete return") + logger.logDebug("authenticate onComplete return") result(RefreshResult(token)) } } onError { error -> - logger.log("authenticate onError") + logger.logOrder("authenticate onError") result(error) } } } else { - logger.log("authenticate showPasswordDialog") + logger.logDebug("authenticate showPasswordDialog") showPasswordDialog(noSecrets) } } protected open fun putSecrets(token: Token, cipher: Cipher? = null, pinKey: SecretKey? = null) { - logger.log("putSecrets pinKey!=null ${pinKey != null}, cipher!=null ${cipher != null}") + logger.logOrder("putSecrets pinKey!=null ${pinKey != null}, cipher!=null ${cipher != null}") executor.createSecretUC(token.refreshToken!!, cipher, pinKey) { onComplete { - logger.log("putSecrets onComplete return") + logger.logOrder("putSecrets onComplete return") result(RefreshResult(token)) } onError { - logger.log("putSecrets onError return") + logger.logOrder("putSecrets onError return") logger.showMessage("Secret was not saved") //todo show message secret was not create result(RefreshResult(token)) } @@ -427,15 +429,15 @@ open class Refresher, M : BaseErrorModel>( /*############################### Dialogs ###################################################*/ protected open fun showAuthDialog(isDecrypting: Boolean, token: Token? = null) { - logger.log("showAuthDialog isDecrypting ${isDecrypting}, token!=null = ${token != null}") + logger.logOrder("showAuthDialog isDecrypting ${isDecrypting}, token!=null = ${token != null}") dialogProvider.menuDialog.let { dialog -> dialog.createDialog() dialog.setStateActions({ e -> - logger.log("showAuthDialog onError ${e.message}") + logger.logOrder("showAuthDialog onError ${e.message}") if (needSecret) secretResult(mapper(e)) else result(mapper(e)) }, { afterOk -> - logger.log("showAuthDialog onDismiss afterOk=${afterOk}") + logger.logOrder("showAuthDialog onDismiss afterOk=${afterOk}") if (!afterOk) { if (needSecret) secretResult() else result() @@ -444,15 +446,15 @@ open class Refresher, M : BaseErrorModel>( dialog.isDecrypting(isDecrypting) dialog.showPassword(!needSecret) dialog.setCallBacks({ - logger.log("showAuthDialog onPinClick") + logger.logOrder("showAuthDialog onPinClick") if (isDecrypting) showDecryptPinDialog() else token?.let { showEncryptPinDialog(token) } }, { - logger.log("showAuthDialog onBiometricClick") + logger.logOrder("showAuthDialog onBiometricClick") if (isDecrypting) showDecryptBiometricDialog() else token?.let { showEncryptBiometricDialog(token) } }, { - logger.log("showAuthDialog onPasswordClick") + logger.logOrder("showAuthDialog onPasswordClick") showPasswordDialog() }) @@ -471,15 +473,15 @@ open class Refresher, M : BaseErrorModel>( /*######################## PIN ##########################*/ protected open fun showDecryptPinDialog() { - logger.log("showDecryptPinDialog") + logger.logOrder("showDecryptPinDialog") dialogProvider.pinDialog.let { dialog -> dialog.createDialog() dialog.setStateActions({ e -> - logger.log("showDecryptPinDialog onError ${e.message}") + logger.logOrder("showDecryptPinDialog onError ${e.message}") if (needSecret) secretResult(mapper(e)) else result(mapper(e)) }, { afterOk -> - logger.log("showDecryptPinDialog onDismiss afterOk=$afterOk") + logger.logOrder("showDecryptPinDialog onDismiss afterOk=$afterOk") if (!afterOk) { if (needSecret) secretResult() else result() @@ -487,7 +489,7 @@ open class Refresher, M : BaseErrorModel>( }) dialog.setCallbacks({ pin -> - logger.log("showDecryptPinDialog onPinComplete needCheck=$needCheck") + logger.logOrder("showDecryptPinDialog onPinComplete needCheck=$needCheck") val pinKey = supporter.util.generatePin( pin, userIdValue, Settings.Secure.getString(activity.get()?.contentResolver, Settings.Secure.ANDROID_ID) @@ -499,11 +501,11 @@ open class Refresher, M : BaseErrorModel>( refreshWithSecret(pinKey = pinKey) }*/ }, { - logger.log("showDecryptPinDialog onPinCanceled showAuthDialog(isDecrypting = true)") + logger.logOrder("showDecryptPinDialog onPinCanceled showAuthDialog(isDecrypting = true)") showAuthDialog(isDecrypting = true) }) - logger.log("showDecryptPinDialog needCheck=$needCheck") + logger.logDebug("showDecryptPinDialog needCheck=$needCheck") if (needCheck) dialog.setTitle(supporter.resProvider.provideAccessConfirmStr()) else @@ -518,14 +520,14 @@ open class Refresher, M : BaseErrorModel>( } protected open fun showEncryptPinDialog(token: Token) { - logger.log("showEncryptPinDialog") + logger.logOrder("showEncryptPinDialog") dialogProvider.pinDialog.let { dialog -> dialog.createDialog() dialog.setStateActions({ e -> - logger.log("showDecryptPinDialog onError ${e.message}") + logger.logOrder("showDecryptPinDialog onError ${e.message}") result(mapper(e)) }, { afterOk -> - logger.log("showEncryptPinDialog onDismiss afterOk=$afterOk") + logger.logOrder("showEncryptPinDialog onDismiss afterOk=$afterOk") if (!afterOk) { result( RefreshResult( @@ -535,7 +537,7 @@ open class Refresher, M : BaseErrorModel>( } }) dialog.setCallbacks({ pin -> - logger.log("showEncryptPinDialog onPinComplete preferredType=PIN_AUTH") + logger.logOrder("showEncryptPinDialog onPinComplete preferredType=PIN_AUTH") val pinKey = supporter.util.generatePin( pin, userIdValue, Settings.Secure.getString(activity.get()?.contentResolver, Settings.Secure.ANDROID_ID) @@ -543,12 +545,12 @@ open class Refresher, M : BaseErrorModel>( typeManager.putPreferredType(AuthType.PIN_AUTH) putSecrets(token = token, pinKey = pinKey) }, { - logger.log("showEncryptPinDialog onPinCanceled showAuthDialog(isDecrypting = false)") + logger.logOrder("showEncryptPinDialog onPinCanceled showAuthDialog(isDecrypting = false)") showAuthDialog(isDecrypting = false, token = token) }) dialog.setTitle(supporter.resProvider.provideEncryptTitle()) - logger.log("showEncryptPinDialog isBiometricAvailable=$isBiometricAvailable") + logger.logDebug("showEncryptPinDialog isBiometricAvailable=$isBiometricAvailable") val cancelId = if (isBiometricAvailable) supporter.resProvider.provideAlterCancelStr() else -1 dialog.setCancelTitle(cancelId) dialog.setCancellable(false) @@ -561,19 +563,21 @@ open class Refresher, M : BaseErrorModel>( /*###################### Biometric ######################*/ protected open fun showDecryptBiometricDialog() { - logger.log("showDecryptBiometricDialog") + logger.logOrder("showDecryptBiometricDialog") dialogProvider.biom.let { dialog -> activity.get()?.let { context -> - logger.log("showDecryptBiometricDialog activity let") + logger.logDebug("showDecryptBiometricDialog activity let") dialog.onAuthenticationCallback({ isCancelled, errString -> + logger.logOrder("Decrypt biometric dialog onAuthenticationCallback onError") + logger.logDebug("onError isCancelled=$isCancelled errString=$errString") context.runOnUiThread { if (isCancelled) {//Операция с отпечатком отменена. on pause - logger.log("$errString isPaused = $stateIsPaused") + logger.logDebug("$errString isPaused = $stateIsPaused") if (!stateIsPaused) { // somehow dialog set error_canceled after resume if it was unblocked not with finger - logger.log("Show decrypt biometric dialog again") + logger.logDebug("Show decrypt biometric dialog again") showDecryptBiometricDialog() } else { - logger.log("Set last action to show decrypt biometric dialog") + logger.logDebug("Set last action to show decrypt biometric dialog") lastAction = { showDecryptBiometricDialog() } } } else { @@ -586,9 +590,9 @@ open class Refresher, M : BaseErrorModel>( } } }, { cipher -> - logger.log("DecryptBiometricDialog preferredType BIOMETRIC_AUTH") + logger.logOrder("DecryptBiometricDialog preferredType BIOMETRIC_AUTH") typeManager.putPreferredType(AuthType.BIOMETRIC_AUTH) - logger.log("DecryptBiometricDialog needCheck $needCheck") + logger.logDebug("DecryptBiometricDialog needCheck $needCheck") //if (needCheck) { checkSecret(cipher = cipher) /*} else { @@ -596,7 +600,7 @@ open class Refresher, M : BaseErrorModel>( }*/ }) - logger.log("showDecryptBiometricDialog needCheck=$needCheck") + logger.logDebug("showDecryptBiometricDialog needCheck=$needCheck") dialog.create(context, userInfo) val title: String = supporter.resProvider.getString( @@ -614,16 +618,16 @@ open class Refresher, M : BaseErrorModel>( dialog.setInfo(title = title, negativeText = negativeText) lastAction = { - logger.log("showDecryptBiometricDialog lastAction DECRYPT_MODE") + logger.logOrder("showDecryptBiometricDialog lastAction DECRYPT_MODE") try { currentBiometricDialog = dialog.authenticate(isDecrypt = true) } catch (e: Exception) { - logger.log("showDecryptBiometricDialog catch exception") + logger.logOrder("showDecryptBiometricDialog catch exception") logger.logError(e) deleteBiometricType() } } - logger.log("showDecryptBiometricDialog stateIsPaused=$stateIsPaused") + logger.logDebug("showDecryptBiometricDialog stateIsPaused=$stateIsPaused") if (!stateIsPaused) { lastAction.invoke() lastAction = {} @@ -633,23 +637,25 @@ open class Refresher, M : BaseErrorModel>( } protected open fun showEncryptBiometricDialog(token: Token) { - logger.log("showEncryptBiometricDialog") + logger.logOrder("showEncryptBiometricDialog") dialogProvider.biom.let { dialog -> activity.get()?.let { context -> - logger.log("showDecryptBiometricDialog activity let") + logger.logOrder("showDecryptBiometricDialog activity let") dialog.onAuthenticationCallback({ isCancelled, errString -> + logger.logOrder("Encrypt biometric dialog onAuthenticationCallback onError") + logger.logDebug("onError isCancelled=$isCancelled errString=$errString") context.runOnUiThread { if (isCancelled) {//Операция с отпечатком отменена. on pause - logger.log("showEncryptBiometricDialog ERROR_CANCELED") + logger.logDebug("showEncryptBiometricDialog ERROR_CANCELED") lastAction = { showEncryptBiometricDialog(token) } } else { - logger.log("showEncryptBiometricDialog showAuthDialog(isDecrypting = false,token)") + logger.logDebug("showEncryptBiometricDialog showAuthDialog(isDecrypting = false,token)") showAuthDialog(isDecrypting = false, token = token) } } }, { cipher -> - logger.log("EncryptBiometricDialog Success preferredType BIOMETRIC_AUTH") + logger.logOrder("EncryptBiometricDialog Success preferredType BIOMETRIC_AUTH") typeManager.putPreferredType(AuthType.BIOMETRIC_AUTH) putSecrets(token = token, cipher = cipher) }) @@ -659,13 +665,13 @@ open class Refresher, M : BaseErrorModel>( val title: String = supporter.resProvider.getString(supporter.resProvider.provideEncryptTitle()) val negativeText = supporter.resProvider.getString(supporter.resProvider.provideAlterCancelStr()) dialog.setInfo(title, negativeText) - logger.log("showEncryptBiometricDialog stateIsPaused=$stateIsPaused") + logger.logDebug("showEncryptBiometricDialog stateIsPaused=$stateIsPaused") lastAction = { - logger.log("showEncryptBiometricDialog lastAction ENCRYPT_MODE") + logger.logOrder("showEncryptBiometricDialog lastAction ENCRYPT_MODE") try { currentBiometricDialog = dialog.authenticate(isDecrypt = false) } catch (e: Exception) { - logger.log("showEncryptBiometricDialog catch exception") + logger.logOrder("showEncryptBiometricDialog catch exception") logger.logError(e) isBiometricAvailable = false showEncryptPinDialog(token) @@ -685,14 +691,14 @@ open class Refresher, M : BaseErrorModel>( /*##################### Password ########################*/ protected open fun showPasswordDialog(noSecrets: Boolean = false) { - logger.log("showPasswordDialog") + logger.logOrder("showPasswordDialog") dialogProvider.passwordDialog.let { dialog -> dialog.createDialog() dialog.setStateActions({ e: Exception -> - logger.log("showPasswordDialog onError ${e.message}") + logger.logOrder("showPasswordDialog onError ${e.message}") result(mapper(e)) }, { afterOk: Boolean -> - logger.log("showPasswordDialog onDismiss afterOk=$afterOk, types not empty=${types.isNotEmpty()}") + logger.logOrder("showPasswordDialog onDismiss afterOk=$afterOk, types not empty=${types.isNotEmpty()}") if (!afterOk) { if (!noSecrets && types.isNotEmpty()) { showAuthDialog(isDecrypting = true) @@ -706,23 +712,23 @@ open class Refresher, M : BaseErrorModel>( result() }*/ }) - logger.log("showPasswordDialog types not empty=${types.isNotEmpty()}") + logger.logDebug("showPasswordDialog types not empty=${types.isNotEmpty()}") if (!noSecrets && types.isNotEmpty()) { dialog.setCancelTitle(supporter.resProvider.provideAlterCancelStr()) dialog.setShowRecreateAuth(true) } - logger.log("showPasswordDialog forgotPasswordActionEnable=$forgotPasswordActionEnable") + logger.logDebug("showPasswordDialog forgotPasswordActionEnable=$forgotPasswordActionEnable") dialog.setForgotPassword(forgotPasswordActionEnable) - logger.log("showPasswordDialog needCheck=$needCheck") + logger.logDebug("showPasswordDialog needCheck=$needCheck") if (needCheck) dialog.setTitle(supporter.resProvider.provideAccessConfirmStr()) else dialog.setTitle(supporter.resProvider.provideAccessStr()) dialog.setCallbacks({ password: String, recreate: Boolean -> - logger.log("showPasswordDialog onPasswordComplete") + logger.logOrder("showPasswordDialog onPasswordComplete") authenticate(password, recreate, noSecrets) }, { - logger.log("showPasswordDialog onPasswordForgot forgotPasswordActionEnable=$forgotPasswordActionEnable") + logger.logOrder("showPasswordDialog onPasswordForgot forgotPasswordActionEnable=$forgotPasswordActionEnable") if (forgotPasswordActionEnable) forgotPasswordAction() }) @@ -739,12 +745,12 @@ open class Refresher, M : BaseErrorModel>( /*################################## Common #################################################*/ protected open fun showDialog(dialog: IBaseDialog, tag: String) { - logger.log("showDialog $tag stateIsPaused=$stateIsPaused") + logger.logOrder("showDialog $tag stateIsPaused=$stateIsPaused") lastAction = { - logger.log("showDialog lastAction invoke") + logger.logOrder("showDialog lastAction invoke") val manager = fragmentManager.get() val context = activity.get() - logger.log("showDialog lastAction fragmentManager != null ${manager != null} context != null ${context != null}") + logger.logDebug("showDialog lastAction fragmentManager != null ${manager != null} context != null ${context != null}") currentDialog = dialog dialog.show(manager, context, tag) } @@ -755,27 +761,27 @@ open class Refresher, M : BaseErrorModel>( } protected open fun deleteBiometricType() { - logger.log("deleteBiometricType BIOMETRIC_AUTH") + logger.logOrder("deleteBiometricType BIOMETRIC_AUTH") executor.deleteSecretTypeUC(AuthType.BIOMETRIC_AUTH) { onComplete { - logger.log("deleteBiometricType $it") + logger.logOrder("deleteBiometricType $it") if (it) { types.remove(AuthType.BIOMETRIC_AUTH) if (types.isEmpty()) { val presentType = AuthType.NONE - logger.log("deleteBiometricType containsSecrets = false presentType=NONE") + logger.logDebug("deleteBiometricType containsSecrets = false presentType=NONE") showPasswordDialog() } else { val presentType = types.firstOrNull() ?: AuthType.NONE typeManager.putPreferredType(presentType) - logger.log("deleteBiometricType presentType=$presentType") + logger.logDebug("deleteBiometricType presentType=$presentType") showAuthDialog(isDecrypting = true) } } } onError { logger.logError(it) - logger.log("deleteBiometricType showAuthDialog(isDecrypting = true)") + logger.logOrder("deleteBiometricType showAuthDialog(isDecrypting = true)") showAuthDialog(isDecrypting = true) } } @@ -789,12 +795,12 @@ open class Refresher, M : BaseErrorModel>( * @param activity FragmentActivity */ override fun setActivity(activity: FragmentActivity) { - logger.log("setActivity") + logger.logOrder("setActivity") this.activity = WeakReference(activity) activity.lifecycle.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun doAfterResume() { - logger.log("activity LifecycleObserver doAfterResume") + logger.logOrder("activity LifecycleObserver doAfterResume") stateIsPaused = false lastAction.invoke() lastAction = {} @@ -802,19 +808,19 @@ open class Refresher, M : BaseErrorModel>( @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun doAfterPause() { - logger.log("activity LifecycleObserver doAfterPause") + logger.logOrder("activity LifecycleObserver doAfterPause") stateIsPaused = true } }) isBiometricAvailable = supporter.util.checkBiometricSupport(activity.applicationContext) { tag: String, msg: String -> - logger.log(tag, msg) + logger.logCategory(CATEGORY_ERROR, tag, msg) } - logger.log("setActivity isBiometricAvailable=$isBiometricAvailable") + logger.logDebug("setActivity isBiometricAvailable=$isBiometricAvailable") } override fun setForgotPasswordAction(enable: Boolean, forgotPasswordAction: () -> Unit) { - logger.log("setForgotPasswordAction enable=$enable") + logger.logOrder("setForgotPasswordAction enable=$enable") this.forgotPasswordActionEnable = enable this.forgotPasswordAction = if (enable) forgotPasswordAction else ({}) } @@ -826,26 +832,26 @@ open class Refresher, M : BaseErrorModel>( * @param manager FragmentManager */ override fun setParams(manager: FragmentManager) { - logger.log("setParams") + logger.logOrder("setParams") fragmentManager = WeakReference(manager) } override fun setCancellationCallback(callback: IContainerCancellationCallback) { - logger.log("setCancellationCallback") + logger.logOrder("setCancellationCallback") containerCancellationCallback = callback } /*############################## Settings END ###############################################*/ override fun clearAccess() { - logger.log("clearAccess") + logger.logOrder("clearAccess") accessToken = "" refreshToken = "" executor.unsubscribe() } override fun clear() { - logger.log("clear") + logger.logOrder("clear") clearAccess() activity.clear() fragmentManager.clear() @@ -873,14 +879,13 @@ open class Refresher, M : BaseErrorModel>( open fun setCheckRefreshError( check: Boolean = false, - errorStatus: E? = null, + errorStatus: String? = null, useExtraCheck: Boolean = false, - extraCheck: (BaseErrorModel) -> Unit = {} + extraCheck: (BaseErrorModel) -> Unit = {} ) { checkRefreshError = check refresherErrorStatusToCheck = errorStatus this.useExtraCheck = useExtraCheck extraRefreshCheck = extraCheck } - } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/error/IErrorMapper.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/error/IErrorMapper.kt index 3128e333..ba75d154 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/error/IErrorMapper.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/error/IErrorMapper.kt @@ -7,7 +7,7 @@ package by.esas.tools.accesscontainer.error import by.esas.tools.logger.BaseErrorModel -interface IErrorMapper, M : BaseErrorModel> { +interface IErrorMapper { fun mapError(e: Exception): M operator fun invoke(e: Exception): M { diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerExecutor.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerExecutor.kt index e3c4f558..afb13312 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerExecutor.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerExecutor.kt @@ -12,24 +12,37 @@ import by.esas.tools.logger.BaseErrorModel import javax.crypto.Cipher import javax.crypto.SecretKey -interface IContainerExecutor, M : BaseErrorModel> { - fun refreshAccessUC(refreshToken: String, block: ContainerRequest.() -> Unit) +interface IContainerExecutor { + fun refreshAccessUC(refreshToken: String, block: ContainerRequest.() -> Unit) - fun getSecretsUC(block: ContainerRequest, E, M>.() -> Unit) + fun getSecretsUC(block: ContainerRequest, M>.() -> Unit) - fun refreshWithSecretUC(pinKey: SecretKey? = null, cipher: Cipher? = null, block: ContainerRequest.() -> Unit) + fun refreshWithSecretUC( + pinKey: SecretKey? = null, + cipher: Cipher? = null, + block: ContainerRequest.() -> Unit + ) - fun checkSecretUC(pinKey: SecretKey? = null, cipher: Cipher? = null, block: ContainerRequest, E, M>.() -> Unit) + fun checkSecretUC( + pinKey: SecretKey? = null, + cipher: Cipher? = null, + block: ContainerRequest, M>.() -> Unit + ) - fun refreshWithSecretUC(secret: String, block: ContainerRequest.() -> Unit) + fun refreshWithSecretUC(secret: String, block: ContainerRequest.() -> Unit) - fun authorizationInSystemUC(password: String, block: ContainerRequest.() -> Unit) + fun authorizationInSystemUC(password: String, block: ContainerRequest.() -> Unit) - fun createSecretUC(token: String, cipher: Cipher? = null, pinKey: SecretKey? = null, block: ContainerRequest.() -> Unit) + fun createSecretUC( + token: String, + cipher: Cipher? = null, + pinKey: SecretKey? = null, + block: ContainerRequest.() -> Unit + ) - fun deleteSecretTypeUC(type: AuthType, block: ContainerRequest.() -> Unit) + fun deleteSecretTypeUC(type: AuthType, block: ContainerRequest.() -> Unit) - fun deleteSecretsUC(block: ContainerRequest.() -> Unit) + fun deleteSecretsUC(block: ContainerRequest.() -> Unit) fun unsubscribe() } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/IUtil.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/IUtil.kt index 2b44a013..0fb5ca44 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/IUtil.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/IUtil.kt @@ -9,7 +9,7 @@ import android.content.Context import by.esas.tools.logger.BaseErrorModel import javax.crypto.SecretKey -interface IUtil, M : BaseErrorModel> { +interface IUtil { fun createErrorModel(code: Int, enumName: String): M fun generatePin(pin: String, userId: String, androidId: String): SecretKey? fun checkBiometricSupport(context: Context?, logging: (String, String) -> Unit): Boolean diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/Supporter.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/Supporter.kt index 11b61b32..79f0718d 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/Supporter.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/Supporter.kt @@ -7,7 +7,7 @@ package by.esas.tools.accesscontainer.support.supporter import by.esas.tools.logger.BaseErrorModel -class Supporter, M : BaseErrorModel>( +class Supporter( val resProvider: ResourceStrProvider, - val util: IUtil + val util: IUtil ) \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index b5676761..03d93f5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' +apply plugin: 'androidx.navigation.safeargs.kotlin' +apply from: '../properties.gradle' android { - compileSdkVersion 30 - buildToolsVersion "30.0.1" + compileSdkVersion compile_sdk_version + buildToolsVersion "30.0.3" buildFeatures { dataBinding = true @@ -19,8 +20,9 @@ android { } defaultConfig { applicationId "by.esas.tools" - minSdkVersion 16 - targetSdkVersion 30 + minSdkVersion min_sdk_version + targetSdkVersion compile_sdk_version + multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -31,25 +33,48 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } - } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "androidx.multidex:multidex:$multidex_version" + + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" + //App implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.core:core-ktx:$corektx_version" + implementation "androidx.biometric:biometric:$biometric_version" // Data binding kapt "com.android.databinding:compiler:$databinding_version" + // Moshi + implementation "com.squareup.moshi:moshi:$moshi_version" + //implementation "com.squareup.moshi:moshi-adapters:$moshi_version" + implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" + //kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version" + + // Network + implementation "com.squareup.retrofit2:retrofit:$retrofit_version" + implementation "com.squareup.okhttp3:okhttp:$okhttp_version" + + // Room + api "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + implementation "androidx.room:room-ktx:$room_version" + // Dagger 2 - implementation "com.google.dagger:dagger:$dagger2_version" - implementation "com.google.dagger:dagger-android:$dagger2_version" - implementation "com.google.dagger:dagger-android-support:$dagger2_version" + //REMEMBER dagger is applied with api so it will be available in app module + //implementation "com.google.dagger:dagger:$dagger2_version" + //implementation "com.google.dagger:dagger-android:$dagger2_version" + //implementation "com.google.dagger:dagger-android-support:$dagger2_version" kapt "com.google.dagger:dagger-compiler:$dagger2_version" kapt "com.google.dagger:dagger-android-processor:$dagger2_version" + implementation project(path: ':baseui') implementation project(path: ':basedaggerui') implementation project(path: ':domain') implementation project(path: ':dialog') @@ -57,4 +82,11 @@ dependencies { implementation project(path: ':inputfieldview') implementation project(path: ':pinview') implementation project(path: ':timeparser') + implementation project(path: ':numpad') + implementation project(path: ':util') + implementation project(path: ':listheader') + implementation project(path: ':customswitch') + implementation project(path: ':topbarview') + implementation project(path: ':cardline') + implementation project(path: ':biometric_decryption') } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cdd7e882..512ffb2f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,13 @@ + + + + + - + diff --git a/app/src/main/java/by/esas/tools/AddInvoiceViewModel.kt b/app/src/main/java/by/esas/tools/AddInvoiceViewModel.kt deleted file mode 100644 index 219e0f21..00000000 --- a/app/src/main/java/by/esas/tools/AddInvoiceViewModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -package by.esas.tools - -import androidx.lifecycle.SavedStateHandle -import by.esas.tools.basedaggerui.inject.factory.AssistedSavedStateViewModelFactory -import by.esas.tools.error_mapper.AppErrorMapper -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject - -class AddInvoiceViewModel @AssistedInject constructor( - @Assisted private val savedStateHandle: SavedStateHandle, - private val mapper: AppErrorMapper -) : AppVM() { - override val TAG: String = AddInvoiceViewModel::class.java.simpleName - - @AssistedFactory - interface Factory : AssistedSavedStateViewModelFactory { - override fun create(savedStateHandle: SavedStateHandle): AddInvoiceViewModel // may be ommited prior kotlin 1.3.60 or after PR #121 in AssistedInject lib - } - - override fun onChangeLanguage() { - - } - -} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/App.kt b/app/src/main/java/by/esas/tools/App.kt index 473debb1..74e60f52 100644 --- a/app/src/main/java/by/esas/tools/App.kt +++ b/app/src/main/java/by/esas/tools/App.kt @@ -2,8 +2,11 @@ package by.esas.tools import android.content.Context import androidx.appcompat.app.AppCompatDelegate +import androidx.multidex.MultiDex +import by.esas.tools.app_data.AppSharedPrefs import by.esas.tools.inject.component.DaggerAppComponent -import by.esas.tools.util.LocaleManager +import by.esas.tools.util.configs.SettingsManager +import by.esas.tools.util.configs.UiModeType import dagger.android.AndroidInjector import dagger.android.DaggerApplication @@ -18,6 +21,8 @@ class App : DaggerApplication() { companion object { lateinit var instance: App lateinit var appContext: Context + var uiMode:UiModeType = UiModeType.SYSTEM + var language:String = "en" } init { @@ -42,8 +47,12 @@ class App : DaggerApplication() { } override fun attachBaseContext(base: Context?) { - super.attachBaseContext(base?.let { return@let LocaleManager.setLocale(base, getLocale(base).language) }) - //MultiDex.install(this) + super.attachBaseContext(base?.let { + uiMode = AppSharedPrefs(base).getTheme() + language = AppSharedPrefs(base).getLanguage() + return@let SettingsManager.updateSettings(base, language , uiMode) + }) + MultiDex.install(this) } } \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/AppActivity.kt b/app/src/main/java/by/esas/tools/AppActivity.kt deleted file mode 100644 index 048e109e..00000000 --- a/app/src/main/java/by/esas/tools/AppActivity.kt +++ /dev/null @@ -1,27 +0,0 @@ -package by.esas.tools - -import androidx.databinding.ViewDataBinding -import by.esas.tools.basedaggerui.simple.SimpleActivity -import by.esas.tools.error_mapper.AppErrorStatusEnum -import by.esas.tools.logger.ErrorModel -import by.esas.tools.logger.handler.ErrorHandler - -abstract class AppActivity : SimpleActivity() { - - override fun provideErrorHandler(): ErrorHandler { - return object : ErrorHandler() { - override fun getErrorMessage(error: ErrorModel): String { - TODO("Not yet implemented") - } - - override fun getErrorMessage(e: Throwable): String { - TODO("Not yet implemented") - } - - override fun mapError(e: Throwable): ErrorModel { - TODO("Not yet implemented") - } - - } - } -} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/AppVM.kt b/app/src/main/java/by/esas/tools/AppVM.kt deleted file mode 100644 index 8603753b..00000000 --- a/app/src/main/java/by/esas/tools/AppVM.kt +++ /dev/null @@ -1,9 +0,0 @@ -package by.esas.tools - -import by.esas.tools.basedaggerui.simple.SimpleViewModel -import by.esas.tools.error_mapper.AppErrorStatusEnum -import by.esas.tools.logger.ErrorModel - -abstract class AppVM : SimpleViewModel() { - -} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/DataBinding.kt b/app/src/main/java/by/esas/tools/DataBinding.kt index fcf6ed19..e45d6b36 100644 --- a/app/src/main/java/by/esas/tools/DataBinding.kt +++ b/app/src/main/java/by/esas/tools/DataBinding.kt @@ -1,34 +1,83 @@ package by.esas.tools -import android.text.Editable -import android.text.TextWatcher import android.view.View +import android.widget.CheckBox +import androidx.core.content.ContextCompat import androidx.databinding.BindingAdapter -import androidx.databinding.InverseBindingAdapter -import androidx.databinding.InverseBindingListener -import by.esas.tools.fields.RohabInputField +import by.esas.tools.entity.TestStatusEnum +import by.esas.tools.topbarview.TopbarView +import com.google.android.material.textview.MaterialTextView +import dagger.Module @BindingAdapter("visibilityGone") fun setVisibilityOrGone(view: View, isVisible: Boolean) { view.visibility = if (isVisible) View.VISIBLE else View.GONE } -@BindingAdapter("android:textAttrChanged") -fun setListeners(view: RohabInputField, attrChange: InverseBindingListener) { - view.inputText?.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - attrChange.onChange() - } - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - } +@BindingAdapter("barTitleText") +fun setBarTitle(view: TopbarView, text: String?) { + view.setTitle(text) +} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - } - }) +@BindingAdapter("buttonsClickable") +fun setButtonsEnabled(view: TopbarView, value: Boolean) { + view.setButtonsEnabled(value) +} + +@BindingAdapter("onEndActionClick") +fun setBarEndActionClick(view: TopbarView, listener: View.OnClickListener) { + view.setEndActionListener(listener) +} + +@BindingAdapter("onStartActionClick") +fun setBarStartActionClick(view: TopbarView, listener: View.OnClickListener) { + view.setStartActionListener(listener) +} + +@BindingAdapter("barShowNavIcon") +fun setBarNavIconVisibility(view: TopbarView, value: Boolean) { + view.setNavIconVisibility(value) +} + +@BindingAdapter("barShowActionIcon") +fun setBarActionIconVisibility(view: TopbarView, value: Boolean) { + view.setActionIconVisibility(value) +} + +@BindingAdapter("barShowDivider") +fun setBarDividerVisibility(view: TopbarView, value: Boolean) { + view.setDividerVisibility(value) } -@InverseBindingAdapter(attribute = "android:text") -fun getText(view: RohabInputField): String { - return view.getText() +@BindingAdapter("caseTestStatus") +fun setInvoiceStatus(view: MaterialTextView, state: TestStatusEnum) { + val bgRes: Int + val textColorRes: Int + val textRes: Int + when (state) { + TestStatusEnum.CHECKED -> { + bgRes = R.drawable.bg_case_status_checked + textColorRes = R.color.green_dark + textRes = R.string.case_status_checked + } + TestStatusEnum.IN_PROCESS -> { + bgRes = R.drawable.bg_case_status_in_process + textColorRes = R.color.blue + textRes = R.string.case_status_in_process + } + TestStatusEnum.FAILED -> { + bgRes = R.drawable.bg_case_status_failed + textColorRes = R.color.red + textRes = R.string.case_status_failed + } + else -> { + bgRes = R.drawable.bg_case_status_unchecked + textColorRes = R.color.gray_dark + textRes = R.string.case_status_unchecked + } + } + view.setBackgroundResource(bgRes) + view.setText(textRes) + view.setTextColor(ContextCompat.getColor(view.context, textColorRes)) } \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/MainActivity.kt b/app/src/main/java/by/esas/tools/MainActivity.kt deleted file mode 100644 index 4de496b0..00000000 --- a/app/src/main/java/by/esas/tools/MainActivity.kt +++ /dev/null @@ -1,179 +0,0 @@ -package by.esas.tools - -import android.content.Context -import android.graphics.Color -import android.os.Build -import android.os.Bundle -import android.view.View -import android.view.WindowInsetsController -import androidx.core.view.ViewCompat -import androidx.core.view.updatePadding -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.ViewModelProvider -import by.esas.tools.checker.Checker -import by.esas.tools.checker.IRequestFocusHandler -import by.esas.tools.checker.checks.LengthCheck -import by.esas.tools.checking.AppChecker -import by.esas.tools.checking.FieldChecking -import by.esas.tools.databinding.ActivityMainBinding -import by.esas.tools.error_mapper.AppErrorMapper -import by.esas.tools.error_mapper.AppErrorStatusEnum -import by.esas.tools.logger.BaseErrorModel -import by.esas.tools.logger.ILogger -import by.esas.tools.logger.LoggerImpl -import by.esas.tools.usecase.GetDefaultCardUseCase -import by.esas.tools.util.LanguageSetter -import by.esas.tools.util.hideSystemUIR -import com.squareup.moshi.Moshi -import kotlinx.coroutines.Dispatchers - -open class MainActivity : AppActivity() { - override val TAG: String = "MainActivity" - override var logger: ILogger = LoggerImpl() - override fun provideViewModel(): MainVM { - return ViewModelProvider(this, viewModelFactory.provideFactory()).get(MainVM::class.java) - } - - override fun provideLayoutId(): Int { - return R.layout.activity_main - } - - override fun provideVariableInd(): Int { - return BR.viewModel - } - - override fun provideLifecycleOwner(): LifecycleOwner { - return this - } - - override fun provideSetter(): LanguageSetter { - return object : LanguageSetter { - override fun getDefaultLanguage(): String { - return "en" - } - - override fun getLanguage(): String { - return "en" - } - - override fun setLanguage(lang: String) { - - } - } - } - - override fun getAppContext(): Context { - return App.appContext - } - - override fun setAppContext(context: Context) { - App.appContext = context - } - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets -> - val statusBar = insets.getSystemWindowInsetTop() - val navigationBar = insets.getSystemWindowInsetBottom() - binding.root.updatePadding(top = statusBar) - return@setOnApplyWindowInsetsListener insets - } - /*viewModel.update = { - binding.fMainAddInvoiceAdditionalContainer.invalidate() - }*/ - - binding.fMainSetErrorPin.setOnClickListener { - doCheck() - if (!binding.aMainText3.getText().equals("12345678")) { - binding.aMainText3.setError("Some error") - } else { - binding.aMainText3.setError("") - } - } - } - - fun doCheck() { - AppChecker() - .setShowError(true) - .setListener(object : Checker.CheckListener {}) - .validate( - listOf( - FieldChecking(binding.aMainText4, true) - .setRequestFocusHandler(object : IRequestFocusHandler { - override fun handleRequestFocus() { - binding.aMainText4.visibility = View.VISIBLE - binding.aMainText4.requestFocus() - //scroll to position - } - }) - .addCheck(LengthCheck(0, 4)) - ) - ) - } - /*val field = findViewById(R.id.a_main_text2) - field.setInputPrefix("1")*/ - /*val spinner = findViewById(R.id.a_main_service_spinner) - spinner.adapter.clear() - (spinner.adapter as ArrayAdapter).addAll(listOf("1", "2", "3")) - spinner.adapter.notifyDataSetChanged() - val currentText = "1" - spinner.setText(currentText)*/ - /*viewModel.addToObservable(viewModel.mainText, Observer { - AppChecker() - .setShowError(true) - .setListener(object : Checker.CheckListener {}) - .validate(listOf(FieldChecking(field, true).addCheck(LengthCheck(0, 4)))) - })*/ - - - override fun setFullScreen() { - //WindowCompat.setDecorFitsSystemWindows(window, false) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - window.setDecorFitsSystemWindows(false) - window.insetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - window.apply { - //clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) - //addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - decorView.systemUiVisibility = - (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) - } else { - decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) - } - statusBarColor = Color.TRANSPARENT - } - - } - } - - fun testError() { - val uc = GetDefaultCardUseCase( - AppErrorMapper(Moshi.Builder().build(), LoggerImpl()), - Dispatchers.Main - ) - uc.execute { - onComplete { - - } - onError { - it.statusEnum - } - onCancel { - - } - } - val model = BaseErrorModel(0, AppErrorStatusEnum.APP_UNPREDICTED_ERROR) - model.statusEnum - } - - override fun hideSystemUI() { - logger.logInfo("hideSystemUI") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - hideSystemUIR(this) - } else hideSystemUIApp(this) - } - - -} diff --git a/app/src/main/java/by/esas/tools/MainVM.kt b/app/src/main/java/by/esas/tools/MainVM.kt deleted file mode 100644 index 977fde95..00000000 --- a/app/src/main/java/by/esas/tools/MainVM.kt +++ /dev/null @@ -1,38 +0,0 @@ -package by.esas.tools - -import androidx.databinding.Observable -import androidx.databinding.ObservableBoolean -import androidx.databinding.ObservableField -import androidx.lifecycle.MutableLiveData -import javax.inject.Inject - -class MainVM @Inject constructor() : AppVM() { - override val TAG: String = "MainVM" - var update: () -> Unit = {} - - override fun onChangeLanguage() { - - } - - fun setUp() { - serviceName.addOnPropertyChangedCallback( - object : Observable.OnPropertyChangedCallback() { - override fun onPropertyChanged(sender: Observable?, propertyId: Int) { - mainText.postValue(serviceName.get() ?: "") - } - } - ) - } - - val serviceName = ObservableField("") - val showAdditional = ObservableBoolean(false) - val mainText = MutableLiveData().apply { postValue("") } - - val password = MutableLiveData("") - - fun onAdditionalClick() { - showAdditional.set(!showAdditional.get()) - update() - } -} - diff --git a/app/src/main/java/by/esas/tools/app_data/AppSharedPrefs.kt b/app/src/main/java/by/esas/tools/app_data/AppSharedPrefs.kt new file mode 100644 index 00000000..79935bcf --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/AppSharedPrefs.kt @@ -0,0 +1,38 @@ +package by.esas.tools.app_data + +import android.content.Context +import android.content.SharedPreferences +import by.esas.tools.util.configs.UiModeType + +class AppSharedPrefs(val context: Context) { + private val sharedPrefs: SharedPreferences + + companion object { + private const val CURRENT_LANGUAGE = "klangkopl" + private const val CURRENT_THEME = "pjuthemred" + private const val SHARED_PREFS_NAME = "basic_tools_pref" + } + + init { + sharedPrefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) + } + + fun setLanguage(lang: String) { + sharedPrefs.edit().putString(CURRENT_LANGUAGE, lang).apply() + } + + fun getLanguage(): String { + return sharedPrefs.getString(CURRENT_LANGUAGE, "en") ?: "en" + } + + fun setTheme(theme: UiModeType) { + sharedPrefs.edit().putString(CURRENT_THEME, theme.name).apply() + } + + fun getTheme(): UiModeType { + val currentTheme = sharedPrefs.getString(CURRENT_THEME, "") ?: "" + return if (currentTheme.isNotBlank()){ + UiModeType.valueOf(currentTheme) + } else UiModeType.SYSTEM + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_data/Cases.kt b/app/src/main/java/by/esas/tools/app_data/Cases.kt new file mode 100644 index 00000000..e75e7249 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/Cases.kt @@ -0,0 +1,185 @@ +package by.esas.tools.app_data + +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.entity.CaseInfo +import by.esas.tools.entity.Modules + +object Cases { + private val cases = arrayListOf() + + fun getAll(): List { + cases.clear() + addCaseItem( + R.id.loggerFragment, + R.string.case_label_logger, + listOf(Modules.LOGGER) + ) + addCaseItem( + R.id.timeparserFragment, + R.string.case_label_timeparser, + listOf(Modules.TIMEPARSER) + ) + addCaseItem( + R.id.biometricDecryptionFragment, + R.string.case_label_biometric_decryption, + listOf(Modules.BIOMETRIC_DECRYPTION) + ) + addCaseItem( + R.id.domainCaseFragment, + R.string.case_label_domain, + listOf(Modules.DOMAIN) + ) + addCaseItem( + R.id.baseuiThemeFragment, + R.string.case_label_baseui_theme, + listOf(Modules.BASE_UI) + ) + addCaseItem( + R.id.baseuiFunctionalityFragment, + R.string.case_label_baseui, + listOf(Modules.BASE_UI) + ) + addCaseItem( + R.id.baseuiNavigationFragment, + R.string.case_label_baseui_navigation, + listOf(Modules.BASE_UI) + ) + addCaseItem( + R.id.bottomDialogFragment, + R.string.case_label_bottom_dialog, + listOf(Modules.DIALOG, Modules.INPUTFIELD_VIEW) + ) + addCaseItem( + R.id.dynamicMessageDialogFragment, + R.string.case_label_dynamic_message_dialog, + listOf(Modules.DIALOG) + ) + addCaseItem( + R.id.inputfieldViewFragment, + R.string.case_label_inputfield_view, + listOf(Modules.INPUTFIELD_VIEW) + ) + addCaseItem( + R.id.inputfieldViewStartIconFragment, + R.string.case_label_inputfield_view_start_icon, + listOf(Modules.INPUTFIELD_VIEW) + ) + addCaseItem( + R.id.inputfieldViewEndIconFragment, + R.string.case_label_inputfield_view_end_icon, + listOf(Modules.INPUTFIELD_VIEW) + ) + addCaseItem( + R.id.checkerFragment, + R.string.case_label_checker, + listOf(Modules.CHECKER, Modules.INPUTFIELD_VIEW) + ) + addCaseItem( + R.id.pinViewFragment, + R.string.case_label_pin_view, + listOf(Modules.PIN_VIEW) + ) + addCaseItem( + R.id.dynamicPinViewFragment, + R.string.case_label_dynamic_pin_view, + listOf(Modules.PIN_VIEW) + ) + addCaseItem( + R.id.savedStateFragment, + R.string.case_label_saved_state, + listOf(Modules.BASE_DAGGER_UI, Modules.BASE_UI) + ) + addCaseItem( + R.id.numpadImageFragment, + R.string.case_label_numpad_image, + listOf(Modules.NUMPAD) + ) + addCaseItem( + R.id.numpadTextFragment, + R.string.case_label_numpad_text, + listOf(Modules.NUMPAD) + ) + addCaseItem( + R.id.utilKeyboardFragment, + R.string.case_label_util_keyboard, + listOf(Modules.UTIL) + ) + addCaseItem( + R.id.utilUtilsFragment, + R.string.case_label_utils, + listOf(Modules.UTIL) + ) + addCaseItem( + R.id.utilSwitchManagerFragment, + R.string.case_label_switch_manager, + listOf(Modules.UTIL, Modules.CUSTOMSWITCH) + ) + addCaseItem( + R.id.customSwitchFragment, + R.string.case_label_custom_switch, + listOf(Modules.CUSTOMSWITCH) + ) + addCaseItem( + R.id.customSwitchProgramFragment, + R.string.case_label_dynamic_custom_switch, + listOf(Modules.CUSTOMSWITCH), + ) + addCaseItem( + R.id.listheaderFragment, + R.string.case_label_listheader, + listOf(Modules.LISTHEADER, Modules.CUSTOMSWITCH) + ) + addCaseItem( + R.id.dynamicListheaderFragment, + R.string.case_label_dynamic_listheader, + listOf(Modules.LISTHEADER) + ) + addCaseItem( + R.id.topbarFragment, + R.string.case_label_topbar_view, + listOf(Modules.TOPBAR_VIEW) + ) + addCaseItem( + R.id.cardlineFragment, + R.string.case_label_cardline, + listOf(Modules.CARDLINE) + ) + addCaseItem( + R.id.dynamicCardlineFragment, + R.string.case_label_dynamic_cardline, + listOf(Modules.CARDLINE) + ) + addCaseItem( + R.id.recyclerFragment, + R.string.case_label_recycler_base, + listOf(Modules.RECYCLER) + ) + addCaseItem( + R.id.stickyCaseFragment, + R.string.case_label_recycler_sticky, + listOf(Modules.RECYCLER) + ) + addCaseItem( + R.id.simpleRecyclerFragment, + R.string.case_label_recycler_simple, + listOf(Modules.RECYCLER) + ) + addCaseItem( + R.id.customRecyclerFragment, + R.string.case_label_recycler_custom, + listOf(Modules.RECYCLER) + ) + + return cases + } + + private fun addCaseItem( + id: Int, + name: Int, + modulesList: List + ) { + val caseName = App.appContext.getString(name) + cases.add(CaseInfo(id, caseName, modulesList)) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_data/db/AppDataBase.kt b/app/src/main/java/by/esas/tools/app_data/db/AppDataBase.kt new file mode 100644 index 00000000..6b76dcd1 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/db/AppDataBase.kt @@ -0,0 +1,29 @@ +package by.esas.tools.app_data.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import by.esas.tools.app_data.db.converter.CaseStatusConverter +import by.esas.tools.app_data.db.dao.CaseStatusDao +import by.esas.tools.entity.CaseStatus + +@Database( entities = [CaseStatus::class], version = 1) + +@TypeConverters(CaseStatusConverter::class) +abstract class AppDataBase: RoomDatabase() { + companion object { + private const val DATABASE_NAME = "ABL_DATABASE" + + fun getInstance(context: Context): AppDataBase { + return Room.databaseBuilder( + context, + AppDataBase::class.java, + DATABASE_NAME + ).build() + } + } + + abstract fun getCaseStatusDao(): CaseStatusDao +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_data/db/converter/CaseStatusConverter.kt b/app/src/main/java/by/esas/tools/app_data/db/converter/CaseStatusConverter.kt new file mode 100644 index 00000000..e712bd67 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/db/converter/CaseStatusConverter.kt @@ -0,0 +1,12 @@ +package by.esas.tools.app_data.db.converter + +import androidx.room.TypeConverter +import by.esas.tools.entity.TestStatusEnum + +class CaseStatusConverter { + @TypeConverter + fun toTestStatusEnum(value: String) = enumValueOf(value) + + @TypeConverter + fun fromTestStatusEnum(value: TestStatusEnum) = value.name +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_data/db/dao/CaseStatusDao.kt b/app/src/main/java/by/esas/tools/app_data/db/dao/CaseStatusDao.kt new file mode 100644 index 00000000..9d329f82 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/db/dao/CaseStatusDao.kt @@ -0,0 +1,26 @@ +package by.esas.tools.app_data.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import by.esas.tools.entity.CaseStatus +import by.esas.tools.entity.TestStatusEnum + +@Dao +interface CaseStatusDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(caseStatus: CaseStatus) + + @Query("UPDATE caseStatusTable SET case_status=:status WHERE case_id = :id") + fun update(id: Int, status: TestStatusEnum) + + @Query("SELECT * FROM caseStatusTable") + fun getAll(): List + + @Query("SELECT * FROM caseStatusTable WHERE case_id = :id") + fun getById(id: Int): CaseStatus + + @Query("DELETE FROM caseStatusTable") + fun deleteAll() +} diff --git a/app/src/main/java/by/esas/tools/app_domain/error_mapper/AppErrorMapper.kt b/app/src/main/java/by/esas/tools/app_domain/error_mapper/AppErrorMapper.kt new file mode 100644 index 00000000..1f0c410f --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/error_mapper/AppErrorMapper.kt @@ -0,0 +1,21 @@ +package by.esas.tools.app_domain.error_mapper + +import by.esas.tools.domain.mapper.BaseErrorMapper +import by.esas.tools.logger.ILogger +import by.esas.tools.utils.logger.ErrorModel +import com.squareup.moshi.Moshi + +class AppErrorMapper(moshi: Moshi, logger: ILogger) : + BaseErrorMapper(moshi, logger) { + override fun mapBaseException(errorText: String?): String { + return AppErrorStatusEnum.APP_UNPREDICTED_ERROR.name + } + + override fun createModel(code: Int, status: String): ErrorModel { + return ErrorModel(code, AppErrorStatusEnum.getAppErrorStatusEnum(status)) + } + + override fun mapHttpStatus(httpEnumString: String): String { + return AppErrorStatusEnum.APP_UNPREDICTED_ERROR.name + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/error_mapper/AppErrorStatusEnum.kt b/app/src/main/java/by/esas/tools/app_domain/error_mapper/AppErrorStatusEnum.kt similarity index 83% rename from app/src/main/java/by/esas/tools/error_mapper/AppErrorStatusEnum.kt rename to app/src/main/java/by/esas/tools/app_domain/error_mapper/AppErrorStatusEnum.kt index e4992595..e9f40b70 100644 --- a/app/src/main/java/by/esas/tools/error_mapper/AppErrorStatusEnum.kt +++ b/app/src/main/java/by/esas/tools/app_domain/error_mapper/AppErrorStatusEnum.kt @@ -1,4 +1,4 @@ -package by.esas.tools.error_mapper +package by.esas.tools.app_domain.error_mapper enum class AppErrorStatusEnum { OK, @@ -26,6 +26,7 @@ enum class AppErrorStatusEnum { APP_INVOICE_NOT_FOUND, APP_INVOICE_QR_NOT_FOUND, APP_DATE_IN_UNEXPECTED_FORMAT, + APP_ILLEGAL_PATTERN_CHARACTER, APP_NO_ACCESS_TOKEN, APP_INVOICE_ID_NOT_SET, @@ -79,5 +80,15 @@ enum class AppErrorStatusEnum { EPOS_INVOICE_NUMBER_TAKEN, SERVICE_INFO_NOT_FOUND, CURRENCY_INFO_NOT_FOUND, - INVOICES_GET_NOT_FOUND + INVOICES_GET_NOT_FOUND; + + companion object { + fun getAppErrorStatusEnum(value: String): AppErrorStatusEnum { + return try { + valueOf(value) + } catch (e: IllegalStateException) { + NOT_SET + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/AddCaseStatusUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/AddCaseStatusUseCase.kt new file mode 100644 index 00000000..456d8d11 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/AddCaseStatusUseCase.kt @@ -0,0 +1,23 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_data.db.dao.CaseStatusDao +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.entity.CaseStatus +import by.esas.tools.entity.TestStatusEnum +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class AddCaseStatusUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext, + private val dao: CaseStatusDao +) : BaseUseCase(errorUtil, foregroundContext) { + override val TAG: String = GetCaseItemsUseCase::class.java.simpleName + + var caseId = -1 + var caseStatus = TestStatusEnum.UNCHECKED + + override suspend fun executeOnBackground() { + dao.insert(CaseStatus(caseId, caseStatus)) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/BaseUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/BaseUseCase.kt new file mode 100644 index 00000000..5c06378c --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/BaseUseCase.kt @@ -0,0 +1,31 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.domain.usecase.IRefresh +import by.esas.tools.domain.usecase.UseCase +import by.esas.tools.utils.logger.ErrorModel +import kotlinx.coroutines.Dispatchers +import kotlin.coroutines.CoroutineContext + +abstract class BaseUseCase(errorUtil: AppErrorMapper, refresher: IRefresh?) : + UseCase(errorUtil, refresher) { + + constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext, + backgroundContext: CoroutineContext = Dispatchers.IO + ) : this(errorUtil, null) { + this.foregroundContext = foregroundContext + this.backgroundContext = backgroundContext + } + + constructor( + errorUtil: AppErrorMapper, + refresher: IRefresh, + foregroundContext: CoroutineContext, + backgroundContext: CoroutineContext = Dispatchers.IO + ) : this(errorUtil, refresher) { + this.foregroundContext = foregroundContext + this.backgroundContext = backgroundContext + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/ClearAllSavedTestStatusesUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/ClearAllSavedTestStatusesUseCase.kt new file mode 100644 index 00000000..8d7c3377 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/ClearAllSavedTestStatusesUseCase.kt @@ -0,0 +1,18 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_data.db.dao.CaseStatusDao +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class ClearAllSavedTestStatusesUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext, + private val dao: CaseStatusDao +) : BaseUseCase(errorUtil, foregroundContext) { + override val TAG: String = GetCaseItemsUseCase::class.java.simpleName + + override suspend fun executeOnBackground() { + dao.deleteAll() + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/FilterCaseUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/FilterCaseUseCase.kt new file mode 100644 index 00000000..8f4b7838 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/FilterCaseUseCase.kt @@ -0,0 +1,28 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.entity.CaseItemInfo +import by.esas.tools.entity.TestStatusEnum +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class FilterCaseUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext +) : BaseUseCase>(errorUtil, foregroundContext) { + override val TAG = FilterCaseUseCase::class.java.simpleName + + var modules = listOf() + var statuses = listOf() + var caseItems = listOf() + + override suspend fun executeOnBackground(): List { + var filteredList = caseItems + if (statuses.isNotEmpty()) + filteredList = filteredList.filter { it.status in statuses } + if (modules.isNotEmpty()) + filteredList = filteredList.filter { it.modules.intersect(modules.toSet()).isNotEmpty() } + + return filteredList + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/GetCaseItemsUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/GetCaseItemsUseCase.kt new file mode 100644 index 00000000..7f99d144 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/GetCaseItemsUseCase.kt @@ -0,0 +1,32 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_data.Cases +import by.esas.tools.app_data.db.dao.CaseStatusDao +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.entity.CaseItemInfo +import by.esas.tools.entity.TestStatusEnum +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class GetCaseItemsUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext, + private val dao: CaseStatusDao +) : BaseUseCase>(errorUtil, foregroundContext) { + override val TAG: String = GetCaseItemsUseCase::class.java.simpleName + + override suspend fun executeOnBackground(): List { + val cases = Cases.getAll() + val savedCaseStatuses = dao.getAll() + val caseItemsList = cases.map { caseInfo -> + CaseItemInfo( + caseInfo.id, + caseInfo.name, + caseInfo.modules, + savedCaseStatuses.find { it.caseId == caseInfo.id }?.status ?: TestStatusEnum.UNCHECKED + ) + } + + return caseItemsList + } +} diff --git a/app/src/main/java/by/esas/tools/usecase/GetDefaultCardUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/GetDefaultCardUseCase.kt similarity index 59% rename from app/src/main/java/by/esas/tools/usecase/GetDefaultCardUseCase.kt rename to app/src/main/java/by/esas/tools/app_domain/usecase/GetDefaultCardUseCase.kt index 48c42283..c1e5688c 100644 --- a/app/src/main/java/by/esas/tools/usecase/GetDefaultCardUseCase.kt +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/GetDefaultCardUseCase.kt @@ -1,17 +1,20 @@ -package by.esas.tools.usecase +package by.esas.tools.app_domain.usecase -import by.esas.tools.error_mapper.AppErrorMapper import by.esas.tools.domain.exception.BaseException import by.esas.tools.domain.exception.BaseStatusEnum +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import kotlinx.coroutines.delay +import javax.inject.Inject import kotlin.coroutines.CoroutineContext -class GetDefaultCardUseCase constructor( +class GetDefaultCardUseCase @Inject constructor( errorUtil: AppErrorMapper, foregroundContext: CoroutineContext -) : BaseUseCase(errorUtil, null) { +) : BaseUseCase(errorUtil, foregroundContext) { override val TAG: String = GetDefaultCardUseCase::class.java.simpleName override suspend fun executeOnBackground(): String { + delay(1000L) throw BaseException(BaseStatusEnum.UNKNOWN_ERROR) } } \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/GetSavedCaseStatusUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/GetSavedCaseStatusUseCase.kt new file mode 100644 index 00000000..c0e5061a --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/GetSavedCaseStatusUseCase.kt @@ -0,0 +1,22 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_data.db.dao.CaseStatusDao +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.entity.CaseStatus +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class GetSavedCaseStatusUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext, + private val dao: CaseStatusDao +) : BaseUseCase(errorUtil, foregroundContext) { + override val TAG: String = GetCaseItemsUseCase::class.java.simpleName + + var caseId = -1 + + override suspend fun executeOnBackground(): CaseStatus { + + return dao.getById(caseId) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/PlayRandomUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/PlayRandomUseCase.kt new file mode 100644 index 00000000..da2841d6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/PlayRandomUseCase.kt @@ -0,0 +1,27 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.domain.exception.BaseException +import by.esas.tools.domain.exception.BaseStatusEnum +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlin.random.Random + +class PlayRandomUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext +): BaseUseCase(errorUtil, foregroundContext){ + override val TAG = PlayRandomUseCase::class.java.simpleName + + var rangeNumbers = 0 + var number = 0 + + override suspend fun executeOnBackground(): String { + if (number == Random.nextInt(0, rangeNumbers+1)) { + return App.appContext.getString(R.string.domain_case_congratulations) + } else + throw BaseException(BaseStatusEnum.UNKNOWN_ERROR) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/app_domain/usecase/SearchCaseUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/SearchCaseUseCase.kt new file mode 100644 index 00000000..ac6eb92b --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/SearchCaseUseCase.kt @@ -0,0 +1,42 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.entity.CaseItemInfo +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class SearchCaseUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext +) : BaseUseCase>(errorUtil, foregroundContext) { + override val TAG: String = SearchCaseUseCase::class.java.simpleName + + var caseItems = listOf() + var search = "" + + override suspend fun executeOnBackground(): List { + return if (search != "") { + var filteredList = caseItems + val searchList = splitSearch(search) + for (i in searchList) { + filteredList = filteredList.filter { item -> + item.name.contains(i, true) || doSearchByModules(item, i) + } + } + filteredList + } else { + caseItems + } + } + + private fun splitSearch(search: String): List { + return search.split(";").mapNotNull { result -> result.trim().takeIf { it.isNotBlank() } } + } + + private fun doSearchByModules(case: CaseItemInfo, search: String): Boolean { + val module = case.modules.find { module -> + module == search + } + return module != null + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/base/AppActivity.kt b/app/src/main/java/by/esas/tools/base/AppActivity.kt new file mode 100644 index 00000000..14111473 --- /dev/null +++ b/app/src/main/java/by/esas/tools/base/AppActivity.kt @@ -0,0 +1,154 @@ +package by.esas.tools.base + +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import android.os.Bundle +import android.view.View +import androidx.annotation.RequiresApi +import androidx.databinding.ViewDataBinding +import by.esas.tools.App +import by.esas.tools.BR +import by.esas.tools.app_data.AppSharedPrefs +import by.esas.tools.basedaggerui.factory.InjectingViewModelFactory +import by.esas.tools.baseui.standard.StandardActivity +import by.esas.tools.logger.ILogger +import by.esas.tools.logger.handler.ErrorHandler +import by.esas.tools.util.TAGk +import by.esas.tools.util.configs.SettingsProvider +import by.esas.tools.util.configs.UiModeType +import by.esas.tools.utils.logger.ErrorModel +import by.esas.tools.utils.logger.LoggerImpl +import dagger.android.AndroidInjection +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasAndroidInjector +import java.util.* +import javax.inject.Inject + + +/** + * To use HasAndroidInjector with Activity do not forget to add + * AndroidInjection.inject(this) + * in the fun onCreate(savedInstanceState: Bundle?) + * + * To use HasAndroidInjector with Fragment do not forget to add + * override fun onAttach(context: Context) { + * AndroidSupportInjection.inject(this) + * super.onAttach(context) + * } + */ +abstract class AppActivity + : StandardActivity(), + HasAndroidInjector { + + @Inject + lateinit var viewModelFactory: InjectingViewModelFactory + + @Inject + lateinit var androidInjector: DispatchingAndroidInjector + + override fun androidInjector(): AndroidInjector? { + return androidInjector + } + + override var logger: ILogger = LoggerImpl() + + override fun onCreate(savedInstanceState: Bundle?) { + AndroidInjection.inject(this) + super.onCreate(savedInstanceState) + } + + override fun provideSwitchableViews(): List { + return emptyList() + } + + override fun provideErrorHandler(): ErrorHandler { + return object : ErrorHandler() { + + override fun getErrorMessage(error: ErrorModel): String { + return "Error message" + } + + override fun getErrorMessage(e: Throwable): String { + return "Error message" + } + + override fun mapError(e: Throwable): ErrorModel { + return viewModel.provideMapper().mapErrorException(this@AppActivity.TAGk, e) + } + } + } + + override fun provideVariableInd(): Int { + return BR.viewModel + } + + override fun provideSetter(): SettingsProvider { + return object : SettingsProvider { + var prefs: AppSharedPrefs = AppSharedPrefs(App.instance) + + override fun getDefaultLanguage(): String { + return "en" + } + + override fun getLanguage(): String { + return prefs.getLanguage() + } + + override fun setLanguage(lang: String) { + setAppContext(updateResources(getAppContext(), lang)) + prefs.setLanguage(lang) + } + + override fun getDefaultMode(): UiModeType { + return UiModeType.SYSTEM + } + + override fun getMode(): UiModeType { + return prefs.getTheme() + } + + override fun setMode(uiMode: UiModeType) { + prefs.setTheme(uiMode) + } + } + } + + /* override fun hideSystemUI() { + logger.logInfo("hideSystemUI") + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + hideSystemUIR(this) + } else { + hideSystemUIApp(this) + } + } catch (e: NullPointerException) { + logger.logError(e) + } + }*/ + + override fun getAppContext(): Context { + return App.appContext + } + + override fun setAppContext(context: Context) { + App.appContext = context + } + + /** + * @see IHandlePopBackArguments + * */ + override fun handlePopBackArguments(arguments: Bundle?) { + if (arguments != null) + intent.putExtras(arguments) + } + + private fun updateResources(context: Context, language: String): Context { + val locale = Locale(language) + Locale.setDefault(locale) + val config = Configuration(context.resources.configuration) + config.setLocale(locale) + return context.createConfigurationContext(config) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/base/AppFragment.kt b/app/src/main/java/by/esas/tools/base/AppFragment.kt new file mode 100644 index 00000000..e67c7439 --- /dev/null +++ b/app/src/main/java/by/esas/tools/base/AppFragment.kt @@ -0,0 +1,85 @@ +package by.esas.tools.base + +import android.content.Context +import android.view.View +import androidx.databinding.ViewDataBinding +import by.esas.tools.App +import by.esas.tools.BR +import by.esas.tools.R +import by.esas.tools.basedaggerui.factory.InjectingViewModelFactory +import by.esas.tools.baseui.standard.StandardFragment +import by.esas.tools.checker.Checker +import by.esas.tools.checker.Checking +import by.esas.tools.logger.BaseErrorModel +import by.esas.tools.logger.BaseLoggerImpl +import by.esas.tools.logger.ILogger +import by.esas.tools.logger.handler.ErrorHandler +import by.esas.tools.util.TAGk +import by.esas.tools.utils.logger.ErrorModel +import by.esas.tools.utils.logger.LoggerImpl +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasAndroidInjector +import dagger.android.support.AndroidSupportInjection +import javax.inject.Inject + +abstract class AppFragment : + StandardFragment(), + HasAndroidInjector { + + @Inject + lateinit var viewModelFactory: InjectingViewModelFactory + + @Inject + lateinit var androidInjector: DispatchingAndroidInjector + + override fun androidInjector(): AndroidInjector? { + return androidInjector + } + + override var logger: ILogger<*> = BaseLoggerImpl(context = provideAppContext()) + + override fun provideAppContext(): Context { + return App.appContext + } + + override fun provideVariableInd(): Int { + return BR.viewModel + } + + override fun provideErrorHandler(): ErrorHandler { + logger.logInfo("provideErrorHandler") + return object : ErrorHandler() { + + override fun getErrorMessage(error: ErrorModel): String { + return error.statusEnum + } + + override fun getErrorMessage(e: Throwable): String { + return e.message ?: resources.getString(R.string.test_error) + } + + override fun mapError(e: Throwable): ErrorModel { + return viewModel.provideMapper().mapErrorException(this.TAGk, e) + } + } + } + + override fun provideChecks(): List { + return emptyList() + } + + override fun provideChecker(): Checker? { + return null + } + + override fun provideSwitchableViews(): List { + logger.logInfo("provideSwitchableViews") + return emptyList() + } + + override fun onAttach(context: Context) { + AndroidSupportInjection.inject(this) + super.onAttach(context) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/base/AppVM.kt b/app/src/main/java/by/esas/tools/base/AppVM.kt new file mode 100644 index 00000000..7c74421b --- /dev/null +++ b/app/src/main/java/by/esas/tools/base/AppVM.kt @@ -0,0 +1,21 @@ +package by.esas.tools.base + +import by.esas.tools.App +import by.esas.tools.baseui.standard.StandardViewModel +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.logger.BaseLoggerImpl +import by.esas.tools.logger.IErrorMapper +import by.esas.tools.logger.ILogger +import by.esas.tools.utils.logger.ErrorModel +import javax.inject.Inject + +abstract class AppVM : StandardViewModel() { + @Inject + lateinit var mapper: AppErrorMapper + + override fun provideMapper(): IErrorMapper { + return mapper + } + + override var logger: ILogger<*> = BaseLoggerImpl(context = App.appContext) +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/dialog/CustomBottomDialog.kt b/app/src/main/java/by/esas/tools/dialog/CustomBottomDialog.kt new file mode 100644 index 00000000..b0f36563 --- /dev/null +++ b/app/src/main/java/by/esas/tools/dialog/CustomBottomDialog.kt @@ -0,0 +1,87 @@ +package by.esas.tools.dialog + +import android.os.Bundle +import android.view.View +import android.widget.Button +import by.esas.tools.R +import by.esas.tools.checker.Checker +import by.esas.tools.checker.Checking +import by.esas.tools.checker.checks.LengthCheck +import by.esas.tools.databinding.DfCustomBottomBinding +import by.esas.tools.util.SwitchManager +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking + +class CustomBottomDialog: BindingBottomDialogFragment() { + + companion object { + const val BOTTOM_DIALOG_CHECK_RESULT = "BOTTOM_DIALOG_CHECK_RESULT" + } + + override fun provideLayoutId(): Int { + return R.layout.df_custom_bottom + } + + override var switcher: SwitchManager = object : SwitchManager() { + override fun enableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = true + true + } else { + super.enableView(view) + } + } + + override fun disableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = false + true + } else { + super.disableView(view) + } + } + } + + override fun provideSwitchableList(): List { + return listOf(binding.dfCustomBottomSendButton, binding.dfCustomBottomInputField) + } + + override fun provideValidationList(): List { + return listOf(FieldChecking(binding.dfCustomBottomInputField).addCheck( + LengthCheck(1, 20, + resources.getString(R.string.validation_text_in_range, 1, 20) + ) + )) + } + + override fun provideVariableId(): Int { + return BR.handler + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.dfCustomBottomSwitcher.setOnCheckedChangeListener { _, isChecked -> + when(isChecked) { + true -> enableControls() + false -> disableControls() + } + } + + binding.dfCustomBottomSendButton.setOnClickListener { + disableControls() + AppChecker().setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + enableControls() + resultBundle.putString(Config.DIALOG_USER_ACTION, Config.DISMISS_DIALOG) + resultBundle.putString(BOTTOM_DIALOG_CHECK_RESULT, binding.dfCustomBottomInputField.getText()) + dismiss() + } + }).validate(provideValidationList()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/dialog/GetPasswordDialog.kt b/app/src/main/java/by/esas/tools/dialog/GetPasswordDialog.kt index f7130a6b..acc044dd 100644 --- a/app/src/main/java/by/esas/tools/dialog/GetPasswordDialog.kt +++ b/app/src/main/java/by/esas/tools/dialog/GetPasswordDialog.kt @@ -9,13 +9,16 @@ import by.esas.tools.BR import by.esas.tools.R import by.esas.tools.checker.Checking import by.esas.tools.databinding.DfPasswordBinding -import by.esas.tools.domain.exception.BaseException -import by.esas.tools.domain.exception.BaseStatusEnum -import kotlinx.android.synthetic.main.df_password.* +import by.esas.tools.dialog.Config.DIALOG_USER_ACTION -class GetPasswordDialog : BindingDialogFragment() { +class GetPasswordDialog : BindingDialogFragment() { override val TAG: String = GetPasswordDialog::class.java.simpleName + companion object { + const val USER_ACTION_FORGOT_PASSWORD: String = "USER_ACTION_FORGOT_PASSWORD" + const val USER_ACTION_COMPLETE_PASSWORD: String = "USER_ACTION_COMPLETE_PASSWORD" + } + /* override fun provideLogger(): LoggerImpl { return LoggerImpl(TAG) }*/ @@ -39,10 +42,6 @@ class GetPasswordDialog : BindingDialogFragment("12345678") private var showRecreateView: Boolean = false - private var callback: PasswordCallback? = null private var titleRes: Int = -1 private var forgotPasswordEnable: Boolean = false private var cancelTitleRes: Int = -1 @@ -64,11 +62,12 @@ class GetPasswordDialog : BindingDialogFragment() { + + companion object { + const val ACTION_SELECT_FILTER_MODULES = "ACTION_SELECT_FILTER_MODULES" + const val ACTION_SELECT_FILTER_STATUSES = "ACTION_SELECT_FILTER_STATUSES" + const val FILTER_DIALOG_KEY = "FILTER_DIALOG_KEY" + } + + private var moduleBoxes = listOf>() + private var statusBoxes = listOf>() + + private var selectedModules = arrayListOf() + private var selectedStatuses = arrayListOf() + + init { + isCancelable = true + } + + override fun provideLayoutId() = R.layout.df_search_filter + + override fun provideSwitchableList(): List = emptyList() + + override fun provideValidationList(): List = emptyList() + + override fun provideVariableId() = BR.handler + + override fun styleSettings() { + setStyle(STYLE_NO_FRAME, R.style.AppTheme) + } + + override fun onStart() { + super.onStart() + + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + + statusBoxes = listOf( + Pair(binding.dfFilterStatusChecked, App.appContext.getString(R.string.case_status_checked)), + Pair(binding.dfFilterStatusUnchecked, App.appContext.getString(R.string.case_status_unchecked)), + Pair(binding.dfFilterStatusFailed, App.appContext.getString(R.string.case_status_failed)), + Pair(binding.dfFilterStatusInProgress, App.appContext.getString(R.string.case_status_in_process)) + ) + moduleBoxes = listOf( + Pair(binding.dfFilterModuleAccesscontainer, Modules.ACCESS_CONTAINER), + Pair(binding.dfFilterModuleBasedaggerui, Modules.BASE_DAGGER_UI), + Pair(binding.dfFilterModuleBaseui, Modules.BASE_UI), + Pair(binding.dfFilterModuleBiometricDecryption, Modules.BIOMETRIC_DECRYPTION), + Pair(binding.dfFilterModuleCardline, Modules.CARDLINE), + Pair(binding.dfFilterModuleChecker, Modules.CHECKER), + Pair(binding.dfFilterModuleCustomswitch, Modules.CUSTOMSWITCH), + Pair(binding.dfFilterModuleDialog, Modules.DIALOG), + Pair(binding.dfFilterModuleDimenUtil, Modules.DIMEN_UTIL), + Pair(binding.dfFilterModuleDomain, Modules.DOMAIN), + Pair(binding.dfFilterModuleInputfieldview, Modules.INPUTFIELD_VIEW), + Pair(binding.dfFilterModuleListheader, Modules.LISTHEADER), + Pair(binding.dfFilterModuleLogger, Modules.LOGGER), + Pair(binding.dfFilterModuleNumpad, Modules.NUMPAD), + Pair(binding.dfFilterModulePinview, Modules.PIN_VIEW), + Pair(binding.dfFilterModuleRecycler, Modules.RECYCLER), + Pair(binding.dfFilterModuleTimeparser, Modules.TIMEPARSER), + Pair(binding.dfFilterModuleTopbarview, Modules.TOPBAR_VIEW), + Pair(binding.dfFilterModuleUtil, Modules.UTIL) + ) + + setSelectedStatuses() + setSelectedModules() + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.dfFilterTopbar.setupHandler(object : ITopbarHandler { + override fun onActionClick() { + resultBundle.putString(Config.DIALOG_USER_ACTION, Config.DISMISS_DIALOG) + resultBundle.putStringArray(ACTION_SELECT_FILTER_STATUSES, emptyArray()) + resultBundle.putStringArray(ACTION_SELECT_FILTER_MODULES, emptyArray()) + + this@SearchFilterDialog.dismiss() + } + + override fun onNavigationClick() { + this@SearchFilterDialog.dismiss() + } + }) + + binding.dfFilterShowResultBtn.setOnClickListener { + resultBundle.putString(Config.DIALOG_USER_ACTION, Config.DISMISS_DIALOG) + resultBundle.putStringArray(ACTION_SELECT_FILTER_STATUSES, getSelectedStatuses().toTypedArray()) + resultBundle.putStringArray(ACTION_SELECT_FILTER_MODULES, getSelectedModules().toTypedArray()) + + this@SearchFilterDialog.dismiss() + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + + outState.putStringArrayList(SELECTED_STATUSES_KEY, selectedStatuses) + outState.putStringArrayList(SELECTED_MODULES_KEY, selectedModules) + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + + selectedStatuses = savedInstanceState?.getStringArrayList(SELECTED_STATUSES_KEY) ?: arrayListOf() + selectedModules = savedInstanceState?.getStringArrayList(SELECTED_MODULES_KEY) ?: arrayListOf() + setSelectedStatuses() + setSelectedModules() + } + + fun setSelectedItems(statuses: List, modules: List) { + selectedStatuses.clear() + selectedStatuses.addAll(statuses) + + selectedModules.clear() + selectedModules.addAll(modules) + } + + private fun setSelectedStatuses() { + selectedStatuses.forEach { + when(it) { + App.appContext.getString(R.string.case_status_checked) -> binding.dfFilterStatusChecked.isChecked = true + App.appContext.getString(R.string.case_status_in_process) -> binding.dfFilterStatusInProgress.isChecked = true + App.appContext.getString(R.string.case_status_failed) -> binding.dfFilterStatusFailed.isChecked = true + App.appContext.getString(R.string.case_status_unchecked) -> binding.dfFilterStatusUnchecked.isChecked = true + } + } + } + + private fun setSelectedModules() { + selectedModules.forEach { selectedModule -> + moduleBoxes.find { it.second == selectedModule }?.first?.isChecked = true + } + } + + private fun getSelectedStatuses() = statusBoxes.filter { it.first.isChecked }.map { it.second } + + private fun getSelectedModules() = moduleBoxes.filter { it.first.isChecked }.map { it.second } +} diff --git a/app/src/main/java/by/esas/tools/entity/CaseInfo.kt b/app/src/main/java/by/esas/tools/entity/CaseInfo.kt new file mode 100644 index 00000000..e217b5a4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/CaseInfo.kt @@ -0,0 +1,7 @@ +package by.esas.tools.entity + +data class CaseInfo( + val id: Int, + val name: String, + val modules: List +) \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/entity/CaseItemInfo.kt b/app/src/main/java/by/esas/tools/entity/CaseItemInfo.kt new file mode 100644 index 00000000..87c8e828 --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/CaseItemInfo.kt @@ -0,0 +1,24 @@ +package by.esas.tools.entity + +import by.esas.tools.recycler.ICheckDiff + +data class CaseItemInfo( + val id: Int, + val name: String, + val modules: List, + val status: TestStatusEnum +) : ICheckDiff { + + override fun checkIfSameItem(item: CaseItemInfo): Boolean { + return id == item.id + } + + override fun checkIfSameContent(item: CaseItemInfo): Boolean { + if (id != item.id) return false + else if (name != item.name) return false + else if (modules.size != item.modules.size || !modules.containsAll(item.modules)) return false + else if (status != item.status) return false + + return true + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/entity/CaseStatus.kt b/app/src/main/java/by/esas/tools/entity/CaseStatus.kt new file mode 100644 index 00000000..190faa56 --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/CaseStatus.kt @@ -0,0 +1,15 @@ +package by.esas.tools.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "caseStatusTable") +data class CaseStatus( + @PrimaryKey + @ColumnInfo(name = "case_id") + val caseId: Int, + + @ColumnInfo(name = "case_status") + var status: TestStatusEnum +) \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/entity/LogItem.kt b/app/src/main/java/by/esas/tools/entity/LogItem.kt new file mode 100644 index 00000000..e98d56a9 --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/LogItem.kt @@ -0,0 +1,7 @@ +package by.esas.tools.entity + +data class LogItem( + val tag: String = "", + val category: String = "", + val message: String +) diff --git a/app/src/main/java/by/esas/tools/entity/Modules.kt b/app/src/main/java/by/esas/tools/entity/Modules.kt new file mode 100644 index 00000000..da4ceb4f --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/Modules.kt @@ -0,0 +1,23 @@ +package by.esas.tools.entity + +object Modules { + const val ACCESS_CONTAINER = "accesscontainer" + const val BASE_DAGGER_UI = "basedaggerui" + const val BASE_UI = "baseui" + const val BIOMETRIC_DECRYPTION = "biometric_decryption" + const val CARDLINE = "cardline" + const val CHECKER = "checker" + const val CUSTOMSWITCH = "customswitch" + const val DIALOG = "dialog" + const val DIMEN_UTIL = "dimen_util" + const val DOMAIN = "domain" + const val INPUTFIELD_VIEW = "inputfieldview" + const val LISTHEADER = "listheader" + const val LOGGER = "logger" + const val NUMPAD = "numpad" + const val PIN_VIEW = "pinview" + const val RECYCLER = "recycler" + const val TIMEPARSER = "timeparser" + const val TOPBAR_VIEW = "topbarview" + const val UTIL = "util" +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/entity/NumpadTextIconEntity.kt b/app/src/main/java/by/esas/tools/entity/NumpadTextIconEntity.kt new file mode 100644 index 00000000..4b2b8dda --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/NumpadTextIconEntity.kt @@ -0,0 +1,7 @@ +package by.esas.tools.entity + +data class NumpadTextIconEntity ( + val iconSize: Int, + val numTextSize: Int, + val imageSize: Int +) \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/entity/TestStatusEnum.kt b/app/src/main/java/by/esas/tools/entity/TestStatusEnum.kt new file mode 100644 index 00000000..6e6c074f --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/TestStatusEnum.kt @@ -0,0 +1,8 @@ +package by.esas.tools.entity + +enum class TestStatusEnum { + CHECKED, + UNCHECKED, + IN_PROCESS, + FAILED +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/error_mapper/AppErrorMapper.kt b/app/src/main/java/by/esas/tools/error_mapper/AppErrorMapper.kt deleted file mode 100644 index 44b10a60..00000000 --- a/app/src/main/java/by/esas/tools/error_mapper/AppErrorMapper.kt +++ /dev/null @@ -1,37 +0,0 @@ -package by.esas.tools.error_mapper - -import by.esas.tools.domain.exception.BaseStatusEnum -import by.esas.tools.domain.mapper.BaseErrorMapper -import by.esas.tools.logger.ErrorModel -import by.esas.tools.logger.ILogger -import com.squareup.moshi.Moshi -import retrofit2.HttpException - -class AppErrorMapper(moshi: Moshi, logger: ILogger) : - BaseErrorMapper(moshi, logger) { - override fun mapBaseException(errorText: String?): AppErrorStatusEnum { - return AppErrorStatusEnum.APP_UNPREDICTED_ERROR - } - - override fun mapBaseStatus(enum: BaseStatusEnum): AppErrorStatusEnum { - return AppErrorStatusEnum.APP_UNPREDICTED_ERROR - } - - override fun mapErrorException(tag: String, throwable: Throwable?): ErrorModel { - val model = super.mapErrorException(tag, throwable) - return ErrorModel(model.code, model.statusEnum) - } - - override fun getHttpError(throwable: HttpException): ErrorModel { - val model = super.getHttpError(throwable) - return ErrorModel(model.code, model.statusEnum) - } - - override fun createModel(code: Int, status: AppErrorStatusEnum): ErrorModel { - return ErrorModel(code, status) - } - - override fun mapHttpStatus(httpEnumString: String): AppErrorStatusEnum { - return AppErrorStatusEnum.APP_UNPREDICTED_ERROR - } -} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/fields/RohabInputField.kt b/app/src/main/java/by/esas/tools/fields/RohabInputField.kt deleted file mode 100644 index f695c177..00000000 --- a/app/src/main/java/by/esas/tools/fields/RohabInputField.kt +++ /dev/null @@ -1,105 +0,0 @@ -package by.esas.tools.fields - -import android.content.Context -import android.util.AttributeSet -import androidx.core.text.BidiFormatter -import by.esas.tools.R -import by.esas.tools.getLocale -import by.esas.tools.inputfieldview.InputFieldView - -class RohabInputField : InputFieldView { - override val TAG: String = RohabInputField::class.java.simpleName - //val logger: ILogger = LoggerImpl() - - override val defaultLabelType: Int = LABEL_TYPE_ON_TOP - private var formattedText: String = "" - - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - initAttrs(attrs) - } - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) - : super(context, attrs, defStyleAttr) { - initAttrs(attrs) - } - - /* @RequiresApi(Build.VERSION_CODES.LOLLIPOP) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) - : super(context, attrs, defStyleAttr, defStyleRes) { - initAttrs(attrs) - }*/ - - init { - //logger.setTag(TAG) - } - - /* Initialize attributes from XML file */ - private fun initAttrs(attrs: AttributeSet?) { - val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RohabInputField) - // Label - val label = typedArray.getString(R.styleable.RohabInputField_inputLabel) ?: "" - // Hint - formattedText = typedArray.getString(R.styleable.RohabInputField_inputHintFormattedText) ?: "" - - typedArray.recycle() - - labelText?.text = "" - if (formattedText.isNotBlank()) { - setInputLabel(label, formattedText) - } else setInputLabel(label) - } - - fun setInputLabel(text: String, formattedText: String) { - val formatter: BidiFormatter = BidiFormatter.getInstance() - val hintTxt = String.format(text, formatter.unicodeWrap(formattedText)) - setInputLabel(hintTxt) - } - - fun setInputLabel(text: String, formattedId: Int) { - val formatter: BidiFormatter = BidiFormatter.getInstance() - val formattedText = if (formattedId != -1) context.resources.getString(formattedId) else "" - val hintTxt = String.format(text, formatter.unicodeWrap(formattedText)) - setInputLabel(hintTxt) - } - - override fun setInputLabel(text: String?) { - if ((labelText?.text?.toString() ?: "") != text && !text.isNullOrBlank()) { - /*if (!isInEditMode) - logger.log("Is RTL direction ${resources.getBoolean(R.bool.is_rtl_direction)}")*/ - var hint = text ?: "" - hint = if (hint.contains("(") && hint.contains(")")) { - val beforeBrace = text.substringBefore("(") - val afterFirstBrace = text.substringAfter("(") - val betweenBraces = afterFirstBrace.substringBefore(")") - val afterSecondBrace = afterFirstBrace.substringAfter(")", "") - val builder = StringBuilder("") - builder.append(beforeBrace.toUpperCase(getLocale(context))).append("(") - .append(betweenBraces).append(")").append( - afterSecondBrace.toUpperCase( - getLocale( - context - ) - ) - ) - builder.toString() - } else hint.toUpperCase(getLocale(context)) - labelText?.text = hint//HtmlCompat.fromHtml(hint,HtmlCompat.FROM_HTML_MODE_LEGACY) - } else hideLabel() - } - - fun setInputLabel(textId: Int, formattedText: String) { - val text = if (textId != -1) context.resources.getString(textId) else "" - val formatter: BidiFormatter = BidiFormatter.getInstance() - val hintTxt = String.format(text, formatter.unicodeWrap(formattedText)) - setInputLabel(hintTxt) - } - - fun setInputLabel(textId: Int, formattedId: Int) { - val text = if (textId != -1) context.resources.getString(textId) else "" - val formattedText = if (formattedId != -1) context.resources.getString(formattedId) else "" - val formatter: BidiFormatter = BidiFormatter.getInstance() - val hintTxt = String.format(text, formatter.unicodeWrap(formattedText)) - setInputLabel(hintTxt) - } -} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/inject/builder/ActivityBuilder.kt b/app/src/main/java/by/esas/tools/inject/builder/ActivityBuilder.kt index f4926c8c..dbfc5f58 100644 --- a/app/src/main/java/by/esas/tools/inject/builder/ActivityBuilder.kt +++ b/app/src/main/java/by/esas/tools/inject/builder/ActivityBuilder.kt @@ -1,6 +1,6 @@ package by.esas.tools.inject.builder -import by.esas.tools.MainActivity +import by.esas.tools.screens.MainActivity import dagger.Module import dagger.android.ContributesAndroidInjector diff --git a/app/src/main/java/by/esas/tools/inject/builder/FragmentBuilder.kt b/app/src/main/java/by/esas/tools/inject/builder/FragmentBuilder.kt index 41ef6aa7..ec629707 100644 --- a/app/src/main/java/by/esas/tools/inject/builder/FragmentBuilder.kt +++ b/app/src/main/java/by/esas/tools/inject/builder/FragmentBuilder.kt @@ -1,11 +1,144 @@ package by.esas.tools.inject.builder +import by.esas.tools.screens.baseui.navigation.BaseUINavigationFragment +import by.esas.tools.screens.baseui.navigation.BaseUINavigationSecondFragment +import by.esas.tools.screens.baseui.other.BaseUIFunctionalityFragment +import by.esas.tools.screens.baseui.ui.BaseUIThemeFragment +import by.esas.tools.screens.biometric.BiometricDecryptionFragment +import by.esas.tools.screens.listheader.ListheaderFragment +import by.esas.tools.screens.custom_switch.CustomSwitchFragment +import by.esas.tools.screens.cardline.CardlineFragment +import by.esas.tools.screens.cardline.dynamic.DynamicCardlineFragment +import by.esas.tools.screens.checker.CheckerInputFieldFragment +import by.esas.tools.screens.custom_switch.program.CustomSwitchProgramFragment +import by.esas.tools.screens.dialog.DynamicMessageDialogFragment +import by.esas.tools.screens.dialog.bottom_dialog.BottomDialogFragment +import by.esas.tools.screens.domain.DomainCaseFragment +import by.esas.tools.screens.inputfield_view.end_icon.InputfieldViewEndIconFragment +import by.esas.tools.screens.inputfield_view.input_field.InputfieldViewFragment +import by.esas.tools.screens.inputfield_view.start_icon.InputfieldViewStartIconFragment +import by.esas.tools.screens.listheader.dynamic.DynamicListheaderFragment +import by.esas.tools.screens.logger.LoggerFragment +import by.esas.tools.screens.menu.MenuFragment +import by.esas.tools.screens.numpad.NumpadImageFragment +import by.esas.tools.screens.numpad.numpad_text.NumpadTextFragment +import by.esas.tools.screens.pin_view.PinViewFragment +import by.esas.tools.screens.pin_view.dynamic.DynamicPinViewFragment +import by.esas.tools.screens.recycler.base.RecyclerFragment +import by.esas.tools.screens.recycler.custom.CustomRecyclerFragment +import by.esas.tools.screens.recycler.simple.SimpleRecyclerFragment +import by.esas.tools.screens.recycler.sticky_case.StickyCaseFragment +import by.esas.tools.screens.saved_state_vm.SavedStateFragment +import by.esas.tools.screens.timeparser.TimeParserFragment +import by.esas.tools.screens.topbar.TopbarFragment +import by.esas.tools.screens.util.UtilKeyboardFragment +import by.esas.tools.screens.util.switchManager.UtilSwitchManagerFragment +import by.esas.tools.screens.util.utils.UtilsFragment import dagger.Module +import dagger.android.ContributesAndroidInjector @Module abstract class FragmentBuilder { - /* @ContributesAndroidInjector - abstract fun bindLoadingFragment(): LoadingFragment*/ + @ContributesAndroidInjector + abstract fun bindMenuFragment(): MenuFragment + @ContributesAndroidInjector + abstract fun bindPinViewFragment(): PinViewFragment + + @ContributesAndroidInjector + abstract fun bindDynamicPinViewFragment(): DynamicPinViewFragment + + @ContributesAndroidInjector + abstract fun bindSavedStateFragment(): SavedStateFragment + + @ContributesAndroidInjector + abstract fun bindNumpadImageFragment(): NumpadImageFragment + + @ContributesAndroidInjector + abstract fun bindUtilKeyboardFragment(): UtilKeyboardFragment + + @ContributesAndroidInjector + abstract fun bindUtilsFragment(): UtilsFragment + + @ContributesAndroidInjector + abstract fun bindUtilSwitchManagerFragment(): UtilSwitchManagerFragment + + @ContributesAndroidInjector + abstract fun bindListheaderFragment(): ListheaderFragment + + @ContributesAndroidInjector + abstract fun bindDynamicListheaderFragment(): DynamicListheaderFragment + + @ContributesAndroidInjector + abstract fun bindCustomSwitchFragment(): CustomSwitchFragment + + @ContributesAndroidInjector + abstract fun bindCustomSwitchProgramFragment(): CustomSwitchProgramFragment + + @ContributesAndroidInjector + abstract fun bindTopbarFragment(): TopbarFragment + + @ContributesAndroidInjector + abstract fun bindCardlineFragment(): CardlineFragment + + @ContributesAndroidInjector + abstract fun bindDynamicCardlineFragment(): DynamicCardlineFragment + + @ContributesAndroidInjector + abstract fun bindDynamicMessageDialogFragment(): DynamicMessageDialogFragment + + @ContributesAndroidInjector + abstract fun bindBottomDialogFragment(): BottomDialogFragment + + @ContributesAndroidInjector + abstract fun bindInputfieldViewFragment(): InputfieldViewFragment + + @ContributesAndroidInjector + abstract fun bindInputfieldViewEndIconFragment(): InputfieldViewEndIconFragment + + @ContributesAndroidInjector + abstract fun bindInputfieldViewStartIconFragment(): InputfieldViewStartIconFragment + + @ContributesAndroidInjector + abstract fun bindNumpadTextFragment(): NumpadTextFragment + + @ContributesAndroidInjector + abstract fun bindBaseUINavigationFragment(): BaseUINavigationFragment + + @ContributesAndroidInjector + abstract fun bindBaseUINavigationSecondFragment(): BaseUINavigationSecondFragment + + @ContributesAndroidInjector + abstract fun bindBaseUIFunctionalityFragment(): BaseUIFunctionalityFragment + + @ContributesAndroidInjector + abstract fun bindBaseUIThemeFragment(): BaseUIThemeFragment + + @ContributesAndroidInjector + abstract fun bindDomainCaseFragment(): DomainCaseFragment + + @ContributesAndroidInjector + abstract fun bindRecyclerFragment(): RecyclerFragment + + @ContributesAndroidInjector + abstract fun bindStickyCaseFragment(): StickyCaseFragment + + @ContributesAndroidInjector + abstract fun bindSimpleRecyclerFragment(): SimpleRecyclerFragment + + @ContributesAndroidInjector + abstract fun bindCustomRecyclerFragment(): CustomRecyclerFragment + + @ContributesAndroidInjector + abstract fun bindCheckerInputFieldFragment(): CheckerInputFieldFragment + + @ContributesAndroidInjector + abstract fun bindBiometricDecryptionFragment(): BiometricDecryptionFragment + + @ContributesAndroidInjector + abstract fun bindTimeParserFragment(): TimeParserFragment + + @ContributesAndroidInjector + abstract fun bindLoggerFragment(): LoggerFragment } diff --git a/app/src/main/java/by/esas/tools/inject/builder/RepositoryBuilder.kt b/app/src/main/java/by/esas/tools/inject/builder/RepositoryBuilder.kt index 63bea2e3..478f0fbe 100644 --- a/app/src/main/java/by/esas/tools/inject/builder/RepositoryBuilder.kt +++ b/app/src/main/java/by/esas/tools/inject/builder/RepositoryBuilder.kt @@ -1,6 +1,5 @@ package by.esas.tools.inject.builder -import dagger.Binds import dagger.Module diff --git a/app/src/main/java/by/esas/tools/inject/builder/ViewModelBuilder.kt b/app/src/main/java/by/esas/tools/inject/builder/ViewModelBuilder.kt index 1d082a76..8db1dea5 100644 --- a/app/src/main/java/by/esas/tools/inject/builder/ViewModelBuilder.kt +++ b/app/src/main/java/by/esas/tools/inject/builder/ViewModelBuilder.kt @@ -1,10 +1,43 @@ package by.esas.tools.inject.builder import androidx.lifecycle.ViewModel -import by.esas.tools.AddInvoiceViewModel -import by.esas.tools.basedaggerui.inject.qualifier.ViewModelKey -import by.esas.tools.MainVM -import by.esas.tools.basedaggerui.inject.factory.AssistedSavedStateViewModelFactory +import by.esas.tools.basedaggerui.factory.AssistedSavedStateViewModelFactory +import by.esas.tools.basedaggerui.qualifier.ViewModelKey +import by.esas.tools.screens.MainVM +import by.esas.tools.screens.baseui.navigation.BaseUINavigationSecondVM +import by.esas.tools.screens.baseui.navigation.BaseUINavigationVM +import by.esas.tools.screens.baseui.other.BaseUIFunctionalityVM +import by.esas.tools.screens.baseui.ui.BaseUIThemeVM +import by.esas.tools.screens.biometric.BiometricDecryptionVM +import by.esas.tools.screens.listheader.ListheaderVM +import by.esas.tools.screens.custom_switch.CustomSwitchVM +import by.esas.tools.screens.cardline.CardlineVM +import by.esas.tools.screens.cardline.dynamic.DynamicCardlineVM +import by.esas.tools.screens.checker.CheckerInputFieldVM +import by.esas.tools.screens.custom_switch.program.CustomSwitchProgramVM +import by.esas.tools.screens.dialog.DynamicMessageDialogVM +import by.esas.tools.screens.dialog.bottom_dialog.BottomDialogVM +import by.esas.tools.screens.domain.DomainCaseVM +import by.esas.tools.screens.inputfield_view.end_icon.InputfieldViewEndIconVM +import by.esas.tools.screens.inputfield_view.input_field.InputfieldViewVM +import by.esas.tools.screens.inputfield_view.start_icon.InputfieldViewStartIconVM +import by.esas.tools.screens.listheader.dynamic.DynamicListheaderVM +import by.esas.tools.screens.logger.LoggerVM +import by.esas.tools.screens.menu.MenuVM +import by.esas.tools.screens.numpad.NumpadImageVM +import by.esas.tools.screens.numpad.numpad_text.NumpadTextVM +import by.esas.tools.screens.pin_view.PinViewVM +import by.esas.tools.screens.pin_view.dynamic.DynamicPinViewVM +import by.esas.tools.screens.recycler.base.RecyclerVM +import by.esas.tools.screens.recycler.custom.CustomRecyclerVM +import by.esas.tools.screens.recycler.simple.SimpleRecyclerVM +import by.esas.tools.screens.recycler.sticky_case.StickyCaseVM +import by.esas.tools.screens.saved_state_vm.SavedStateVM +import by.esas.tools.screens.timeparser.TimeParserVM +import by.esas.tools.screens.topbar.TopbarVM +import by.esas.tools.screens.util.UtilKeyboardVM +import by.esas.tools.screens.util.switchManager.UtilSwitchManagerVM +import by.esas.tools.screens.util.utils.UtilsVM import dagger.Binds import dagger.Module import dagger.multibindings.IntoMap @@ -20,6 +53,171 @@ abstract class ViewModelBuilder { @Binds @IntoMap - @ViewModelKey(AddInvoiceViewModel::class) - abstract fun bindVMFactory(f: AddInvoiceViewModel.Factory): AssistedSavedStateViewModelFactory + @ViewModelKey(MenuVM::class) + abstract fun bindMenuViewModel(menuViewModel: MenuVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(PinViewVM::class) + abstract fun bindPinViewVM(pinViewVM: PinViewVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(DynamicPinViewVM::class) + abstract fun bindDynamicPinViewVM(dynamicPinViewVM: DynamicPinViewVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(NumpadImageVM::class) + abstract fun bindNumpadImageVM(numpadImageVM: NumpadImageVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(UtilKeyboardVM::class) + abstract fun bindUtilKeyboardVM(utilKeyboardVM: UtilKeyboardVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(UtilsVM::class) + abstract fun bindUtilsVM(utilsVM: UtilsVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(UtilSwitchManagerVM::class) + abstract fun bindUtilSwitchManagerVM(utilSwitchManagerVM: UtilSwitchManagerVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(ListheaderVM::class) + abstract fun bindListheaderVM(listheaderVM: ListheaderVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(DynamicListheaderVM::class) + abstract fun bindDynamicListheaderVM(dynamicListheaderVM: DynamicListheaderVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(CustomSwitchVM::class) + abstract fun bindCustomSwitchVM(customSwitchVM: CustomSwitchVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(CustomSwitchProgramVM::class) + abstract fun bindCustomSwitchProgramVM(customSwitchProgramVM: CustomSwitchProgramVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(TopbarVM::class) + abstract fun bindTopbarVM(topbarVM: TopbarVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(CardlineVM::class) + abstract fun bindCardlineVM(cardlineVM: CardlineVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(DynamicCardlineVM::class) + abstract fun bindDynamicCardlineVM(dynamicCardlineVM: DynamicCardlineVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(DynamicMessageDialogVM::class) + abstract fun bindDynamicMessageDialogVM(dynamicMessageDialogVM: DynamicMessageDialogVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(BottomDialogVM::class) + abstract fun bindBottomDialogVM(bottomDialogVM: BottomDialogVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(InputfieldViewEndIconVM::class) + abstract fun bindInputfieldViewEndIconVM(inputfieldViewEndIconVM: InputfieldViewEndIconVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(InputfieldViewVM::class) + abstract fun bindInputfieldViewVM(inputfieldViewVM: InputfieldViewVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(InputfieldViewStartIconVM::class) + abstract fun bindInputfieldViewStartIconVM(inputfieldViewStartIconVM: InputfieldViewStartIconVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(NumpadTextVM::class) + abstract fun bindNumpadTextVM(numpadTextVM: NumpadTextVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(BaseUINavigationSecondVM::class) + abstract fun bindBaseUINavigationSecondVM(baseUINavigationSecondVM: BaseUINavigationSecondVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(BaseUINavigationVM::class) + abstract fun bindBaseUINavigationVM(baseUINavigationVM: BaseUINavigationVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(BaseUIFunctionalityVM::class) + abstract fun bindBaseUIFunctionalityVM(baseUIFunctionalityVM: BaseUIFunctionalityVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(BaseUIThemeVM::class) + abstract fun bindBaseUIThemeVM(baseUIThemeVM: BaseUIThemeVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(DomainCaseVM::class) + abstract fun bindDomainCaseVM(domainCaseVM: DomainCaseVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(CheckerInputFieldVM::class) + abstract fun bindCheckerInputFieldVM(сheckerInputFieldVM: CheckerInputFieldVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(BiometricDecryptionVM::class) + abstract fun bindBiometricDecryptionVM(biometricDecryptionVM: BiometricDecryptionVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(SavedStateVM::class) + abstract fun bindVMFactory(f: SavedStateVM.Factory): AssistedSavedStateViewModelFactory + + @Binds + @IntoMap + @ViewModelKey(RecyclerVM::class) + abstract fun bindRecyclerVM(viewModel: RecyclerVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(StickyCaseVM::class) + abstract fun bindStickyCaseVM(viewModel: StickyCaseVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(SimpleRecyclerVM::class) + abstract fun bindSimpleRecyclerVM(viewModel: SimpleRecyclerVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(CustomRecyclerVM::class) + abstract fun bindCustomRecyclerVM(viewModel: CustomRecyclerVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(TimeParserVM::class) + abstract fun bindTimeParserVM(viewModel: TimeParserVM): ViewModel + + @Binds + @IntoMap + @ViewModelKey(LoggerVM::class) + abstract fun bindLoggerVM(viewModel: LoggerVM): ViewModel } diff --git a/app/src/main/java/by/esas/tools/inject/builder/ViewModelFactoryBuilder.kt b/app/src/main/java/by/esas/tools/inject/builder/ViewModelFactoryBuilder.kt index faa1bf15..269b5c5e 100644 --- a/app/src/main/java/by/esas/tools/inject/builder/ViewModelFactoryBuilder.kt +++ b/app/src/main/java/by/esas/tools/inject/builder/ViewModelFactoryBuilder.kt @@ -1,7 +1,7 @@ package by.esas.tools.inject.builder import androidx.lifecycle.ViewModelProvider -import by.esas.tools.basedaggerui.inject.factory.ViewModelFactory +import by.esas.tools.basedaggerui.factory.ViewModelFactory import dagger.Binds import dagger.Module diff --git a/app/src/main/java/by/esas/tools/inject/module/ContextModule.kt b/app/src/main/java/by/esas/tools/inject/module/ContextModule.kt index 9d26f589..2275ce40 100644 --- a/app/src/main/java/by/esas/tools/inject/module/ContextModule.kt +++ b/app/src/main/java/by/esas/tools/inject/module/ContextModule.kt @@ -2,12 +2,11 @@ package by.esas.tools.inject.module import android.app.Application import android.content.Context -import by.esas.tools.error_mapper.AppErrorMapper -import by.esas.tools.error_mapper.AppErrorStatusEnum +import by.esas.tools.app_domain.error_mapper.AppErrorMapper import by.esas.tools.inject.builder.ViewModelFactoryBuilder -import by.esas.tools.logger.ErrorModel import by.esas.tools.logger.ILogger -import by.esas.tools.logger.LoggerImpl +import by.esas.tools.utils.logger.ErrorModel +import by.esas.tools.utils.logger.LoggerImpl import com.squareup.moshi.Moshi import dagger.Module import dagger.Provides @@ -35,12 +34,12 @@ class ContextModule { @Provides @Singleton - fun provideCrashlyticsMapper(moshi: Moshi, logger: ILogger): AppErrorMapper { + fun provideErrorMapper(moshi: Moshi, logger: ILogger): AppErrorMapper { return AppErrorMapper(moshi, logger) } @Provides - fun provideLogger(): ILogger { + fun provideLogger(): ILogger { return LoggerImpl().apply { setTag("LoggerImpl") } } } diff --git a/app/src/main/java/by/esas/tools/inject/module/NetworkModule.kt b/app/src/main/java/by/esas/tools/inject/module/NetworkModule.kt index f2730c2a..29a05ccf 100644 --- a/app/src/main/java/by/esas/tools/inject/module/NetworkModule.kt +++ b/app/src/main/java/by/esas/tools/inject/module/NetworkModule.kt @@ -1,43 +1,16 @@ package by.esas.tools.inject.module import android.content.Context +import by.esas.tools.app_data.db.AppDataBase +import by.esas.tools.app_data.db.dao.CaseStatusDao import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import dagger.Module import dagger.Provides -import okhttp3.OkHttpClient -import retrofit2.Retrofit -import java.util.concurrent.TimeUnit import javax.inject.Singleton @Module class NetworkModule { - /* @Provides - @Singleton - fun providesRetrofit( - moshiConverterFactory: MoshiConverterFactory, - okHttpClient: OkHttpClient - ): Retrofit.Builder { - return Retrofit.Builder() - .addConverterFactory(moshiConverterFactory) - .addCallAdapterFactory(CoroutineCallAdapterFactory()) - .client(okHttpClient) - } - - @Provides - @Singleton - fun providesOkHttpClient(logger: ILogger): OkHttpClient { - val client = OkHttpClient.Builder() - // .cache(cache) - .connectTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - val interceptor = HttpLoggingInterceptor() - interceptor.level = HttpLoggingInterceptor.Level.BODY - client.addNetworkInterceptor(interceptor) - val tlsSupportingClient = enableTls12On(client = client, logger = logger) - return tlsSupportingClient.build() - }*/ @Provides @Singleton @@ -47,6 +20,43 @@ class NetworkModule { .build() } + /*############ Data Base ############*/ + + @Singleton + @Provides + fun provideAppDataBase(context: Context): AppDataBase = AppDataBase.getInstance(context) + + @Singleton + @Provides + fun provideCaseStatusDao(appDataBase: AppDataBase): CaseStatusDao = appDataBase.getCaseStatusDao() + + /* @Provides + @Singleton + fun providesRetrofit( + moshiConverterFactory: MoshiConverterFactory, + okHttpClient: OkHttpClient + ): Retrofit.Builder { + return Retrofit.Builder() + .addConverterFactory(moshiConverterFactory) + .addCallAdapterFactory(CoroutineCallAdapterFactory()) + .client(okHttpClient) + } + + @Provides + @Singleton + fun providesOkHttpClient(logger: ILogger): OkHttpClient { + val client = OkHttpClient.Builder() + // .cache(cache) + .connectTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + val interceptor = HttpLoggingInterceptor() + interceptor.level = HttpLoggingInterceptor.Level.BODY + client.addNetworkInterceptor(interceptor) + val tlsSupportingClient = enableTls12On(client = client, logger = logger) + return tlsSupportingClient.build() + }*/ + /*@Provides @Singleton fun providesMoshiConverterFactory(moshi: Moshi): MoshiConverterFactory { diff --git a/app/src/main/java/by/esas/tools/logger/ErrorModel.kt b/app/src/main/java/by/esas/tools/logger/ErrorModel.kt deleted file mode 100644 index ad2a1212..00000000 --- a/app/src/main/java/by/esas/tools/logger/ErrorModel.kt +++ /dev/null @@ -1,5 +0,0 @@ -package by.esas.tools.logger - -import by.esas.tools.error_mapper.AppErrorStatusEnum - -class ErrorModel(code: Int, statusEnum: AppErrorStatusEnum) : BaseErrorModel(code, statusEnum) \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/MainActivity.kt b/app/src/main/java/by/esas/tools/screens/MainActivity.kt new file mode 100644 index 00000000..1fe3b2de --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/MainActivity.kt @@ -0,0 +1,146 @@ +package by.esas.tools.screens + +import android.os.Bundle +import android.view.MotionEvent +import androidx.core.view.GravityCompat +import androidx.fragment.app.FragmentResultListener +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.NavController +import androidx.navigation.Navigation +import by.esas.tools.R +import by.esas.tools.base.AppActivity +import by.esas.tools.baseui.Config.ERROR_MESSAGE_DIALOG +import by.esas.tools.databinding.ActivityMainBinding +import by.esas.tools.logger.Action +import by.esas.tools.topbarview.ITopbarHandler + +/** + * To use HasAndroidInjector with Activity do not forget to add + * AndroidInjection.inject(this) + * in the fun onCreate(savedInstanceState: Bundle?) + * + * To use HasAndroidInjector with Fragment do not forget to add + * override fun onAttach(context: Context) { + * AndroidSupportInjection.inject(this) + * super.onAttach(context) + * } + */ +class MainActivity : AppActivity() { + + companion object { + const val NEED_TO_UPDATE_MENU: String = "NEED_TO_UPDATE_MENU" + const val NEED_TO_UPDATE_CURRENT_CASE: String = "NEED_TO_UPDATE_CURRENT_CASE" + const val CURRENT_CASE_ID: String = "CURRENT_CASE_ID" + } + + lateinit var navController: NavController + private var topDestination = R.id.menuFragment + + override fun provideViewModel(): MainVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(MainVM::class.java) + } + + override fun provideLayoutId(): Int { + return R.layout.activity_main + } + + override fun provideRequestKeys(): List { + return listOf(ERROR_MESSAGE_DIALOG, MainVM.CASE_STATUS_DIALOG, MainVM.CLEAR_CASES_TEST_DATA_DIALOG) + } + + override fun provideFragmentResultListener(requestKey: String): FragmentResultListener? { + return when (requestKey) { + MainVM.CASE_STATUS_DIALOG, MainVM.CLEAR_CASES_TEST_DATA_DIALOG -> { + FragmentResultListener { key, result -> + val actionName = result.getString(by.esas.tools.dialog.Config.DIALOG_USER_ACTION) + result.putString(MainVM.DIALOG_KEY, key) + if (!actionName.isNullOrBlank()) { + viewModel.handleAction(Action(actionName, result)) + } else { + enableControls(result) + } + } + } + else -> { + super.provideFragmentResultListener(requestKey) + } + } + } + + override fun handleAction(action: Action): Boolean { + when(action.name) { + NEED_TO_UPDATE_MENU -> { + val bundle = Bundle() + bundle.putBoolean(NEED_TO_UPDATE_MENU, true) + intent.putExtras(bundle) + } + NEED_TO_UPDATE_CURRENT_CASE -> { + val newId = action.parameters?.getInt(CURRENT_CASE_ID) ?: -1 + viewModel.currentCaseId = newId + } + else -> return super.handleAction(action) + } + return true + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setupNavigation() + setupSettingsMenu() + + binding.aMainTopBar.setupHandler(object : ITopbarHandler { + override fun onNavigationClick() { + onBackPressed() + } + + override fun onActionClick() { + navController.currentDestination?.let { + if (it.id == topDestination) + binding.aMainDrawerLay.openDrawer(GravityCompat.END) + else + viewModel.openCaseStatusDialog(it.label.toString()) + } + } + }) + } + + override fun setupObservers() { + super.setupObservers() + + viewModel.updateMenuLive.observe(this) { + handleAction(Action(NEED_TO_UPDATE_MENU)) + if (viewModel.needToRecreate) { + viewModel.needToRecreate = false + recreateActivity() + } + } + } + + override fun handleTouchOutOfInputField(event: MotionEvent) { + if (event.action == MotionEvent.ACTION_DOWN && navController.currentDestination?.id != R.id.utilKeyboardFragment) { + super.handleTouchOutOfInputField(event) + } + } + + private fun setupNavigation() { + navController = Navigation.findNavController(this, R.id.a_main_nav_host_fragment) + navController.addOnDestinationChangedListener { _, destination, _ -> + if (destination.id == topDestination) viewModel.currentCaseId = -1 + viewModel.hasBackBtn.set(destination.id != topDestination) + viewModel.hasSettingsBtn.set(destination.id == topDestination) + viewModel.title.set(destination.label.toString()) + binding.aMainTopBar.setEndActionViewVisibility(viewModel.currentCaseId != -1) + } + } + + private fun setupSettingsMenu() { + binding.aMainSettingsMenu.setNavigationItemSelectedListener { + when(it.itemId) { + R.id.menuUiSettings -> navController.navigate(R.id.baseuiThemeFragment) + R.id.menuUpdateTest -> viewModel.openClearCasesTestDataDialog() + } + binding.aMainDrawerLay.closeDrawer(GravityCompat.END) + true + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/MainVM.kt b/app/src/main/java/by/esas/tools/screens/MainVM.kt new file mode 100644 index 00000000..b1331629 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/MainVM.kt @@ -0,0 +1,112 @@ +package by.esas.tools.screens + +import androidx.databinding.ObservableBoolean +import androidx.databinding.ObservableField +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.app_domain.usecase.AddCaseStatusUseCase +import by.esas.tools.app_domain.usecase.ClearAllSavedTestStatusesUseCase +import by.esas.tools.base.AppVM +import by.esas.tools.dialog.MessageDialog +import by.esas.tools.entity.TestStatusEnum +import by.esas.tools.logger.Action +import javax.inject.Inject + +class MainVM @Inject constructor( + private val clearAllCaseStatuses: ClearAllSavedTestStatusesUseCase, + private val addCaseStatus: AddCaseStatusUseCase +) : AppVM() { + + companion object { + const val CASE_STATUS_DIALOG = "CASE_STATUS_DIALOG" + const val CLEAR_CASES_TEST_DATA_DIALOG = "CLEAR_CASES_TEST_DATA_DIALOG" + const val DIALOG_KEY = "DIALOG_KEY" + } + + override fun handleAction(action: Action?): Boolean { + val dialogKey = action?.parameters?.getString(DIALOG_KEY) + val actionName = action?.name + if (dialogKey == CASE_STATUS_DIALOG && actionName == MessageDialog.USER_ACTION_ITEM_PICKED) { + val status = when (action.parameters?.getString(MessageDialog.ITEM_NAME)) { + App.appContext.getString(R.string.case_status_checked) -> TestStatusEnum.CHECKED + App.appContext.getString(R.string.case_status_failed) -> TestStatusEnum.FAILED + App.appContext.getString(R.string.case_status_in_process) -> TestStatusEnum.IN_PROCESS + else -> TestStatusEnum.UNCHECKED + } + addCaseStatus.caseId = currentCaseId + addCaseStatus.caseStatus = status + addCaseStatus.execute { + onComplete { + logger.showMessage(R.string.case_status_is_changed) + updateMenuLive.postValue(true) + } + onError { handleError(it) } + } + } + else if (dialogKey == CLEAR_CASES_TEST_DATA_DIALOG && actionName == MessageDialog.USER_ACTION_POSITIVE_CLICKED) { + clearAllCaseStatuses.execute { + onComplete { + updateMenuLive.postValue(true) + needToRecreate = true + } + onError { + handleError(it) + } + } + } + else return super.handleAction(action) + + return true + } + + val updateMenuLive = MutableLiveData() + + val hasBackBtn = ObservableBoolean(false) + val hasSettingsBtn = ObservableBoolean(true) + val title = ObservableField(App.appContext.resources.getString(R.string.menu)) + var currentCaseId = -1 + var needToRecreate = false + + fun openCaseStatusDialog(caseLabel: String) { + val dialog = createCaseStatusDialog(caseLabel) + showDialog(dialog) + } + + fun openClearCasesTestDataDialog() { + val dialog = createClearCasesTestDataDialog() + showDialog(dialog) + } + + private fun createCaseStatusDialog(caseLabel: String): MessageDialog { + val dialog = MessageDialog() + dialog.setRequestKey(CASE_STATUS_DIALOG) + dialog.setNegativeButton(R.string.cancel) + dialog.setTitle(R.string.case_status_dialog_title) + dialog.setMessage( + App.appContext.getString(R.string.case_status_dialog_message) + .format(caseLabel) + ) + dialog.setItems( + listOf( + App.appContext.getString(R.string.case_status_checked), + App.appContext.getString(R.string.case_status_failed), + App.appContext.getString(R.string.case_status_in_process), + App.appContext.getString(R.string.case_status_unchecked) + ) + ) + + return dialog + } + + private fun createClearCasesTestDataDialog(): MessageDialog { + val dialog = MessageDialog() + dialog.setRequestKey(CLEAR_CASES_TEST_DATA_DIALOG) + dialog.setPositiveButton(R.string.clear) + dialog.setNegativeButton(R.string.cancel) + dialog.setTitle(R.string.case_clear_all_dialog_title) + dialog.setMessage(R.string.case_clear_all_dialog_message) + + return dialog + } +} diff --git a/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationFragment.kt b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationFragment.kt new file mode 100644 index 00000000..90221f15 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationFragment.kt @@ -0,0 +1,66 @@ +package by.esas.tools.screens.baseui.navigation + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.checker.Checker +import by.esas.tools.checker.Checking +import by.esas.tools.databinding.FMainBaseuiNavigationBinding +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking + +class BaseUINavigationFragment: AppFragment() { + + companion object { + const val ARGUMENTS_DATA = "ARGUMENTS_DATA" + } + + override val fragmentDestinationId = R.id.baseuiNavigationFragment + + override fun provideLayoutId() = R.layout.f_main_baseui_navigation + + override fun provideViewModel(): BaseUINavigationVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(BaseUINavigationVM::class.java) + } + + override fun provideChecks(): List { + return listOf(FieldChecking(binding.fBaseeuiNavigationInputField, true)) + } + + override fun provideChecker(): Checker { + return AppChecker().setMode(false).setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + enableControls() + viewModel.navigateToSecondFragment(binding.fBaseeuiNavigationInputField.getText()) + } + }) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + getArgs() + + binding.fBaseeuiNavigationButtonWithoutArgs.setOnClickListener { + viewModel.navigateToSecondFragment() + } + binding.fBaseeuiNavigationButtonWithArgs.setOnClickListener { + provideChecker().validate(provideChecks()) + } + } + + private fun getArgs() { + val data = activity?.intent?.getStringExtra(ARGUMENTS_DATA) + if (data != null) { + Toast.makeText(requireContext(), "Get data: $data", Toast.LENGTH_LONG).show() + activity?.intent?.removeExtra(ARGUMENTS_DATA) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationSecondFragment.kt b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationSecondFragment.kt new file mode 100644 index 00000000..3226d1f6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationSecondFragment.kt @@ -0,0 +1,63 @@ +package by.esas.tools.screens.baseui.navigation + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.checker.Checker +import by.esas.tools.checker.Checking +import by.esas.tools.databinding.FMainBaseuiNavigationSecondBinding +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking + +class BaseUINavigationSecondFragment: AppFragment() { + override val fragmentDestinationId = R.id.baseuiNavigationSecondFragment + + override fun provideLayoutId() = R.layout.f_main_baseui_navigation_second + + override fun provideViewModel(): BaseUINavigationSecondVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(BaseUINavigationSecondVM::class.java) + } + + override fun provideChecks(): List { + return listOf( + FieldChecking(binding.fBaseuiNavigationSecondInputField, true) + ) + } + + override fun provideChecker(): Checker { + return AppChecker().setMode(false).setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + enableControls() + val bundle = Bundle() + bundle.putString(BaseUINavigationFragment.ARGUMENTS_DATA, binding.fBaseuiNavigationSecondInputField.getText()) + viewModel.popBack(0, inclusive = false, parameters = bundle) + } + }) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + getArgs() + + binding.fBaseuiNavigationSecondPopBackWithArgs.setOnClickListener { + provideChecker().validate(provideChecks()) + } + binding.fBaseuiNavigationSecondPopBack.setOnClickListener { + viewModel.popBack(destination = 0, inclusive = false, parameters = null) + } + binding.fBaseuiNavigationSecondPopBackInclusive.setOnClickListener { + viewModel.popBack(destination = R.id.baseuiNavigationFragment, inclusive = true, parameters = null) + } + } + + private fun getArgs() { + binding.fBaseuiNavigationSecondArgs.text = arguments?.get("date").toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationSecondVM.kt b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationSecondVM.kt new file mode 100644 index 00000000..eacffd86 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationSecondVM.kt @@ -0,0 +1,6 @@ +package by.esas.tools.screens.baseui.navigation + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class BaseUINavigationSecondVM @Inject constructor(): AppVM() {} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationVM.kt b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationVM.kt new file mode 100644 index 00000000..10654b64 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationVM.kt @@ -0,0 +1,12 @@ +package by.esas.tools.screens.baseui.navigation + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class BaseUINavigationVM @Inject constructor(): AppVM() { + fun navigateToSecondFragment(date: String = "") { + navigate(BaseUINavigationFragmentDirections.actionMenuFragmentToBaseuiNavigationSecondFragment( + date = date + )) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/baseui/other/BaseUIFunctionalityFragment.kt b/app/src/main/java/by/esas/tools/screens/baseui/other/BaseUIFunctionalityFragment.kt new file mode 100644 index 00000000..7eb24f43 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/other/BaseUIFunctionalityFragment.kt @@ -0,0 +1,174 @@ +package by.esas.tools.screens.baseui.other + +import android.Manifest +import android.os.Build +import android.os.Bundle +import android.view.View +import android.widget.Button +import android.widget.Toast +import androidx.activity.result.ActivityResultCallback +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainBaseuiFunctionalityBinding +import by.esas.tools.dialog.MessageDialog +import by.esas.tools.logger.Action +import by.esas.tools.logger.handler.ErrorAction +import by.esas.tools.logger.handler.ShowErrorType +import by.esas.tools.util.SwitchManager +import by.esas.tools.utils.logger.ErrorModel + +class BaseUIFunctionalityFragment: AppFragment() { + override val fragmentDestinationId = R.id.baseuiFunctionalityFragment + + override fun provideLayoutId() = R.layout.f_main_baseui_functionality + + override fun provideViewModel(): BaseUIFunctionalityVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(BaseUIFunctionalityVM::class.java) + } + + override fun provideSwitchableViews(): List { + return listOf( + binding.fBaseuiFunctionalityMessageDialogFragmentBtn, + binding.fBaseuiFunctionalityMessageDialogVmBtn, + binding.fBaseuiFunctionalityInputField, + binding.fBaseuiFunctionalityPermissionCheckBtn, + binding.fBaseuiFunctionalityPermissionRequestBtn, + binding.fBaseuiFunctionalityPermissionsGroupBtn + ) + } + + override var switcher: SwitchManager = object : SwitchManager(){ + override fun enableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = true + true + } else + super.enableView(view) + } + + override fun disableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = false + true + } else + super.disableView(view) + } + } + + override fun providePermissions(parameters: Bundle?): Array { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) + else + arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + + override fun providePermissionResultCallback(): ActivityResultCallback> { + return ActivityResultCallback> { result -> + if (result.all { it.value }) { + Toast.makeText(context, resources.getString(R.string.baseui_functionality_permissions_confirmed), Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(context, resources.getString(R.string.baseui_functionality_permissions_denied), Toast.LENGTH_SHORT).show() + } + } + } + + override fun enableControls(parameters: Bundle?) { + super.enableControls(parameters) + + binding.fBaseuiSwitcher.isChecked = true + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fBaseuiFunctionalityMessageDialogFragmentBtn.setOnClickListener { + val dialog = MessageDialog() + dialog.setMessage(resources.getString(R.string.baseui_functionality_test_message)) + dialog.setTitle(resources.getString(R.string.baseui_functionality_test_dialog)) + showDialog(dialog, "DIALOG") + } + + binding.fBaseuiFunctionalityMessageDialogVmBtn.setOnClickListener { + viewModel.showMessageDialog() + } + + binding.fBaseuiSwitcher.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) enableControls() + else disableControls() + } + + binding.fBaseuiFunctionalityPermissionCheckBtn.setOnClickListener { + if (checkPermissions(providePermissions(null), null, false)) + Toast.makeText(requireContext(), resources.getString(R.string.baseui_functionality_permissions_confirmed), Toast.LENGTH_LONG).show() + else + Toast.makeText(requireContext(), resources.getString(R.string.baseui_functionality_permissions_denied), Toast.LENGTH_LONG).show() + } + + binding.fBaseuiFunctionalityPermissionRequestBtn.setOnClickListener { + if (checkPermissions(providePermissions(null), null, false)) + Toast.makeText(requireContext(), resources.getString(R.string.baseui_functionality_permissions_already_confirmed), Toast.LENGTH_LONG).show() + else + handleAction(Action(Action.ACTION_CHECK_AND_REQUEST_PERMISSIONS)) + } + + binding.fBaseuiFunctionalityPermissionsGroupBtn.setOnClickListener { + val permissions = arrayOf(Manifest.permission.CAMERA, Manifest.permission.CALL_PHONE) + if (checkPermissions(permissions, null, false)) + Toast.makeText(requireContext(), resources.getString(R.string.baseui_functionality_permissions_already_confirmed), Toast.LENGTH_LONG).show() + else + checkPermissions(permissions, null, true) + } + + binding.fBaseuiFunctionalityVmErrorDialogBtn.setOnClickListener { + viewModel.handleError( + ErrorModel(400, AppErrorStatusEnum.UNKNOWN_ERROR), + showType = ShowErrorType.SHOW_ERROR_DIALOG.name + ) + } + binding.fBaseuiFunctionalityVmErrorMessageBtn.setOnClickListener { + viewModel.handleError( + ErrorModel(400, AppErrorStatusEnum.UNKNOWN_ERROR), + showType = ShowErrorType.SHOW_ERROR_MESSAGE.name + ) + } + binding.fBaseuiFunctionalityVmErrorNothingBtn.setOnClickListener { + viewModel.handleError( + ErrorModel(400, AppErrorStatusEnum.UNKNOWN_ERROR), + showType = ShowErrorType.SHOW_NOTHING.name + ) + } + + binding.fBaseuiFunctionalityErrorDialogBtn.setOnClickListener { + handleError( + ErrorAction.Companion.create( + ErrorModel(400, AppErrorStatusEnum.UNKNOWN_ERROR), + showType = ShowErrorType.SHOW_ERROR_DIALOG.name, + ErrorAction.ACTION_ERROR, + null + ) + ) + } + binding.fBaseuiFunctionalityErrorMessageBtn.setOnClickListener { + handleError( + ErrorAction.Companion.create( + ErrorModel(400, AppErrorStatusEnum.UNKNOWN_ERROR), + showType = ShowErrorType.SHOW_ERROR_MESSAGE.name, + ErrorAction.ACTION_ERROR, + null + ) + ) + } + binding.fBaseuiFunctionalityErrorNothingBtn.setOnClickListener { + handleError( + ErrorAction.Companion.create( + ErrorModel(400, AppErrorStatusEnum.UNKNOWN_ERROR), + showType = ShowErrorType.SHOW_NOTHING.name, + ErrorAction.ACTION_ERROR, + null + ) + ) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/baseui/other/BaseUIFunctionalityVM.kt b/app/src/main/java/by/esas/tools/screens/baseui/other/BaseUIFunctionalityVM.kt new file mode 100644 index 00000000..bd09e0e7 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/other/BaseUIFunctionalityVM.kt @@ -0,0 +1,17 @@ +package by.esas.tools.screens.baseui.other + +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.base.AppVM +import by.esas.tools.dialog.MessageDialog +import javax.inject.Inject + +class BaseUIFunctionalityVM @Inject constructor(): AppVM() { + + fun showMessageDialog() { + val dialog = MessageDialog() + dialog.setMessage(App.appContext.resources.getString(R.string.baseui_functionality_test_message)) + dialog.setTitle(App.appContext.resources.getString(R.string.baseui_functionality_test_dialog)) + showDialog(dialog) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/baseui/ui/BaseUIThemeFragment.kt b/app/src/main/java/by/esas/tools/screens/baseui/ui/BaseUIThemeFragment.kt new file mode 100644 index 00000000..c08ac75a --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/ui/BaseUIThemeFragment.kt @@ -0,0 +1,73 @@ +package by.esas.tools.screens.baseui.ui + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.app_data.AppSharedPrefs +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainBaseuiThemeBinding +import by.esas.tools.logger.Action +import by.esas.tools.screens.MainActivity +import by.esas.tools.util.configs.UiModeType + +class BaseUIThemeFragment: AppFragment() { + override val fragmentDestinationId = R.id.baseuiThemeFragment + + override fun provideLayoutId() = R.layout.f_main_baseui_theme + + override fun provideViewModel(): BaseUIThemeVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(BaseUIThemeVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setCheckedLang() + setCheckedTheme() + + binding.fBaseuiThemeSetLangBtn.setOnClickListener { + if (activity is MainActivity) + (activity as MainActivity).handleAction(Action(MainActivity.NEED_TO_UPDATE_MENU)) + viewModel.requestLanguageChange(getLanguage()) + } + binding.fBaseuiThemeSetModeBtn.setOnClickListener { + viewModel.requestThemeChange(getThemeMode()) + } + } + + private fun setCheckedLang() { + val checkedLang = AppSharedPrefs(App.instance).getLanguage() + when(checkedLang) { + "ru" -> binding.fBaseuiThemeLangRadioRussian.isChecked = true + "en" -> binding.fBaseuiThemeLangRadioEnglish.isChecked = true + } + } + + private fun setCheckedTheme() { + val checkedTheme = AppSharedPrefs(App.instance).getTheme() + when(checkedTheme) { + UiModeType.SYSTEM -> binding.fBaseuiThemeModeRadioSystem.isChecked = true + UiModeType.DAY -> binding.fBaseuiThemeModeRadioLight.isChecked = true + UiModeType.NIGHT -> binding.fBaseuiThemeModeRadioNight.isChecked = true + } + } + + private fun getLanguage(): String { + val selectedItem = binding.fBaseuiThemeLangRadioGroup.checkedRadioButtonId + return when(selectedItem) { + R.id.f_baseui_theme_lang_radio_russian -> "ru" + else -> "en" + } + } + + private fun getThemeMode(): UiModeType { + val selectedItem = binding.fBaseuiThemeModeRadioGroup.checkedRadioButtonId + return when(selectedItem) { + R.id.f_baseui_theme_mode_radio_light -> UiModeType.DAY + R.id.f_baseui_theme_mode_radio_night -> UiModeType.NIGHT + else -> UiModeType.SYSTEM + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/baseui/ui/BaseUIThemeVM.kt b/app/src/main/java/by/esas/tools/screens/baseui/ui/BaseUIThemeVM.kt new file mode 100644 index 00000000..4a241323 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/ui/BaseUIThemeVM.kt @@ -0,0 +1,6 @@ +package by.esas.tools.screens.baseui.ui + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class BaseUIThemeVM @Inject constructor(): AppVM() {} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionFragment.kt b/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionFragment.kt new file mode 100644 index 00000000..8850af92 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionFragment.kt @@ -0,0 +1,61 @@ +package by.esas.tools.screens.biometric + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.biometric_decryption.checkBiometricSupport +import by.esas.tools.databinding.FMainBiometricDecryptionBinding + +class BiometricDecryptionFragment : + AppFragment() { + override val fragmentDestinationId = R.id.biometricDecryptionFragment + + override fun provideLayoutId() = R.layout.f_main_biometric_decryption + + override fun provideViewModel(): BiometricDecryptionVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(BiometricDecryptionVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.showBiometricDialog = { block: (Fragment) -> Unit -> block(this) } + viewModel.isBiometricAvailable = checkBiometricSupport(requireContext()) { tag, msg -> + logger.log(tag, msg) + } + + binding.fBiometricDecryptionDialogBtn.setOnClickListener { + if (viewModel.isBiometricAvailable) + viewModel.showBiometricDialog() + else + Toast.makeText(requireContext(), + resources.getString(R.string.biometric_decryption_not_available), + Toast.LENGTH_SHORT + ).show() + } + + binding.fBiometricDecryptionCheckBtn.setOnClickListener { + val message = if (viewModel.isBiometricAvailable) resources.getString(R.string.biometric_decryption_available) + else resources.getString(R.string.biometric_decryption_not_available) + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + } + } + + override fun setupObservers() { + super.setupObservers() + viewModel.messageLive.observe(viewLifecycleOwner) { + Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() + } + } + + override fun onDestroy() { + super.onDestroy() + viewModel.showBiometricDialog = {} + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionVM.kt b/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionVM.kt new file mode 100644 index 00000000..dddbbdda --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionVM.kt @@ -0,0 +1,59 @@ +package by.esas.tools.screens.biometric + +import androidx.biometric.BiometricPrompt +import androidx.fragment.app.Fragment +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.base.AppVM +import by.esas.tools.biometric_decryption.Biometric +import javax.inject.Inject + +class BiometricDecryptionVM @Inject constructor() : AppVM() { + val messageLive = MutableLiveData() + var isBiometricAvailable = false + var showBiometricDialog: (block: (Fragment) -> Unit) -> Unit = {} + + fun showBiometricDialog() { + showBiometricDialog { + val biometricCallback = object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + logger.logInfo(errString.toString()) + messageLive.postValue(errString.toString()) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + logger.logInfo(App.appContext.resources.getString(R.string.biometric_decryption_authentication_succeeded)) + messageLive.postValue(App.appContext.resources.getString(R.string.biometric_decryption_authentication_succeeded)) + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + logger.logInfo(App.appContext.resources.getString(R.string.biometric_decryption_authentication_failed)) + messageLive.postValue(App.appContext.resources.getString(R.string.biometric_decryption_authentication_failed)) + } + } + val builder = Biometric( + fragment = it, + callback = biometricCallback, + listener = object : Biometric.BiometricUserInfo { + override fun getCurrentUser(): String { + return "" + } + + override fun setCurrentUserIv(iv: ByteArray) { + } + + override fun getCurrentUserIv(): ByteArray { + return byteArrayOf() + } + }) + val title = App.appContext.resources.getString(R.string.biometric_decryption_dialog_title) + val negativeText = App.appContext.resources.getString(R.string.biometric_decryption_dialog_close) + val info = builder.createInfo(title, negativeText) + builder.authenticate(Biometric.DialogMode.ENCRYPT_MODE, info) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/cardline/CardlineFragment.kt b/app/src/main/java/by/esas/tools/screens/cardline/CardlineFragment.kt new file mode 100644 index 00000000..e2e69720 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/cardline/CardlineFragment.kt @@ -0,0 +1,40 @@ +package by.esas.tools.screens.cardline + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainCardlineBinding + +class CardlineFragment : AppFragment() { + override val fragmentDestinationId = R.id.cardlineFragment + + override fun provideLayoutId(): Int = R.layout.f_main_cardline + + override fun provideViewModel(): CardlineVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(CardlineVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainCardlineChangeStyle.setOnClickListener { + binding.fMainCardlineStyle.apply { + setStartIcon(R.drawable.ic_star) + setStartIconColor(R.color.main_color_2) + setEndIcon(R.drawable.ic_star) + setEndIconColor(R.color.main_color_2) + setBottomDividerColor(R.color.main_color_2) + setTopDividerColor(R.color.main_color_2) + setCardTitleStyle(R.style.TextStyle) + setCardValueStyle(R.style.TextStyle) + setAllAlignVertical(0.5f) + } + it.isEnabled = false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/cardline/CardlineVM.kt b/app/src/main/java/by/esas/tools/screens/cardline/CardlineVM.kt new file mode 100644 index 00000000..df317911 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/cardline/CardlineVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.cardline + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class CardlineVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/cardline/dynamic/DynamicCardlineFragment.kt b/app/src/main/java/by/esas/tools/screens/cardline/dynamic/DynamicCardlineFragment.kt new file mode 100644 index 00000000..723b8def --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/cardline/dynamic/DynamicCardlineFragment.kt @@ -0,0 +1,219 @@ +package by.esas.tools.screens.cardline.dynamic + +import android.os.Bundle +import android.view.View +import android.widget.RadioButton +import android.widget.RadioGroup +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.cardline.CardLine +import by.esas.tools.databinding.FMainDynamicCardlineBinding +import by.esas.tools.utils.getDimensInDp +import by.esas.tools.utils.getFloatValue + +class DynamicCardlineFragment : AppFragment() { + override val fragmentDestinationId = R.id.dynamicCardlineFragment + + override fun provideLayoutId() = R.layout.f_main_dynamic_cardline + + override fun provideViewModel(): DynamicCardlineVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(DynamicCardlineVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setTopDividerSettingsLay() + setBottomDividerSettingsLay() + setStartIconSettingsLay() + setEndIconSettingsLay() + + binding.fDynamicCardlineButtonCreate.setOnClickListener { + binding.fDynamicCardlineContainer.removeAllViews() + binding.fDynamicCardlineContainer.addView(createCustomCardline()) + } + + binding.fDynamicCardlineButtonCreateDefault.setOnClickListener { + binding.fDynamicCardlineContainer.removeAllViews() + binding.fDynamicCardlineContainer.addView(createDefaultCardline()) + } + + } + + private fun createDefaultCardline(): CardLine { + return CardLine(requireContext()).apply { + this.setDefaultValues() + } + } + + private fun createCustomCardline(): CardLine { + val cardLine = CardLine(requireContext()) + + cardLine.setCardTitle(binding.fDynamicCardlineTitle.getText()) + val titleStyle = + getTextStyle(binding.fDynamicCardlineSpinnerTitleStyle.selectedItem.toString()) + cardLine.setCardTitleStyle(titleStyle) + cardLine.isTitleSingleLine(binding.fDynamicCardlineTitleSingleCheck.isChecked) + setTitleWidthPercent(cardLine) + + cardLine.setCardValue(binding.fDynamicCardlineValue.getText()) + val valueStyle = + getTextStyle(binding.fDynamicCardlineSpinnerValueStyle.selectedItem.toString()) + cardLine.setCardValueStyle(valueStyle) + cardLine.isTitleSingleLine(binding.fDynamicCardlineValueSingleCheck.isChecked) + cardLine.setupTextAlignVertical(binding.fDynamicCardlineTitleValueBias.getFloatValue()) + + setTopDivider(cardLine) + setBottomDivider(cardLine) + + setStartIcon(cardLine) + setEndIcon(cardLine) + + setCardlineContainerPaddings(cardLine) + return cardLine + } + + private fun setBottomDividerSettingsLay() { + binding.fDynamicCardlineDivBottomCheck.setOnCheckedChangeListener { _, isChecked -> + val layVisibility = if (isChecked) View.VISIBLE else View.GONE + binding.fDynamicCardlineDivBottomLay.visibility = layVisibility + } + } + + private fun setTopDividerSettingsLay() { + binding.fDynamicCardlineDivTopCheck.setOnCheckedChangeListener { _, isChecked -> + val layVisibility = if (isChecked) View.VISIBLE else View.GONE + binding.fDynamicCardlineDivTopLay.visibility = layVisibility + } + } + + private fun getTextStyle(style: String): Int { + return when (style) { + resources.getString(R.string.style_1) -> R.style.CustomSwitcherTitleTextStyle + resources.getString(R.string.style_2) -> R.style.CustomSwitcherTextStyleBold + else -> R.style.CustomSwitcherTextStyleNormal + } + } + + private fun setTopDivider(cardLine: CardLine) { + if (binding.fDynamicCardlineDivTopCheck.isChecked) { + cardLine.setTopDividerHeight(getDimensInDp(binding.fDynamicCardlineDivTopHeight)) + val divColor = getColorFromRadioGroup(binding.fDynamicCardlineDivTopRadioGroup as RadioGroup) + cardLine.setTopDividerColorRes(divColor) + } + } + + private fun setBottomDivider(cardLine: CardLine) { + if (binding.fDynamicCardlineDivBottomCheck.isChecked) { + cardLine.setBottomDividerHeight(getDimensInDp(binding.fDynamicCardlineDivBottomHeight)) + val divColor = getColorFromRadioGroup(binding.fDynamicCardlineDivBottomRadioGroup as RadioGroup) + cardLine.setBottomDividerColorRes(divColor) + } + } + + private fun setCardlineContainerPaddings(cardLine: CardLine) { + cardLine.setContainerPaddings( + getDimensInDp(binding.fDynamicCardlinePaddingLeft), + getDimensInDp(binding.fDynamicCardlinePaddingTop), + getDimensInDp(binding.fDynamicCardlinePaddingRight), + getDimensInDp(binding.fDynamicCardlinePaddingBottom) + ) + } + + private fun setTitleWidthPercent(cardLine: CardLine) { + val percent = binding.fDynamicCardlineTitleWidthPercent.getText() + if (percent.isNotEmpty() && percent.toFloat() in 0f..1f) + cardLine.setupTitleWidthPercent(percent.toFloat()) + } + + private fun setStartIconSettingsLay() { + binding.fDynamicCardlineStartIconCheck.setOnCheckedChangeListener { _, isChecked -> + val layVisibility = if (isChecked) View.VISIBLE else View.GONE + binding.fDynamicCardlineStartIconLay.visibility = layVisibility + binding.fDynamicCardlineStartImagePaddingsLay1.visibility = layVisibility + binding.fDynamicCardlineStartImagePaddingsLay2.visibility = layVisibility + binding.fDynamicCardlineStartImageSizeLay.visibility = layVisibility + binding.fDynamicCardlineStartImagePaddingsText.visibility = layVisibility + } + } + + private fun getStartIconImage(): Int { + val checkedButtonId = binding.fDynamicCardlineStartImageGroup.checkedRadioButtonId + val checkedButton = + binding.fDynamicCardlineStartImageGroup.findViewById(checkedButtonId) + + return when (checkedButton) { + binding.fDynamicCardlineStartImageRadio1 -> R.drawable.ic_star + binding.fDynamicCardlineStartImageRadio2 -> R.drawable.ic_add + else -> R.drawable.ic_remove + } + } + + private fun setStartIcon(cardLine: CardLine) { + if (binding.fDynamicCardlineStartIconCheck.isChecked) { + cardLine.setStartIcon(getStartIconImage()) + cardLine.setStartIconColorRes(getColorFromRadioGroup(binding.fDynamicCardlineStartImageColorGroup as RadioGroup)) + cardLine.setStartIconPadding( + getDimensInDp(binding.fDynamicCardlineStartImagePaddingLeft), + getDimensInDp(binding.fDynamicCardlineStartImagePaddingTop), + getDimensInDp(binding.fDynamicCardlineStartImagePaddingRight), + getDimensInDp(binding.fDynamicCardlineStartImagePaddingBottom) + ) + cardLine.setStartIconSize(getDimensInDp(binding.fDynamicCardlineStartImageSize)) + cardLine.setStartIconAlignVertical(binding.fDynamicCardlineStartImageVerticalBias.getFloatValue()) + } + } + + private fun setEndIconSettingsLay() { + binding.fDynamicCardlineEndIconCheck.setOnCheckedChangeListener { _, isChecked -> + val layVisibility = if (isChecked) View.VISIBLE else View.GONE + binding.fDynamicCardlineEndIconLay.visibility = layVisibility + binding.fDynamicCardlineEndImagePaddingsLay1.visibility = layVisibility + binding.fDynamicCardlineEndImagePaddingsLay2.visibility = layVisibility + binding.fDynamicCardlineEndImageSizeLay.visibility = layVisibility + binding.fDynamicCardlineEndImagePaddingsText.visibility = layVisibility + } + } + + private fun getEndIconImage(): Int { + val checkedButtonId = binding.fDynamicCardlineEndImageGroup.checkedRadioButtonId + val checkedButton = + binding.fDynamicCardlineEndImageGroup.findViewById(checkedButtonId) + + return when (checkedButton) { + binding.fDynamicCardlineEndImageRadio1 -> R.drawable.ic_star + binding.fDynamicCardlineEndImageRadio2 -> R.drawable.ic_add + else -> R.drawable.ic_remove + } + } + + private fun setEndIcon(cardLine: CardLine) { + if (binding.fDynamicCardlineEndIconCheck.isChecked) { + cardLine.setEndIcon(getEndIconImage()) + cardLine.setEndIconColorRes(getColorFromRadioGroup(binding.fDynamicCardlineEndImageColorGroup as RadioGroup)) + cardLine.setEndIconPadding( + getDimensInDp(binding.fDynamicCardlineEndImagePaddingLeft), + getDimensInDp(binding.fDynamicCardlineEndImagePaddingTop), + getDimensInDp(binding.fDynamicCardlineEndImagePaddingRight), + getDimensInDp(binding.fDynamicCardlineEndImagePaddingBottom) + ) + cardLine.setEndIconSize(getDimensInDp(binding.fDynamicCardlineEndImageSize)) + cardLine.setEndIconAlignVertical(binding.fDynamicCardlineEndImageVerticalBias.getFloatValue()) + } + } + + private fun getColorFromRadioGroup(radioGroup: RadioGroup): Int { + val checkedButtonId = radioGroup.checkedRadioButtonId + val checkedButton = radioGroup.findViewById(checkedButtonId) + + return when (radioGroup.indexOfChild(checkedButton)) { + 0 -> R.color.orange + 1 -> R.color.purple + else -> R.color.red + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/cardline/dynamic/DynamicCardlineVM.kt b/app/src/main/java/by/esas/tools/screens/cardline/dynamic/DynamicCardlineVM.kt new file mode 100644 index 00000000..df5922c9 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/cardline/dynamic/DynamicCardlineVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.cardline.dynamic + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class DynamicCardlineVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/checker/CheckerInputFieldFragment.kt b/app/src/main/java/by/esas/tools/screens/checker/CheckerInputFieldFragment.kt new file mode 100644 index 00000000..46d4247c --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/checker/CheckerInputFieldFragment.kt @@ -0,0 +1,155 @@ +package by.esas.tools.screens.checker + +import android.os.Build +import android.os.Bundle +import android.view.View +import android.widget.AdapterView +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.checker.Checker +import by.esas.tools.checker.Checking +import by.esas.tools.checker.checks.BiggerCheck +import by.esas.tools.checker.checks.CustomCheck +import by.esas.tools.checker.checks.DateRangeCheck +import by.esas.tools.checker.checks.LengthCheck +import by.esas.tools.checker.checks.MinLengthCheck +import by.esas.tools.checker.checks.RangeCheck +import by.esas.tools.checker.checks.RegexCheck +import by.esas.tools.databinding.FMainCheckerBinding +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale +import java.util.TimeZone + +class CheckerInputFieldFragment : AppFragment() { + override val fragmentDestinationId = R.id.checkerFragment + + override fun provideLayoutId() = R.layout.f_main_checker + + override fun provideViewModel(): CheckerInputFieldVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(CheckerInputFieldVM::class.java) + } + + override fun provideChecks(): List { + return listOf( + FieldChecking(binding.fCheckerInputNotEmpty, true), + FieldChecking(binding.fCheckerInputRange) + .addCheck(RangeCheck(1.0, 999.0, resources.getString(R.string.checker_error_range))), + FieldChecking(binding.fCheckerInputRegex) + .addCheck(RegexCheck("^[a-zA-Zа-яА-ЯёЁ,._\\-+=?!]*\$", resources.getString(R.string.checker_error_regex))), + FieldChecking(binding.fCheckerInputLength) + .addCheck(LengthCheck(1, 20, resources.getString(R.string.checker_error_length))), + FieldChecking(binding.fCheckerInputBigger) + .addCheck(BiggerCheck(10, resources.getString(R.string.checker_error_bigger))), + FieldChecking(binding.fCheckerInputMinLength) + .addCheck(MinLengthCheck(5, resources.getString(R.string.checker_error_min_length))), + FieldChecking(binding.fCheckerInputCustom) + .addCheck( + CustomCheck( + { binding.fCheckerInputCustom.getText() == binding.fCheckerInputMinLength.getText() }, + resources.getString(R.string.checker_error_custom) + ) + ), + FieldChecking(binding.fCheckerInputDate).addCheck( + DateRangeCheck( + getCheckerDateRange().first, + getCheckerDateRange().second, + errorMessage = resources.getString(R.string.checker_error_date), + pattern = binding.fCheckerPatternSpinner.selectedItem.toString() + ) + ) + ) + } + + override fun provideChecker(): Checker { + return AppChecker().setMode(false).setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + enableControls() + Toast.makeText(context, resources.getString(R.string.checker_everything_correct), Toast.LENGTH_LONG).show() + } + }) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fCheckerInputDate.setText( + getFormattedDate( + getCalendarDate( + binding.fCheckerCalendar.year, + binding.fCheckerCalendar.month, + binding.fCheckerCalendar.dayOfMonth + ) + ) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + binding.fCheckerCalendar.setOnDateChangedListener { _, year, monthOfYear, dayOfMonth -> + val date = getCalendarDate(year, monthOfYear, dayOfMonth) + binding.fCheckerInputDate.setText(getFormattedDate(date)) + } + } + + binding.fCheckerPatternSpinner.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + val date = getCalendarDate( + binding.fCheckerCalendar.year, + binding.fCheckerCalendar.month, + binding.fCheckerCalendar.dayOfMonth + ) + binding.fCheckerInputDate.setText(getFormattedDate(date)) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + + binding.fCheckerCheckButton.setOnClickListener { + provideChecker().setMode(!binding.fCheckerSwitcher.isChecked).validate(provideChecks()) + } + } + + private fun getCalendarDate(year: Int, month: Int, day: Int): Date { + return Date(year - 1900, month, day) + } + + private fun getFormattedDate(date: Date): String { + val formatter = SimpleDateFormat( + binding.fCheckerPatternSpinner.selectedItem.toString(), + Locale.getDefault() + ) + formatter.timeZone = TimeZone.getDefault() + + return formatter.format(date) + } + + private fun getCheckerDateRange(): Pair { + val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) + formatter.timeZone = TimeZone.getDefault() + + val year = Calendar.getInstance().get(Calendar.YEAR) + val currentMonth = Calendar.getInstance().get(Calendar.MONTH) + 1 + val lastDayOfMonth = Calendar.getInstance().getActualMaximum(Calendar.DAY_OF_MONTH) + + val dateStart = formatter.parse("1-$currentMonth-$year") + val dateEnd = formatter.parse("$lastDayOfMonth-$currentMonth-$year") + + return Pair(dateStart.time, dateEnd.time) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/checker/CheckerInputFieldVM.kt b/app/src/main/java/by/esas/tools/screens/checker/CheckerInputFieldVM.kt new file mode 100644 index 00000000..d453e520 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/checker/CheckerInputFieldVM.kt @@ -0,0 +1,6 @@ +package by.esas.tools.screens.checker + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class CheckerInputFieldVM @Inject constructor(): AppVM() {} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchFragment.kt b/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchFragment.kt new file mode 100644 index 00000000..0a56ff88 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchFragment.kt @@ -0,0 +1,144 @@ +package by.esas.tools.screens.custom_switch + +import android.os.Bundle +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.Button +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.customswitch.CustomSwitch +import by.esas.tools.customswitch.ISwitchHandler +import by.esas.tools.databinding.FMainCustomSwitchBinding +import by.esas.tools.util.SwitchManager +import by.esas.tools.util.defocusAndHideKeyboard + +class CustomSwitchFragment : AppFragment() { + override val fragmentDestinationId = R.id.customSwitchFragment + override fun provideLayoutId() = R.layout.f_main_custom_switch + + override fun provideViewModel(): CustomSwitchVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(CustomSwitchVM::class.java) + } + + override var switcher: SwitchManager = object : SwitchManager() { + override fun enableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = true + true + } else + super.enableView(view) + } + + override fun disableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = false + true + } else + super.disableView(view) + } + } + + override fun provideSwitchableViews(): List { + return listOf( + binding.fMainSwitcherCheck, + binding.fMainSwitcherCheckField, + binding.fMainSwitcherStyle, + binding.fMainSwitcherEditTitle, + binding.fMainSwitcherEditInfo, + binding.fMainSwitcherEditTitleBtn, + binding.fMainSwitcherEditInfoBtn, + binding.fMainSwitcherChangeStyleButton + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainSwitcherCheck.setSwitchHandler(object : ISwitchHandler { + override fun onSwitchChange(isChecked: Boolean) { + val message = if (isChecked) resources.getString(R.string.custom_switch_status_on) + else resources.getString(R.string.custom_switch_status_off) + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + + override fun prepareToSwitchOn(): Boolean { + return binding.fMainSwitcherCheckField.getText().isNotEmpty() + } + }) + + binding.fMainSwitcherEditTitle.inputText?.setOnEditorActionListener { titleView, actionId, event -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + binding.fMainSwitcherStyle.setTitle(titleView.text.toString()) + defocusAndHideKeyboard(activity) + true + } else + false + } + binding.fMainSwitcherEditInfo.inputText?.setOnEditorActionListener { titleView, actionId, event -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + binding.fMainSwitcherStyle.setInfo(titleView.text.toString()) + true + } else + false + } + + binding.fMainSwitcherEditTitleBtn.setOnClickListener { + binding.fMainSwitcherStyle.setTitle(binding.fMainSwitcherEditTitle.getText()) + } + binding.fMainSwitcherEditInfoBtn.setOnClickListener { + binding.fMainSwitcherStyle.setInfo(binding.fMainSwitcherEditInfo.getText()) + } + + binding.fMainSwitcherLay.addView(createDisableSwitcher()) + } + + override fun setupObservers() { + super.setupObservers() + viewModel.styleIsNew.observe(viewLifecycleOwner) { + setCustomSwitcherStyle(it) + } + } + + private fun createDisableSwitcher(): CustomSwitch { + val newSwitcher = CustomSwitch(requireContext()) + newSwitcher.apply { + setTitle(resources.getString(R.string.custom_switch_disable_title)) + setTitleStyle(R.style.TextStyle) + setInfo(resources.getString(R.string.custom_switch_disable_instruction)) + setInfoStyle(R.style.HintTextStyle) + setDefaultValue() + switcherIsChecked(true) + setSwitchHandler(object : ISwitchHandler { + override fun onSwitchChange(isChecked: Boolean) { + when (isChecked) { + true -> enableControls() + false -> disableControls() + } + } + }) + } + return newSwitcher + } + + private fun setCustomSwitcherStyle(isNew: Boolean) { + if (isNew) + binding.fMainSwitcherStyle.apply { + setSwitchTrackTint(R.color.switcher_selector) + setSwitchThumbTint(R.color.switcher_thumb_selector) + setTitleStyle(R.style.CustomSwitcherTitleTextStyle) + setInfoStyle(R.style.CustomSwitcherInfoTextStyle) + } + else + binding.fMainSwitcherStyle.apply { + setSwitchTrackTint(R.color.switcher_selector_yellow_light) + setSwitchThumbTint(R.color.switcher_selector_yellow) + setTitleStyle(R.style.CustomSwitcherTextStyleBold) + setInfoStyle(R.style.CustomSwitcherTextStyleNormal) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchVM.kt b/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchVM.kt new file mode 100644 index 00000000..9d0d9e64 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchVM.kt @@ -0,0 +1,13 @@ +package by.esas.tools.screens.custom_switch + +import androidx.lifecycle.MutableLiveData +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class CustomSwitchVM @Inject constructor(): AppVM() { + val styleIsNew = MutableLiveData() + + fun updateSwitcherStyle(){ + styleIsNew.value = if (styleIsNew.value != null ) !styleIsNew.value!! else true + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/custom_switch/program/CustomSwitchProgramFragment.kt b/app/src/main/java/by/esas/tools/screens/custom_switch/program/CustomSwitchProgramFragment.kt new file mode 100644 index 00000000..0a28b1b4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/custom_switch/program/CustomSwitchProgramFragment.kt @@ -0,0 +1,129 @@ +package by.esas.tools.screens.custom_switch.program + +import android.os.Bundle +import android.view.View +import android.view.View.TEXT_ALIGNMENT_CENTER +import android.view.View.TEXT_ALIGNMENT_TEXT_END +import android.view.View.TEXT_ALIGNMENT_TEXT_START +import android.widget.RadioButton +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.customswitch.CustomSwitch +import by.esas.tools.customswitch.ISwitchHandler +import by.esas.tools.databinding.FMainCustomSwitchProgramBinding +import by.esas.tools.inputfieldview.dpToPx + +class CustomSwitchProgramFragment : AppFragment() { + override val fragmentDestinationId = R.id.customSwitchProgramFragment + + override fun provideLayoutId() = R.layout.f_main_custom_switch_program + + override fun provideViewModel(): CustomSwitchProgramVM { + return ViewModelProvider(this, viewModelFactory.provideFactory())[CustomSwitchProgramVM::class.java] + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fSwitcherProgramButtonCreate.setOnClickListener { + binding.fSwitcherProgramContainer.removeAllViews() + val customSwitch = createCustomSwitch() + customSwitch.setSwitchHandler(object : ISwitchHandler { + override fun onSwitchChange(isChecked: Boolean) { + val message = if (isChecked) resources.getString(R.string.custom_switch_status_on) + else resources.getString(R.string.custom_switch_status_off) + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } + }) + binding.fSwitcherProgramContainer.addView(customSwitch) + } + } + + private fun createCustomSwitch(): CustomSwitch { + val customSwitch = CustomSwitch(requireContext()) + customSwitch.setDefaultValue() + + customSwitch.setSwitchThumbTint(getThumdColor()) + customSwitch.setSwitchTrackTint(getTrackColor()) + + customSwitch.setTitle(binding.fSwitcherProgramTitle.getText()) + val titleStyle = getTextStyle(binding.fSwitcherProgramSpinnerTitle.selectedItem.toString()) + customSwitch.setTitleStyle(titleStyle) + customSwitch.setInfo(binding.fSwitcherProgramInfo.getText()) + val infoStyle = getTextStyle(binding.fSwitcherProgramSpinnerInfo.selectedItem.toString()) + customSwitch.setInfoStyle(infoStyle) + customSwitch.setInfoAlignment(getInfoAlignment()) + + setSwitcherPaddings(customSwitch) + setInfoPaddings(customSwitch) + return customSwitch + } + + private fun getThumdColor(): Int { + val checkedButtonId = binding.fSwitcherProgramThumbRadioGroup.checkedRadioButtonId + val checkedButton = binding.fSwitcherProgramThumbRadioGroup.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fSwitcherProgramThumbRadio1 -> R.color.switcher_selector_orange + binding.fSwitcherProgramThumbRadio2 -> R.color.switcher_thumb_selector + binding.fSwitcherProgramThumbRadio3 -> R.color.switcher_selector_red + binding.fSwitcherProgramThumbRadio4 -> R.color.switcher_selector_green + else -> R.color.switcher_selector_yellow + } + } + + private fun getTrackColor(): Int { + val checkedButtonId = binding.fSwitcherProgramTrackRadioGroup.checkedRadioButtonId + val checkedButton = binding.fSwitcherProgramTrackRadioGroup.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fSwitcherProgramTrackRadio1 -> R.color.switcher_selector_orange_light + binding.fSwitcherProgramTrackRadio2 -> R.color.switcher_selector + binding.fSwitcherProgramTrackRadio3 -> R.color.switcher_selector_holo_red + binding.fSwitcherProgramTrackRadio4 -> R.color.switcher_selector_green_light + else -> R.color.switcher_selector_yellow_light + } + } + + private fun getInfoAlignment(): Int { + val alignment = binding.fSwitcherProgramSpinnerAlignment.selectedItem.toString() + return when(alignment) { + resources.getString(R.string.alignment_left) -> TEXT_ALIGNMENT_TEXT_START + resources.getString(R.string.alignment_center) -> TEXT_ALIGNMENT_CENTER + else -> TEXT_ALIGNMENT_TEXT_END + } + } + + private fun setSwitcherPaddings(customSwitch: CustomSwitch) { + val left = getPaddings(binding.fSwitcherProgramPaddingsSwitcherLeft.getText()) + val top = getPaddings(binding.fSwitcherProgramPaddingsSwitcherTop.getText()) + val right = getPaddings(binding.fSwitcherProgramPaddingsSwitcherRight.getText()) + val bottom = getPaddings(binding.fSwitcherProgramPaddingsSwitcherBottom.getText()) + + customSwitch.setSwitchPaddings(left, top, right, bottom) + } + + private fun setInfoPaddings(customSwitch: CustomSwitch) { + val left = getPaddings(binding.fSwitcherProgramPaddingsInfoLeft.getText()) + val top = getPaddings(binding.fSwitcherProgramPaddingsInfoTop.getText()) + val right = getPaddings(binding.fSwitcherProgramPaddingsInfoRight.getText()) + val bottom = getPaddings(binding.fSwitcherProgramPaddingsInfoBottom.getText()) + + customSwitch.setInfoPaddings(left, top, right, bottom) + } + + private fun getPaddings(paddings: String): Int { + val paddings = if (paddings.isNotEmpty()) paddings.toInt() else 0 + return dpToPx(paddings).toInt() + } + + private fun getTextStyle(style: String): Int { + return when(style){ + resources.getString(R.string.style_1) -> R.style.CustomSwitcherTitleTextStyle + resources.getString(R.string.style_2) -> R.style.CustomSwitcherTextStyleBold + else -> R.style.CustomSwitcherTextStyleNormal + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/custom_switch/program/CustomSwitchProgramVM.kt b/app/src/main/java/by/esas/tools/screens/custom_switch/program/CustomSwitchProgramVM.kt new file mode 100644 index 00000000..c9fc6b89 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/custom_switch/program/CustomSwitchProgramVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.custom_switch.program + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class CustomSwitchProgramVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogFragment.kt b/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogFragment.kt new file mode 100644 index 00000000..011de599 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogFragment.kt @@ -0,0 +1,216 @@ +package by.esas.tools.screens.dialog + +import android.os.Bundle +import android.view.View +import android.widget.RadioButton +import android.widget.RadioGroup +import android.widget.Toast +import androidx.fragment.app.FragmentResultListener +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.baseui.Config +import by.esas.tools.databinding.FMainDynamicMessageDialogBinding +import by.esas.tools.dialog.MessageDialog +import by.esas.tools.dialog.MessageDialog.Companion.ITEM_NAME +import by.esas.tools.dialog.MessageDialog.Companion.USER_ACTION_ITEM_PICKED +import by.esas.tools.dialog.MessageDialog.Companion.USER_ACTION_NEGATIVE_CLICKED +import by.esas.tools.dialog.MessageDialog.Companion.USER_ACTION_NEUTRAL_CLICKED +import by.esas.tools.dialog.MessageDialog.Companion.USER_ACTION_POSITIVE_CLICKED +import by.esas.tools.logger.Action + +private const val DYNAMIC_MESSAGE_DIALOG = "DYNAMIC_MESSAGE_DIALOG" + +class DynamicMessageDialogFragment : + AppFragment() { + override val fragmentDestinationId = R.id.dynamicMessageDialogFragment + override fun provideLayoutId() = R.layout.f_main_dynamic_message_dialog + + override fun provideViewModel(): DynamicMessageDialogVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(DynamicMessageDialogVM::class.java) + } + + override fun provideRequestKeys(): List { + return listOf(DYNAMIC_MESSAGE_DIALOG, Config.ERROR_MESSAGE_DIALOG) + } + + override fun provideFragmentResultListener(requestKey: String): FragmentResultListener? { + return if (requestKey == DYNAMIC_MESSAGE_DIALOG) { + FragmentResultListener { key, result -> + val actionName = result.getString(by.esas.tools.dialog.Config.DIALOG_USER_ACTION) + if (!actionName.isNullOrBlank()) { + handleAction(Action(actionName, result)) + } else { + enableControls(result) + } + } + } else { + super.provideFragmentResultListener(requestKey) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setPositiveButtonLay() + setNegativeButtonLay() + setNeutralButtonLay() + + binding.fDynamicMessageDialogShowButton.setOnClickListener { + if (checkFieldsForNotEmpty()) + showDialog(createDialog(), DYNAMIC_MESSAGE_DIALOG) + else + Toast.makeText( + requireContext(), + resources.getString(R.string.message_dialog_dynamic_empty_dialog), + Toast.LENGTH_LONG + ).show() + } + } + + override fun handleAction(action: Action): Boolean { + when (action.name) { + USER_ACTION_POSITIVE_CLICKED -> Toast.makeText( + requireContext(), + resources.getString(R.string.message_dialog_positive_click), + Toast.LENGTH_SHORT + ).show() + USER_ACTION_NEGATIVE_CLICKED -> Toast.makeText( + requireContext(), + resources.getString(R.string.message_dialog_negative_click), + Toast.LENGTH_SHORT + ).show() + USER_ACTION_NEUTRAL_CLICKED -> Toast.makeText( + requireContext(), + resources.getString(R.string.message_dialog_neutral_click), + Toast.LENGTH_SHORT + ).show() + USER_ACTION_ITEM_PICKED -> Toast.makeText( + requireContext(), + action.parameters?.getString(ITEM_NAME), + Toast.LENGTH_SHORT + ).show() + else -> return super.handleAction(action) + } + return true + } + + private fun setPositiveButtonLay() { + binding.fDynamicMessageDialogBtnPositiveCheck.setOnCheckedChangeListener { _, isChecked -> + viewModel.btnPositiveLayVisibility.set(isChecked) + } + } + + private fun setNegativeButtonLay() { + binding.fDynamicMessageDialogBtnNegativeCheck.setOnCheckedChangeListener { _, isChecked -> + viewModel.btnNegativeLayVisibility.set(isChecked) + } + } + + private fun setNeutralButtonLay() { + binding.fDynamicMessageDialogBtnNeutralCheck.setOnCheckedChangeListener { _, isChecked -> + viewModel.btnNeutralLayVisibility.set(isChecked) + } + } + + private fun createDialog(): MessageDialog { + val dialog = MessageDialog() + dialog.setRequestKey(DYNAMIC_MESSAGE_DIALOG) + dialog.setTitle( + binding.fDynamicMessageDialogTitle.getText(), + getTextStyle(binding.fDynamicMessageDialogSpinnerTitle.selectedItem.toString()) + ) + dialog.setMessage( + binding.fDynamicMessageDialogMessage.getText(), + getTextStyle(binding.fDynamicMessageDialogSpinnerMessage.selectedItem.toString()) + ) + dialog.setPositiveButton( + binding.fDynamicMessageDialogBtnPositive.getText(), + appearance = getPositiveButtonAppearance() + ) + dialog.setNegativeButton( + binding.fDynamicMessageDialogBtnNegative.getText(), + getNegativeButtonAppearance() + ) + dialog.setNeutralButton( + binding.fDynamicMessageDialogBtnNeutral.getText(), + getNeutralButtonAppearance() + ) + dialog.isCancelable = binding.fDynamicMessageDialogCloseCheck.isChecked + setDialogItems(dialog) + + return dialog + } + + private fun getTextStyle(style: String): Int { + return when (style) { + resources.getString(R.string.style_1) -> R.style.CustomSwitcherTitleTextStyle + resources.getString(R.string.style_2) -> R.style.CustomSwitcherTextStyleBold + else -> R.style.CustomSwitcherTextStyleNormal + } + } + + private fun getPositiveButtonAppearance(): MessageDialog.ButtonAppearance? { + return if (binding.fDynamicMessageDialogBtnPositiveCheck.isChecked) + MessageDialog.ButtonAppearance( + getTextStyle(binding.fDynamicMessageDialogBtnPositiveSpinner.selectedItem.toString()), + getColorFromRadioGroup(binding.fDynamicMessageDialogBtnPositiveColor as RadioGroup), + binding.fDynamicMessageDialogBtnPositiveCaps.isChecked + ) else + null + } + + private fun getNegativeButtonAppearance(): MessageDialog.ButtonAppearance? { + return if (binding.fDynamicMessageDialogBtnNegativeCheck.isChecked) + MessageDialog.ButtonAppearance( + getTextStyle(binding.fDynamicMessageDialogBtnNegativeSpinner.selectedItem.toString()), + getColorFromRadioGroup(binding.fDynamicMessageDialogBtnNegativeColor as RadioGroup), + binding.fDynamicMessageDialogBtnNegativeCaps.isChecked + ) else + null + } + + private fun getNeutralButtonAppearance(): MessageDialog.ButtonAppearance? { + return if (binding.fDynamicMessageDialogBtnNeutralCheck.isChecked) + MessageDialog.ButtonAppearance( + getTextStyle(binding.fDynamicMessageDialogBtnNeutralSpinner.selectedItem.toString()), + getColorFromRadioGroup(binding.fDynamicMessageDialogBtnNeutralColor as RadioGroup), + binding.fDynamicMessageDialogBtnNeutralCaps.isChecked + ) else + null + } + + private fun setDialogItems(dialog: MessageDialog) { + if (binding.fDynamicMessageDialogItemsListCheck.isChecked) + dialog.setItems( + listOf( + resources.getString(R.string.item_1), + resources.getString(R.string.item_2), + resources.getString(R.string.item_3) + ) + ) + } + + private fun getColorFromRadioGroup(radioGroup: RadioGroup): Int { + val checkedButtonId = radioGroup.checkedRadioButtonId + val checkedButton = radioGroup.findViewById(checkedButtonId) + + return when (radioGroup.indexOfChild(checkedButton)) { + 0 -> R.color.orange + 1 -> R.color.purple + else -> R.color.red + } + } + + private fun checkFieldsForNotEmpty(): Boolean { + return binding.fDynamicMessageDialogTitle.getText().isNotEmpty() || + binding.fDynamicMessageDialogMessage.getText().isNotEmpty() || + binding.fDynamicMessageDialogBtnPositive.getText().isNotEmpty() || + binding.fDynamicMessageDialogBtnNeutral.getText().isNotEmpty() || + binding.fDynamicMessageDialogBtnNegative.getText().isNotEmpty() || + binding.fDynamicMessageDialogItemsListCheck.isChecked + } +} diff --git a/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogVM.kt b/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogVM.kt new file mode 100644 index 00000000..bcc319bd --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogVM.kt @@ -0,0 +1,11 @@ +package by.esas.tools.screens.dialog + +import androidx.databinding.ObservableBoolean +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class DynamicMessageDialogVM @Inject constructor(): AppVM() { + val btnNeutralLayVisibility = ObservableBoolean(false) + val btnPositiveLayVisibility = ObservableBoolean(false) + val btnNegativeLayVisibility = ObservableBoolean(false) +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/dialog/bottom_dialog/BottomDialogFragment.kt b/app/src/main/java/by/esas/tools/screens/dialog/bottom_dialog/BottomDialogFragment.kt new file mode 100644 index 00000000..1f4efe75 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/dialog/bottom_dialog/BottomDialogFragment.kt @@ -0,0 +1,74 @@ +package by.esas.tools.screens.dialog.bottom_dialog + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.fragment.app.FragmentResultListener +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainBottomDialogBinding +import by.esas.tools.dialog.Config +import by.esas.tools.dialog.Config.DIALOG_USER_ACTION +import by.esas.tools.dialog.CustomBottomDialog +import by.esas.tools.logger.Action + +private const val BOTTOM_DIALOG = "BOTTOM_DIALOG" + +class BottomDialogFragment: AppFragment() { + override val fragmentDestinationId = R.id.bottomDialogFragment + + override fun provideLayoutId() = R.layout.f_main_bottom_dialog + + override fun provideViewModel(): BottomDialogVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(BottomDialogVM::class.java) + } + + override fun provideRequestKeys(): List = listOf(BOTTOM_DIALOG) + + override fun provideFragmentResultListener(requestKey: String): FragmentResultListener? { + return if (requestKey == BOTTOM_DIALOG) { + FragmentResultListener { _, result -> + val actionName = result.getString(DIALOG_USER_ACTION) + if (!actionName.isNullOrBlank()) { + handleAction(Action(actionName, result)) + } else { + enableControls(result) + } + } + } else { + super.provideFragmentResultListener(requestKey) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.bottomDialogFrag.setOnClickListener { + showDialog(createBottomDialog(), BOTTOM_DIALOG) + } + + binding.bottomDialogVm.setOnClickListener { + viewModel.showBottomDialog(BOTTOM_DIALOG) + } + } + + override fun handleAction(action: Action): Boolean { + when (action.name) { + Config.DISMISS_DIALOG -> Toast.makeText( + requireContext(), + action.parameters?.getString(CustomBottomDialog.BOTTOM_DIALOG_CHECK_RESULT), + Toast.LENGTH_LONG + ).show() + else -> return super.handleAction(action) + } + return true + } + + private fun createBottomDialog(): CustomBottomDialog { + return CustomBottomDialog().apply { + setRequestKey(BOTTOM_DIALOG) + isCancelable = false + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/dialog/bottom_dialog/BottomDialogVM.kt b/app/src/main/java/by/esas/tools/screens/dialog/bottom_dialog/BottomDialogVM.kt new file mode 100644 index 00000000..a46a7618 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/dialog/bottom_dialog/BottomDialogVM.kt @@ -0,0 +1,15 @@ +package by.esas.tools.screens.dialog.bottom_dialog + +import by.esas.tools.base.AppVM +import by.esas.tools.dialog.CustomBottomDialog +import javax.inject.Inject + +class BottomDialogVM @Inject constructor(): AppVM() { + + fun showBottomDialog(requestKey: String) { + showDialog(CustomBottomDialog().apply { + setRequestKey(requestKey) + isCancelable = false + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/domain/DomainCaseFragment.kt b/app/src/main/java/by/esas/tools/screens/domain/DomainCaseFragment.kt new file mode 100644 index 00000000..6e1e3d37 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/domain/DomainCaseFragment.kt @@ -0,0 +1,105 @@ +package by.esas.tools.screens.domain + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.checker.Checker +import by.esas.tools.checker.Checking +import by.esas.tools.databinding.FMainDomainCaseBinding +import by.esas.tools.logger.handler.ErrorHandler +import by.esas.tools.util.TAGk +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking +import by.esas.tools.utils.logger.ErrorModel + +class DomainCaseFragment : AppFragment() { + override val fragmentDestinationId = R.id.domainCaseFragment + override fun provideLayoutId() = R.layout.f_main_domain_case + + override fun provideViewModel(): DomainCaseVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(DomainCaseVM::class.java) + } + + override fun provideChecks(): List { + return listOf( + FieldChecking(binding.fDomainCaseNumber, true), + FieldChecking(binding.fDomainCaseRange, true) + ) + } + + override fun provideErrorHandler(): ErrorHandler { + return object : ErrorHandler() { + + override fun getErrorMessage(error: ErrorModel): String { + return resources.getString(R.string.domain_case_fail) + } + + override fun getErrorMessage(e: Throwable): String { + return resources.getString(R.string.domain_case_fail) + } + + override fun mapError(e: Throwable): ErrorModel { + return viewModel.provideMapper().mapErrorException(this.TAGk, e) + } + } + } + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fDomainCasePlayBtn.setOnClickListener { + AppChecker().setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + enableControls() + viewModel.playRandomNumber( + binding.fDomainCaseRange.getText().toInt(), + binding.fDomainCaseNumber.getText().toInt() + ) + } + }).validate(provideChecks()) + } + binding.fDomainCaseEncryptAesBtn.setOnClickListener { + val checks = listOf(FieldChecking(binding.fDomainCaseInputEncryptAes, true)) + + AppChecker().setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + enableControls() + val aesResult = viewModel.encryptAES(binding.fDomainCaseInputEncryptAes.getText()) + binding.fDomainCaseEncryptAesData.text = resources.getString(R.string.domain_case_encrypt) + aesResult.first + binding.fDomainCaseDecryptAesData.text = resources.getString(R.string.domain_case_decrypt) + aesResult.second + } + }).validate(checks) + } + + binding.fDomainCaseEncryptRsaBtn.setOnClickListener { + val checks = listOf(FieldChecking(binding.fDomainCaseInputEncryptRsa, true)) + AppChecker().setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + enableControls() + val rsaResult = viewModel.encryptRSA(binding.fDomainCaseInputEncryptRsa.getText()) + binding.fDomainCaseEncryptRsaData.text = resources.getString(R.string.domain_case_encrypt) + rsaResult.first + binding.fDomainCaseDecryptRsaData.text = resources.getString(R.string.domain_case_decrypt) + rsaResult.second + } + }).validate(checks) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/domain/DomainCaseVM.kt b/app/src/main/java/by/esas/tools/screens/domain/DomainCaseVM.kt new file mode 100644 index 00000000..d901f139 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/domain/DomainCaseVM.kt @@ -0,0 +1,54 @@ +package by.esas.tools.screens.domain + +import android.util.Base64 +import by.esas.tools.R +import by.esas.tools.app_domain.usecase.PlayRandomUseCase +import by.esas.tools.base.AppVM +import by.esas.tools.dialog.MessageDialog +import by.esas.tools.domain.util.decryptAES +import by.esas.tools.domain.util.decryptRSA +import by.esas.tools.domain.util.encryptAES +import by.esas.tools.domain.util.generateKey +import by.esas.tools.domain.util.generateRSAKey +import by.esas.tools.logger.handler.ShowErrorType +import javax.inject.Inject + +class DomainCaseVM @Inject constructor( + private val playRandom: PlayRandomUseCase +) : AppVM() { + + fun playRandomNumber(range: Int, number: Int) { + playRandom.rangeNumbers = range + playRandom.number = number + playRandom.execute { + onComplete { result -> + val dialog = MessageDialog() + dialog.setMessage(result) + dialog.setTitle(R.string.domain_case_congratulations) + dialog.setPositiveButton(R.string.ok) + showDialog(dialog) + } + onError { errorModel -> + handleError(errorModel, showType = ShowErrorType.SHOW_ERROR_DIALOG.name) + } + } + } + + fun encryptAES(data: String): Pair { + val secret = generateKey() + val encryptData = encryptAES(data, secret) + val decryptData = decryptAES(encryptData, secret) + + return Pair(encryptData, decryptData) + } + + fun encryptRSA(data: String): Pair { + val pairKey = generateRSAKey() + val publicKey: String = Base64.encodeToString(pairKey.public.encoded, Base64.NO_WRAP) + val privateStr: String = Base64.encodeToString(pairKey.private.encoded, Base64.NO_WRAP) + val encryptData = by.esas.tools.domain.util.encryptRSA(data, publicKey) + val decryptData = decryptRSA(encryptData, privateStr) + + return Pair(encryptData, decryptData) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/inputfield_view/end_icon/InputfieldViewEndIconFragment.kt b/app/src/main/java/by/esas/tools/screens/inputfield_view/end_icon/InputfieldViewEndIconFragment.kt new file mode 100644 index 00000000..cf8343bc --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/end_icon/InputfieldViewEndIconFragment.kt @@ -0,0 +1,110 @@ +package by.esas.tools.screens.inputfield_view.end_icon + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainInputfieldviewEndIconBinding +import by.esas.tools.inputfieldview.InputFieldView + +class InputfieldViewEndIconFragment : + AppFragment() { + override val fragmentDestinationId = R.id.inputfieldViewEndIconFragment + + override fun provideLayoutId() = R.layout.f_main_inputfieldview_end_icon + + override fun provideViewModel(): InputfieldViewEndIconVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get( + InputfieldViewEndIconVM::class.java + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fInputfieldviewEndCase4Input.setEndIconClickListener(object : + InputFieldView.IconClickListener { + override fun onIconClick() { + Toast.makeText( + requireContext(), + resources.getString(R.string.inputfieldview_end_icon_click), + Toast.LENGTH_SHORT + ).show() + } + }) + + binding.fInputfieldviewEndCase5Input.setEndIconClickListener(object : + InputFieldView.IconClickListener { + override fun onIconClick() { + Toast.makeText( + requireContext(), + resources.getString(R.string.inputfieldview_end_icon_click), + Toast.LENGTH_SHORT + ).show() + } + }) + + binding.fInputfieldviewEndCase6Input.setEndIconCheckListener(object : + InputFieldView.IconCheckedListener { + override fun onCheckChanged(isChanged: Boolean) { + val message = + if (isChanged) resources.getString(R.string.inputfieldview_end_icon_is_checked) + else resources.getString(R.string.inputfieldview_end_icon_is_unchecked) + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + } + }) + + binding.fInputfieldviewEndSetErrorButton.setOnClickListener { + setErrors() + } + binding.fInputfieldviewEndResetErrorButton.setOnClickListener { + resetErrors() + } + binding.fInputfieldviewEndChangeStyleButton.setOnClickListener { + changeStyles() + } + } + + private fun setErrors() { + binding.fInputfieldviewEndCase1Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewEndCase2Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewEndCase3Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewEndCase4Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewEndCase5Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewEndCase6Input.setError(resources.getString(R.string.error_text)) + } + + private fun resetErrors() { + binding.fInputfieldviewEndCase1Input.setError(null) + binding.fInputfieldviewEndCase2Input.setError(null) + binding.fInputfieldviewEndCase3Input.setError(null) + binding.fInputfieldviewEndCase4Input.setError(null) + binding.fInputfieldviewEndCase5Input.setError(null) + binding.fInputfieldviewEndCase6Input.setError(null) + } + + private fun changeStyles() { + binding.fInputfieldviewEndCase2Input.apply { + setEndIconDrawable(R.drawable.ic_close) + setEndIconTintRes(R.color.inputfieldview_selector_icon_color) + } + binding.fInputfieldviewEndCase3Input.apply { + setupIconsSize(by.esas.tools.inputfieldview.dpToPx(36).toInt()) + setEndIconTintRes(R.color.inputfieldview_selector_icon_color) + } + binding.fInputfieldviewEndCase4Input.apply { + setupIconsSize(by.esas.tools.inputfieldview.dpToPx(36).toInt()) + setEndIconDrawable(R.drawable.ic_search) + setEndIconTintRes(R.color.inputfieldview_selector_icon_color) + } + binding.fInputfieldviewEndCase5Input.apply { + setEndTextStyle(R.style.CustomSwitcherInfoTextStyle) + setEndText(resources.getString(R.string.action)) + } + binding.fInputfieldviewEndCase6Input.apply { + setEndIconTintRes(R.color.inputfieldview_selector_icon_color) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/inputfield_view/end_icon/InputfieldViewEndIconVM.kt b/app/src/main/java/by/esas/tools/screens/inputfield_view/end_icon/InputfieldViewEndIconVM.kt new file mode 100644 index 00000000..7d10873d --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/end_icon/InputfieldViewEndIconVM.kt @@ -0,0 +1,6 @@ +package by.esas.tools.screens.inputfield_view.end_icon + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class InputfieldViewEndIconVM @Inject constructor(): AppVM() {} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/inputfield_view/input_field/InputfieldViewFragment.kt b/app/src/main/java/by/esas/tools/screens/inputfield_view/input_field/InputfieldViewFragment.kt new file mode 100644 index 00000000..793b1ecf --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/input_field/InputfieldViewFragment.kt @@ -0,0 +1,97 @@ +package by.esas.tools.screens.inputfield_view.input_field + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainInputfieldviewBinding +import by.esas.tools.inputfieldview.InputFieldView + +class InputfieldViewFragment: AppFragment() { + override val fragmentDestinationId = R.id.inputfieldViewFragment + + override fun provideLayoutId() = R.layout.f_main_inputfieldview + + override fun provideViewModel(): InputfieldViewVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(InputfieldViewVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fInputfieldviewCase2.setStartIconClickListener(object : InputFieldView.IconClickListener { + override fun onIconClick() { + Toast.makeText( + requireContext(), + resources.getString(R.string.inputfieldview_start_icon_click), + Toast.LENGTH_SHORT + ).show() + } + }) + + binding.fInputfieldviewSetErrorButton.setOnClickListener { + setErrors() + } + binding.fInputfieldviewResetErrorButton.setOnClickListener { + resetErrors() + } + binding.fInputfieldviewChangeStyleButton.setOnClickListener { + changeStyle() + } + } + + private fun setErrors() { + binding.fInputfieldviewCase1.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewCase2.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewCase3.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewCase4.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewCase5.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewCase6.setError(resources.getString(R.string.error_text)) + } + + private fun resetErrors() { + binding.fInputfieldviewCase1.setError(null) + binding.fInputfieldviewCase2.setError(null) + binding.fInputfieldviewCase3.setError(null) + binding.fInputfieldviewCase4.setError(null) + binding.fInputfieldviewCase5.setError(null) + binding.fInputfieldviewCase6.setError(null) + } + + private fun changeStyle() { + binding.fInputfieldviewCase2.apply { + setInputLabel(R.string.inputfieldview_new_label) + setLabelStyle(R.style.CustomSwitcherTextStyleBold) + setStartIconDrawable(R.drawable.ic_search) + setStartIconTintRes(R.color.yellow) + setEndIconDrawable(R.drawable.ic_close) + setEndIconTintRes(R.color.yellow) + setHelpStyle(R.style.CustomSwitcherInfoTextStyle) + inputBox?.setBgColorRes(R.color.orange_light) + } + binding.fInputfieldviewCase3.apply { + inputBox?.setStrokeColorRes(R.color.yellow) + setStartIconTintRes(R.color.yellow) + setEndIconDrawable(R.drawable.ic_search) + setEndIconTintRes(R.color.yellow) + setInputStyle(R.style.CustomSwitcherTextStyleNormal) + } + binding.fInputfieldviewCase4.apply { + setStartIconTintRes(R.color.purple) + setEndText(resources.getString(R.string.action)) + setEndTextStyle(R.style.CustomSwitcherTitleTextStyle) + } + binding.fInputfieldviewCase5.apply { + setInputLabel(R.string.inputfieldview_new_label) + setupIconsSize(by.esas.tools.inputfieldview.dpToPx(36).toInt()) + inputBox?.setStrokeColorRes(R.color.purple) + } + binding.fInputfieldviewCase6.apply { + setInputLabel(R.string.inputfieldview_new_label) + setLabelType(InputFieldView.LABEL_TYPE_ON_TOP) + setHelp(R.string.test_text) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/inputfield_view/input_field/InputfieldViewVM.kt b/app/src/main/java/by/esas/tools/screens/inputfield_view/input_field/InputfieldViewVM.kt new file mode 100644 index 00000000..41dfe134 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/input_field/InputfieldViewVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.inputfield_view.input_field + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class InputfieldViewVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/inputfield_view/start_icon/InputfieldViewStartIconFragment.kt b/app/src/main/java/by/esas/tools/screens/inputfield_view/start_icon/InputfieldViewStartIconFragment.kt new file mode 100644 index 00000000..01ecd4b6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/start_icon/InputfieldViewStartIconFragment.kt @@ -0,0 +1,83 @@ +package by.esas.tools.screens.inputfield_view.start_icon + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainInputfieldviewStartIconBinding +import by.esas.tools.inputfieldview.InputFieldView + +class InputfieldViewStartIconFragment: AppFragment() { + override val fragmentDestinationId = R.id.inputfieldViewStartIconFragment + + override fun provideLayoutId() = R.layout.f_main_inputfieldview_start_icon + + override fun provideViewModel(): InputfieldViewStartIconVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(InputfieldViewStartIconVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fInputfieldviewStartCase2Input.setStartIconCheckListener(object : + InputFieldView.IconCheckedListener { + override fun onCheckChanged(isChanged: Boolean) { + val message = + if (isChanged) resources.getString(R.string.inputfieldview_start_icon_is_checked) + else resources.getString(R.string.inputfieldview_start_icon_is_unchecked) + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() + } + }) + binding.fInputfieldviewStartCase3Input.setStartIconClickListener(object : + InputFieldView.IconClickListener { + override fun onIconClick() { + Toast.makeText( + requireContext(), + resources.getString(R.string.inputfieldview_start_icon_click), + Toast.LENGTH_SHORT + ).show() + } + }) + binding.fInputfieldviewStartSetErrorButton.setOnClickListener { + setErrors() + } + binding.fInputfieldviewStartResetErrorButton.setOnClickListener { + resetErrors() + } + binding.fInputfieldviewStartChangeStyleButton.setOnClickListener { + changeStyles() + } + } + + private fun changeStyles() { + binding.fInputfieldviewStartCase2Input.apply { + setStartIconTintRes(R.color.inputfieldview_selector_icon_color) + } + binding.fInputfieldviewStartCase3Input.apply { + setupIconsSize(by.esas.tools.inputfieldview.dpToPx(36).toInt()) + setStartIconTintRes(R.color.inputfieldview_selector_icon_color) + setStartIconDrawable(R.drawable.ic_search) + } + binding.fInputfieldviewStartCase5Input.apply { + setStartIconTintRes(R.color.inputfieldview_selector_icon_color) + } + } + + private fun setErrors() { + binding.fInputfieldviewStartCase1Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewStartCase2Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewStartCase3Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewStartCase4Input.setError(resources.getString(R.string.error_text)) + binding.fInputfieldviewStartCase5Input.setError(resources.getString(R.string.error_text)) + } + + private fun resetErrors() { + binding.fInputfieldviewStartCase1Input.setError(null) + binding.fInputfieldviewStartCase2Input.setError(null) + binding.fInputfieldviewStartCase3Input.setError(null) + binding.fInputfieldviewStartCase4Input.setError(null) + binding.fInputfieldviewStartCase5Input.setError(null) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/inputfield_view/start_icon/InputfieldViewStartIconVM.kt b/app/src/main/java/by/esas/tools/screens/inputfield_view/start_icon/InputfieldViewStartIconVM.kt new file mode 100644 index 00000000..bd603d2d --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/start_icon/InputfieldViewStartIconVM.kt @@ -0,0 +1,6 @@ +package by.esas.tools.screens.inputfield_view.start_icon + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class InputfieldViewStartIconVM @Inject constructor(): AppVM() {} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/listheader/ListheaderFragment.kt b/app/src/main/java/by/esas/tools/screens/listheader/ListheaderFragment.kt new file mode 100644 index 00000000..3e9dd606 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/listheader/ListheaderFragment.kt @@ -0,0 +1,104 @@ +package by.esas.tools.screens.listheader + +import android.os.Build +import android.os.Bundle +import android.view.View +import android.widget.Button +import android.widget.TextView +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.customswitch.ISwitchHandler +import by.esas.tools.databinding.FMainListheaderBinding +import by.esas.tools.listheader.ListHeader +import by.esas.tools.util.SwitchManager + +class ListheaderFragment: AppFragment() { + override val fragmentDestinationId = R.id.listheaderFragment + + override fun provideLayoutId() = R.layout.f_main_listheader + + override fun provideViewModel(): ListheaderVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(ListheaderVM::class.java) + } + + override var switcher = object : SwitchManager(){ + override fun enableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = true + true + } else + super.enableView(view) + } + + override fun disableView(view: View): Boolean { + return if (view is Button){ + view.isEnabled = false + true + } else + super.disableView(view) + } + } + + override fun provideSwitchableViews(): List { + return listOf( + binding.fMainListheader1, + binding.fMainListheader2, + binding.fMainListheader3, + binding.fMainListheaderAddButton + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainListheaderSwitcher.switcherIsChecked(true) + binding.fMainListheaderSwitcher.setSwitchHandler(object : ISwitchHandler { + override fun onSwitchChange(isChecked: Boolean) { + if (isChecked) + enableControls() + else + disableControls() + } + }) + + binding.fMainListheader1.addOpenedListener(object : ListHeader.ListOpenedListener { + override fun onListStateChanged(isOpen: Boolean) { + if (isOpen) + binding.fMainListheader1.setArrowIcon(R.drawable.ic_arrow_up) + else + binding.fMainListheader1.setArrowIcon(R.drawable.ic_arrow_down) + } + }) + binding.fMainListheaderAddButton.setOnClickListener { + binding.fMainListheader1.addChild(createTestTextView()) + } + + binding.fMainListheader2.setArrowListener { + Toast.makeText(requireContext(), resources.getString(R.string.listheader_icon_click), Toast.LENGTH_SHORT).show() + } + + binding.fMainListheader3.addOpenedListener(object : ListHeader.ListOpenedListener { + override fun onListStateChanged(isOpen: Boolean) { + if (isOpen) + binding.fMainListheaderHiddenLay3.visibility = View.VISIBLE + else + binding.fMainListheaderHiddenLay3.visibility = View.GONE + } + }) + binding.fMainListheader3.setActionListener{ + Toast.makeText(requireContext(), resources.getString(R.string.listheader_action_click), Toast.LENGTH_SHORT).show() + } + } + + private fun createTestTextView(): TextView { + return TextView(requireContext()).apply { + text = resources.getString(R.string.listheader_test_text) + if (Build.VERSION.SDK_INT >= 23) + setTextAppearance(R.style.HintTextStyle) + else + setTextAppearance(this.context, R.style.HintTextStyle) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/listheader/ListheaderVM.kt b/app/src/main/java/by/esas/tools/screens/listheader/ListheaderVM.kt new file mode 100644 index 00000000..a9341cd0 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/listheader/ListheaderVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.listheader + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class ListheaderVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/listheader/dynamic/DynamicListheaderFragment.kt b/app/src/main/java/by/esas/tools/screens/listheader/dynamic/DynamicListheaderFragment.kt new file mode 100644 index 00000000..685a509d --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/listheader/dynamic/DynamicListheaderFragment.kt @@ -0,0 +1,148 @@ +package by.esas.tools.screens.listheader.dynamic + +import android.os.Build +import android.os.Bundle +import android.view.View +import android.widget.RadioButton +import android.widget.TextView +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainDynamicListheaderBinding +import by.esas.tools.listheader.ListHeader +import by.esas.tools.utils.getDimensInDp + +class DynamicListheaderFragment() : + AppFragment() { + override val fragmentDestinationId = R.id.dynamicListheaderFragment + + override fun provideLayoutId() = R.layout.f_main_dynamic_listheader + + override fun provideViewModel(): DynamicListheaderVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(DynamicListheaderVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fDynamicListheaderButtonCreate.setOnClickListener { + binding.fDynamicListheaderContainer.removeAllViews() + binding.fDynamicListheaderContainer.addView(createListheader()) + } + + binding.fDynamicListheaderButtonCreateDefault.setOnClickListener { + binding.fDynamicListheaderContainer.removeAllViews() + binding.fDynamicListheaderContainer.addView(createDefaultListheader()) + } + } + + private fun createListheader(): ListHeader { + val listHeader = ListHeader(requireContext()) + listHeader.setListTitle(binding.fDynamicListheaderTitle.getText()) + listHeader.setListActionText(binding.fDynamicListheaderActionText.getText()) + listHeader.setActionListener{ + Toast.makeText(requireContext(), resources.getString(R.string.listheader_action_click), Toast.LENGTH_SHORT).show() + } + listHeader.setListTitleStyle(getStyle(binding.fDynamicListheaderSpinnerTitle.selectedItem.toString())) + listHeader.setListActionStyle(getStyle(binding.fDynamicListheaderSpinnerActionText.selectedItem.toString())) + listHeader.setArrowIcon(getActionImages().first) + listHeader.setArrowIconSize(getDimensInDp(binding.fDynamicListheaderImageSize)) + listHeader.setArrowIconTintResource(getActionImageTint()) + listHeader.addOpenedListener(object : ListHeader.ListOpenedListener { + override fun onListStateChanged(isOpen: Boolean) { + when(isOpen) { + true -> listHeader.setArrowIcon(getActionImages().first) + false -> listHeader.setArrowIcon(getActionImages().second) + } + } + }) + setListheaderPaddings(listHeader) + setListheaderMargins(listHeader) + + listHeader.setDefaultContainerListener() + listHeader.setArrowClickable(true) + listHeader.setArrowListener { + Toast.makeText(requireContext(), resources.getString(R.string.listheader_icon_click), Toast.LENGTH_SHORT).show() + } + + listHeader.addChild(createTestTextView()) + listHeader.addChild(createTestTextView()) + listHeader.addChild(createTestTextView()) + + return listHeader + } + + private fun createDefaultListheader(): ListHeader { + return ListHeader(requireContext()).apply { + this.setDefaultValues() + this.setActionListener{ + Toast.makeText(requireContext(), resources.getString(R.string.listheader_action_click), Toast.LENGTH_SHORT).show() + } + this.addChild(createTestTextView()) + this.addChild(createTestTextView()) + this.addChild(createTestTextView()) + } + } + + private fun getStyle(style: String): Int { + return when(style){ + resources.getString(R.string.style_1) -> R.style.CustomSwitcherTitleTextStyle + resources.getString(R.string.style_2) -> R.style.CustomSwitcherTextStyleBold + else -> R.style.CustomSwitcherTextStyleNormal + } + } + + private fun getActionImages(): Pair { + val checkedButtonId = binding.fDynamicListheaderActionImage.checkedRadioButtonId + val checkedButton = binding.fDynamicListheaderActionImage.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fDynamicListheaderImageRadio1 -> Pair(R.drawable.ic_arrow_upward, R.drawable.ic_arrow_downward) + binding.fDynamicListheaderImageRadio2 -> Pair(R.drawable.ic_remove, R.drawable.ic_add) + else -> Pair(R.drawable.ic_arrow_drop_up, R.drawable.ic_arrow_drop_down) + } + } + + private fun getActionImageTint(): Int { + val checkedButtonId = binding.fDynamicListheaderTintRadioGroup.checkedRadioButtonId + val checkedButton = binding.fDynamicListheaderTintRadioGroup.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fDynamicListheaderTintRadio1 -> R.color.orange + binding.fDynamicListheaderTintRadio2 -> R.color.purple + binding.fDynamicListheaderTintRadio3 -> R.color.red + binding.fDynamicListheaderTintRadio4 -> R.color.green + else -> R.color.yellow + } + } + + private fun setListheaderPaddings(listHeader: ListHeader) { + listHeader.setupPaddings( + getDimensInDp(binding.fDynamicListheaderPaddingLeft), + getDimensInDp(binding.fDynamicListheaderPaddingTop), + getDimensInDp(binding.fDynamicListheaderPaddingRight), + getDimensInDp(binding.fDynamicListheaderPaddingBottom) + ) + } + + private fun setListheaderMargins(listHeader: ListHeader) { + listHeader.setChildrenMargins( + getDimensInDp(binding.fDynamicListheaderMarginTop), + getDimensInDp(binding.fDynamicListheaderPaddingBottom) + ) + } + + private fun createTestTextView(): TextView { + return TextView(requireContext()).apply { + text = resources.getString(R.string.listheader_test_text) + if (Build.VERSION.SDK_INT >= 23) + setTextAppearance(R.style.HintTextStyle) + else + setTextAppearance(this.context, R.style.HintTextStyle) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/listheader/dynamic/DynamicListheaderVM.kt b/app/src/main/java/by/esas/tools/screens/listheader/dynamic/DynamicListheaderVM.kt new file mode 100644 index 00000000..97fa934b --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/listheader/dynamic/DynamicListheaderVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.listheader.dynamic + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class DynamicListheaderVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/logger/LoggerFragment.kt b/app/src/main/java/by/esas/tools/screens/logger/LoggerFragment.kt new file mode 100644 index 00000000..6cd6ea98 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/LoggerFragment.kt @@ -0,0 +1,147 @@ +package by.esas.tools.screens.logger + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import by.esas.tools.R +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import by.esas.tools.base.AppFragment +import by.esas.tools.checker.Checker +import by.esas.tools.databinding.FMainLoggerBinding +import by.esas.tools.entity.LogItem +import by.esas.tools.logger.ILogger +import by.esas.tools.logger.handler.ErrorAction +import by.esas.tools.logger.handler.ShowErrorType +import by.esas.tools.screens.logger.recycler.LogItemAdapter +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking +import by.esas.tools.utils.logger.ErrorModel + +class LoggerFragment : AppFragment() { + override val fragmentDestinationId = R.id.loggerFragment + + override fun provideLayoutId() = R.layout.f_main_logger + + override fun provideViewModel(): LoggerVM { + return ViewModelProvider(this, viewModelFactory.provideFactory())[LoggerVM::class.java] + } + + override var logger: ILogger<*> = object : ILogger { + override var currentTag = "CaseLoggerImpl" + + override fun log(tag: String, msg: String, level: Int) { + setLog(LogItem(tag, "", msg)) + } + + override fun logCategory(category: String, tag: String, msg: String) { + setLog(LogItem(tag, category, msg)) + } + + override fun logError(throwable: Throwable) { + setLog(LogItem("", ILogger.CATEGORY_ERROR, throwable.message.toString())) + } + + override fun logError(error: ErrorModel) { + setLog(LogItem("", ILogger.CATEGORY_ERROR, error.statusEnum)) + } + + override fun sendLogs(msg: String) { + setLog(LogItem("", "", msg)) + } + + override fun showMessage(msg: String, length: Int) { + Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() + } + + override fun showMessage(msgRes: Int, length: Int) { + Toast.makeText(requireContext(), resources.getString(msgRes), Toast.LENGTH_SHORT).show() + } + } + + private val logAdapter = LogItemAdapter() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecycler() + setupVMRecycler() + + binding.fLoggerTagBtn.setOnClickListener { + AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + logger.log(binding.fLoggerTag.getText(), binding.fLoggerTagMessage.getText()) + } + }).validate(listOf(FieldChecking(binding.fLoggerTag), FieldChecking(binding.fLoggerTagMessage))) + } + binding.fLoggerCategoryBtn.setOnClickListener { + AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + logger.logCategory( + binding.fLoggerCategory.getText(), + binding.fLoggerCategoryTag.getText(), + binding.fLoggerCategoryMessage.getText() + ) + } + }).validate( + listOf( + FieldChecking(binding.fLoggerCategory), + FieldChecking(binding.fLoggerCategoryTag), + FieldChecking(binding.fLoggerCategoryMessage) + ) + ) + } + binding.fLoggerErrorBtn.setOnClickListener { + handleError( + ErrorAction.Companion.create( + ErrorModel(0, AppErrorStatusEnum.UNKNOWN_ERROR), + showType = ShowErrorType.SHOW_ERROR_DIALOG.name, + ErrorAction.ACTION_ERROR, + null + ) + ) + } + binding.fLoggerToastBtn.setOnClickListener { + AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + logger.showMessage(binding.fLoggerToastMessage.getText()) + } + }).validate(listOf(FieldChecking(binding.fLoggerToastMessage))) + } + binding.fLoggerVmTagBtn.setOnClickListener { + viewModel.logTag(listOf(FieldChecking(binding.fLoggerVmTag),FieldChecking(binding.fLoggerVmTagMessage))) + } + binding.fLoggerVmCategoryBtn.setOnClickListener { + viewModel.logCategory(listOf( + FieldChecking(binding.fLoggerVmCategory), + FieldChecking(binding.fLoggerVmCategoryTag), + FieldChecking(binding.fLoggerVmCategoryMessage) + )) + } + binding.fLoggerVmToastBtn.setOnClickListener { + viewModel.showMessage(listOf(FieldChecking(binding.fLoggerVmToastMessage))) + } + } + + override fun setupObservers() { + super.setupObservers() + viewModel.needScrolling.observe(viewLifecycleOwner) { + if (isBindingInitialized()) binding.fLoggerLogRecyclerVm.scrollToPosition(viewModel.logVMAdapter.itemList.size - 1) + } + } + + private fun setupRecycler() { + binding.fLoggerLogRecycler.adapter = logAdapter + binding.fLoggerLogRecycler.layoutManager = LinearLayoutManager(requireContext()) + } + + private fun setupVMRecycler() { + binding.fLoggerLogRecyclerVm.adapter = viewModel.logVMAdapter + binding.fLoggerLogRecyclerVm.layoutManager = LinearLayoutManager(requireContext()) + } + + private fun setLog(log: LogItem) { + logAdapter.addItem(log) + if (isBindingInitialized()) binding.fLoggerLogRecycler.scrollToPosition(logAdapter.itemList.size - 1) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/logger/LoggerVM.kt b/app/src/main/java/by/esas/tools/screens/logger/LoggerVM.kt new file mode 100644 index 00000000..a906ec80 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/LoggerVM.kt @@ -0,0 +1,95 @@ +package by.esas.tools.screens.logger + +import android.widget.Toast +import androidx.databinding.ObservableField +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import by.esas.tools.base.AppVM +import by.esas.tools.checker.Checker +import by.esas.tools.entity.LogItem +import by.esas.tools.logger.ILogger +import by.esas.tools.screens.logger.recycler.LogItemAdapter +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking +import by.esas.tools.utils.logger.ErrorModel +import javax.inject.Inject + +class LoggerVM @Inject constructor(): AppVM() { + + val message = ObservableField() + val tag = ObservableField() + val tagMessage = ObservableField() + val category = ObservableField() + val categoryTag = ObservableField() + val categoryMessage = ObservableField() + val toastMessage = ObservableField() + + val logVMAdapter = LogItemAdapter() + val needScrolling = MutableLiveData() + + override var logger: ILogger<*> = object : ILogger { + override var currentTag = "CaseLoggerVMImpl" + + override fun log(tag: String, msg: String, level: Int) { + setLog(LogItem(tag = tag, message = msg)) + } + + override fun logCategory(category: String, tag: String, msg: String) { + setLog(LogItem(category = category, tag = tag, message = msg)) + } + + override fun logError(throwable: Throwable) { + setLog(LogItem(category = ILogger.CATEGORY_ERROR, message = throwable.message.toString())) + } + + override fun logError(error: ErrorModel) { + setLog(LogItem(category = ILogger.CATEGORY_ERROR, message = error.statusEnum)) + } + + override fun sendLogs(msg: String) { + setLog(LogItem(message = msg)) + } + + override fun showMessage(msg: String, length: Int) { + Toast.makeText(App.appContext, msg, length).show() + } + + override fun showMessage(msgRes: Int, length: Int) { + Toast.makeText(App.appContext, App.appContext.getString(msgRes), length).show() + } + } + + fun logTag(checks: List) { + AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + logger.log(tag.get().toString(), tagMessage.get().toString()) + } + }).validate(checks) + } + + fun logCategory(checks: List) { + AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + logger.logCategory(category.get().toString(), categoryTag.get().toString(), categoryMessage.get().toString()) + } + }).validate(checks) + } + + fun logError() { + handleError(ErrorModel(0, AppErrorStatusEnum.UNKNOWN_ERROR)) + } + + fun showMessage(checks: List) { + AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + logger.showMessage(toastMessage.get().toString(), Toast.LENGTH_SHORT) + } + }).validate(checks) + } + + private fun setLog(log: LogItem) { + logVMAdapter.addItem(log) + needScrolling.value = true + } +} diff --git a/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemAdapter.kt b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemAdapter.kt new file mode 100644 index 00000000..20c27682 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemAdapter.kt @@ -0,0 +1,15 @@ +package by.esas.tools.screens.logger.recycler + +import android.view.ViewGroup +import by.esas.tools.entity.LogItem +import by.esas.tools.recycler.BaseRecyclerAdapter +import by.esas.tools.recycler.BaseViewHolder + +class LogItemAdapter: BaseRecyclerAdapter() { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseViewHolder { + return LogItemViewHolder.create(parent, LogItemViewModel()) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemViewHolder.kt b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemViewHolder.kt new file mode 100644 index 00000000..8ffbdc2a --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemViewHolder.kt @@ -0,0 +1,25 @@ +package by.esas.tools.screens.logger.recycler + +import android.view.LayoutInflater +import android.view.ViewGroup +import by.esas.tools.BR +import by.esas.tools.databinding.ILogItemBinding +import by.esas.tools.entity.LogItem +import by.esas.tools.recycler.BaseViewHolder + +class LogItemViewHolder(binding: ILogItemBinding, viewModel: LogItemViewModel): BaseViewHolder(binding, viewModel) { + + companion object { + fun create( + parent: ViewGroup, + viewModel: LogItemViewModel + ): LogItemViewHolder { + val binding = ILogItemBinding.inflate(LayoutInflater.from(parent.context),parent,false) + return LogItemViewHolder( + binding, viewModel + ) + } + } + + override fun provideVariable() = BR.item +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemViewModel.kt b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemViewModel.kt new file mode 100644 index 00000000..a6cab0fc --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemViewModel.kt @@ -0,0 +1,24 @@ +package by.esas.tools.screens.logger.recycler + +import androidx.databinding.ObservableField +import by.esas.tools.entity.LogItem +import by.esas.tools.recycler.BaseItemViewModel +import java.lang.StringBuilder + +class LogItemViewModel: BaseItemViewModel() { + val log = ObservableField() + + override fun bindItem(item: LogItem, position: Int) { + this.position.set(position) + log.set(setLogText(item.tag, item.category, item.message)) + } + + private fun setLogText(tag: String, category: String, message: String): String { + val text = StringBuilder() + if (tag.isNotEmpty()) text.append("Tag: $tag, ") + if (category.isNotEmpty()) text.append("category: $category, ") + text.append("message: $message") + + return text.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/menu/MenuFragment.kt b/app/src/main/java/by/esas/tools/screens/menu/MenuFragment.kt new file mode 100644 index 00000000..d7a1f834 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/MenuFragment.kt @@ -0,0 +1,165 @@ +package by.esas.tools.screens.menu + +import android.content.Context +import android.os.Bundle +import android.view.View +import androidx.core.view.children +import androidx.fragment.app.FragmentResultListener +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.baseui.Config.ERROR_MESSAGE_DIALOG +import by.esas.tools.databinding.FragmentMenuBinding +import by.esas.tools.dialog.SearchFilterDialog +import by.esas.tools.inputfieldview.InputFieldView +import by.esas.tools.logger.Action +import by.esas.tools.screens.MainActivity +import by.esas.tools.screens.MainActivity.Companion.CURRENT_CASE_ID +import by.esas.tools.screens.MainActivity.Companion.NEED_TO_UPDATE_CURRENT_CASE +import by.esas.tools.screens.MainActivity.Companion.NEED_TO_UPDATE_MENU +import by.esas.tools.screens.menu.recycler.CaseAdapter +import by.esas.tools.util.defocusAndHideKeyboard +import com.google.android.material.chip.Chip + +class MenuFragment : AppFragment() { + + private val caseAdapter = CaseAdapter( + onClick = { item -> + logger.logInfo("${item.name} clicked") + if (activity is MainActivity) { + val bundle = Bundle() + bundle.putInt(CURRENT_CASE_ID, item.id) + (activity as MainActivity).handleAction(Action(NEED_TO_UPDATE_CURRENT_CASE, bundle)) + } + navController?.navigate(item.id) + } + ) + + override val fragmentDestinationId: Int = R.id.menuFragment + + override fun provideLayoutId(): Int { + return R.layout.fragment_menu + } + + override fun provideViewModel(): MenuVM { + return ViewModelProvider(this, viewModelFactory.provideFactory())[MenuVM::class.java] + } + + override fun provideRequestKeys(): List { + return listOf(ERROR_MESSAGE_DIALOG, SearchFilterDialog.FILTER_DIALOG_KEY) + } + + override fun provideFragmentResultListener(requestKey: String): FragmentResultListener? { + return if (requestKey == SearchFilterDialog.FILTER_DIALOG_KEY) { + FragmentResultListener { _, result -> + val statusesResult = + result.getStringArray(SearchFilterDialog.ACTION_SELECT_FILTER_STATUSES) + val modulesResult = + result.getStringArray(SearchFilterDialog.ACTION_SELECT_FILTER_MODULES) + if (statusesResult != null && modulesResult != null) { + viewModel.onFilterChanged(statusesResult.toList(), modulesResult.toList()) + } else { + enableControls(result) + } + } + } else { + super.provideFragmentResultListener(requestKey) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupCaseRecycler() + setupSearchView() + val needUpdate = activity?.intent?.getBooleanExtra(NEED_TO_UPDATE_MENU, false) ?: false + if (needUpdate) { + viewModel.setCases() + activity?.intent?.removeExtra(NEED_TO_UPDATE_MENU) + } + } + + override fun setupObservers() { + super.setupObservers() + viewModel.casesListLive.observe(viewLifecycleOwner) { list -> + caseAdapter.setItems(list) + } + viewModel.filterStatusesLive.observe(viewLifecycleOwner) { list -> + updateFilterStatuses(list) + } + viewModel.filterModulesLive.observe(viewLifecycleOwner) { list -> + updateFilterModules(list) + } + } + + private fun setupSearchView() { + binding.fMenuCasesSearch.apply { + setupEditorActionListener(object : InputFieldView.EditorActionListener { + override fun onActionClick() { + defocusAndHideKeyboard(activity) + viewModel.onSearchChanged(viewModel.search) + } + }) + setStartIconClickListener(object : InputFieldView.IconClickListener { + override fun onIconClick() { + viewModel.onSearchChanged(viewModel.search) + } + }) + setEndIconClickListener(object : InputFieldView.IconClickListener { + override fun onIconClick() { + val filterDialog = SearchFilterDialog() + filterDialog.setRequestKey(SearchFilterDialog.FILTER_DIALOG_KEY) + filterDialog.setSelectedItems( + binding.fMenuFilterStatuses.children.map { (it as Chip).text.toString() }.toList(), + binding.fMenuFilterModules.children.map { (it as Chip).text.toString() }.toList() + ) + viewModel.showDialog(filterDialog) + } + }) + } + } + + private fun setupCaseRecycler() { + binding.fMenuRecycler.apply { + adapter = caseAdapter + layoutManager = LinearLayoutManager(this@MenuFragment.requireContext()) + } + binding.fMenuRecycler.hasFixedSize() + } + + private fun updateFilterStatuses(statuses: List) { + binding.fMenuFilterStatuses.removeAllViews() + statuses.forEach { + val chip = createFilterChip(binding.fMenuFilterStatuses.context, it) + chip.setOnCloseIconClickListener { + viewModel.deleteFilterStatusItem(chip.text.toString()) + } + binding.fMenuFilterStatuses.addView(chip) + } + } + + private fun updateFilterModules(modules: List) { + binding.fMenuFilterModules.removeAllViews() + modules.forEach { + val chip = createFilterChip(binding.fMenuFilterModules.context, it) + chip.setOnCloseIconClickListener { + viewModel.deleteFilterModuleItem(chip.text.toString()) + } + binding.fMenuFilterModules.addView(chip) + } + } + + private fun createFilterChip(context: Context, text: String): Chip { + val chip = Chip(context) + chip.setCloseIconResource(R.drawable.ic_close) + chip.isCloseIconVisible = true + chip.setChipIconTintResource(R.color.main_color_2) + chip.setChipBackgroundColorResource(R.color.main_color_4) + chip.setTextAppearance(R.style.TextStyle_Normal16) + chip.textEndPadding = 0f + chip.text = text + + return chip + } +} diff --git a/app/src/main/java/by/esas/tools/screens/menu/MenuVM.kt b/app/src/main/java/by/esas/tools/screens/menu/MenuVM.kt new file mode 100644 index 00000000..cd18f899 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/MenuVM.kt @@ -0,0 +1,112 @@ +package by.esas.tools.screens.menu + +import androidx.databinding.ObservableBoolean +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.app_domain.usecase.FilterCaseUseCase +import by.esas.tools.app_domain.usecase.GetCaseItemsUseCase +import by.esas.tools.app_domain.usecase.SearchCaseUseCase +import by.esas.tools.base.AppVM +import by.esas.tools.entity.CaseItemInfo +import by.esas.tools.entity.TestStatusEnum +import by.esas.tools.logger.Action +import javax.inject.Inject + +class MenuVM @Inject constructor( + private val searchCase: SearchCaseUseCase, + private val filterCase: FilterCaseUseCase, + private val getCaseItems: GetCaseItemsUseCase +) : AppVM() { + + var prevSearch = "" + var search = "" + var isEmpty = ObservableBoolean(false) + val casesListLive = MutableLiveData>() + val filterStatusesLive = MutableLiveData>() + val filterModulesLive = MutableLiveData>() + + private val allCases: MutableList = mutableListOf() + + init { + setCases() + } + + fun setCases() { + allCases.clear() + getCaseItems.execute { + onComplete { + allCases.addAll(it) + updateAdapter(it) + } + onError { handleError(error = it) } + } + } + + fun onSearchChanged(value: String) { + if (prevSearch != value) { + disableControls() + prevSearch = value + searchCase.caseItems = allCases + searchCase.search = search + searchCase.execute { + onComplete { itemsList -> + updateAdapter(itemsList) + enableControls() + } + onError { + handleError(error = it) + } + } + } + } + + fun onFilterChanged(statuses: List, modules: List) { + filterStatusesLive.value = statuses + filterModulesLive.value = modules + + if (statuses.isEmpty() && modules.isEmpty() && allCases.size != casesListLive.value?.size) + updateAdapter(allCases) + else { + filterCase.caseItems = allCases + filterCase.statuses = statuses.map { + when(it) { + App.appContext.getString(R.string.case_status_checked) -> TestStatusEnum.CHECKED + App.appContext.getString(R.string.case_status_in_process) -> TestStatusEnum.IN_PROCESS + App.appContext.getString(R.string.case_status_failed) -> TestStatusEnum.FAILED + else -> TestStatusEnum.UNCHECKED + } + } + filterCase.modules = modules + + filterCase.execute { + onComplete { itemsList -> + updateAdapter(itemsList) + enableControls() + } + onError { + handleError(error = it) + } + } + } + } + + fun deleteFilterModuleItem(item: String) { + filterModulesLive.value?.toMutableList()?.let { + it.remove(item) + onFilterChanged(filterStatusesLive.value?.toList() ?: emptyList(), it) + } + } + + fun deleteFilterStatusItem(item: String) { + filterStatusesLive.value?.toMutableList()?.let { + it.remove(item) + onFilterChanged(it, filterModulesLive.value ?: emptyList()) + } + } + + private fun updateAdapter(list: List) { + casesListLive.value = list + isEmpty.set(list.isEmpty()) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseAdapter.kt b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseAdapter.kt new file mode 100644 index 00000000..b2d2db38 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseAdapter.kt @@ -0,0 +1,17 @@ +package by.esas.tools.screens.menu.recycler + +import android.view.ViewGroup +import by.esas.tools.entity.CaseItemInfo +import by.esas.tools.recycler.BaseRecyclerAdapter +import by.esas.tools.recycler.BaseViewHolder + +class CaseAdapter(onClick: (CaseItemInfo) -> Unit) : + BaseRecyclerAdapter(onItemClick = onClick) { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseViewHolder { + return CaseViewHolder.create(parent, CaseViewModel()) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewHolder.kt b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewHolder.kt new file mode 100644 index 00000000..8032fbc3 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewHolder.kt @@ -0,0 +1,24 @@ +package by.esas.tools.screens.menu.recycler + +import android.view.LayoutInflater +import android.view.ViewGroup +import by.esas.tools.databinding.ICaseBinding +import by.esas.tools.recycler.BaseViewHolder +import by.esas.tools.BR +import by.esas.tools.entity.CaseItemInfo + +class CaseViewHolder(binding: ICaseBinding, viewModel: CaseViewModel): BaseViewHolder(binding, viewModel) { + companion object { + fun create( + parent: ViewGroup, + viewModel: CaseViewModel + ): CaseViewHolder{ + val binding = ICaseBinding.inflate(LayoutInflater.from(parent.context),parent,false) + return CaseViewHolder( + binding, viewModel + ) + } + } + + override fun provideVariable(): Int = BR.item +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewModel.kt b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewModel.kt new file mode 100644 index 00000000..bbadfaee --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewModel.kt @@ -0,0 +1,19 @@ +package by.esas.tools.screens.menu.recycler + +import androidx.databinding.ObservableField +import by.esas.tools.entity.CaseItemInfo +import by.esas.tools.entity.TestStatusEnum +import by.esas.tools.recycler.BaseItemViewModel + +class CaseViewModel: BaseItemViewModel() { + val caseName = ObservableField("") + val caseModules = ObservableField("") + val caseTestStatus = ObservableField() + + override fun bindItem(item: CaseItemInfo, position: Int) { + this.position.set(position) + caseName.set(item.name) + caseModules.set(item.modules.joinToString()) + caseTestStatus.set(item.status) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/numpad/NumpadImageFragment.kt b/app/src/main/java/by/esas/tools/screens/numpad/NumpadImageFragment.kt new file mode 100644 index 00000000..18908f22 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/numpad/NumpadImageFragment.kt @@ -0,0 +1,104 @@ +package by.esas.tools.screens.numpad + +import android.os.Bundle +import android.view.View +import android.widget.ImageButton +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.customswitch.ISwitchHandler +import by.esas.tools.databinding.FMainNumpadImageBinding +import by.esas.tools.dpToPx +import by.esas.tools.numpad.INumPadHandler +import by.esas.tools.util.SwitchManager +import com.google.android.material.button.MaterialButton + +class NumpadImageFragment : AppFragment() { + override fun provideSwitchableViews(): List { + return listOf( + binding.fMainNumpadImage, + binding.fMainNumpadIconsChangeButton, + binding.fMainNumpadIconsIncreaseButton, + binding.fMainNumpadIconsDecreaseButton + ) + } + + override var switcher: SwitchManager = object : SwitchManager(){ + override fun enableView(view: View): Boolean { + return if (view is MaterialButton || view is ImageButton){ + view.isEnabled = true + true + } else { + super.enableView(view) + } + } + + override fun disableView(view: View): Boolean { + return if (view is MaterialButton || view is ImageButton){ + view.isEnabled = false + true + } else { + super.disableView(view) + } + } + } + + override val fragmentDestinationId = R.id.numpadImageFragment + + override fun provideLayoutId() = R.layout.f_main_numpad_image + + override fun provideViewModel(): NumpadImageVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(NumpadImageVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainNumpadSwitcher.switcherIsChecked(true) + binding.fMainNumpadSwitcher.setSwitchHandler(object : ISwitchHandler{ + override fun onSwitchChange(isChecked: Boolean) { + if (isChecked) + enableControls() + else + disableControls() + } + }) + binding.fMainNumpadImage.setNumpadHandler(object : INumPadHandler { + override fun onNumClick(num: Int) { + viewModel.onIconClick(num) + } + + override fun onRightIconClick() { + viewModel.onRestoreClick() + } + + override fun onLeftIconClick() { + viewModel.onCancelClick() + } + }) + binding.fMainNumpadImage.setIconsContainersPaddings( + dpToPx(10), dpToPx(10), dpToPx(10), dpToPx(10) + ) + } + + override fun setupObservers() { + super.setupObservers() + viewModel.iconsSizeLive.observe(this) { size -> + binding.fMainNumpadImage.setIconsSize(dpToPx(size)) + } + viewModel.iconsIsDefaultLive.observe(this) { isDefault -> + if (isDefault) { + binding.fMainNumpadImage.setDefaultNumpadIcons() + } else { + binding.fMainNumpadImage.apply { + setNumbersIconsImageResources(viewModel.numpadIconsList) + setRightIconImageResource(viewModel.numpadRightIconImage) + setLeftIconImageResource(viewModel.numpadLeftIconImage) + } + } + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/numpad/NumpadImageVM.kt b/app/src/main/java/by/esas/tools/screens/numpad/NumpadImageVM.kt new file mode 100644 index 00000000..ae4505e9 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/numpad/NumpadImageVM.kt @@ -0,0 +1,86 @@ +package by.esas.tools.screens.numpad + +import androidx.databinding.ObservableField +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import by.esas.tools.R +import by.esas.tools.base.AppVM +import javax.inject.Inject + +private const val SMALLEST_ICON_SIZE = 20 +private const val LARGEST_ICON_SIZE = 80 + +class NumpadImageVM @Inject constructor(): AppVM() { + + private val builder: StringBuilder = StringBuilder() + val numText = ObservableField() + + private val _iconsSizeLive = MutableLiveData(50) + val iconsSizeLive: LiveData = _iconsSizeLive + + private val _iconsIsDefaultLive = MutableLiveData(true) + val iconsIsDefaultLive: LiveData = _iconsIsDefaultLive + + val numpadIconsList = listOf( + R.drawable.number_0, + R.drawable.number_1, + R.drawable.number_2, + R.drawable.number_3, + R.drawable.number_4, + R.drawable.number_5, + R.drawable.number_6, + R.drawable.number_7, + R.drawable.number_8, + R.drawable.number_9, + ) + + val numpadRightIconImage = R.drawable.numpad_backspace + val numpadLeftIconImage = R.drawable.numpad_cancel + + fun onRestoreClick(){ + disableControls() + if (builder.isNotEmpty()){ + builder.setLength(builder.length - 1) + numText.set(builder.toString()) + } + enableControls() + } + + fun onIconClick(num: Int){ + disableControls() + builder.append(num) + numText.set(builder.toString()) + enableControls() + } + + fun onCancelClick(){ + disableControls() + builder.clear() + numText.set("") + enableControls() + } + + fun decreaseIconsSize(){ + _iconsSizeLive.value?.let { size -> + var iconsSize = size + if (iconsSize != SMALLEST_ICON_SIZE){ + iconsSize -= 10 + _iconsSizeLive.value = iconsSize + } + } + } + + fun increaseIconsSize(){ + _iconsSizeLive.value?.let { size -> + var iconsSize = size + if (iconsSize != LARGEST_ICON_SIZE){ + iconsSize += 10 + _iconsSizeLive.value = iconsSize + } + } + } + + fun updateIconsImages(){ + _iconsIsDefaultLive.value = !iconsIsDefaultLive.value!! + } +} diff --git a/app/src/main/java/by/esas/tools/screens/numpad/numpad_text/NumpadTextFragment.kt b/app/src/main/java/by/esas/tools/screens/numpad/numpad_text/NumpadTextFragment.kt new file mode 100644 index 00000000..fcc7a927 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/numpad/numpad_text/NumpadTextFragment.kt @@ -0,0 +1,102 @@ +package by.esas.tools.screens.numpad.numpad_text + +import android.os.Bundle +import android.view.View +import android.widget.ImageButton +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.customswitch.ISwitchHandler +import by.esas.tools.databinding.FMainNumpadTextBinding +import by.esas.tools.numpad.INumPadHandler +import by.esas.tools.util.SwitchManager +import by.esas.tools.dpToPx +import com.google.android.material.button.MaterialButton + +class NumpadTextFragment : AppFragment() { + override fun provideSwitchableViews(): List { + return listOf( + binding.fMainNumpadText, + binding.fMainNumpadTextStyleChangeButton, + binding.fMainNumpadTextIconsIncreaseButton, + binding.fMainNumpadTextIconsDecreaseButton + ) + } + + override var switcher: SwitchManager = object : SwitchManager(){ + override fun enableView(view: View): Boolean { + return if (view is MaterialButton || view is ImageButton){ + view.isEnabled = true + true + } else { + super.enableView(view) + } + } + + override fun disableView(view: View): Boolean { + return if (view is MaterialButton || view is ImageButton){ + view.isEnabled = false + true + } else { + super.disableView(view) + } + } + } + + override val fragmentDestinationId = R.id.numpadTextFragment + override fun provideLayoutId() = R.layout.f_main_numpad_text + + override fun provideViewModel(): NumpadTextVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(NumpadTextVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainNumpadTextSwitcher.switcherIsChecked(true) + binding.fMainNumpadTextSwitcher.setSwitchHandler(object : ISwitchHandler { + override fun onSwitchChange(isChecked: Boolean) { + if (isChecked) + enableControls() + else + disableControls() + } + }) + + binding.fMainNumpadText.setNumpadHandler(object : INumPadHandler{ + override fun onNumClick(num: Int) { + viewModel.onIconClick(num) + } + + override fun onLeftIconClick() { + viewModel.onCancelClick() + } + + override fun onRightIconClick() { + viewModel.onRestoreClick() + } + }) + + } + + override fun setupObservers() { + super.setupObservers() + viewModel.iconsParamsLive.observe(this) { params -> + binding.fMainNumpadText.apply { + setIconsSize(dpToPx(params.iconSize), dpToPx(params.iconSize)) + setNumbersTextSize(params.numTextSize) + setLeftIconSize(dpToPx(params.imageSize), dpToPx(params.imageSize)) + setRightIconSize(dpToPx(params.imageSize), dpToPx(params.imageSize)) + } + } + viewModel.iconsIsDefaultLive.observe(this) { isDefault -> + if (isDefault) + binding.fMainNumpadText.setNumbersDefaultStyle() + else + binding.fMainNumpadText.setNumbersTextStyle(R.style.NumpadTextNumbersStyle) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/numpad/numpad_text/NumpadTextVM.kt b/app/src/main/java/by/esas/tools/screens/numpad/numpad_text/NumpadTextVM.kt new file mode 100644 index 00000000..e9b142ca --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/numpad/numpad_text/NumpadTextVM.kt @@ -0,0 +1,79 @@ +package by.esas.tools.screens.numpad.numpad_text + +import androidx.databinding.ObservableField +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import by.esas.tools.base.AppVM +import by.esas.tools.entity.NumpadTextIconEntity +import javax.inject.Inject + +private const val SMALLEST_NUM_TEXT_SIZE = 40 +private const val LARGEST_NUM_TEXT_SIZE = 100 + +class NumpadTextVM @Inject constructor() : AppVM() { + private val builder: StringBuilder = StringBuilder() + val numText = ObservableField() + + private val defaultIconsParams = NumpadTextIconEntity(70, 70, 50) + private val _iconsParamsLive = MutableLiveData(defaultIconsParams) + val iconsParamsLive: LiveData = _iconsParamsLive + + private val _iconsIsDefaultLive = MutableLiveData() + val iconsIsDefaultLive: LiveData = _iconsIsDefaultLive + + fun onRestoreClick() { + disableControls() + if (builder.isNotEmpty()) { + builder.setLength(builder.length - 1) + numText.set(builder.toString()) + } + enableControls() + } + + fun onIconClick(num: Int) { + disableControls() + builder.append(num) + numText.set(builder.toString()) + enableControls() + } + + fun onCancelClick() { + disableControls() + builder.clear() + numText.set("") + enableControls() + } + + fun decreaseIconsSize() { + _iconsParamsLive.value?.let { params -> + if (params.numTextSize != SMALLEST_NUM_TEXT_SIZE) { + _iconsParamsLive.value = NumpadTextIconEntity( + params.iconSize - 10, + params.numTextSize - 10, + params.imageSize - 10 + ) + } + } + } + + fun increaseIconsSize() { + _iconsParamsLive.value?.let { params -> + if (params.numTextSize != LARGEST_NUM_TEXT_SIZE) { + _iconsParamsLive.value = NumpadTextIconEntity( + params.iconSize + 10, + params.numTextSize + 10, + params.imageSize + 10 + ) + } + } + } + + fun updateIconsStyle() { + _iconsIsDefaultLive.value = iconsIsDefaultLive.value.let { + if (it == null) + false + else + !it + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/pin_view/PinViewFragment.kt b/app/src/main/java/by/esas/tools/screens/pin_view/PinViewFragment.kt new file mode 100644 index 00000000..4bb71a47 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/pin_view/PinViewFragment.kt @@ -0,0 +1,49 @@ +package by.esas.tools.screens.pin_view + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainPinViewBinding + +class PinViewFragment : AppFragment() { + + override val fragmentDestinationId: Int = R.id.pinViewFragment + + override fun provideLayoutId(): Int { + return R.layout.f_main_pin_view + } + + override fun provideViewModel(): PinViewVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(PinViewVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fPinViewBtnAdd.setOnClickListener { + binding.fPinView.fillPin() + } + + binding.fPinViewBtnRestore.setOnClickListener { + binding.fPinView.unFillPin() + } + + binding.fPinViewClear.setOnClickListener { + binding.fPinView.clearPins() + } + + binding.fPinViewCustomBtnAdd.setOnClickListener { + binding.fPinViewCustom.fillPin() + } + + binding.fPinViewCustomBtnRestore.setOnClickListener { + binding.fPinViewCustom.unFillPin() + } + + binding.fPinViewCustomClear.setOnClickListener { + binding.fPinViewCustom.clearPins() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/pin_view/PinViewVM.kt b/app/src/main/java/by/esas/tools/screens/pin_view/PinViewVM.kt new file mode 100644 index 00000000..d279b72e --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/pin_view/PinViewVM.kt @@ -0,0 +1,8 @@ +package by.esas.tools.screens.pin_view + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class PinViewVM @Inject constructor() : AppVM() { + +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/pin_view/dynamic/DynamicPinViewFragment.kt b/app/src/main/java/by/esas/tools/screens/pin_view/dynamic/DynamicPinViewFragment.kt new file mode 100644 index 00000000..f96fd1c8 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/pin_view/dynamic/DynamicPinViewFragment.kt @@ -0,0 +1,108 @@ +package by.esas.tools.screens.pin_view.dynamic + +import android.os.Bundle +import android.view.View +import android.widget.RadioButton +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainDynamicPinViewBinding +import by.esas.tools.inputfieldview.dpToPx +import by.esas.tools.pinview.PinView + +class DynamicPinViewFragment: AppFragment() { + override val fragmentDestinationId = R.id.dynamicPinViewFragment + + override fun provideLayoutId() = R.layout.f_main_dynamic_pin_view + + override fun provideViewModel(): DynamicPinViewVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(DynamicPinViewVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fDynamicPinButtonCreate.setOnClickListener { + binding.fDynamicPinContainer.removeAllViews() + binding.fDynamicPinButtonsLay.visibility = View.VISIBLE + + val pin = PinView(requireContext()) + pin.setDefaults() + pin.setPinAmount(binding.fDynamicPinAmount.getText().convertToInt()) + pin.setFilledTintRes(getFilledTint()) + pin.setUnfilledTintRes(getUnfilledTint()) + pin.setUnfilledImage(getUnfilledImage()) + pin.setFilledImage(getFilledImage()) + pin.setAnimInDuration(binding.fDynamicPinTimeIn.getText().convertToInt()) + pin.setAnimOutDuration(binding.fDynamicPinTimeOut.getText().convertToInt()) + pin.setPinSize(dpToPx(binding.fDynamicPinSize.getText().convertToInt())) + pin.setPinMargins(dpToPx(binding.fDynamicPinMargins.getText().convertToInt())) + + binding.fDynamicPinContainer.addView(pin) + setupPinButtonsListeners(pin) + } + } + + private fun setupPinButtonsListeners(pinView: PinView) { + binding.fDynamicPinButtonAdd.setOnClickListener { pinView.fillPin() } + binding.fDynamicPinButtonDelete.setOnClickListener { pinView.unFillPin() } + binding.fDynamicPinButtonClear.setOnClickListener { pinView.clearPins() } + } + + private fun getFilledTint(): Int { + val checkedButtonId = binding.fDynamicPinFilledRadioGroup.checkedRadioButtonId + val checkedButton = binding.fDynamicPinFilledRadioGroup.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fDynamicPinFilledRadio1 -> R.color.orange + binding.fDynamicPinFilledRadio2 -> R.color.purple + binding.fDynamicPinFilledRadio3 -> R.color.red + binding.fDynamicPinFilledRadio4 -> R.color.green + else -> R.color.yellow + } + } + + private fun getUnfilledTint(): Int { + val checkedButtonId = binding.fDynamicPinUnfilledRadioGroup.checkedRadioButtonId + val checkedButton = binding.fDynamicPinUnfilledRadioGroup.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fDynamicPinUnfilledRadio1 -> R.color.orange + binding.fDynamicPinUnfilledRadio2 -> R.color.purple + binding.fDynamicPinUnfilledRadio3 -> R.color.red + binding.fDynamicPinUnfilledRadio4 -> R.color.green + else -> R.color.yellow + } + } + + private fun getFilledImage(): Int { + val checkedButtonId = binding.fDynamicPinImageFilledRadioGroup.checkedRadioButtonId + val checkedButton = binding.fDynamicPinImageFilledRadioGroup.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fDynamicPinImageFilledRadio1 -> R.drawable.ic_pin_star_filled + binding.fDynamicPinImageFilledRadio2 -> R.drawable.ic_favorite + else -> R.drawable.ic_cloud + } + } + + private fun getUnfilledImage(): Int { + val checkedButtonId = binding.fDynamicPinImageUnfilledRadioGroup.checkedRadioButtonId + val checkedButton = binding.fDynamicPinImageUnfilledRadioGroup.findViewById(checkedButtonId) + + return when(checkedButton) { + binding.fDynamicPinImageUnfilledRadio1 -> R.drawable.ic_pin_star_unfilled + binding.fDynamicPinImageUnfilledRadio2 -> R.drawable.ic_favorite_border + else -> R.drawable.ic_cloud_queue + } + } + + private fun getPaddings(paddings: String): Int { + val paddings = if (paddings.isNotEmpty()) paddings.toInt() else 0 + return dpToPx(paddings).toInt() + } + + private fun String.convertToInt(): Int { + return if (this.isNotEmpty()) this.toInt() else 0 + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/pin_view/dynamic/DynamicPinViewVM.kt b/app/src/main/java/by/esas/tools/screens/pin_view/dynamic/DynamicPinViewVM.kt new file mode 100644 index 00000000..e1eacbee --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/pin_view/dynamic/DynamicPinViewVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.pin_view.dynamic + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class DynamicPinViewVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/FirstEntity.kt b/app/src/main/java/by/esas/tools/screens/recycler/FirstEntity.kt new file mode 100644 index 00000000..26a42825 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/FirstEntity.kt @@ -0,0 +1,11 @@ +package by.esas.tools.screens.recycler + +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemModel + +class FirstEntity( + val name: String +) + +fun FirstEntity.mapToSimple(isLast: Boolean, alignment: Int): SimpleItemModel { + return SimpleItemModel(name = name, shortName = name, isChoosed = false, isLast = isLast, textAlignment = alignment) +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/RecyclerLists.kt b/app/src/main/java/by/esas/tools/screens/recycler/RecyclerLists.kt new file mode 100644 index 00000000..627c0de8 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/RecyclerLists.kt @@ -0,0 +1,44 @@ +package by.esas.tools.screens.recycler + +object RecyclerLists { + val firstList: List = listOf( + FirstEntity("Alpha"), + FirstEntity("Beta"), + FirstEntity("Gamma"), + FirstEntity("Delta"), + FirstEntity("Epsilon"), + FirstEntity("Zeta"), + FirstEntity("Eta"), + FirstEntity("Theta"), + FirstEntity("Iota"), + FirstEntity("Cappa"), + FirstEntity("Lambda"), + FirstEntity("Mi"), + FirstEntity("Ni"), + FirstEntity("Csi"), + FirstEntity("Omicron"), + FirstEntity("Pi"), + FirstEntity("Rho"), + FirstEntity("Sigma"), + FirstEntity("Tau"), + FirstEntity("Ipsilon"), + FirstEntity("Phi"), + FirstEntity("Chi"), + FirstEntity("Psi"), + FirstEntity("Omega") + ) + val secondList: List = listOf( + FirstEntity("One"), + FirstEntity("Two"), + FirstEntity("Three"), + FirstEntity("Four"), + FirstEntity("Five"), + FirstEntity("Six"), + FirstEntity("Seven"), + FirstEntity("Eight"), + FirstEntity("Nine"), + FirstEntity("Ten"), + FirstEntity("Eleven"), + FirstEntity("Twelve") + ) +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/base/RecyclerFragment.kt b/app/src/main/java/by/esas/tools/screens/recycler/base/RecyclerFragment.kt new file mode 100644 index 00000000..268f4f94 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/base/RecyclerFragment.kt @@ -0,0 +1,32 @@ +package by.esas.tools.screens.recycler.base + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainRecyclerBinding +import by.esas.tools.screens.recycler.RecyclerLists + +class RecyclerFragment : AppFragment() { + + override val fragmentDestinationId: Int = R.id.recyclerFragment + + override fun provideLayoutId(): Int { + return R.layout.f_main_recycler + } + + override fun provideViewModel(): RecyclerVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(RecyclerVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.fMainRecyclerCaseOne.adapter = viewModel.adapter + binding.fMainRecyclerCaseOne.layoutManager = LinearLayoutManager(context) + binding.fMainRecyclerCaseOne.setHasFixedSize(true) + + viewModel.adapter.setItems(RecyclerLists.firstList) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/base/RecyclerVM.kt b/app/src/main/java/by/esas/tools/screens/recycler/base/RecyclerVM.kt new file mode 100644 index 00000000..9d951fff --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/base/RecyclerVM.kt @@ -0,0 +1,88 @@ +package by.esas.tools.screens.recycler.base + +import android.widget.Toast +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.base.AppVM +import by.esas.tools.databinding.IRecyclerCaseTwoBinding +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemAdapter +import by.esas.tools.screens.recycler.FirstEntity +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.base.adapter.FirstAdapter +import javax.inject.Inject + +class RecyclerVM @Inject constructor() : AppVM() { + + val newEntity: MutableLiveData = MutableLiveData("") + val withPositionFlag: MutableLiveData = MutableLiveData(false) + var currentListIsFirst: Boolean = true + + val adapter: FirstAdapter = FirstAdapter( + { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "adapter: Click on item ${item.name}", Toast.LENGTH_SHORT).show() + }, { pos, item -> + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "adapter: Click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ) + .show() + }, { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "adapter: Long click on item ${item.name}", Toast.LENGTH_SHORT).show() + }, { pos, item -> + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "adapter: Long click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ).show() + } + ) + + val customAdapter: SimpleItemAdapter = SimpleItemAdapter.createCustom( + inflater = IRecyclerCaseTwoBinding::class.java, + onItemClick = { item -> + Toast.makeText(App.instance, "customAdapter: Click on item ${item.name}", Toast.LENGTH_SHORT).show() + }, + onItemClickPosition = { pos, item -> + Toast.makeText( + App.instance, + "customAdapter: Click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ).show() + }, + onItemLongClick = { item -> + Toast.makeText(App.instance, "customAdapter: Long click on item ${item.name}", Toast.LENGTH_SHORT).show() + }, + onItemLongClickPosition = { pos, item -> + Toast.makeText( + App.instance, + "customAdapter: Long click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ).show() + } + ) + + fun addItem() { + adapter.addItem(FirstEntity(newEntity.value ?: "Empty")) + } + + fun clearItems() { + adapter.cleanItems() + } + + fun setAnotherList() { + adapter.setItems( + if (currentListIsFirst) { + currentListIsFirst = false + RecyclerLists.secondList + } else { + currentListIsFirst = true + RecyclerLists.firstList + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstAdapter.kt b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstAdapter.kt new file mode 100644 index 00000000..91d18468 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstAdapter.kt @@ -0,0 +1,24 @@ +package by.esas.tools.screens.recycler.base.adapter + +import android.view.ViewGroup +import by.esas.tools.recycler.BaseRecyclerAdapter +import by.esas.tools.recycler.BaseViewHolder +import by.esas.tools.screens.recycler.FirstEntity + +class FirstAdapter( + onClick: (FirstEntity) -> Unit, + onClickPosition: (Int, FirstEntity) -> Unit, + onLongClick: (FirstEntity) -> Unit, + onLongClickPosition: (Int, FirstEntity) -> Unit +) : BaseRecyclerAdapter( + onItemClick = onClick, onItemClickPosition = onClickPosition, + onItemLongClick = onLongClick, onItemLongClickPosition = onLongClickPosition +) { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseViewHolder { + return FirstViewHolder.create(parent, FirstItemVM()) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstItemVM.kt b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstItemVM.kt new file mode 100644 index 00000000..4d92b4b9 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstItemVM.kt @@ -0,0 +1,23 @@ +package by.esas.tools.screens.recycler.base.adapter + +import androidx.databinding.ObservableField +import by.esas.tools.recycler.BaseItemViewModel +import by.esas.tools.screens.recycler.FirstEntity + +class FirstItemVM : BaseItemViewModel() { + val text = ObservableField("") + val counter = ObservableField("") + private var innerCounter = 0 + + override fun bindItem(item: FirstEntity, position: Int) { + this.position.set(position) + text.set(item.name) + innerCounter = 0 + counter.set(innerCounter.toString()) + } + + override fun onItemClick() { + super.onItemClick() + counter.set((++innerCounter).toString()) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstViewHolder.kt b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstViewHolder.kt new file mode 100644 index 00000000..fe0d3b96 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstViewHolder.kt @@ -0,0 +1,23 @@ +package by.esas.tools.screens.recycler.base.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import by.esas.tools.BR +import by.esas.tools.databinding.IRecyclerBaseBinding +import by.esas.tools.recycler.BaseViewHolder +import by.esas.tools.screens.recycler.FirstEntity + +class FirstViewHolder(binding: IRecyclerBaseBinding, viewModel: FirstItemVM) : + BaseViewHolder(binding, viewModel) { + companion object { + fun create( + parent: ViewGroup, + viewModel: FirstItemVM + ): FirstViewHolder { + val binding = IRecyclerBaseBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return FirstViewHolder(binding, viewModel) + } + } + + override fun provideVariable(): Int = BR.item +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/custom/CustomRecyclerFragment.kt b/app/src/main/java/by/esas/tools/screens/recycler/custom/CustomRecyclerFragment.kt new file mode 100644 index 00000000..8177e923 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/custom/CustomRecyclerFragment.kt @@ -0,0 +1,65 @@ +package by.esas.tools.screens.recycler.custom + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainCustomRecyclerBinding +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemModel +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.mapToSimple + +class CustomRecyclerFragment : AppFragment() { + + override val fragmentDestinationId: Int = R.id.customRecyclerFragment + + override fun provideLayoutId(): Int { + return R.layout.f_main_custom_recycler + } + + override fun provideViewModel(): CustomRecyclerVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(CustomRecyclerVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.fMainCustomRecyclerCaseOne.adapter = viewModel.adapter + binding.fMainCustomRecyclerCaseOne.layoutManager = LinearLayoutManager(context) + binding.fMainCustomRecyclerCaseOne.setHasFixedSize(false) + + val lastIndex = RecyclerLists.firstList.lastIndex + viewModel.adapter.setItems(RecyclerLists.firstList.mapIndexed { index, entity -> + entity.mapToSimple(lastIndex == index, viewModel.currentAlignment) + }) + binding.fMainCustomRecyclerAlignLeft.setOnClickListener { + viewModel.currentAlignment = View.TEXT_ALIGNMENT_TEXT_START + resetCurrentList() + } + binding.fMainCustomRecyclerAlignCenter.setOnClickListener { + viewModel.currentAlignment = View.TEXT_ALIGNMENT_CENTER + resetCurrentList() + } + binding.fMainCustomRecyclerAlignRight.setOnClickListener { + viewModel.currentAlignment = View.TEXT_ALIGNMENT_TEXT_END + resetCurrentList() + } + } + + private fun resetCurrentList() { + val newList = viewModel.adapter.itemList.map { + SimpleItemModel( + name = it.name, + shortName = it.shortName, + isChoosed = it.isChoosed, + isLast = it.isLast, + textAlignment = viewModel.currentAlignment + ) + } + viewModel.adapter.setItems(newList) + //Total rewrite of the adapter is used because otherwise part of + // item's text alignment is not updating correctly + binding.fMainCustomRecyclerCaseOne.adapter = viewModel.adapter + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/custom/CustomRecyclerVM.kt b/app/src/main/java/by/esas/tools/screens/recycler/custom/CustomRecyclerVM.kt new file mode 100644 index 00000000..c4502c37 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/custom/CustomRecyclerVM.kt @@ -0,0 +1,101 @@ +package by.esas.tools.screens.recycler.custom + +import android.view.View +import android.widget.Toast +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.base.AppVM +import by.esas.tools.databinding.ICustomBinding +import by.esas.tools.recycler.databinding.IPickedBinding +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemAdapter +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemModel +import by.esas.tools.screens.recycler.FirstEntity +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.mapToSimple +import java.util.* +import java.util.logging.Handler +import javax.inject.Inject +import kotlin.concurrent.schedule + +class CustomRecyclerVM @Inject constructor() : AppVM() { + + val newEntity: MutableLiveData = MutableLiveData("") + val withPositionFlag: MutableLiveData = MutableLiveData(false) + val makeItemsSelectable: MutableLiveData = MutableLiveData(false) + val isSingleItemSelectable: MutableLiveData = MutableLiveData(true) + var currentListIsFirst: Boolean = true + var currentAlignment: Int = View.TEXT_ALIGNMENT_CENTER + + val adapter: SimpleItemAdapter = SimpleItemAdapter.createCustom( + inflater = ICustomBinding::class.java, + onItemClick = { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "simpleAdapter: Click on item ${item.name}", Toast.LENGTH_SHORT).show() + }, + onItemClickPosition = { pos, item -> + doOnClickWithPosition(pos, item) + }, + onItemLongClick = { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "simpleAdapter: Long click on item ${item.name}", Toast.LENGTH_SHORT) + .show() + }, + onItemLongClickPosition = { pos, item -> + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "simpleAdapter: Long click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ).show() + } + ) + + private fun doOnClickWithPosition(pos: Int, item: SimpleItemModel) { + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "simpleAdapter: Click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ).show() + if (makeItemsSelectable.value == true) { + if (item.isChoosed) adapter.setItemUnpicked(pos) + else adapter.setItemPicked(pos, isSingleItemSelectable.value == true) + } + } + + fun addItem() { + val name = newEntity.value ?: "Empty" + adapter.itemList.get(adapter.itemList.lastIndex).isLast = false + adapter.addItem( + SimpleItemModel( + name = name, + shortName = name, + isChoosed = false, + isLast = true, + textAlignment = currentAlignment + ) + ) + } + + fun clearItems() { + adapter.cleanItems() + } + + fun setAnotherList() { + adapter.setItems( + if (currentListIsFirst) { + currentListIsFirst = false + val lastIndex = RecyclerLists.secondList.lastIndex + RecyclerLists.secondList.mapIndexed { index, entity -> + entity.mapToSimple(lastIndex == index, currentAlignment) + } + } else { + currentListIsFirst = true + val lastIndex = RecyclerLists.firstList.lastIndex + RecyclerLists.firstList.mapIndexed { index, entity -> + entity.mapToSimple(lastIndex == index, currentAlignment) + } + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/simple/SimpleRecyclerFragment.kt b/app/src/main/java/by/esas/tools/screens/recycler/simple/SimpleRecyclerFragment.kt new file mode 100644 index 00000000..009e7f2d --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/simple/SimpleRecyclerFragment.kt @@ -0,0 +1,65 @@ +package by.esas.tools.screens.recycler.simple + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainSimpleRecyclerBinding +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemModel +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.mapToSimple + +class SimpleRecyclerFragment : AppFragment() { + + override val fragmentDestinationId: Int = R.id.simpleRecyclerFragment + + override fun provideLayoutId(): Int { + return R.layout.f_main_simple_recycler + } + + override fun provideViewModel(): SimpleRecyclerVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(SimpleRecyclerVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.fMainSimpleRecyclerCaseOne.adapter = viewModel.adapter + binding.fMainSimpleRecyclerCaseOne.layoutManager = LinearLayoutManager(context) + binding.fMainSimpleRecyclerCaseOne.setHasFixedSize(false) + + val lastIndex = RecyclerLists.firstList.lastIndex + viewModel.adapter.setItems(RecyclerLists.firstList.mapIndexed { index, entity -> + entity.mapToSimple(lastIndex == index, viewModel.currentAlignment) + }) + binding.fMainSimpleRecyclerAlignLeft.setOnClickListener { + viewModel.currentAlignment = View.TEXT_ALIGNMENT_TEXT_START + resetCurrentList() + } + binding.fMainSimpleRecyclerAlignCenter.setOnClickListener { + viewModel.currentAlignment = View.TEXT_ALIGNMENT_CENTER + resetCurrentList() + } + binding.fMainSimpleRecyclerAlignRight.setOnClickListener { + viewModel.currentAlignment = View.TEXT_ALIGNMENT_TEXT_END + resetCurrentList() + } + } + + private fun resetCurrentList() { + val newList = viewModel.adapter.itemList.map { + SimpleItemModel( + name = it.name, + shortName = it.shortName, + isChoosed = it.isChoosed, + isLast = it.isLast, + textAlignment = viewModel.currentAlignment + ) + } + viewModel.adapter.setItems(newList) + //Total rewrite of the adapter is used because otherwise part of + // item's text alignment is not updating correctly + binding.fMainSimpleRecyclerCaseOne.adapter = viewModel.adapter + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/simple/SimpleRecyclerVM.kt b/app/src/main/java/by/esas/tools/screens/recycler/simple/SimpleRecyclerVM.kt new file mode 100644 index 00000000..9a3b1742 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/simple/SimpleRecyclerVM.kt @@ -0,0 +1,98 @@ +package by.esas.tools.screens.recycler.simple + +import android.view.View +import android.widget.Toast +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.base.AppVM +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemAdapter +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemModel +import by.esas.tools.screens.recycler.FirstEntity +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.mapToSimple +import java.util.* +import java.util.logging.Handler +import javax.inject.Inject +import kotlin.concurrent.schedule + +class SimpleRecyclerVM @Inject constructor() : AppVM() { + + val newEntity: MutableLiveData = MutableLiveData("") + val withPositionFlag: MutableLiveData = MutableLiveData(false) + val makeItemsSelectable: MutableLiveData = MutableLiveData(false) + val isSingleItemSelectable: MutableLiveData = MutableLiveData(true) + var currentListIsFirst: Boolean = true + var currentAlignment: Int = View.TEXT_ALIGNMENT_CENTER + + val adapter: SimpleItemAdapter = SimpleItemAdapter( + onItemClick = { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "simpleAdapter: Click on item ${item.name}", Toast.LENGTH_SHORT).show() + }, + onItemClickPosition = { pos, item -> + doOnClickWithPosition(pos, item) + }, + onItemLongClick = { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "simpleAdapter: Long click on item ${item.name}", Toast.LENGTH_SHORT) + .show() + }, + onItemLongClickPosition = { pos, item -> + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "simpleAdapter: Long click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ).show() + } + ) + + private fun doOnClickWithPosition(pos: Int, item: SimpleItemModel) { + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "simpleAdapter: Click on item ${item.name} with position $pos", + Toast.LENGTH_SHORT + ).show() + if (makeItemsSelectable.value == true) { + if (item.isChoosed) adapter.setItemUnpicked(pos) + else adapter.setItemPicked(pos, isSingleItemSelectable.value == true) + } + } + + fun addItem() { + val name = newEntity.value ?: "Empty" + adapter.itemList.get(adapter.itemList.lastIndex).isLast = false + adapter.addItem( + SimpleItemModel( + name = name, + shortName = name, + isChoosed = false, + isLast = true, + textAlignment = currentAlignment + ) + ) + } + + fun clearItems() { + adapter.cleanItems() + } + + fun setAnotherList() { + adapter.setItems( + if (currentListIsFirst) { + currentListIsFirst = false + val lastIndex = RecyclerLists.secondList.lastIndex + RecyclerLists.secondList.mapIndexed { index, entity -> + entity.mapToSimple(lastIndex == index, currentAlignment) + } + } else { + currentListIsFirst = true + val lastIndex = RecyclerLists.firstList.lastIndex + RecyclerLists.firstList.mapIndexed { index, entity -> + entity.mapToSimple(lastIndex == index, currentAlignment) + } + } + ) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/StickyCaseFragment.kt b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/StickyCaseFragment.kt new file mode 100644 index 00000000..0d1c3c61 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/StickyCaseFragment.kt @@ -0,0 +1,42 @@ +package by.esas.tools.screens.recycler.sticky_case + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainStickyRecyclerBinding +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.sticky_case.sticky_adapter.CaseStickyHeader +import by.esas.tools.screens.recycler.sticky_case.sticky_adapter.CaseStickyItemVM + +class StickyCaseFragment : AppFragment() { + + override val fragmentDestinationId: Int = R.id.stickyCaseFragment + + override fun provideLayoutId(): Int { + return R.layout.f_main_sticky_recycler + } + + override fun provideViewModel(): StickyCaseVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(StickyCaseVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.fMainStickyRecycler.adapter = viewModel.adapter + binding.fMainStickyRecycler.layoutManager = LinearLayoutManager(context) + binding.fMainStickyRecycler.setHasFixedSize(true) + + viewModel.adapter.setEntities(RecyclerLists.firstList) + viewModel.adapter.stickyHeaderDecoration = CaseStickyHeader.create( + parent = binding.fMainStickyRecycler, + viewModel = CaseStickyItemVM(), + listener = viewModel.adapter.listener + ) + viewModel.adapter.stickyHeaderDecoration?.let { + binding.fMainStickyRecycler.addItemDecoration(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/StickyCaseVM.kt b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/StickyCaseVM.kt new file mode 100644 index 00000000..f426c0d8 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/StickyCaseVM.kt @@ -0,0 +1,72 @@ +package by.esas.tools.screens.recycler.sticky_case + +import android.widget.Toast +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.base.AppVM +import by.esas.tools.recycler.sticky.StickyEntity +import by.esas.tools.screens.recycler.FirstEntity +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.sticky_case.sticky_adapter.CaseStickyAdapter +import javax.inject.Inject + +class StickyCaseVM @Inject constructor() : AppVM() { + + val newEntity: MutableLiveData = MutableLiveData("") + val withPositionFlag: MutableLiveData = MutableLiveData(false) + var currentListIsFirst: Boolean = true + + val adapter: CaseStickyAdapter = CaseStickyAdapter( + { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "stickyAdapter: Click on ${takeTextForSticky(item)}", Toast.LENGTH_SHORT) + .show() + }, { pos, item -> + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "stickyAdapter: Click on ${takeTextForSticky(item)} with position $pos", + Toast.LENGTH_SHORT + ).show() + }, { item -> + if (withPositionFlag.value == false) + Toast.makeText(App.instance, "adapter: Long click on ${takeTextForSticky(item)}", Toast.LENGTH_SHORT) + .show() + }, { pos, item -> + if (withPositionFlag.value == true) + Toast.makeText( + App.instance, + "adapter: Long click on ${takeTextForSticky(item)} with position $pos", + Toast.LENGTH_SHORT + ).show() + } + ) + + private fun takeTextForSticky(item: StickyEntity): String { + return if (item.header != null) { + "header ${item.header?.headerText}" + } else { + "item ${item.entity?.name}" + } + } + + fun addItem() { + adapter.addEntity(FirstEntity(newEntity.value ?: "Empty")) + } + + fun clearItems() { + adapter.cleanItems() + } + + fun setAnotherList() { + adapter.setEntities( + if (currentListIsFirst) { + currentListIsFirst = false + RecyclerLists.secondList + } else { + currentListIsFirst = true + RecyclerLists.firstList + } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseCompareData.kt b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseCompareData.kt new file mode 100644 index 00000000..c1b225af --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseCompareData.kt @@ -0,0 +1,18 @@ +package by.esas.tools.screens.recycler.sticky_case.sticky_adapter + +import by.esas.tools.recycler.sticky.CompareData +import by.esas.tools.recycler.sticky.StickyHeader + +class CaseCompareData(val firstLetter: String) : CompareData { + override fun compareTo(another: CompareData): Boolean { + return if (another is CaseCompareData) { + firstLetter == another.firstLetter + } else { + false + } + } + + override fun createHeader(): StickyHeader { + return StickyHeader(firstLetter) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyAdapter.kt b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyAdapter.kt new file mode 100644 index 00000000..7c1bd0d1 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyAdapter.kt @@ -0,0 +1,66 @@ +package by.esas.tools.screens.recycler.sticky_case.sticky_adapter + +import android.view.ViewGroup +import by.esas.tools.recycler.sticky.CompareData +import by.esas.tools.recycler.sticky.StickyEntity +import by.esas.tools.recycler.sticky.StickyRecyclerAdapter +import by.esas.tools.screens.recycler.FirstEntity + +class CaseStickyAdapter( + onClick: (StickyEntity) -> Unit, + onClickPosition: (Int, StickyEntity) -> Unit, + onLongClick: (StickyEntity) -> Unit, + onLongClickPosition: (Int, StickyEntity) -> Unit +) : StickyRecyclerAdapter( + onItemClick = onClick, + onItemClickPosition = onClickPosition, + onItemLongClick = onLongClick, + onItemLongClickPosition = onLongClickPosition +) { + /* + private val adapterScope = CoroutineScope(Dispatchers.Default) + override fun setEntities(items: List) { + if (semaphore.tryAcquire(timeOut, TimeUnit.MILLISECONDS)) { + adapterScope.launch { + val addList: MutableList> = analyzeItems(items, true) + val diffCallback = StickyDiffCallback(itemList, addList) + val difResult = DiffUtil.calculateDiff(diffCallback) + + withContext(Dispatchers.Main) { + itemList.clear() + itemList.addAll(addList) + difResult.dispatchUpdatesTo(this@CaseStickyAdapter) + semaphore.release() + } + } + } + }*/ + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) + : CaseStickyViewHolder<*> { + return when (viewType) { + 0 -> { + CaseStickyViewHolder.createItem(parent, CaseStickyItemVM()) + } + 1 -> { + CaseStickyViewHolder.createTitle(parent, CaseStickyItemVM()) + } + else -> { + CaseStickyViewHolder.createItem(parent, CaseStickyItemVM()) + } + } + } + + override fun createNew(entity: FirstEntity?): CompareData { + return if (entity != null) { + val letter = if (entity.name.isNotEmpty()) { + entity.name.uppercase().substring(0, 1) + } else { + "-" + } + CaseCompareData(letter) + } else { + CaseCompareData("-") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyHeader.kt b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyHeader.kt new file mode 100644 index 00000000..7c05c67f --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyHeader.kt @@ -0,0 +1,36 @@ +package by.esas.tools.screens.recycler.sticky_case.sticky_adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import by.esas.tools.BR +import by.esas.tools.R +import by.esas.tools.databinding.IStickyRecyclerHeaderBinding +import by.esas.tools.recycler.sticky.RecyclerStickyHeaderView +import by.esas.tools.recycler.sticky.StickyEntity +import by.esas.tools.screens.recycler.FirstEntity + +class CaseStickyHeader( + binding: IStickyRecyclerHeaderBinding, viewModel: CaseStickyItemVM, + listener: IStickyHeader> +) : RecyclerStickyHeaderView, IStickyRecyclerHeaderBinding, CaseStickyItemVM> + (binding, viewModel, listener) { + override fun provideHeaderLayout(): Int = R.layout.i_sticky_recycler_header + + companion object { + fun create( + parent: ViewGroup, + viewModel: CaseStickyItemVM, + listener: IStickyHeader> + ): RecyclerStickyHeaderView, IStickyRecyclerHeaderBinding, CaseStickyItemVM> { + val binding = IStickyRecyclerHeaderBinding + .inflate(LayoutInflater.from(parent.context), parent, false) + return CaseStickyHeader( + binding, + viewModel, + listener + ) + } + } + + override fun provideVariable(): Int = BR.item +} diff --git a/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyItemVM.kt b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyItemVM.kt new file mode 100644 index 00000000..882de76b --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyItemVM.kt @@ -0,0 +1,29 @@ +package by.esas.tools.screens.recycler.sticky_case.sticky_adapter + +import androidx.databinding.ObservableField +import by.esas.tools.recycler.BaseItemViewModel +import by.esas.tools.recycler.sticky.StickyEntity +import by.esas.tools.screens.recycler.FirstEntity + +class CaseStickyItemVM : BaseItemViewModel>() { + val title = ObservableField("t") + + val name = ObservableField("n") + val counter = ObservableField("0") + private var innerCounter = 0 + + override fun bindItem(item: StickyEntity, position: Int) { + this.position.set(position) + item.header?.run { title.set(headerText) } + item.entity?.let { + name.set(it.name) + innerCounter = 0 + counter.set(innerCounter.toString()) + } + } + + override fun onItemClick() { + super.onItemClick() + counter.set((++innerCounter).toString()) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyViewHolder.kt b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyViewHolder.kt new file mode 100644 index 00000000..4bdae111 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyViewHolder.kt @@ -0,0 +1,43 @@ +package by.esas.tools.screens.recycler.sticky_case.sticky_adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.databinding.ViewDataBinding +import by.esas.tools.BR +import by.esas.tools.databinding.IStickyRecyclerCaseBinding +import by.esas.tools.databinding.IStickyRecyclerHeaderBinding +import by.esas.tools.recycler.BaseViewHolder +import by.esas.tools.recycler.sticky.StickyEntity +import by.esas.tools.screens.recycler.FirstEntity + +class CaseStickyViewHolder(binding: B, viewModel: CaseStickyItemVM) : + BaseViewHolder, CaseStickyItemVM, B> + (binding, viewModel) { + companion object { + fun createItem( + parent: ViewGroup, + viewModel: CaseStickyItemVM + ): CaseStickyViewHolder { + val binding = + IStickyRecyclerCaseBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return CaseStickyViewHolder( + binding, + viewModel + ) + } + + fun createTitle( + parent: ViewGroup, + viewModel: CaseStickyItemVM + ): CaseStickyViewHolder { + val binding = + IStickyRecyclerHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return CaseStickyViewHolder( + binding, + viewModel + ) + } + } + + override fun provideVariable(): Int = BR.item +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/saved_state_vm/SavedStateFragment.kt b/app/src/main/java/by/esas/tools/screens/saved_state_vm/SavedStateFragment.kt new file mode 100644 index 00000000..f38735df --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/saved_state_vm/SavedStateFragment.kt @@ -0,0 +1,19 @@ +package by.esas.tools.screens.saved_state_vm + +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainSavedStateBinding + +class SavedStateFragment : AppFragment() { + + override val fragmentDestinationId: Int = R.id.savedStateFragment + + override fun provideLayoutId(): Int { + return R.layout.f_main_saved_state + } + + override fun provideViewModel(): SavedStateVM { + return ViewModelProvider(this, viewModelFactory.provideFactory(this, arguments)).get(SavedStateVM::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/saved_state_vm/SavedStateVM.kt b/app/src/main/java/by/esas/tools/screens/saved_state_vm/SavedStateVM.kt new file mode 100644 index 00000000..d550bcc4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/saved_state_vm/SavedStateVM.kt @@ -0,0 +1,22 @@ +package by.esas.tools.screens.saved_state_vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.SavedStateHandle +import by.esas.tools.base.AppVM +import by.esas.tools.basedaggerui.factory.AssistedSavedStateViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +class SavedStateVM @AssistedInject constructor( + @Assisted private val savedStateHandle: SavedStateHandle +) : AppVM() { + + @AssistedFactory + interface Factory : AssistedSavedStateViewModelFactory { + override fun create(savedStateHandle: SavedStateHandle): SavedStateVM + } + + val firstField = savedStateHandle.getLiveData("firstField", "") + val secondField = MutableLiveData("") +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/timeparser/TimeParserFragment.kt b/app/src/main/java/by/esas/tools/screens/timeparser/TimeParserFragment.kt new file mode 100644 index 00000000..50a4ff87 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/timeparser/TimeParserFragment.kt @@ -0,0 +1,98 @@ +package by.esas.tools.screens.timeparser + +import android.os.Build +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import by.esas.tools.base.AppFragment +import by.esas.tools.checker.Checker +import by.esas.tools.databinding.FMainTimeparserBinding +import by.esas.tools.logger.handler.ErrorHandler +import by.esas.tools.util.TAGk +import by.esas.tools.utils.checking.AppChecker +import by.esas.tools.utils.checking.FieldChecking +import by.esas.tools.utils.logger.ErrorModel +import java.util.Date + +class TimeParserFragment : AppFragment() { + override val fragmentDestinationId = R.id.timeparserFragment + override fun provideLayoutId() = R.layout.f_main_timeparser + + override fun provideViewModel(): TimeParserVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(TimeParserVM::class.java) + } + + override fun provideErrorHandler(): ErrorHandler { + return object : ErrorHandler() { + + override fun getErrorMessage(error: ErrorModel): String { + return when(error.getStatusAsEnum()) { + AppErrorStatusEnum.APP_DATE_IN_UNEXPECTED_FORMAT -> resources.getString(R.string.timeparser_error_date_in_unexpected_format) + AppErrorStatusEnum.APP_ILLEGAL_PATTERN_CHARACTER -> resources.getString(R.string.timeparser_error_illegal_pattern_character) + else -> resources.getString(R.string.timeparser_error_unexpected) + } + } + + override fun getErrorMessage(e: Throwable): String { + return e.message.toString() + } + + override fun mapError(e: Throwable): ErrorModel { + return viewModel.provideMapper().mapErrorException(this.TAGk, e) + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setDateFromCalendar( + binding.fTimeparserCalendar.year, + binding.fTimeparserCalendar.month, + binding.fTimeparserCalendar.dayOfMonth) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + binding.fTimeparserCalendar.setOnDateChangedListener { _, year, monthOfYear, dayOfMonth -> + setDateFromCalendar(year, monthOfYear, dayOfMonth) + } + } + + binding.fTimeparserPattern2Btn.setOnClickListener { + if (binding.fTimeparserPattern2.getText().isNotEmpty()) + viewModel.onGetUTCPatternClick() + else + binding.fTimeparserPattern2.setError(R.string.timeparser_empty_field) + } + binding.fTimeparserPattern3Btn.setOnClickListener { + if (binding.fTimeparserPattern3.getText().isNotEmpty()) + viewModel.onGetLocalePatternClick() + else + binding.fTimeparserPattern3.setError(R.string.timeparser_empty_field) + } + binding.fTimeparserPattern4Btn.setOnClickListener { + val parserChecker = AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + viewModel.onGetDateFromLocalPatternClick() + } + }) + parserChecker.validate(listOf(FieldChecking(binding.fTimeparserPattern4), FieldChecking(binding.fTimeparserDate4))) + } + binding.fTimeparserPattern5Btn.setOnClickListener { + val parserChecker = AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + viewModel.onGetDateFromUTCPatternClick() + } + }) + parserChecker.validate(listOf(FieldChecking(binding.fTimeparserPattern5), FieldChecking(binding.fTimeparserDate5))) + } + } + + private fun setDateFromCalendar(year: Int, month: Int, day: Int) { + viewModel.selectedDate = Date( year - 1900, month, day) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/timeparser/TimeParserVM.kt b/app/src/main/java/by/esas/tools/screens/timeparser/TimeParserVM.kt new file mode 100644 index 00000000..5b2796ef --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/timeparser/TimeParserVM.kt @@ -0,0 +1,89 @@ +package by.esas.tools.screens.timeparser + +import androidx.databinding.ObservableField +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import by.esas.tools.base.AppVM +import by.esas.tools.timeparser.TimeParser +import by.esas.tools.timeparser.TimeParser.getDateFromLocal +import by.esas.tools.timeparser.TimeParser.getDateFromUTC +import by.esas.tools.timeparser.TimeParser.getLocal +import by.esas.tools.timeparser.TimeParser.getUTC +import by.esas.tools.utils.logger.ErrorModel +import java.lang.IllegalArgumentException +import java.util.Date +import javax.inject.Inject + +class TimeParserVM @Inject constructor() : AppVM() { + var selectedDate: Date? = null + + val resultField1 = ObservableField("") + val resultField2 = ObservableField("") + val resultField3 = ObservableField("") + val resultField4 = ObservableField("") + val resultField5 = ObservableField("") + + val patternField2 = ObservableField("") + val patternField3 = ObservableField("") + val patternField4 = ObservableField("") + val patternField5 = ObservableField("") + + val dateField4 = ObservableField("") + val dateField5 = ObservableField("") + + fun onGetUTCDateDefaultClick() { + resultField1.set(TimeParser.getUTCDate(selectedDate).toString()) + } + + fun onGetUTCDefaultClick() { + resultField2.set(selectedDate?.getUTC()) + } + + fun onGetLocaleDefaultClick() { + resultField3.set(selectedDate?.getLocal()) + } + + fun onGetUTCPatternClick() { + if (!patternField2.get().isNullOrEmpty()) + try { + resultField2.set(selectedDate?.getUTC(outPattern = patternField2.get()!!)) + } catch (e: IllegalArgumentException) { + handleError(ErrorModel(0, AppErrorStatusEnum.APP_ILLEGAL_PATTERN_CHARACTER)) + } catch (e: Exception) { + handleError(e) + } + } + + fun onGetLocalePatternClick() { + if (!patternField3.get().isNullOrEmpty()) + try { + resultField3.set(selectedDate?.getLocal(outPattern = patternField3.get()!!)) + } catch (e: IllegalArgumentException) { + handleError(ErrorModel(0, AppErrorStatusEnum.APP_ILLEGAL_PATTERN_CHARACTER)) + } catch (e: Exception) { + handleError(e) + } + } + + fun onGetDateFromLocalPatternClick() { + if (!patternField4.get().isNullOrEmpty() && !dateField4.get().isNullOrEmpty()) + try { + resultField4.set( + getDateFromLocal( + dateField4.get(), + localPattern = patternField4.get()!! + ).toString() + ) + } catch (e: IllegalArgumentException) { + handleError(ErrorModel(0, AppErrorStatusEnum.APP_DATE_IN_UNEXPECTED_FORMAT)) + } + } + + fun onGetDateFromUTCPatternClick() { + if (!patternField5.get().isNullOrEmpty() && !dateField5.get().isNullOrEmpty()) + try { + resultField5.set(getDateFromUTC(dateField5.get(), patternField5.get()!!).toString()) + } catch (e: Exception) { + handleError(ErrorModel(0, AppErrorStatusEnum.APP_DATE_IN_UNEXPECTED_FORMAT)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/topbar/TopbarFragment.kt b/app/src/main/java/by/esas/tools/screens/topbar/TopbarFragment.kt new file mode 100644 index 00000000..87eaf573 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/topbar/TopbarFragment.kt @@ -0,0 +1,58 @@ +package by.esas.tools.screens.topbar + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainTopbarBinding +import by.esas.tools.topbarview.ITopbarHandler + +class TopbarFragment: AppFragment() { + override val fragmentDestinationId = R.id.topbarFragment + override fun provideLayoutId() = R.layout.f_main_topbar + + override fun provideViewModel(): TopbarVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(TopbarVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainTopbarButtonChangeIcons.setOnClickListener { + binding.fMainTopbarCase6.apply { + setTitle(R.string.topbar_title) + setStartActionTitle(R.string.topbar_action_cancel) + setActionIconImage(R.drawable.ic_done) + } + } + + binding.fMainTopbarButtonChangeStyle.setOnClickListener { + binding.fMainTopbarCase7.apply { + setTitleStyle(R.style.TextStyle) + setStartActionViewStyle(R.style.TextStyle) + setEndActionViewStyle(R.style.TextStyle) + setDividerVisibility(true) + setDividerColor(R.color.main_color_2) + } + } + + binding.fMainTopbarCase3.setupHandler(getTopbarHandler()) + binding.fMainTopbarCase6.setupHandler(getTopbarHandler()) + binding.fMainTopbarCase9.setupHandler(getTopbarHandler()) + binding.fMainTopbarCase10.setupHandler(getTopbarHandler()) + } + + private fun getTopbarHandler(): ITopbarHandler = object : ITopbarHandler { + override fun onNavigationClick() { + Toast.makeText(requireContext(), resources.getString(R.string.topbar_nav_click), Toast.LENGTH_SHORT).show() + } + + override fun onActionClick() { + Toast.makeText(requireContext(), resources.getString(R.string.topbar_action_click), Toast.LENGTH_SHORT).show() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/topbar/TopbarVM.kt b/app/src/main/java/by/esas/tools/screens/topbar/TopbarVM.kt new file mode 100644 index 00000000..750caeec --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/topbar/TopbarVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.topbar + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class TopbarVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/util/UtilKeyboardFragment.kt b/app/src/main/java/by/esas/tools/screens/util/UtilKeyboardFragment.kt new file mode 100644 index 00000000..3d018790 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/UtilKeyboardFragment.kt @@ -0,0 +1,38 @@ +package by.esas.tools.screens.util + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainUtilKeyboardBinding +import by.esas.tools.util.defocusAndHideKeyboard +import by.esas.tools.util.focusAndShowKeyboard +import by.esas.tools.util.showKeyboard + +class UtilKeyboardFragment : AppFragment() { + override val fragmentDestinationId = R.id.utilKeyboardFragment + override fun provideLayoutId() = R.layout.f_main_util_keyboard + + override fun provideViewModel(): UtilKeyboardVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(UtilKeyboardVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + focusAndShowKeyboard(activity, binding.fMainUtilCase1Edit) + binding.fMainUtilCase1Button.setOnClickListener { + defocusAndHideKeyboard(activity) + } + binding.fMainUtilCase2ButtonShow.setOnClickListener { + showKeyboard(activity, false) + } + binding.fMainUtilCase2ButtonHide.setOnClickListener { + by.esas.tools.util.hideKeyboard(activity) + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/util/UtilKeyboardVM.kt b/app/src/main/java/by/esas/tools/screens/util/UtilKeyboardVM.kt new file mode 100644 index 00000000..aea87f9e --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/UtilKeyboardVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.util + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class UtilKeyboardVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/util/switchManager/UtilSwitchManagerFragment.kt b/app/src/main/java/by/esas/tools/screens/util/switchManager/UtilSwitchManagerFragment.kt new file mode 100644 index 00000000..9347cfba --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/switchManager/UtilSwitchManagerFragment.kt @@ -0,0 +1,103 @@ +package by.esas.tools.screens.util.switchManager + +import android.os.Bundle +import android.view.View +import android.widget.Button +import android.widget.Toast +import androidx.appcompat.widget.SwitchCompat +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.customswitch.ISwitchHandler +import by.esas.tools.databinding.FMainUtilSwitchManagerBinding +import by.esas.tools.util.SwitchManager + +class UtilSwitchManagerFragment: AppFragment() { + override val fragmentDestinationId = R.id.utilSwitchManagerFragment + + override fun provideLayoutId() = R.layout.f_main_util_switch_manager + + override fun provideViewModel(): UtilSwitchManagerVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(UtilSwitchManagerVM::class.java) + } + + override fun provideSwitchableViews(): List { + return listOf( + binding.fMainUtilManagerBaseButton, + binding.fMainUtilManagerBaseEdit, + binding.fMainUtilManagerBaseTestSwitcher, + binding.fMainUtilManagerBaseSwitchMaterial + ) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainUtilManagerBaseSwitcher.setSwitchHandler(object : ISwitchHandler { + override fun onSwitchChange(isChecked: Boolean) { + if (isChecked) + enableControls() + else + disableControls() + } + }) + binding.fMainUtilManagerBaseSwitcher.switcherIsChecked(true) + + binding.fMainUtilManagerCustomSwitcher.setSwitchHandler(object : ISwitchHandler { + override fun onSwitchChange(isChecked: Boolean) { + val switcher = provideSwitchManagerForCustomSwitcher() + provideViewsForCustomSwitcher().forEach { view -> + view?.let { + if (isChecked) + switcher.enableView(view) + else + switcher.disableView(view) + } + } + } + }) + binding.fMainUtilManagerCustomSwitcher.switcherIsChecked(true) + + binding.fMainUtilManagerBaseButton.setOnClickListener { + Toast.makeText(requireContext(), resources.getString(R.string.util_manager_button_click), Toast.LENGTH_SHORT).show() + } + binding.fMainUtilManagerCustomButton.setOnClickListener { + Toast.makeText(requireContext(), resources.getString(R.string.util_manager_button_click), Toast.LENGTH_SHORT).show() + } + } + + private fun provideSwitchManagerForCustomSwitcher(): SwitchManager { + return object : SwitchManager() { + override fun enableView(view: View): Boolean { + return when (view){ + is Button, is SwitchCompat -> { + view.isEnabled = true + true + } + else -> + super.enableView(view) + } + } + + override fun disableView(view: View): Boolean { + return when (view){ + is Button, is SwitchCompat -> { + view.isEnabled = false + false + } + else -> + super.disableView(view) + } + } + } + } + + private fun provideViewsForCustomSwitcher(): List { + return listOf( + binding.fMainUtilManagerCustomButton, + binding.fMainUtilManagerCustomEdit, + binding.fMainUtilManagerCustomTestSwitcher, + binding.fMainUtilManagerCustomSwitchMaterial + ) + } +} diff --git a/app/src/main/java/by/esas/tools/screens/util/switchManager/UtilSwitchManagerVM.kt b/app/src/main/java/by/esas/tools/screens/util/switchManager/UtilSwitchManagerVM.kt new file mode 100644 index 00000000..921a6c95 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/switchManager/UtilSwitchManagerVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.util.switchManager + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class UtilSwitchManagerVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/util/utils/UtilsFragment.kt b/app/src/main/java/by/esas/tools/screens/util/utils/UtilsFragment.kt new file mode 100644 index 00000000..9781f396 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/utils/UtilsFragment.kt @@ -0,0 +1,55 @@ +package by.esas.tools.screens.util.utils + +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.base.AppFragment +import by.esas.tools.databinding.FMainUtilUtilsBinding +import by.esas.tools.util.* + +class UtilsFragment: AppFragment() { + override val fragmentDestinationId = R.id.utilUtilsFragment + + override fun provideLayoutId() = R.layout.f_main_util_utils + + override fun provideViewModel(): UtilsVM { + return ViewModelProvider(this, viewModelFactory.provideFactory()).get(UtilsVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fMainUtilsCase1Button.setOnClickListener { + val data = binding.fMainUtilsCase1Edit.getText() + if (data.isNotEmpty()) + binding.fMainUtilsCase1Answer.text = setupResult(data.toDouble().toFormattedInput()) + } + + binding.fMainUtilsCase2Button.setOnClickListener { + val data = binding.fMainUtilsCase2Edit.getText() + if (data.isNotEmpty()) + binding.fMainUtilsCase2Answer.text = setupResult(data.toDouble().toFormattedString()) + } + + binding.fMainUtilsCase3Button.setOnClickListener { + val result = getOnlyNumbers(binding.fMainUtilsCase3Edit.getText()) + binding.fMainUtilsCase3Answer.text = setupResult(result) + } + + binding.fMainUtilsCase4Button.setOnClickListener { + val result = luhnCheck(binding.fMainUtilsCase4Edit.getText()) + binding.fMainUtilsCase4Answer.text = setupResult(result.toString()) + } + + binding.fMainUtilsCase5Button.setOnClickListener { + val data = binding.fMainUtilsCase5Edit.getText() + if (data.length >= 13) + binding.fMainUtilsCase5Answer.text = setupResult(getCardType(data)) + } + } + + private fun setupResult(result: String): String { + return resources.getString(R.string.utils_result) + result + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/util/utils/UtilsVM.kt b/app/src/main/java/by/esas/tools/screens/util/utils/UtilsVM.kt new file mode 100644 index 00000000..07dc2581 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/utils/UtilsVM.kt @@ -0,0 +1,7 @@ +package by.esas.tools.screens.util.utils + +import by.esas.tools.base.AppVM +import javax.inject.Inject + +class UtilsVM @Inject constructor(): AppVM() { +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/usecase/BaseUseCase.kt b/app/src/main/java/by/esas/tools/usecase/BaseUseCase.kt deleted file mode 100644 index c5b039d1..00000000 --- a/app/src/main/java/by/esas/tools/usecase/BaseUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package by.esas.tools.usecase - -import by.esas.tools.domain.usecase.IRefresh -import by.esas.tools.domain.usecase.UseCase -import by.esas.tools.error_mapper.AppErrorMapper -import by.esas.tools.error_mapper.AppErrorStatusEnum -import by.esas.tools.logger.ErrorModel - -abstract class BaseUseCase(errorUtil: AppErrorMapper, refresher: IRefresh?) : - UseCase(errorUtil, refresher) \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/utils/FieldUtils.kt b/app/src/main/java/by/esas/tools/utils/FieldUtils.kt new file mode 100644 index 00000000..f2fdd5f9 --- /dev/null +++ b/app/src/main/java/by/esas/tools/utils/FieldUtils.kt @@ -0,0 +1,20 @@ +package by.esas.tools.utils + +import android.widget.EditText +import by.esas.tools.inputfieldview.InputFieldView +import by.esas.tools.inputfieldview.dpToPx + +fun getDimensInDp(editField: EditText): Int { + val text = editField.text.toString() + return if (text.isEmpty()) 0 else dpToPx(text.toInt()).toInt() +} + +fun getDimensInDp(inputFieldView: InputFieldView): Int { + val text = inputFieldView.getText() + return if (text.isEmpty()) 0 else dpToPx(text.toInt()).toInt() +} + +fun InputFieldView.getFloatValue(): Float { + val text = this.getText() + return if (text.isEmpty()) 0f else text.toFloat() +} diff --git a/app/src/main/java/by/esas/tools/Utils.kt b/app/src/main/java/by/esas/tools/utils/Utils.kt similarity index 88% rename from app/src/main/java/by/esas/tools/Utils.kt rename to app/src/main/java/by/esas/tools/utils/Utils.kt index f44f90fb..991d1ad3 100644 --- a/app/src/main/java/by/esas/tools/Utils.kt +++ b/app/src/main/java/by/esas/tools/utils/Utils.kt @@ -2,6 +2,7 @@ package by.esas.tools import android.app.Activity import android.content.Context +import android.content.res.Resources import android.view.View import java.util.* @@ -25,4 +26,8 @@ fun hideSystemUIApp(activity: Activity?) { or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION /*or View.SYSTEM_UI_FLAG_FULLSCREEN*/) } +} + +fun dpToPx(dp: Int): Int { + return (dp * Resources.getSystem().displayMetrics.density).toInt() } \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/checking/AppChecker.kt b/app/src/main/java/by/esas/tools/utils/checking/AppChecker.kt similarity index 94% rename from app/src/main/java/by/esas/tools/checking/AppChecker.kt rename to app/src/main/java/by/esas/tools/utils/checking/AppChecker.kt index a3a89327..04f9dcac 100644 --- a/app/src/main/java/by/esas/tools/checking/AppChecker.kt +++ b/app/src/main/java/by/esas/tools/utils/checking/AppChecker.kt @@ -1,4 +1,4 @@ -package by.esas.tools.checking +package by.esas.tools.utils.checking import by.esas.tools.checker.BaseCheck import by.esas.tools.checker.Checker @@ -29,12 +29,12 @@ open class AppChecker : Checker() { result = tempResult } } else { - checks.forEach { + for(it in checks) { result = it.checkField(showError, getter) && result if (!result) { if (focusOnFirstError && it is IFocusableChecking) it.requestFocus() - return@forEach + break } } } diff --git a/app/src/main/java/by/esas/tools/checking/FieldChecking.kt b/app/src/main/java/by/esas/tools/utils/checking/FieldChecking.kt similarity index 98% rename from app/src/main/java/by/esas/tools/checking/FieldChecking.kt rename to app/src/main/java/by/esas/tools/utils/checking/FieldChecking.kt index 94ff1a1b..8283c99a 100644 --- a/app/src/main/java/by/esas/tools/checking/FieldChecking.kt +++ b/app/src/main/java/by/esas/tools/utils/checking/FieldChecking.kt @@ -1,4 +1,4 @@ -package by.esas.tools.checking +package by.esas.tools.utils.checking import by.esas.tools.checker.BaseCheck import by.esas.tools.checker.Checking diff --git a/app/src/main/java/by/esas/tools/checking/ValueChecking.kt b/app/src/main/java/by/esas/tools/utils/checking/ValueChecking.kt similarity index 97% rename from app/src/main/java/by/esas/tools/checking/ValueChecking.kt rename to app/src/main/java/by/esas/tools/utils/checking/ValueChecking.kt index 013ce446..fe024a89 100644 --- a/app/src/main/java/by/esas/tools/checking/ValueChecking.kt +++ b/app/src/main/java/by/esas/tools/utils/checking/ValueChecking.kt @@ -1,4 +1,4 @@ -package by.esas.tools.checking +package by.esas.tools.utils.checking import androidx.lifecycle.MutableLiveData import by.esas.tools.checker.BaseCheck diff --git a/app/src/main/java/by/esas/tools/utils/logger/ErrorModel.kt b/app/src/main/java/by/esas/tools/utils/logger/ErrorModel.kt new file mode 100644 index 00000000..b78e9f80 --- /dev/null +++ b/app/src/main/java/by/esas/tools/utils/logger/ErrorModel.kt @@ -0,0 +1,11 @@ +package by.esas.tools.utils.logger + +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import by.esas.tools.logger.BaseErrorModel + +class ErrorModel(code: Int, statusEnum: AppErrorStatusEnum) : BaseErrorModel(code, statusEnum.name) { + + fun getStatusAsEnum(): AppErrorStatusEnum { + return AppErrorStatusEnum.getAppErrorStatusEnum(statusEnum) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/logger/LoggerImpl.kt b/app/src/main/java/by/esas/tools/utils/logger/LoggerImpl.kt similarity index 50% rename from app/src/main/java/by/esas/tools/logger/LoggerImpl.kt rename to app/src/main/java/by/esas/tools/utils/logger/LoggerImpl.kt index ff72a731..918c1ca0 100644 --- a/app/src/main/java/by/esas/tools/logger/LoggerImpl.kt +++ b/app/src/main/java/by/esas/tools/utils/logger/LoggerImpl.kt @@ -1,12 +1,12 @@ -package by.esas.tools.logger +package by.esas.tools.utils.logger import android.util.Log -import by.esas.tools.error_mapper.AppErrorStatusEnum +import by.esas.tools.logger.ILogger -class LoggerImpl : ILogger { - var baseTag: String = "LoggerImpl" +class LoggerImpl : ILogger { + override var currentTag: String = "LoggerImpl" override fun setTag(tag: String) { - this.baseTag = tag + this.currentTag = tag } override fun showMessage(msg: String, length: Int) { @@ -17,28 +17,20 @@ class LoggerImpl : ILogger { } - override fun logLocally(msg: String) { - Log.w(baseTag, msg) - } - - override fun log(msg: String) { - Log.w(baseTag, msg) - } - override fun log(tag: String, msg: String, level: Int) { - Log.w(baseTag, msg) + Log.w(currentTag, msg) } override fun logError(throwable: Throwable) { - Log.e(baseTag, throwable.message, throwable) + Log.e(currentTag, throwable.message, throwable) } override fun logError(msg: String) { - Log.e(baseTag, msg) + Log.e(currentTag, msg) } override fun logInfo(msg: String) { - Log.i(baseTag, msg) + Log.i(currentTag, msg) } override fun sendLogs(msg: String) { @@ -46,6 +38,11 @@ class LoggerImpl : ILogger { } override fun logError(error: ErrorModel) { - Log.e(baseTag, error.statusEnum.name) + Log.e(currentTag, error.statusEnum) + } + + + override fun logCategory(category: String, tag: String, msg: String) { + Log.e(tag, "$category: $msg") } } \ No newline at end of file diff --git a/app/src/main/res/color/button_selector.xml b/app/src/main/res/color/button_selector.xml new file mode 100644 index 00000000..54d9d2a8 --- /dev/null +++ b/app/src/main/res/color/button_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/inputfieldview_selector_custom_color.xml b/app/src/main/res/color/inputfieldview_selector_custom_color.xml new file mode 100644 index 00000000..5a80db14 --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_custom_color.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/inputfieldview_selector_icon_color.xml b/app/src/main/res/color/inputfieldview_selector_icon_color.xml new file mode 100644 index 00000000..541bc8ff --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_icon_color.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/inputfieldview_selector_stroke_color.xml b/app/src/main/res/color/inputfieldview_selector_stroke_color.xml new file mode 100644 index 00000000..2890f656 --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_stroke_color.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/inputfieldview_selector_text_color.xml b/app/src/main/res/color/inputfieldview_selector_text_color.xml new file mode 100644 index 00000000..14dbba33 --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_text_color.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector.xml b/app/src/main/res/color/switcher_selector.xml new file mode 100644 index 00000000..9db364b9 --- /dev/null +++ b/app/src/main/res/color/switcher_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_green.xml b/app/src/main/res/color/switcher_selector_green.xml new file mode 100644 index 00000000..8156daa0 --- /dev/null +++ b/app/src/main/res/color/switcher_selector_green.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_green_light.xml b/app/src/main/res/color/switcher_selector_green_light.xml new file mode 100644 index 00000000..c247f92a --- /dev/null +++ b/app/src/main/res/color/switcher_selector_green_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_holo_red.xml b/app/src/main/res/color/switcher_selector_holo_red.xml new file mode 100644 index 00000000..e648616e --- /dev/null +++ b/app/src/main/res/color/switcher_selector_holo_red.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_orange.xml b/app/src/main/res/color/switcher_selector_orange.xml new file mode 100644 index 00000000..ea1f2a0c --- /dev/null +++ b/app/src/main/res/color/switcher_selector_orange.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_orange_light.xml b/app/src/main/res/color/switcher_selector_orange_light.xml new file mode 100644 index 00000000..3462ca54 --- /dev/null +++ b/app/src/main/res/color/switcher_selector_orange_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_red.xml b/app/src/main/res/color/switcher_selector_red.xml new file mode 100644 index 00000000..295ee730 --- /dev/null +++ b/app/src/main/res/color/switcher_selector_red.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_yellow.xml b/app/src/main/res/color/switcher_selector_yellow.xml new file mode 100644 index 00000000..94280055 --- /dev/null +++ b/app/src/main/res/color/switcher_selector_yellow.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_selector_yellow_light.xml b/app/src/main/res/color/switcher_selector_yellow_light.xml new file mode 100644 index 00000000..faf85402 --- /dev/null +++ b/app/src/main/res/color/switcher_selector_yellow_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/switcher_thumb_selector.xml b/app/src/main/res/color/switcher_thumb_selector.xml new file mode 100644 index 00000000..054c8ade --- /dev/null +++ b/app/src/main/res/color/switcher_thumb_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-night/card.png b/app/src/main/res/drawable-night/card.png new file mode 100644 index 00000000..779cb8ff Binary files /dev/null and b/app/src/main/res/drawable-night/card.png differ diff --git a/app/src/main/res/drawable-v24/card.png b/app/src/main/res/drawable-v24/card.png new file mode 100644 index 00000000..5e161992 Binary files /dev/null and b/app/src/main/res/drawable-v24/card.png differ diff --git a/app/src/main/res/drawable-v24/ic_search.xml b/app/src/main/res/drawable-v24/ic_search.xml new file mode 100644 index 00000000..3bd9249a --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_search.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/bg_case_status_checked.xml b/app/src/main/res/drawable/bg_case_status_checked.xml new file mode 100644 index 00000000..4349ea1b --- /dev/null +++ b/app/src/main/res/drawable/bg_case_status_checked.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_case_status_failed.xml b/app/src/main/res/drawable/bg_case_status_failed.xml new file mode 100644 index 00000000..7f74ca11 --- /dev/null +++ b/app/src/main/res/drawable/bg_case_status_failed.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_case_status_in_process.xml b/app/src/main/res/drawable/bg_case_status_in_process.xml new file mode 100644 index 00000000..9acc5f12 --- /dev/null +++ b/app/src/main/res/drawable/bg_case_status_in_process.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_case_status_unchecked.xml b/app/src/main/res/drawable/bg_case_status_unchecked.xml new file mode 100644 index 00000000..69f38026 --- /dev/null +++ b/app/src/main/res/drawable/bg_case_status_unchecked.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/frame_card.xml b/app/src/main/res/drawable/frame_card.xml new file mode 100644 index 00000000..44b16394 --- /dev/null +++ b/app/src/main/res/drawable/frame_card.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 00000000..89633bb1 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 00000000..8452791c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 00000000..9e345b86 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_downward.xml b/app/src/main/res/drawable/ic_arrow_downward.xml new file mode 100644 index 00000000..dcfb7840 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_downward.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_drop_down.xml b/app/src/main/res/drawable/ic_arrow_drop_down.xml new file mode 100644 index 00000000..c1c897af --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_down.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_drop_up.xml b/app/src/main/res/drawable/ic_arrow_drop_up.xml new file mode 100644 index 00000000..10c5932c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_up.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 00000000..1227182e --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_upward.xml b/app/src/main/res/drawable/ic_arrow_upward.xml new file mode 100644 index 00000000..46d5d587 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_upward.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 00000000..a0a94e3c --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cancel_round.xml b/app/src/main/res/drawable/ic_cancel_round.xml new file mode 100644 index 00000000..34e48f40 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel_round.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_checkbox_filled.xml b/app/src/main/res/drawable/ic_checkbox_filled.xml new file mode 100644 index 00000000..f8a66154 --- /dev/null +++ b/app/src/main/res/drawable/ic_checkbox_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_checkbox_unfilled.xml b/app/src/main/res/drawable/ic_checkbox_unfilled.xml new file mode 100644 index 00000000..30c54718 --- /dev/null +++ b/app/src/main/res/drawable/ic_checkbox_unfilled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 00000000..844b6b62 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud.xml b/app/src/main/res/drawable/ic_cloud.xml new file mode 100644 index 00000000..a860632d --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud_queue.xml b/app/src/main/res/drawable/ic_cloud_queue.xml new file mode 100644 index 00000000..72f7d891 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_queue.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 00000000..ef366491 --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_favorite.xml b/app/src/main/res/drawable/ic_favorite.xml new file mode 100644 index 00000000..52d28592 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_favorite_border.xml b/app/src/main/res/drawable/ic_favorite_border.xml new file mode 100644 index 00000000..23d6089f --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_border.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 00000000..b8090430 --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu.xml b/app/src/main/res/drawable/ic_menu.xml new file mode 100644 index 00000000..543cee9e --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_password_toggle_off.xml b/app/src/main/res/drawable/ic_password_toggle_off.xml new file mode 100644 index 00000000..25ea6535 --- /dev/null +++ b/app/src/main/res/drawable/ic_password_toggle_off.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_password_toggle_on.xml b/app/src/main/res/drawable/ic_password_toggle_on.xml new file mode 100644 index 00000000..b2ed2646 --- /dev/null +++ b/app/src/main/res/drawable/ic_password_toggle_on.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pin_star_filled.xml b/app/src/main/res/drawable/ic_pin_star_filled.xml new file mode 100644 index 00000000..12a4a505 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_star_filled.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_pin_star_unfilled.xml b/app/src/main/res/drawable/ic_pin_star_unfilled.xml new file mode 100644 index 00000000..59aef558 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_star_unfilled.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_remove.xml b/app/src/main/res/drawable/ic_remove.xml new file mode 100644 index 00000000..3e0b7768 --- /dev/null +++ b/app/src/main/res/drawable/ic_remove.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 00000000..298a5a1f --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_star.xml b/app/src/main/res/drawable/ic_star.xml new file mode 100644 index 00000000..9bfb7c2a --- /dev/null +++ b/app/src/main/res/drawable/ic_star.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/number_0.xml b/app/src/main/res/drawable/number_0.xml new file mode 100644 index 00000000..3bbacb84 --- /dev/null +++ b/app/src/main/res/drawable/number_0.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/number_1.xml b/app/src/main/res/drawable/number_1.xml new file mode 100644 index 00000000..4cfaf4f5 --- /dev/null +++ b/app/src/main/res/drawable/number_1.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_2.xml b/app/src/main/res/drawable/number_2.xml new file mode 100644 index 00000000..1912041c --- /dev/null +++ b/app/src/main/res/drawable/number_2.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_3.xml b/app/src/main/res/drawable/number_3.xml new file mode 100644 index 00000000..1800fae7 --- /dev/null +++ b/app/src/main/res/drawable/number_3.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_4.xml b/app/src/main/res/drawable/number_4.xml new file mode 100644 index 00000000..8b40c553 --- /dev/null +++ b/app/src/main/res/drawable/number_4.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_5.xml b/app/src/main/res/drawable/number_5.xml new file mode 100644 index 00000000..107dbf4f --- /dev/null +++ b/app/src/main/res/drawable/number_5.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_6.xml b/app/src/main/res/drawable/number_6.xml new file mode 100644 index 00000000..a9b36c35 --- /dev/null +++ b/app/src/main/res/drawable/number_6.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_7.xml b/app/src/main/res/drawable/number_7.xml new file mode 100644 index 00000000..b2cc8126 --- /dev/null +++ b/app/src/main/res/drawable/number_7.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_8.xml b/app/src/main/res/drawable/number_8.xml new file mode 100644 index 00000000..1a2d391b --- /dev/null +++ b/app/src/main/res/drawable/number_8.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/number_9.xml b/app/src/main/res/drawable/number_9.xml new file mode 100644 index 00000000..41600b4a --- /dev/null +++ b/app/src/main/res/drawable/number_9.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/numpad_backspace.xml b/app/src/main/res/drawable/numpad_backspace.xml new file mode 100644 index 00000000..e81142f6 --- /dev/null +++ b/app/src/main/res/drawable/numpad_backspace.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/numpad_cancel.xml b/app/src/main/res/drawable/numpad_cancel.xml new file mode 100644 index 00000000..f4a9ec97 --- /dev/null +++ b/app/src/main/res/drawable/numpad_cancel.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/radio_background.xml b/app/src/main/res/drawable/radio_background.xml new file mode 100644 index 00000000..8fee3943 --- /dev/null +++ b/app/src/main/res/drawable/radio_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/radio_background_checked.xml b/app/src/main/res/drawable/radio_background_checked.xml new file mode 100644 index 00000000..d0d8b2c2 --- /dev/null +++ b/app/src/main/res/drawable/radio_background_checked.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/radio_background_unchecked.xml b/app/src/main/res/drawable/radio_background_unchecked.xml new file mode 100644 index 00000000..852db49b --- /dev/null +++ b/app/src/main/res/drawable/radio_background_unchecked.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_check_box.xml b/app/src/main/res/drawable/selector_check_box.xml new file mode 100644 index 00000000..9fd4aeeb --- /dev/null +++ b/app/src/main/res/drawable/selector_check_box.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_password_toggle.xml b/app/src/main/res/drawable/selector_password_toggle.xml new file mode 100644 index 00000000..92bb65f3 --- /dev/null +++ b/app/src/main/res/drawable/selector_password_toggle.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 901d457f..f0a2c7b9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,242 +7,63 @@ - + type="by.esas.tools.screens.MainVM" /> - + tools:openDrawer="end"> - - - - - - - - - - - - - - - - - + android:background="@color/colorBackground" + android:descendantFocusability="beforeDescendants" + android:focusableInTouchMode="true"> - - - - - - - - - - - - - - - - + + + + + + + - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/color_radio_group.xml b/app/src/main/res/layout/color_radio_group.xml new file mode 100644 index 00000000..1142a0cd --- /dev/null +++ b/app/src/main/res/layout/color_radio_group.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/df_custom_bottom.xml b/app/src/main/res/layout/df_custom_bottom.xml new file mode 100644 index 00000000..47c0b695 --- /dev/null +++ b/app/src/main/res/layout/df_custom_bottom.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + +