diff --git a/README.md b/README.md index 6ce78fd0..4d8d8b75 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # BasicTools + [![Release](https://jitpack.io/v/esasby/basic-tools.svg)] (https://jitpack.io/#esasby/basic-tools) diff --git a/accesscontainer/build.gradle b/accesscontainer/build.gradle deleted file mode 100644 index 71000eee..00000000 --- a/accesscontainer/build.gradle +++ /dev/null @@ -1,64 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'maven-publish' - -// 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 - - defaultConfig { - minSdkVersion 16 - } -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "androidx.core:core-ktx:$corektx_version" - - //Module - implementation project(path: ':logger') -} diff --git a/accesscontainer/build.gradle.kts b/accesscontainer/build.gradle.kts new file mode 100644 index 00000000..8749c7e7 --- /dev/null +++ b/accesscontainer/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + alias(libs.plugins.androidLibrary) + alias(libs.plugins.jetbrainsKotlinAndroid) + `maven-publish` +} + +val packageName: String by rootProject.extra +val compileSdkVer: Int by rootProject.extra +val minSdkVersion: Int by rootProject.extra +val repoName: String by rootProject.extra +val libVersion: String = rootProject.extra.get("accesscontainerLib").toString() + +android { + namespace = "${packageName}.${project.name}" + compileSdk = compileSdkVer + defaultConfig { + minSdk = minSdkVersion + } + /* compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + }*/ + kotlinOptions { + jvmTarget = "1.8" + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +dependencies { + implementation(libs.appcompat) + + //Module + api(project(":logger")) +} + +publishing { + publications { + register("release") { + groupId = packageName + artifactId = project.name + version = libVersion + afterEvaluate { + from(components["release"]) + } + } + } +} diff --git a/accesscontainer/proguard-rules.pro b/accesscontainer/proguard-rules.pro index f1b42451..2f9dc5a4 100644 --- a/accesscontainer/proguard-rules.pro +++ b/accesscontainer/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html 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..53877294 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/ContainerRequest.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/ContainerRequest.kt @@ -7,8 +7,8 @@ package by.esas.tools.accesscontainer import by.esas.tools.logger.BaseErrorModel +class ContainerRequest { -class ContainerRequest, M : BaseErrorModel> { 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..634f1dea 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/IRefreshContainer.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/IRefreshContainer.kt @@ -6,45 +6,43 @@ package by.esas.tools.accesscontainer import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager 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 fun setCancellationCallback(callback: IContainerCancellationCallback) fun setActivity(activity: FragmentActivity) - fun setParams(manager: FragmentManager) fun setForgotPasswordAction(enable: Boolean, forgotPasswordAction: () -> Unit) /** * 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: (M) -> 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 + * This function return secret after PIN or Biometric decryption success **/ - 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..5d3208f4 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/Refresher.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/Refresher.kt @@ -7,7 +7,6 @@ package by.esas.tools.accesscontainer import android.provider.Settings import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent @@ -15,6 +14,9 @@ import by.esas.tools.accesscontainer.dialog.BiometricUserInfo import by.esas.tools.accesscontainer.dialog.DialogProvider import by.esas.tools.accesscontainer.dialog.IBaseDialog import by.esas.tools.accesscontainer.dialog.IBiometric +import by.esas.tools.accesscontainer.dialog.setters.MenuResultHandler +import by.esas.tools.accesscontainer.dialog.setters.PasswordResultHandler +import by.esas.tools.accesscontainer.dialog.setters.PinResultHandler import by.esas.tools.accesscontainer.entity.AuthType import by.esas.tools.accesscontainer.entity.RefreshResult import by.esas.tools.accesscontainer.entity.Token @@ -26,20 +28,25 @@ 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 + + /*region Params*/ + protected var accessToken: String = "" protected var refreshToken: String = "" @@ -47,21 +54,22 @@ open class Refresher, M : BaseErrorModel>( protected val defaultCancellationCallback: IContainerCancellationCallback = object : IContainerCancellationCallback { override fun onCancel() { - logger.log("defaultCancellationCallback onCancel does nothing") + logger.i(TAG, "defaultCancellationCallback onCancel does nothing") //does nothing. should be set before refresh usage } } protected var containerCancellationCallback: IContainerCancellationCallback = defaultCancellationCallback // cancellation callback invoke if UC callback not set? - protected var fragmentManager: WeakReference = - WeakReference(null) // fragment manager for simple dialogs - protected var activity: WeakReference = WeakReference(null) // activity for biometric dialog + /*protected var fragmentManager: WeakReference = + 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,26 +90,29 @@ 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) - } + /*endregion Params*/ + + /*region ############################# Base functionality ##########################################*/ override fun setUserId(userId: String) { this.userIdValue = userId } override fun getToken(): String { - logger.log("getToken()") + logger.order(TAG, "getToken()") return accessToken } override fun setToken(token: Token) { - logger.log("setToken() refreshToken isNullOrBlank = ${token.refreshToken.isNullOrBlank()}") + logger.order( + TAG, + "setToken() refreshToken isNullOrBlank = ${token.refreshToken.isNullOrBlank()}" + ) accessToken = token.accessToken refreshToken = token.refreshToken ?: "" } @@ -111,60 +122,76 @@ open class Refresher, M : BaseErrorModel>( } /** - * Запуск запроса на восстановление маркера доступа с последующим обновлением - * данных контейнера и выполнением переданного блока кода + * Initiate a request to restore an access token, then update + * the container data and execute the passed block of code */ - override fun refresh(onComplete: (String?) -> Unit, onError: (BaseErrorModel) -> Unit, onCancel: () -> Unit) { - logger.log("refresh") + override fun refresh( + onComplete: (String?) -> Unit, + onError: (M) -> Unit, + onCancel: () -> Unit + ) { + logger.order(TAG, "refresh") this.refreshExplicitly = true - result = ContainerRequest() + result = ContainerRequest() .apply { onComplete { tokenResponse -> - logger.log("refresh result.onComplete token != null = ${tokenResponse.token != null}") + logger.order( + TAG, + "refresh result.onComplete token != null = ${tokenResponse.token != null}" + ) tokenResponse.token?.let { setToken(it) } onComplete(tokenResponse.token?.accessToken) } onError { - logger.log("refresh result.onError") - logger.logError(it) + logger.order(TAG, "refresh result.onError") + logger.errorModel(TAG, it) onError(it) } onCancel { - logger.log("refresh result.onCancel") + logger.order(TAG, "refresh result.onCancel") onCancel() } } - logger.log("refresh needCheck = false") + logger.d(TAG, "refresh needCheck = false") needCheck = false needSecret = false if (refreshToken.isNotEmpty()) { - logger.log("refresh refreshToken isNotEmpty") + logger.d(TAG, "refresh refreshToken isNotEmpty") refreshAccess(refreshToken) } else { - logger.log("refresh refreshToken isEmpty") + logger.d(TAG, "refresh refreshToken isEmpty") userIdValue = userInfo.getCurrentUser() - logger.log("refresh userId = $userIdValue") + logger.userInfo(TAG, "refresh userId = $userIdValue") getSecrets() } } override fun onCancel() { - logger.log("onCancel") + logger.order(TAG, "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.order(TAG, "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.order( + TAG, + "checkAccess result.onComplete hasAccess = ${tokenResponse.hasAccess}" + ) if (tokenResponse.hasAccess) { - logger.log("checkAccess result.onComplete token != null = ${tokenResponse.token != null}") + logger.d( + TAG, + "checkAccess result.onComplete token != null = ${tokenResponse.token != null}" + ) tokenResponse.token?.let { token -> setToken(token) } @@ -172,52 +199,56 @@ open class Refresher, M : BaseErrorModel>( } else result() } onError { - logger.log("checkAccess result.onError") - logger.logError(it) + logger.order(TAG, "checkAccess result.onError") + logger.errorModel(TAG, it) result(it) } onCancel { - logger.log("checkAccess result.onCancel") + logger.order(TAG, "checkAccess result.onCancel") result() } } - logger.log("checkAccess needCheck = true") + logger.d(TAG, "checkAccess needCheck = true needSecret = false") needCheck = true needSecret = false userIdValue = userInfo.getCurrentUser() - logger.log("checkAccess userId = $userIdValue") + logger.userInfo(TAG, "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.order(TAG, "getSecret") + val result = ContainerRequest().apply(response) this.secretResult = result - logger.log("getSecret needSecret = true, needCheck = false") + logger.d(TAG, "getSecret needSecret = true, needCheck = false") needSecret = true needCheck = false userIdValue = userInfo.getCurrentUser() - logger.log("getSecret userId = $userIdValue") + logger.userInfo(TAG, "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.order(TAG, "saveRefresh result.onComplete") + this@Refresher.refreshToken = tokenResponse.token?.refreshToken ?: "" result(accessToken) } onError { - logger.log("saveRefresh result.onError") - logger.logError(it) + logger.order(TAG, "saveRefresh result.onError") + logger.errorModel(TAG, it) result(it) } onCancel { - logger.log("saveRefresh result.onCancel") + logger.order(TAG, "saveRefresh result.onCancel") result() } } @@ -229,19 +260,66 @@ open class Refresher, M : BaseErrorModel>( } } - /*############################ Simple refresh token #########################################*/ + override fun clearAccess() { + logger.order(TAG, "clearAccess") + accessToken = "" + refreshToken = "" + executor.unsubscribe() + } + + override fun clear() { + logger.order(TAG, "clear") + clearAccess() + containerCancellationCallback = defaultCancellationCallback + result = ContainerRequest() + userIdValue = "" + isBiometricAvailable = false + needCheck = false + refreshExplicitly = false + types.clear() + //presentType = AuthType.NONE + stateIsPaused = false + lastAction = {} + currentDialog?.dismiss() + currentDialog = null + currentBiometricDialog?.cancelAuthentication() + currentBiometricDialog = null + dialogProvider.menuDialog.clear(activity.get()) + dialogProvider.pinDialog.clear(activity.get()) + dialogProvider.passwordDialog.clear(activity.get()) + dialogProvider.biom.clear() + activity.clear() + forgotPasswordActionEnable = false + forgotPasswordAction = {} + } + + open fun setCheckRefreshError( + check: Boolean = false, + errorStatus: String? = null, + useExtraCheck: Boolean = false, + extraCheck: (BaseErrorModel) -> Unit = {} + ) { + checkRefreshError = check + refresherErrorStatusToCheck = errorStatus + this.useExtraCheck = useExtraCheck + extraRefreshCheck = extraCheck + } + + /*endregion ########################### Base functionality #########################################*/ + + /*region ############################ Simple refresh token #########################################*/ protected open fun refreshAccess(refreshToken: String) { - logger.log("refreshAccess") + logger.order(TAG, "refreshAccess") executor.refreshAccessUC(refreshToken) { onComplete { token -> - logger.log("refreshAccess onComplete it != null ${token != null}") + logger.order(TAG, "refreshAccess onComplete it != null ${token != null}") if (token != null) { result(RefreshResult(token = token)) } } onError { error -> - logger.log("refreshAccess onError error = $error") + logger.order(TAG, "refreshAccess onError error = $error") if (checkRefreshError) { checkRefreshError(error) } else { @@ -252,10 +330,11 @@ open class Refresher, M : BaseErrorModel>( } protected open fun checkRefreshError(error: M) { + logger.order(TAG, "checkRefreshError") if (useExtraCheck) { extraRefreshCheck(error) } else { - if (error.statusEnum == refresherErrorStatusToCheck) { + if (error.status == refresherErrorStatusToCheck) { //deleteSecrets() showPasswordDialog(true) } else @@ -263,105 +342,137 @@ open class Refresher, M : BaseErrorModel>( } } - /*############################# Simple refresh token END ####################################*/ + /*endregion ############################# Simple refresh token END ####################################*/ - /*############################# Refresh token with dialogs ##################################*/ + /*region ############################# Refresh token with dialogs ##################################*/ protected open fun getSecrets() { - logger.log("getSecrets") + logger.order(TAG, "getSecrets") types.clear() executor.getSecretsUC { onComplete { - logger.log("getSecrets onComplete types not empty = ${it.isNotEmpty()}") + logger.order(TAG, "getSecrets onComplete types not empty = ${it.isNotEmpty()}") if (it.isNotEmpty()) { doWhenHasSecrets(it) } else { if (needSecret) - secretResult(supporter.util.createErrorModel(0, ErrorStatusEnum.HAS_NO_SECRETS.name)) + secretResult(mapper(0, ErrorStatusEnum.HAS_NO_SECRETS.name)) else showPasswordDialog() } } onError { throwable -> - logger.log("getSecrets onError") + logger.order(TAG, "getSecrets onError") result(throwable) } } } + protected fun showDialogForType(type: AuthType) { + when (type) { + AuthType.PIN_AUTH -> { + showDecryptPinDialog() + } + + AuthType.BIOMETRIC_AUTH -> { + showDecryptBiometricDialog() + } + + AuthType.NONE -> { + showPasswordDialog() + } + } + } + protected open fun doWhenHasSecrets(list: List) { + types.clear() types.addAll(list) - logger.log("getSecrets onComplete onlyOnePresent = ${list.size == 1}") + logger.order(TAG, "getSecrets onComplete onlyOnePresent = ${list.size == 1}") val preferred = typeManager.getPreferredType() - logger.log("getSecrets onComplete preferred = $preferred") - if (preferred == AuthType.NONE || types.contains(preferred)) { - when (preferred) { - AuthType.PIN_AUTH -> { - showDecryptPinDialog() - } - AuthType.BIOMETRIC_AUTH -> { - showDecryptBiometricDialog() - } - AuthType.NONE -> { - if (needSecret) { - secretResult( - supporter.util.createErrorModel(0, ErrorStatusEnum.HAS_NO_SECRETS.name) - ) - } else { - showPasswordDialog() - } - } + logger.d(TAG, "getSecrets onComplete preferred = $preferred") + + if (!needSecret) { + //needSecret != true + if (preferred == AuthType.NONE || list.contains(preferred)) { + showDialogForType(preferred) + } else if (list.size > 1 || !list.contains(AuthType.NONE)) { + //FIX + //keep in mind that list can not be empty + showAuthDialog(isDecrypting = true) + } else { + showDialogForType(list[0]) } } else { - showAuthDialog(isDecrypting = true) + //needSecret == true + if (preferred != AuthType.NONE && list.contains(preferred)) { + //preferred is not NONE and List contains preferred + showDialogForType(preferred) + } else { + //preferred is NONE or list does not contain preferred + val filtered = list.filter { it != AuthType.NONE } + if (filtered.isEmpty()) { + secretResult(mapper(0, ErrorStatusEnum.HAS_NO_SECRETS.name)) + } else if (filtered.size == 1) { + showDialogForType(filtered[0]) + } else { + showAuthDialog(isDecrypting = true) + } + } } } -/*############################# Refresh token with dialogs END ##############################*/ + /*endregion ############################# Refresh token with dialogs END ##############################*/ -/*############################# Check access with dialogs ###################################*/ + /*region ############################# 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.order( + TAG, + "checkSecret pinKey!=null ${pinKey != null}, cipher!=null ${cipher != null}" + ) executor.checkSecretUC(pinKey, cipher) { onComplete { - logger.log("checkSecret onComplete ${it.first}") + logger.order(TAG, "checkSecret onComplete ${it.first}") if (it.first) { when { needSecret -> secretResult(it.second) refreshExplicitly -> { - logger.log("checkSecret onComplete refreshToken != null") + logger.d(TAG, "checkSecret onComplete refreshToken != null") refreshWithSecret(it.second) } + else -> { - logger.log("checkSecret onComplete return") + logger.d(TAG, "checkSecret onComplete return") result(RefreshResult()) } } } else { - logger.log("checkSecret onComplete failed check return error SECRET_CHECK_NOT_MATCH") - val error = supporter.util.createErrorModel(0, ErrorStatusEnum.SECRET_CHECK_NOT_MATCH.name) + logger.d( + TAG, + "checkSecret onComplete failed check return error SECRET_CHECK_NOT_MATCH" + ) + val error = mapper(0, ErrorStatusEnum.SECRET_CHECK_NOT_MATCH.name) if (needSecret) secretResult(error) else result(error) } } onError { error -> - logger.log("checkSecret onError") + logger.order(TAG, "checkSecret onError") result(error) } } } protected open fun refreshWithSecret(secret: String) { - logger.log("refreshWithSecret") + logger.order(TAG, "refreshWithSecret") executor.refreshWithSecretUC(secret) { onComplete { token -> - logger.log("refreshWithSecret onComplete") + logger.order(TAG, "refreshWithSecret onComplete") result(RefreshResult(token)) } onError { error -> - logger.log("refreshWithSecret onError") + logger.order(TAG, "refreshWithSecret onError") if (checkRefreshError) { checkRefreshError(error) } else { @@ -371,17 +482,26 @@ open class Refresher, M : BaseErrorModel>( } } -/*############################## Check access with dialogs END ##############################*/ + /*endregion ############################## Check access with dialogs END ##############################*/ - /*############################### Authentication with password ##############################*/ + /*region ############################### 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.order( + TAG, + "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.order( + TAG, + "authenticate onComplete it.refreshToken != null ${token.refreshToken != null}" + ) if (token.refreshToken != null) { - logger.log("authenticate onComplete isBiometricAvailable $isBiometricAvailable") + logger.d( + TAG, + "authenticate onComplete isBiometricAvailable $isBiometricAvailable" + ) if (recreate) { if (isBiometricAvailable) { @@ -393,191 +513,231 @@ open class Refresher, M : BaseErrorModel>( result(RefreshResult(token)) } } else { - logger.log("authenticate onComplete return") + logger.d(TAG, "authenticate onComplete return") result(RefreshResult(token)) } } onError { error -> - logger.log("authenticate onError") + logger.order(TAG, "authenticate onError") result(error) } } } else { - logger.log("authenticate showPasswordDialog") + logger.d(TAG, "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.order( + TAG, + "putSecrets pinKey!=null ${pinKey != null}, cipher!=null ${cipher != null}" + ) executor.createSecretUC(token.refreshToken!!, cipher, pinKey) { onComplete { - logger.log("putSecrets onComplete return") + logger.order(TAG, "putSecrets onComplete return") result(RefreshResult(token)) } onError { - logger.log("putSecrets onError return") - logger.showMessage("Secret was not saved") //todo show message secret was not create + logger.order(TAG, "putSecrets onError return") + logger.i(TAG, "Secret was not saved") //todo show message secret was not create result(RefreshResult(token)) } } } -/*############################## Authentication with password END ###########################*/ + /*endregion ############################## Authentication with password END ###########################*/ -/*############################### Dialogs ###################################################*/ + /*############################### Dialogs ###################################################*/ + + /*region ############################### Menu dialog ##############################################*/ protected open fun showAuthDialog(isDecrypting: Boolean, token: Token? = null) { - logger.log("showAuthDialog isDecrypting ${isDecrypting}, token!=null = ${token != null}") + logger.order( + TAG, + "showAuthDialog isDecrypting ${isDecrypting}, token!=null = ${token != null}" + ) dialogProvider.menuDialog.let { dialog -> dialog.createDialog() - dialog.setStateActions({ e -> - logger.log("showAuthDialog onError ${e.message}") - if (needSecret) secretResult(mapper(e)) - else result(mapper(e)) - }, { afterOk -> - logger.log("showAuthDialog onDismiss afterOk=${afterOk}") - if (!afterOk) { - if (needSecret) secretResult() - else result() - } - }) - dialog.isDecrypting(isDecrypting) - dialog.showPassword(!needSecret) - dialog.setCallBacks({ - logger.log("showAuthDialog onPinClick") - if (isDecrypting) showDecryptPinDialog() - else token?.let { showEncryptPinDialog(token) } - }, { - logger.log("showAuthDialog onBiometricClick") - if (isDecrypting) showDecryptBiometricDialog() - else token?.let { showEncryptBiometricDialog(token) } - }, { - logger.log("showAuthDialog onPasswordClick") - showPasswordDialog() - }) - - val titleRes: Int = if (isDecrypting) -1 else supporter.resProvider.provideEncryptTitle() - dialog.setTitle(titleRes) + //setup menu dialog parameters + val showPin: Boolean + val showBiom: Boolean if (isDecrypting) { - dialog.setPinPresent(types.contains(AuthType.PIN_AUTH)) - dialog.setBiometricPresent(types.contains(AuthType.BIOMETRIC_AUTH)) + showPin = types.contains(AuthType.PIN_AUTH) + showBiom = types.contains(AuthType.BIOMETRIC_AUTH) } else { - dialog.setPinPresent(true) - dialog.setBiometricPresent(isBiometricAvailable) + showPin = true + showBiom = isBiometricAvailable } + dialog.setupParameterBundle( + isDecrypting = isDecrypting, + showPassword = !needSecret, + showPin = showPin, + showBiom = showBiom + ) + + //setup menu dialog result handler + dialog.setupResultHandler(provideMenuResultHandler(isDecrypting, token)) + + val titleRes: Int = + if (isDecrypting) -1 else supporter.resProvider.provideEncryptTitle() + dialog.setTitle(titleRes) showDialog(dialog.getDialog(), "OperatorAuthDialog") } } - /*######################## PIN ##########################*/ + protected open fun provideMenuResultHandler( + decrypting: Boolean, + accessToken: Token? + ): MenuResultHandler { + return object : MenuResultHandler(decrypting, accessToken) { + override fun onCancel() { + logger.order(TAG, "showAuthDialog onCancel") + if (needSecret) secretResult() + else result() + } + + override fun onPinClick() { + logger.order(TAG, "showAuthDialog onPinClick") + if (this.decrypting) showDecryptPinDialog() + else this.accessToken?.let { showEncryptPinDialog(it) } + } + + override fun onPasswordClick() { + logger.order(TAG, "showAuthDialog onPasswordClick") + showPasswordDialog() + } + + override fun onBiomClick() { + if (this.decrypting) showDecryptBiometricDialog() + else this.accessToken?.let { showEncryptBiometricDialog(it) } + } + } + } + + /*endregion ############################### Menu dialog ##############################################*/ + + /*region ######################## PIN ##########################*/ + protected open fun showDecryptPinDialog() { - logger.log("showDecryptPinDialog") + logger.order(TAG, "showDecryptPinDialog") dialogProvider.pinDialog.let { dialog -> dialog.createDialog() - dialog.setStateActions({ e -> - logger.log("showDecryptPinDialog onError ${e.message}") - if (needSecret) secretResult(mapper(e)) - else result(mapper(e)) - }, { afterOk -> - logger.log("showDecryptPinDialog onDismiss afterOk=$afterOk") - if (!afterOk) { - if (needSecret) secretResult() - else result() - } - }) - dialog.setCallbacks({ pin -> - logger.log("showDecryptPinDialog onPinComplete needCheck=$needCheck") - val pinKey = supporter.util.generatePin( - pin, userIdValue, - Settings.Secure.getString(activity.get()?.contentResolver, Settings.Secure.ANDROID_ID) - ) - typeManager.putPreferredType(AuthType.PIN_AUTH) - //if (needCheck) { - checkSecret(pinKey = pinKey) - /*} else { - refreshWithSecret(pinKey = pinKey) - }*/ - }, { - logger.log("showDecryptPinDialog onPinCanceled showAuthDialog(isDecrypting = true)") - showAuthDialog(isDecrypting = true) - }) + val cancelId = supporter.resProvider.provideAlterCancelStr() + val hasAnother = if (needSecret) types.size > 1 else true + dialog.setupParameterBundle(cancelId, cancelId, false, hasAnother) + + dialog.setupResultHandler(provideDecryptingPinHandler()) - logger.log("showDecryptPinDialog needCheck=$needCheck") + logger.d(TAG, "showDecryptPinDialog needCheck=$needCheck") if (needCheck) dialog.setTitle(supporter.resProvider.provideAccessConfirmStr()) else dialog.setTitle(supporter.resProvider.provideAccessStr()) - val cancelId = supporter.resProvider.provideAlterCancelStr() - dialog.setCancelTitle(cancelId) - dialog.setCancellable(false) - if (needSecret) dialog.setHasAnother(types.size > 1) - else dialog.setHasAnother(true) showDialog(dialog.getDialog(), "OperatorPinDecryptDialog") } } protected open fun showEncryptPinDialog(token: Token) { - logger.log("showEncryptPinDialog") + logger.order(TAG, "showEncryptPinDialog") dialogProvider.pinDialog.let { dialog -> dialog.createDialog() - dialog.setStateActions({ e -> - logger.log("showDecryptPinDialog onError ${e.message}") - result(mapper(e)) - }, { afterOk -> - logger.log("showEncryptPinDialog onDismiss afterOk=$afterOk") - if (!afterOk) { - result( - RefreshResult( - token - ) - ) - } - }) - dialog.setCallbacks({ pin -> - logger.log("showEncryptPinDialog onPinComplete preferredType=PIN_AUTH") + + logger.d(TAG, "showEncryptPinDialog isBiometricAvailable=$isBiometricAvailable") + val cancelId = + if (isBiometricAvailable) supporter.resProvider.provideAlterCancelStr() else -1 + dialog.setupParameterBundle(cancelId, cancelId, false, isBiometricAvailable) + + dialog.setupResultHandler(provideEncryptingPinHandler(token)) + + dialog.setTitle(supporter.resProvider.provideEncryptTitle()) + showDialog(dialog.getDialog(), "OperatorPinEncryptDialog") + } + } + + protected open fun provideDecryptingPinHandler(): PinResultHandler { + return object : PinResultHandler(null) { + override fun onCancel() { + if (needSecret) secretResult() + else result() + } + + override fun onAnotherClicked() { + logger.order( + TAG, + "showDecryptPinDialog onPinCanceled showAuthDialog(isDecrypting = true)" + ) + showAuthDialog(isDecrypting = true) + } + + override fun onComplete(pin: String) { + logger.order(TAG, "showDecryptPinDialog onPinComplete needCheck=$needCheck") val pinKey = supporter.util.generatePin( pin, userIdValue, - Settings.Secure.getString(activity.get()?.contentResolver, Settings.Secure.ANDROID_ID) + Settings.Secure.getString( + activity.get()?.contentResolver, + Settings.Secure.ANDROID_ID + ) ) typeManager.putPreferredType(AuthType.PIN_AUTH) - putSecrets(token = token, pinKey = pinKey) - }, { - logger.log("showEncryptPinDialog onPinCanceled showAuthDialog(isDecrypting = false)") - showAuthDialog(isDecrypting = false, token = token) - }) + checkSecret(pinKey = pinKey) + } + } + } - dialog.setTitle(supporter.resProvider.provideEncryptTitle()) - logger.log("showEncryptPinDialog isBiometricAvailable=$isBiometricAvailable") - val cancelId = if (isBiometricAvailable) supporter.resProvider.provideAlterCancelStr() else -1 - dialog.setCancelTitle(cancelId) - dialog.setCancellable(false) - showDialog(dialog.getDialog(), "OperatorPinEncryptDialog") + protected open fun provideEncryptingPinHandler(token: Token): PinResultHandler { + return object : PinResultHandler(token) { + override fun onCancel() { + result(RefreshResult(this.token)) + } + + override fun onAnotherClicked() { + logger.order( + TAG, + "showEncryptPinDialog onPinCanceled showAuthDialog(isDecrypting = false)" + ) + showAuthDialog(isDecrypting = false, token = this.token) + } + + override fun onComplete(pin: String) { + logger.order(TAG, "showEncryptPinDialog onPinComplete preferredType=PIN_AUTH") + val pinKey = supporter.util.generatePin( + pin, userIdValue, + Settings.Secure.getString( + activity.get()?.contentResolver, + Settings.Secure.ANDROID_ID + ) + ) + typeManager.putPreferredType(AuthType.PIN_AUTH) + this.token?.let { putSecrets(token = it, pinKey = pinKey) } + } } } -/*######################## PIN END #####################*/ + /*endregion######################## PIN END #####################*/ -/*###################### Biometric ######################*/ + /*region ###################### Biometric ######################*/ protected open fun showDecryptBiometricDialog() { - logger.log("showDecryptBiometricDialog") + logger.order(TAG, "showDecryptBiometricDialog") dialogProvider.biom.let { dialog -> activity.get()?.let { context -> - logger.log("showDecryptBiometricDialog activity let") + logger.d(TAG, "showDecryptBiometricDialog activity let") dialog.onAuthenticationCallback({ isCancelled, errString -> + logger.order(TAG, "Decrypt biometric dialog onAuthenticationCallback onError") + logger.d(TAG, "onError isCancelled=$isCancelled errString=$errString") context.runOnUiThread { - if (isCancelled) {//Операция с отпечатком отменена. on pause - logger.log("$errString isPaused = $stateIsPaused") + if (isCancelled) {//Fingerprint operation cancelled. on pause + logger.d(TAG, "$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.d(TAG, "Show decrypt biometric dialog again") showDecryptBiometricDialog() } else { - logger.log("Set last action to show decrypt biometric dialog") + logger.d(TAG, "Set last action to show decrypt biometric dialog") lastAction = { showDecryptBiometricDialog() } } } else { - logger.showMessage(errString) + logger.i(TAG, errString) if (needSecret) { if (types.size > 1) showAuthDialog(isDecrypting = true) else secretResult() @@ -586,9 +746,9 @@ open class Refresher, M : BaseErrorModel>( } } }, { cipher -> - logger.log("DecryptBiometricDialog preferredType BIOMETRIC_AUTH") + logger.order(TAG, "DecryptBiometricDialog preferredType BIOMETRIC_AUTH") typeManager.putPreferredType(AuthType.BIOMETRIC_AUTH) - logger.log("DecryptBiometricDialog needCheck $needCheck") + logger.d(TAG, "DecryptBiometricDialog needCheck $needCheck") //if (needCheck) { checkSecret(cipher = cipher) /*} else { @@ -596,7 +756,7 @@ open class Refresher, M : BaseErrorModel>( }*/ }) - logger.log("showDecryptBiometricDialog needCheck=$needCheck") + logger.d(TAG, "showDecryptBiometricDialog needCheck=$needCheck") dialog.create(context, userInfo) val title: String = supporter.resProvider.getString( @@ -604,8 +764,9 @@ open class Refresher, M : BaseErrorModel>( else supporter.resProvider.provideAccessStr() ) val negativeText: String = if (needSecret) { - val negativeId = if (types.size > 1) supporter.resProvider.provideAlterCancelStr() - else supporter.resProvider.provideCancelStr() + val negativeId = + if (types.size > 1) supporter.resProvider.provideAlterCancelStr() + else supporter.resProvider.provideCancelStr() supporter.resProvider.getString(negativeId) } else { val negativeId = supporter.resProvider.provideAlterCancelStr() @@ -614,16 +775,16 @@ open class Refresher, M : BaseErrorModel>( dialog.setInfo(title = title, negativeText = negativeText) lastAction = { - logger.log("showDecryptBiometricDialog lastAction DECRYPT_MODE") + logger.order(TAG, "showDecryptBiometricDialog lastAction DECRYPT_MODE") try { currentBiometricDialog = dialog.authenticate(isDecrypt = true) } catch (e: Exception) { - logger.log("showDecryptBiometricDialog catch exception") - logger.logError(e) + logger.order(TAG, "showDecryptBiometricDialog catch exception") + logger.throwable(TAG, e) deleteBiometricType() } } - logger.log("showDecryptBiometricDialog stateIsPaused=$stateIsPaused") + logger.d(TAG, "showDecryptBiometricDialog stateIsPaused=$stateIsPaused") if (!stateIsPaused) { lastAction.invoke() lastAction = {} @@ -633,40 +794,47 @@ open class Refresher, M : BaseErrorModel>( } protected open fun showEncryptBiometricDialog(token: Token) { - logger.log("showEncryptBiometricDialog") + logger.order(TAG, "showEncryptBiometricDialog") dialogProvider.biom.let { dialog -> activity.get()?.let { context -> - logger.log("showDecryptBiometricDialog activity let") + logger.order(TAG, "showDecryptBiometricDialog activity let") dialog.onAuthenticationCallback({ isCancelled, errString -> + logger.order(TAG, "Encrypt biometric dialog onAuthenticationCallback onError") + logger.d(TAG, "onError isCancelled=$isCancelled errString=$errString") context.runOnUiThread { - if (isCancelled) {//Операция с отпечатком отменена. on pause - logger.log("showEncryptBiometricDialog ERROR_CANCELED") + if (isCancelled) {//Fingerprint operation cancelled. on pause + logger.d(TAG, "showEncryptBiometricDialog ERROR_CANCELED") lastAction = { showEncryptBiometricDialog(token) } } else { - logger.log("showEncryptBiometricDialog showAuthDialog(isDecrypting = false,token)") + logger.d( + TAG, + "showEncryptBiometricDialog showAuthDialog(isDecrypting = false,token)" + ) showAuthDialog(isDecrypting = false, token = token) } } }, { cipher -> - logger.log("EncryptBiometricDialog Success preferredType BIOMETRIC_AUTH") + logger.order(TAG, "EncryptBiometricDialog Success preferredType BIOMETRIC_AUTH") typeManager.putPreferredType(AuthType.BIOMETRIC_AUTH) putSecrets(token = token, cipher = cipher) }) dialog.create(context, userInfo) - val title: String = supporter.resProvider.getString(supporter.resProvider.provideEncryptTitle()) - val negativeText = supporter.resProvider.getString(supporter.resProvider.provideAlterCancelStr()) + 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.d(TAG, "showEncryptBiometricDialog stateIsPaused=$stateIsPaused") lastAction = { - logger.log("showEncryptBiometricDialog lastAction ENCRYPT_MODE") + logger.order(TAG, "showEncryptBiometricDialog lastAction ENCRYPT_MODE") try { currentBiometricDialog = dialog.authenticate(isDecrypt = false) } catch (e: Exception) { - logger.log("showEncryptBiometricDialog catch exception") - logger.logError(e) + logger.order(TAG, "showEncryptBiometricDialog catch exception") + logger.throwable(TAG, e) isBiometricAvailable = false showEncryptPinDialog(token) } @@ -679,74 +847,81 @@ open class Refresher, M : BaseErrorModel>( } } -/*#################### Biometric END ####################*/ - + /*endregion #################### Biometric END ####################*/ -/*##################### Password ########################*/ + /*region ##################### Password ########################*/ protected open fun showPasswordDialog(noSecrets: Boolean = false) { - logger.log("showPasswordDialog") + logger.order(TAG, "showPasswordDialog") dialogProvider.passwordDialog.let { dialog -> dialog.createDialog() - dialog.setStateActions({ e: Exception -> - logger.log("showPasswordDialog onError ${e.message}") - result(mapper(e)) - }, { afterOk: Boolean -> - logger.log("showPasswordDialog onDismiss afterOk=$afterOk, types not empty=${types.isNotEmpty()}") - if (!afterOk) { - if (!noSecrets && types.isNotEmpty()) { - showAuthDialog(isDecrypting = true) - } else { - result() - } - } - /*if (!afterOk && types.isNotEmpty()) { - showAuthDialog(isDecrypting = true) - } else if (!afterOk && types.isEmpty()) { - result() - }*/ - }) - logger.log("showPasswordDialog types not empty=${types.isNotEmpty()}") - if (!noSecrets && types.isNotEmpty()) { - dialog.setCancelTitle(supporter.resProvider.provideAlterCancelStr()) - dialog.setShowRecreateAuth(true) - } - logger.log("showPasswordDialog forgotPasswordActionEnable=$forgotPasswordActionEnable") - dialog.setForgotPassword(forgotPasswordActionEnable) - logger.log("showPasswordDialog needCheck=$needCheck") + logger.d(TAG, "showPasswordDialog types not empty=${types.isNotEmpty()}") + logger.d( + TAG, + "showPasswordDialog forgotPasswordActionEnable=$forgotPasswordActionEnable" + ) + dialog.setupParameterBundle( + cancelBtnRes = supporter.resProvider.provideCancelStr(), + anotherBtnRes = supporter.resProvider.provideAlterCancelStr(), + isCancellable = true, + showAnother = !noSecrets && types.isNotEmpty(), + forgotPasswordActionEnable = forgotPasswordActionEnable + ) + dialog.setupResultHandler(providePasswordHandler(noSecrets)) + + logger.d(TAG, "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") - authenticate(password, recreate, noSecrets) - }, { - logger.log("showPasswordDialog onPasswordForgot forgotPasswordActionEnable=$forgotPasswordActionEnable") - if (forgotPasswordActionEnable) - forgotPasswordAction() - }) showDialog(dialog.getDialog(), "PasswordDialog") } } + protected open fun providePasswordHandler(noSecrets: Boolean): PasswordResultHandler = + object : PasswordResultHandler(noSecrets) { + override fun onCancel() { + result() + } + + override fun onComplete(password: String, recreate: Boolean) { + logger.order(TAG, "showPasswordDialog onPasswordComplete") + authenticate(password, recreate, this.noSecrets) + } + + override fun onForgotClicked() { + logger.order( + TAG, + "showPasswordDialog onPasswordForgot forgotPasswordActionEnable=$forgotPasswordActionEnable" + ) + if (forgotPasswordActionEnable) + forgotPasswordAction() + } + + override fun onAnotherClicked() { + showAuthDialog(isDecrypting = true) + } + } -/*##################### Password END ####################*/ + /*endregion ##################### Password END ####################*/ -/*################################## Dialogs END ############################################*/ + /*################################## Dialogs END ############################################*/ -/*################################## Common #################################################*/ + /*region ################################## Common #################################################*/ protected open fun showDialog(dialog: IBaseDialog, tag: String) { - logger.log("showDialog $tag stateIsPaused=$stateIsPaused") + logger.order(TAG, "showDialog $tag stateIsPaused=$stateIsPaused") lastAction = { - logger.log("showDialog lastAction invoke") - val manager = fragmentManager.get() + logger.order(TAG, "showDialog lastAction invoke") + //val manager = fragmentManager.get() val context = activity.get() - logger.log("showDialog lastAction fragmentManager != null ${manager != null} context != null ${context != null}") + logger.d( + TAG, + "showDialog lastAction fragmentManager != null {manager != null} context != null ${context != null}" + ) currentDialog = dialog - dialog.show(manager, context, tag) + dialog.show(context, tag) } if (!stateIsPaused) { lastAction.invoke() @@ -755,132 +930,79 @@ open class Refresher, M : BaseErrorModel>( } protected open fun deleteBiometricType() { - logger.log("deleteBiometricType BIOMETRIC_AUTH") + logger.order(TAG, "deleteBiometricType BIOMETRIC_AUTH") executor.deleteSecretTypeUC(AuthType.BIOMETRIC_AUTH) { onComplete { - logger.log("deleteBiometricType $it") + logger.order(TAG, "deleteBiometricType $it") if (it) { types.remove(AuthType.BIOMETRIC_AUTH) if (types.isEmpty()) { val presentType = AuthType.NONE - logger.log("deleteBiometricType containsSecrets = false presentType=NONE") + logger.d( + TAG, + "deleteBiometricType containsSecrets = false presentType=NONE" + ) showPasswordDialog() } else { val presentType = types.firstOrNull() ?: AuthType.NONE typeManager.putPreferredType(presentType) - logger.log("deleteBiometricType presentType=$presentType") + logger.d(TAG, "deleteBiometricType presentType=$presentType") showAuthDialog(isDecrypting = true) } } } onError { - logger.logError(it) - logger.log("deleteBiometricType showAuthDialog(isDecrypting = true)") + logger.errorModel(TAG, it) + logger.order(TAG, "deleteBiometricType showAuthDialog(isDecrypting = true)") showAuthDialog(isDecrypting = true) } } } -/*#################################### Common END ###########################################*/ + /*endregion #################################### Common END ###########################################*/ -/*############################## Settings ###################################################*/ + /*region ############################## Settings ###################################################*/ /** - * Установка Activity необходимая для работы диалогов + * Setting up Activity required for dialogs to work * * @param activity FragmentActivity */ override fun setActivity(activity: FragmentActivity) { - logger.log("setActivity") + logger.order(TAG, "setActivity") this.activity = WeakReference(activity) activity.lifecycle.addObserver(object : LifecycleObserver { + //FIX @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun doAfterResume() { - logger.log("activity LifecycleObserver doAfterResume") + logger.order(TAG, "activity LifecycleObserver doAfterResume") stateIsPaused = false lastAction.invoke() lastAction = {} } + //FIX @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun doAfterPause() { - logger.log("activity LifecycleObserver doAfterPause") + logger.order(TAG, "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.d(TAG, "setActivity isBiometricAvailable=$isBiometricAvailable") } override fun setForgotPasswordAction(enable: Boolean, forgotPasswordAction: () -> Unit) { - logger.log("setForgotPasswordAction enable=$enable") + logger.order(TAG, "setForgotPasswordAction enable=$enable") this.forgotPasswordActionEnable = enable this.forgotPasswordAction = if (enable) forgotPasswordAction else ({}) } - /** - * Установка Params необходимая для работы диалогов - * - * @param root ViewGroup - * @param manager FragmentManager - */ - override fun setParams(manager: FragmentManager) { - logger.log("setParams") - fragmentManager = WeakReference(manager) - } - override fun setCancellationCallback(callback: IContainerCancellationCallback) { - logger.log("setCancellationCallback") + logger.order(TAG, "setCancellationCallback") containerCancellationCallback = callback } -/*############################## Settings END ###############################################*/ - - override fun clearAccess() { - logger.log("clearAccess") - accessToken = "" - refreshToken = "" - executor.unsubscribe() - } - - override fun clear() { - logger.log("clear") - clearAccess() - activity.clear() - fragmentManager.clear() - containerCancellationCallback = defaultCancellationCallback - result = ContainerRequest() - userIdValue = "" - isBiometricAvailable = false - needCheck = false - refreshExplicitly = false - types.clear() - //presentType = AuthType.NONE - stateIsPaused = false - lastAction = {} - currentDialog?.dismiss() - currentDialog = null - currentBiometricDialog?.cancelAuthentication() - currentBiometricDialog = null - dialogProvider.menuDialog.clear() - dialogProvider.pinDialog.clear() - dialogProvider.passwordDialog.clear() - dialogProvider.biom.clear() - forgotPasswordActionEnable = false - forgotPasswordAction = {} - } - - open fun setCheckRefreshError( - check: Boolean = false, - errorStatus: E? = null, - useExtraCheck: Boolean = false, - extraCheck: (BaseErrorModel) -> Unit = {} - ) { - checkRefreshError = check - refresherErrorStatusToCheck = errorStatus - this.useExtraCheck = useExtraCheck - extraRefreshCheck = extraCheck - } - + /*endregion ############################## Settings END ###############################################*/ } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/BiometricUserInfo.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/BiometricUserInfo.kt index 11cd9304..812329ef 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/BiometricUserInfo.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/BiometricUserInfo.kt @@ -6,7 +6,8 @@ package by.esas.tools.accesscontainer.dialog interface BiometricUserInfo { - fun getCurrentUser(): String - fun setCurrentUserIv(iv: ByteArray) - fun getCurrentUserIv(): ByteArray - } \ No newline at end of file + + fun getCurrentUser(): String + fun setCurrentUserIv(iv: ByteArray) + fun getCurrentUserIv(): ByteArray +} \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/DialogProvider.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/DialogProvider.kt index 0defd58c..b95abec4 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/DialogProvider.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/DialogProvider.kt @@ -5,21 +5,23 @@ package by.esas.tools.accesscontainer.dialog +import androidx.fragment.app.FragmentActivity import by.esas.tools.accesscontainer.dialog.setters.MenuDialogSetter import by.esas.tools.accesscontainer.dialog.setters.SetBiometricDialog import by.esas.tools.accesscontainer.dialog.setters.SetPasswordDialog import by.esas.tools.accesscontainer.dialog.setters.SetPinDialog abstract class DialogProvider { + abstract val passwordDialog: SetPasswordDialog abstract val menuDialog: MenuDialogSetter abstract val pinDialog: SetPinDialog abstract val biom: SetBiometricDialog - fun clear() { - passwordDialog.clear() - menuDialog.clear() - pinDialog.clear() + fun clear(context: FragmentActivity?) { + passwordDialog.clear(context) + menuDialog.clear(context) + pinDialog.clear(context) biom.clear() } } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBaseDialog.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBaseDialog.kt index 91488fa5..21a83cae 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBaseDialog.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBaseDialog.kt @@ -5,10 +5,12 @@ package by.esas.tools.accesscontainer.dialog -import android.content.Context -import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentResultListener interface IBaseDialog { + fun dismiss() - fun show(manager: FragmentManager?, context: Context?, tag: String) + fun show(context: FragmentActivity?, tag: String) + fun provideResultListener(tag: String): FragmentResultListener } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBiometric.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBiometric.kt index 83e50153..2c5c47c1 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBiometric.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/IBiometric.kt @@ -6,6 +6,7 @@ package by.esas.tools.accesscontainer.dialog interface IBiometric { + fun cancelAuthentication() //fun authenticate(isDecrypt: Boolean, title: String, negativeText: String): IBiometric? } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/IDialogSetter.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/IDialogSetter.kt index 2a6c2604..e3589ce4 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/IDialogSetter.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/IDialogSetter.kt @@ -5,14 +5,13 @@ package by.esas.tools.accesscontainer.dialog.setters +import androidx.fragment.app.FragmentActivity import by.esas.tools.accesscontainer.dialog.IBaseDialog interface IDialogSetter { + fun createDialog() fun getDialog(): IBaseDialog - fun setStateActions(error: (Exception) -> Unit, dismiss: (Boolean) -> Unit) - fun setTitle(titleRes: Int) - - fun clear() + fun clear(context: FragmentActivity?) } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/MenuDialogSetter.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/MenuDialogSetter.kt index c2abf5ed..81c04c6d 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/MenuDialogSetter.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/MenuDialogSetter.kt @@ -5,11 +5,8 @@ package by.esas.tools.accesscontainer.dialog.setters -interface MenuDialogSetter : - IDialogSetter { - fun isDecrypting(decrypting: Boolean) - fun setCallBacks(pin: () -> Unit?, biom: () -> Unit?, password: () -> Unit?) - fun showPassword(value: Boolean) - fun setPinPresent(present: Boolean) - fun setBiometricPresent(present: Boolean) +interface MenuDialogSetter : IDialogSetter { + + fun setupParameterBundle(isDecrypting: Boolean, showPassword: Boolean, showPin: Boolean, showBiom: Boolean) + fun setupResultHandler(handler: MenuResultHandler) } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/MenuResultHandler.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/MenuResultHandler.kt new file mode 100644 index 00000000..070246fe --- /dev/null +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/MenuResultHandler.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2022 Electronic Systems And Services Ltd. + * SPDX-License-Identifier: Apache-2.0 + */ + +package by.esas.tools.accesscontainer.dialog.setters + +import by.esas.tools.accesscontainer.entity.Token + +abstract class MenuResultHandler( + protected val decrypting: Boolean, + protected val accessToken: Token? +) { + + abstract fun onCancel() + abstract fun onPinClick() + abstract fun onPasswordClick() + abstract fun onBiomClick() +} \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/PasswordResultHandler.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/PasswordResultHandler.kt new file mode 100644 index 00000000..fb21ac88 --- /dev/null +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/PasswordResultHandler.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2022 Electronic Systems And Services Ltd. + * SPDX-License-Identifier: Apache-2.0 + */ + +package by.esas.tools.accesscontainer.dialog.setters + +abstract class PasswordResultHandler(protected var noSecrets: Boolean) { + + abstract fun onCancel() + abstract fun onComplete(password: String, recreate: Boolean = false) + abstract fun onForgotClicked() + abstract fun onAnotherClicked() +} \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/PinResultHandler.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/PinResultHandler.kt new file mode 100644 index 00000000..949268df --- /dev/null +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/PinResultHandler.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2022 Electronic Systems And Services Ltd. + * SPDX-License-Identifier: Apache-2.0 + */ + +package by.esas.tools.accesscontainer.dialog.setters + +import by.esas.tools.accesscontainer.entity.Token + +abstract class PinResultHandler(protected val token: Token?) { + + abstract fun onCancel() + abstract fun onAnotherClicked() + abstract fun onComplete(pin: String) +} \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetBiometricDialog.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetBiometricDialog.kt index f0337883..d933a09c 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetBiometricDialog.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetBiometricDialog.kt @@ -11,6 +11,7 @@ import by.esas.tools.accesscontainer.dialog.IBiometric import javax.crypto.Cipher interface SetBiometricDialog { + fun onAuthenticationCallback(onError: (Boolean, String) -> Unit, onSuccess: (Cipher?) -> Unit) fun create(activity: FragmentActivity, userInfo: BiometricUserInfo) fun setInfo(title: String, negativeText: String) diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPasswordDialog.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPasswordDialog.kt index f264d63a..6b810e15 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPasswordDialog.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPasswordDialog.kt @@ -5,11 +5,15 @@ package by.esas.tools.accesscontainer.dialog.setters -interface SetPasswordDialog : - IDialogSetter { - fun setCancelTitle(cancelBtnRes: Int) - fun setShowRecreateAuth(recreate: Boolean) - fun setForgotPassword(forgotPasswordActionEnable: Boolean) +interface SetPasswordDialog : IDialogSetter { - fun setCallbacks(complete: (String, Boolean) -> Unit, forgot: () -> Unit) + fun setupParameterBundle( + cancelBtnRes: Int, + anotherBtnRes: Int, + isCancellable: Boolean, + showAnother: Boolean, + forgotPasswordActionEnable: Boolean + ) + + fun setupResultHandler(handler: PasswordResultHandler) } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPinDialog.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPinDialog.kt index 53df78d7..939cc730 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPinDialog.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/dialog/setters/SetPinDialog.kt @@ -5,10 +5,14 @@ package by.esas.tools.accesscontainer.dialog.setters -interface SetPinDialog : - IDialogSetter { - fun setCallbacks(complete: (String) -> Unit, cancel: () -> Unit) - fun setCancelTitle(cancelRes: Int) - fun setCancellable(value: Boolean) - fun setHasAnother(value: Boolean) +interface SetPinDialog : IDialogSetter { + + fun setupParameterBundle( + cancelBtnRes: Int, + anotherBtnRes: Int, + cancellable: Boolean, + hasAnother: Boolean + ) + + fun setupResultHandler(handler: PinResultHandler) } \ 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..f395a873 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,10 +7,16 @@ package by.esas.tools.accesscontainer.error import by.esas.tools.logger.BaseErrorModel -interface IErrorMapper, M : BaseErrorModel> { +interface IErrorMapper { + fun mapError(e: Exception): M + fun createErrorModel(code: Int, enumName: String): M operator fun invoke(e: Exception): M { return mapError(e) } + + operator fun invoke(code: Int, enumName: String): M { + return createErrorModel(code, enumName) + } } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerCancellationCallback.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerCancellationCallback.kt index ccf7fd2e..586605ef 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerCancellationCallback.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerCancellationCallback.kt @@ -6,5 +6,6 @@ package by.esas.tools.accesscontainer.support interface IContainerCancellationCallback { + fun onCancel() } \ No newline at end of file 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..2f39b9e3 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,38 @@ 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 getSecretsUC(block: ContainerRequest, E, M>.() -> Unit) + fun refreshAccessUC(refreshToken: String, block: ContainerRequest.() -> Unit) - fun refreshWithSecretUC(pinKey: SecretKey? = null, cipher: Cipher? = null, block: ContainerRequest.() -> Unit) + fun getSecretsUC(block: ContainerRequest, M>.() -> Unit) - fun checkSecretUC(pinKey: SecretKey? = null, cipher: Cipher? = null, block: ContainerRequest, E, M>.() -> Unit) + fun refreshWithSecretUC( + pinKey: SecretKey? = null, + cipher: Cipher? = null, + block: ContainerRequest.() -> Unit + ) - fun refreshWithSecretUC(secret: String, block: ContainerRequest.() -> Unit) + fun checkSecretUC( + pinKey: SecretKey? = null, + cipher: Cipher? = null, + block: ContainerRequest, M>.() -> Unit + ) - fun authorizationInSystemUC(password: String, block: ContainerRequest.() -> Unit) + fun refreshWithSecretUC(secret: String, block: ContainerRequest.() -> Unit) - fun createSecretUC(token: String, cipher: Cipher? = null, pinKey: SecretKey? = null, block: ContainerRequest.() -> Unit) + fun authorizationInSystemUC(password: String, block: ContainerRequest.() -> Unit) - fun deleteSecretTypeUC(type: AuthType, block: ContainerRequest.() -> Unit) + fun createSecretUC( + refreshToken: String, + cipher: Cipher? = null, + pinKey: SecretKey? = null, + block: ContainerRequest.() -> Unit + ) - fun deleteSecretsUC(block: ContainerRequest.() -> Unit) + fun deleteSecretTypeUC(type: AuthType, 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/IContainerLogger.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerLogger.kt index c0060863..a1a9fe86 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerLogger.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/IContainerLogger.kt @@ -8,6 +8,7 @@ package by.esas.tools.accesscontainer.support import android.widget.Toast interface IContainerLogger { + fun setTag(tag: String) fun log(msg: String) fun log(res: Int) diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/ITypeManager.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/ITypeManager.kt index e28c30ae..c820fd4f 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/ITypeManager.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/ITypeManager.kt @@ -8,6 +8,7 @@ package by.esas.tools.accesscontainer.support import by.esas.tools.accesscontainer.entity.AuthType interface ITypeManager { + fun getPreferredType(): AuthType fun putPreferredType(type: AuthType) } \ 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..011be144 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 @@ -6,11 +6,10 @@ package by.esas.tools.accesscontainer.support.supporter import android.content.Context -import by.esas.tools.logger.BaseErrorModel import javax.crypto.SecretKey -interface IUtil, M : BaseErrorModel> { - fun createErrorModel(code: Int, enumName: String): M +interface IUtil { + fun generatePin(pin: String, userId: String, androidId: String): SecretKey? fun checkBiometricSupport(context: Context?, logging: (String, String) -> Unit): Boolean } \ No newline at end of file diff --git a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/ResourceStrProvider.kt b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/ResourceStrProvider.kt index ab09eac4..229f7117 100644 --- a/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/ResourceStrProvider.kt +++ b/accesscontainer/src/main/java/by/esas/tools/accesscontainer/support/supporter/ResourceStrProvider.kt @@ -9,6 +9,7 @@ import android.content.Context import androidx.annotation.StringRes interface ResourceStrProvider { + fun requireContext(): Context fun getString(@StringRes resId: Int): String fun provideAlterCancelStr(): Int 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..b8d8b0c2 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 @@ -5,9 +5,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 deleted file mode 100644 index b5676761..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,60 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'kotlin-kapt' - -android { - compileSdkVersion 30 - buildToolsVersion "30.0.1" - - buildFeatures { - dataBinding = true - } - compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 - } - kotlinOptions { - jvmTarget = "1.8" - } - defaultConfig { - applicationId "by.esas.tools" - minSdkVersion 16 - targetSdkVersion 30 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "androidx.core:core-ktx:$corektx_version" - - // Data binding - kapt "com.android.databinding:compiler:$databinding_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" - kapt "com.google.dagger:dagger-compiler:$dagger2_version" - kapt "com.google.dagger:dagger-android-processor:$dagger2_version" - - implementation project(path: ':basedaggerui') - implementation project(path: ':domain') - implementation project(path: ':dialog') - implementation project(path: ':checker') - implementation project(path: ':inputfieldview') - implementation project(path: ':pinview') - implementation project(path: ':timeparser') -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..121cc16d --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,100 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsKotlinAndroid) + alias(libs.plugins.kotlinKapt) + alias(libs.plugins.safeargs) +} + +val packageName: String by rootProject.extra +val compileSdkVer: Int by rootProject.extra +val minSdkVersion: Int by rootProject.extra + +android { + namespace = "by.esas.tools" + compileSdk = compileSdkVer + buildToolsVersion = "34.0.0" + + defaultConfig { + minSdk = minSdkVersion + applicationId = "by.esas.tools" + targetSdk = compileSdkVer + multiDexEnabled = true + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildFeatures { + dataBinding = true + } + + /*compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + }*/ + kotlinOptions { + jvmTarget = "1.8" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + signingConfig = signingConfigs.getByName("debug") + } + } +} + +dependencies { + implementation(libs.multidex) + + // Kotlin + implementation(libs.kotlinx.coroutines.android) + + //App + implementation(libs.appcompat) + implementation(libs.core.ktx) + implementation(libs.biometric) + + // Moshi + implementation(libs.moshi) + implementation(libs.moshi.kotlin) + + // Network + //implementation "com.squareup.retrofit2:retrofit:2.6.4" + implementation(libs.okhttp3) + + // Room + api(libs.room.runtime) + kapt(libs.room.compiler) + implementation(libs.room.ktx) + + // Dagger 2 + //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(libs.dagger.compiler) + kapt(libs.dagger.android.processor) + + implementation(project(":baseui")) + implementation(project(":basedaggerui")) + implementation(project(":domain")) + implementation(project(":error_mapper")) + implementation(project(":dialog_message")) + implementation(project(":checker")) + implementation(project(":inputfieldview")) + implementation(project(":pinview")) + implementation(project(":timeparser")) + implementation(project(":numpad")) + implementation(project(":util")) + implementation(project(":util_ui")) + implementation(project(":listheader")) + implementation(project(":customswitch")) + implementation(project(":topbarview")) + implementation(project(":cardline")) + implementation(project(":biometric_decryption")) + implementation(project(":accesscontainer")) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b42451..2f9dc5a4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cdd7e882..2033c9f3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,10 @@ - + + + + - + 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..db1cc191 100644 --- a/app/src/main/java/by/esas/tools/App.kt +++ b/app/src/main/java/by/esas/tools/App.kt @@ -2,12 +2,15 @@ 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_ui.configs.SettingsManager import dagger.android.AndroidInjector import dagger.android.DaggerApplication class App : DaggerApplication() { + override fun applicationInjector(): AndroidInjector { return DaggerAppComponent .builder() @@ -16,6 +19,7 @@ class App : DaggerApplication() { } companion object { + lateinit var instance: App lateinit var appContext: Context } @@ -42,8 +46,10 @@ 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 { + val prefs = AppSharedPrefs(base) + return@let by.esas.tools.util_ui.configs.SettingsManager.updateSettings(base, prefs.getLanguage(), prefs.getTheme()) + }) + 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..0c53ff54 100644 --- a/app/src/main/java/by/esas/tools/DataBinding.kt +++ b/app/src/main/java/by/esas/tools/DataBinding.kt @@ -1,34 +1,80 @@ package by.esas.tools -import android.text.Editable -import android.text.TextWatcher import android.view.View +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 @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() - } +@BindingAdapter("barTitleText") +fun setBarTitle(view: TopbarView, text: String?) { + view.setTitle(text) +} - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - } +@BindingAdapter("buttonsClickable") +fun setButtonsEnabled(view: TopbarView, value: Boolean) { + view.setButtonsEnabled(value) +} - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - } - }) +@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) } -@InverseBindingAdapter(attribute = "android:text") -fun getText(view: RohabInputField): String { - return view.getText() +@BindingAdapter("barShowActionIcon") +fun setBarActionIconVisibility(view: TopbarView, value: Boolean) { + view.setActionIconVisibility(value) +} + +@BindingAdapter("barShowDivider") +fun setBarDividerVisibility(view: TopbarView, value: Boolean) { + view.setDividerVisibility(value) +} + +@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/Utils.kt b/app/src/main/java/by/esas/tools/Utils.kt deleted file mode 100644 index f44f90fb..00000000 --- a/app/src/main/java/by/esas/tools/Utils.kt +++ /dev/null @@ -1,28 +0,0 @@ -package by.esas.tools - -import android.app.Activity -import android.content.Context -import android.view.View -import java.util.* - -fun getLocale(context: Context): Locale { - return Locale.getDefault() -} - -/* Standard */ -fun hideSystemUIApp(activity: Activity?) { - // Enables regular immersive mode. - // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE. - // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY - if (activity != null) { - activity.window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - // Set the content to appear under the system bars so that the - // content doesn't resize when the system bars hide and show. - or View.SYSTEM_UI_FLAG_LAYOUT_STABLE - or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - //or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - // Hide the nav bar and status bar - or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - /*or View.SYSTEM_UI_FLAG_FULLSCREEN*/) - } -} \ No newline at end of file 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..581c3fed --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/AppSharedPrefs.kt @@ -0,0 +1,40 @@ +package by.esas.tools.app_data + +import android.content.Context +import android.content.SharedPreferences +import by.esas.tools.util_ui.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: by.esas.tools.util_ui.configs.UiModeType) { + sharedPrefs.edit().putString(CURRENT_THEME, theme.name).apply() + } + + fun getTheme(): by.esas.tools.util_ui.configs.UiModeType { + val currentTheme = sharedPrefs.getString(CURRENT_THEME, "") ?: "" + return if (currentTheme.isNotBlank()) { + by.esas.tools.util_ui.configs.UiModeType.valueOf(currentTheme) + } else by.esas.tools.util_ui.configs.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..bc648297 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/Cases.kt @@ -0,0 +1,198 @@ +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) + ) + addCaseItem( + R.id.accessContainerFragment, + R.string.case_label_access_container, + listOf( + Modules.ACCESS_CONTAINER, + Modules.LISTHEADER, + Modules.INPUTFIELD_VIEW, + Modules.NUMPAD, + Modules.DIALOG + ) + ) + + return cases + } + + //REMEMBER to add some static mapping to set static ID's for 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..f97b534c --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/db/AppDataBase.kt @@ -0,0 +1,31 @@ +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..15f4ea26 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/db/converter/CaseStatusConverter.kt @@ -0,0 +1,13 @@ +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..d36701cf --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_data/db/dao/CaseStatusDao.kt @@ -0,0 +1,27 @@ +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..18f08fe6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/error_mapper/AppErrorMapper.kt @@ -0,0 +1,23 @@ +package by.esas.tools.app_domain.error_mapper + +import by.esas.tools.error_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 { + logger.order("createModel") + 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 62% 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..90ab3b3c 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,6 @@ -package by.esas.tools.error_mapper +package by.esas.tools.app_domain.error_mapper + +import by.esas.tools.accesscontainer.error.ErrorStatusEnum enum class AppErrorStatusEnum { OK, @@ -21,19 +23,15 @@ enum class AppErrorStatusEnum { //App APP_UNPREDICTED_ERROR, - APP_INVOICE_NOT_ADDED, - APP_INVOICE_NOT_CHANGED, - 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, - APP_INVOICE_NOT_SET, APP_CONTEXT_PARAMETERS_EMPTY, APP_USER_HAS_NO_SECRETS, //Decryption + BIOMETRIC_UNAVAILABLE, SECRET_CHECK_NOT_MATCH, DECRYPTION_FAILED, APP_REFRESH_TOKEN_DECRYPTION_FAILED, @@ -74,10 +72,27 @@ enum class AppErrorStatusEnum { API_DELETE_PHONE_ERROR, API_PHONE_NUMBER_NOT_CONFIRMED, API_PHONE_ALREADY_SET, - API_CODE_INVALID_OR_EXPIRED, + API_CODE_INVALID_OR_EXPIRED; + + companion object { + + fun getAppErrorStatusEnum(value: String): AppErrorStatusEnum { + return try { + valueOf(value) + } catch (e: IllegalArgumentException) { + mapToAppErrorStatus(value) + } + } - EPOS_INVOICE_NUMBER_TAKEN, - SERVICE_INFO_NOT_FOUND, - CURRENCY_INFO_NOT_FOUND, - INVOICES_GET_NOT_FOUND + private fun mapToAppErrorStatus(value: String): AppErrorStatusEnum { + return try { + when (ErrorStatusEnum.valueOf(value)) { + ErrorStatusEnum.HAS_NO_SECRETS -> APP_USER_HAS_NO_SECRETS + ErrorStatusEnum.SECRET_CHECK_NOT_MATCH -> SECRET_CHECK_NOT_MATCH + } + } catch (e: IllegalArgumentException) { + UNKNOWN_ERROR + } + } + } } \ No newline at end of file diff --git a/domain/src/main/java/by/esas/tools/domain/exception/BaseException.kt b/app/src/main/java/by/esas/tools/app_domain/error_mapper/BaseException.kt similarity index 71% rename from domain/src/main/java/by/esas/tools/domain/exception/BaseException.kt rename to app/src/main/java/by/esas/tools/app_domain/error_mapper/BaseException.kt index fe83cf72..799b2034 100644 --- a/domain/src/main/java/by/esas/tools/domain/exception/BaseException.kt +++ b/app/src/main/java/by/esas/tools/app_domain/error_mapper/BaseException.kt @@ -1,11 +1,12 @@ /* - * Copyright 2021 Electronic Systems And Services Ltd. + * Copyright 2024 Electronic Systems And Services Ltd. * SPDX-License-Identifier: Apache-2.0 */ -package by.esas.tools.domain.exception +package by.esas.tools.app_domain.error_mapper class BaseException(message: String) : Exception(message) { + override fun toString(): String { return "BaseException(messageEnum=$message)" } 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..63d64ad7 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/AddCaseStatusUseCase.kt @@ -0,0 +1,24 @@ +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..86adbb96 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/ClearAllSavedTestStatusesUseCase.kt @@ -0,0 +1,19 @@ +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..15f8f275 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/FilterCaseUseCase.kt @@ -0,0 +1,29 @@ +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..e67d9c24 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/GetCaseItemsUseCase.kt @@ -0,0 +1,33 @@ +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/app_domain/usecase/GetDefaultCardUseCase.kt b/app/src/main/java/by/esas/tools/app_domain/usecase/GetDefaultCardUseCase.kt new file mode 100644 index 00000000..a4984103 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/GetDefaultCardUseCase.kt @@ -0,0 +1,21 @@ +package by.esas.tools.app_domain.usecase + +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.app_domain.error_mapper.BaseException +import by.esas.tools.error_mapper.error.BaseStatusEnum +import kotlinx.coroutines.delay +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class GetDefaultCardUseCase @Inject constructor( + errorUtil: AppErrorMapper, + foregroundContext: CoroutineContext +) : 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..ec333079 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/GetSavedCaseStatusUseCase.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 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..eb312d88 --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/PlayRandomUseCase.kt @@ -0,0 +1,31 @@ +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.app_domain.error_mapper.BaseException +import by.esas.tools.error_mapper.error.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 { + val random = Random.nextInt(0, rangeNumbers + 1) + logger.i("Random number is $random") + if (number == random) { + 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..2fd3708c --- /dev/null +++ b/app/src/main/java/by/esas/tools/app_domain/usecase/SearchCaseUseCase.kt @@ -0,0 +1,43 @@ +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..62b15646 --- /dev/null +++ b/app/src/main/java/by/esas/tools/base/AppActivity.kt @@ -0,0 +1,130 @@ +package by.esas.tools.base + +import android.content.Context +import android.os.Bundle +import android.view.View +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.Action +import by.esas.tools.logger.handler.ErrorMessageHelper +import by.esas.tools.util.TAGk +import by.esas.tools.utils.logger.ErrorModel +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 fun onCreate(savedInstanceState: Bundle?) { + AndroidInjection.inject(this) + super.onCreate(savedInstanceState) + } + + override fun provideSwitchableViews(): List { + return emptyList() + } + + override fun provideErrorStringHelper(): ErrorMessageHelper { + logger.order(TAGk, "provideErrorStringHelper") + return object : ErrorMessageHelper { + + override fun getErrorMessage(error: ErrorModel): String { + return error.status + } + } + } + + override fun handleAction(action: Action): Boolean { + return if (action is MessageAction) { + if (action.hasTextMessage()) { + showMessage(action.getMessage(), action.getDuration()) + true + } else { + showMessage(action.getResId(), action.getDuration()) + true + } + } else { + super.handleAction(action) + } + } + + override fun provideVariableInd(): Int { + return BR.viewModel + } + + override fun provideSetter(): by.esas.tools.util_ui.configs.SettingsProvider { + return object : by.esas.tools.util_ui.configs.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) { + prefs.setLanguage(lang) + } + + override fun getDefaultMode(): by.esas.tools.util_ui.configs.UiModeType { + return by.esas.tools.util_ui.configs.UiModeType.SYSTEM + } + + override fun getMode(): by.esas.tools.util_ui.configs.UiModeType { + return prefs.getTheme() + } + + override fun setMode(uiMode: by.esas.tools.util_ui.configs.UiModeType) { + prefs.setTheme(uiMode) + } + } + } + + 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) + } +} \ 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..c70a93dd --- /dev/null +++ b/app/src/main/java/by/esas/tools/base/AppFragment.kt @@ -0,0 +1,89 @@ +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.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.Action +import by.esas.tools.logger.ILogger +import by.esas.tools.logger.handler.ErrorMessageHelper +import by.esas.tools.utils.logger.ErrorModel +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 fun provideAppContext(): Context { + return App.appContext + } + + override fun provideVariableInd(): Int { + return BR.viewModel + } + + override fun provideErrorStringHelper(): ErrorMessageHelper { + logger.order(TAG, "provideErrorStringHelper") + return object : ErrorMessageHelper { + + override fun getErrorMessage(error: ErrorModel): String { + return error.status + } + } + } + + override fun handleAction(action: Action): Boolean { + return if (action is MessageAction) { + if (action.hasTextMessage()) { + showMessage(action.getMessage(), action.getDuration()) + true + } else { + showMessage(action.getResId(), action.getDuration()) + true + } + } else { + super.handleAction(action) + } + } + + override fun provideChecks(): List { + logger.order(TAG, "provideChecks") + return emptyList() + } + + override fun provideChecker(): Checker? { + logger.order(TAG, "provideChecker") + return null + } + + override fun provideSwitchableViews(): List { + logger.order(TAG, "provideSwitchableViews") + return emptyList() + } + + override fun onAttach(context: Context) { + logger.order(TAG, "onAttach injection") + AndroidSupportInjection.inject(this) + logger.order(TAG, "super.onAttach") + 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..e23d20ce --- /dev/null +++ b/app/src/main/java/by/esas/tools/base/AppVM.kt @@ -0,0 +1,28 @@ +package by.esas.tools.base + +import android.widget.Toast +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.baseui.standard.StandardViewModel +import by.esas.tools.logger.IErrorMapper +import by.esas.tools.logger.ILogger +import by.esas.tools.util.TAGk +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 + } + + fun showMessage(text: String, duration: Int = Toast.LENGTH_SHORT) { + requestAction(MessageAction(text, duration)) + } + + fun showMessage(resId: Int, duration: Int = Toast.LENGTH_SHORT) { + requestAction(MessageAction(resId, duration)) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/base/MessageAction.kt b/app/src/main/java/by/esas/tools/base/MessageAction.kt new file mode 100644 index 00000000..3d5caa7a --- /dev/null +++ b/app/src/main/java/by/esas/tools/base/MessageAction.kt @@ -0,0 +1,51 @@ +package by.esas.tools.base + +import android.os.Bundle +import android.widget.Toast +import by.esas.tools.logger.Action + +class MessageAction : Action { + + constructor(text: String, duration: Int) : super( + ACTION_SHOW_MESSAGE, + Bundle(), + false + ) { + parameters?.putString(SHOW_MESSAGE_TEXT, text) + parameters?.putInt(SHOW_MESSAGE_DURATION, duration) + } + + constructor(resId: Int, duration: Int) : super( + ACTION_SHOW_MESSAGE, + Bundle(), + false + ) { + parameters?.putInt(SHOW_MESSAGE_RES, resId) + parameters?.putInt(SHOW_MESSAGE_DURATION, duration) + } + + fun getMessage(): String { + return parameters?.getString(SHOW_MESSAGE_TEXT) ?: "" + } + + fun getResId(): Int { + return parameters?.getInt(SHOW_MESSAGE_RES) ?: 0 + } + + fun getDuration(): Int { + return parameters?.getInt(SHOW_MESSAGE_DURATION) ?: Toast.LENGTH_SHORT + } + + fun hasTextMessage(): Boolean { + return !parameters?.keySet()?.find { + it == SHOW_MESSAGE_TEXT + }.isNullOrBlank() + } + + companion object { + const val ACTION_SHOW_MESSAGE: String = "ACTION_SHOW_MESSAGE" + const val SHOW_MESSAGE_TEXT: String = "SHOW_MESSAGE_TEXT" + const val SHOW_MESSAGE_RES: String = "SHOW_MESSAGE_RES" + const val SHOW_MESSAGE_DURATION: String = "SHOW_MESSAGE_DURATION" + } +} \ 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..aea0856e --- /dev/null +++ b/app/src/main/java/by/esas/tools/dialog/CustomBottomDialog.kt @@ -0,0 +1,93 @@ +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.dialog_core.Config +import by.esas.tools.util_ui.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) + logger.i(logger.getTag()) + 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 deleted file mode 100644 index f7130a6b..00000000 --- a/app/src/main/java/by/esas/tools/dialog/GetPasswordDialog.kt +++ /dev/null @@ -1,136 +0,0 @@ -package by.esas.tools.dialog - -import android.os.Bundle -import android.view.View -import android.widget.EditText -import androidx.databinding.ObservableBoolean -import androidx.databinding.ObservableField -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.* - -class GetPasswordDialog : BindingDialogFragment() { - override val TAG: String = GetPasswordDialog::class.java.simpleName - - /* override fun provideLogger(): LoggerImpl { - return LoggerImpl(TAG) - }*/ - - override fun provideLayoutId(): Int { - return R.layout.df_password - } - - override fun provideSwitchableList(): List { - return emptyList()//listOf(df_password_layout.inputText) - } - - override fun provideValidationList(): List { - return emptyList()/*listOf( - BaseChecking(df_password_layout.inputLayout) - .addCheck(NotEmptyCheck("Can't be empty")) - )*/ - } - - override fun provideProgressBar(): View? { - return null - } - - fun setPasswordCallback(callback: PasswordCallback) { - this.callback = callback - } - - fun setShowRecreateAuth(value: Boolean) { - showRecreateView = value - } - - val showForgotPassword = ObservableBoolean(false) - val showRecreateCheck = ObservableBoolean(false) - val isChecked = ObservableBoolean(false) - val password = ObservableField("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 - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - isCancelable = false - //setHeader(view.findViewById(R.id.df_password_title), view.context.getString(titleRes)) - showRecreateCheck.set(showRecreateView) - showForgotPassword.set(forgotPasswordEnable) - binding.dfPasswordCancelBtn.setText(cancelTitleRes) - } - - fun onCancelClick() { - afterOk = false - dismiss() - } - - fun onContinueClick() { - validatePassword() - } - - fun onForgotPasswordClick() { - afterOk = true - callback?.onPasswordForgot() - dismiss() - } - - private fun validatePassword() { - /*disableControls() - Validator.with(App.appContext).setListener(object : Validator.OnValidateListener { - override fun onValidateFailed() { - enableControls() - } - - override fun onValidateSuccess(values: List) { - callback?.onPasswordComplete( - df_password_layout.inputText.text.toString(), - isChecked.get() - ) - afterOk = true - enableControls() - dismiss() - } - }).validate(*validationList.toTypedArray())*/ - } - - override fun disableControls() { - super.disableControls() - df_password_continue_btn.isClickable = false - df_password_cancel_btn.isClickable = false - } - - override fun enableControls() { - super.enableControls() - df_password_continue_btn.isClickable = true - df_password_cancel_btn.isClickable = true - } - - fun setForgotPassword(enable: Boolean) { - forgotPasswordEnable = enable - } - - fun setTitle(resId: Int) { - //titleRes = if (resId == -1) R.string.dialog_password_title else resId - } - - fun setCancelTitle(resId: Int) { - //cancelTitleRes = if (resId == -1) R.string.dialog_cancel_btn else resId - } - - interface PasswordCallback { - fun onPasswordComplete(password: String, recreate: Boolean) - fun onPasswordForgot() - } - - override fun provideVariableId(): Int { - return BR.handler - } -} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/dialog/SearchFilterDialog.kt b/app/src/main/java/by/esas/tools/dialog/SearchFilterDialog.kt new file mode 100644 index 00000000..10a8b742 --- /dev/null +++ b/app/src/main/java/by/esas/tools/dialog/SearchFilterDialog.kt @@ -0,0 +1,169 @@ +package by.esas.tools.dialog + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.checker.Checking +import by.esas.tools.databinding.DfSearchFilterBinding +import by.esas.tools.dialog_core.Config +import by.esas.tools.entity.Modules +import by.esas.tools.topbarview.ITopbarHandler + +private const val SELECTED_MODULES_KEY = "SELECTED_MODULES_KEY" +private const val SELECTED_STATUSES_KEY = "SELECTED_STATUSES_KEY" + +class SearchFilterDialog : 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..95048d59 --- /dev/null +++ b/app/src/main/java/by/esas/tools/entity/Modules.kt @@ -0,0 +1,24 @@ +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..a1324b6e --- /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..e9c9dc2c 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,12 +1,12 @@ package by.esas.tools.inject.builder -import by.esas.tools.MainActivity +import by.esas.tools.screens.MainActivity import dagger.Module import dagger.android.ContributesAndroidInjector - @Module abstract class ActivityBuilder { + @ContributesAndroidInjector(modules = [FragmentBuilder::class]) abstract fun bindMainActivity(): MainActivity } 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..f6a6111d 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,148 @@ package by.esas.tools.inject.builder +import by.esas.tools.screens.access_container.AccessContainerFragment +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.cardline.CardlineFragment +import by.esas.tools.screens.cardline.dynamic.DynamicCardlineFragment +import by.esas.tools.screens.checker.CheckerInputFieldFragment +import by.esas.tools.screens.custom_switch.CustomSwitchFragment +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.ListheaderFragment +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 + + @ContributesAndroidInjector + abstract fun bindAccessContainerFragment(): AccessContainerFragment } 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..1005dbd1 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,13 +1,11 @@ package by.esas.tools.inject.builder -import dagger.Binds import dagger.Module - @Module abstract class RepositoryBuilder { - /* @Binds - abstract fun bindsAppRepository(repository: AppRepository): IAppRepository*/ + /* @Binds + abstract fun bindsAppRepository(repository: AppRepository): IAppRepository*/ } 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..d37fbaec 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,15 +1,48 @@ 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.access_container.AccessContainerVM +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.cardline.CardlineVM +import by.esas.tools.screens.cardline.dynamic.DynamicCardlineVM +import by.esas.tools.screens.checker.CheckerInputFieldVM +import by.esas.tools.screens.custom_switch.CustomSwitchVM +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.ListheaderVM +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 - @Module abstract class ViewModelBuilder { @@ -20,6 +53,176 @@ 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 + + @Binds + @IntoMap + @ViewModelKey(AccessContainerVM::class) + abstract fun bindAccessContainerVM(viewModel: AccessContainerVM): 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..5416dc8f 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,11 +1,10 @@ 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 - @Module( includes = [ (RepositoryBuilder::class), diff --git a/app/src/main/java/by/esas/tools/inject/component/AppComponent.kt b/app/src/main/java/by/esas/tools/inject/component/AppComponent.kt index c4310db2..5058ef7c 100644 --- a/app/src/main/java/by/esas/tools/inject/component/AppComponent.kt +++ b/app/src/main/java/by/esas/tools/inject/component/AppComponent.kt @@ -11,7 +11,6 @@ import dagger.android.AndroidInjector import dagger.android.support.AndroidSupportInjectionModule import javax.inject.Singleton - @Singleton @Component( modules = [ @@ -26,6 +25,7 @@ interface AppComponent : AndroidInjector { @Suppress("UNCHECKED_CAST") @Component.Builder interface Builder { + @BindsInstance fun application(application: Application): Builder 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..9249534f 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,10 @@ 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 com.squareup.moshi.Moshi import dagger.Module import dagger.Provides @@ -15,7 +13,6 @@ import kotlinx.coroutines.Dispatchers import javax.inject.Singleton import kotlin.coroutines.CoroutineContext - @Module( includes = [ ViewModelFactoryBuilder::class @@ -35,12 +32,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 { - return LoggerImpl().apply { setTag("LoggerImpl") } + fun provideLogger(): ILogger { + return ILogger() } } 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..9c363eed 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 { @@ -58,12 +68,10 @@ class NetworkModule { fun provideSharedPrefs(context: Context): AppSharedPrefs { return AppSharedPrefs(context) }*/ - - } - /* @Provides - @Singleton - fun providesTrustManagerFactory(context: Context): TrustManagerFactory { - return createTrustManager(context) - }*/ \ No newline at end of file +/* @Provides + @Singleton + fun providesTrustManagerFactory(context: Context): TrustManagerFactory { + return createTrustManager(context) + }*/ \ No newline at end of file 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/logger/LoggerImpl.kt b/app/src/main/java/by/esas/tools/logger/LoggerImpl.kt deleted file mode 100644 index ff72a731..00000000 --- a/app/src/main/java/by/esas/tools/logger/LoggerImpl.kt +++ /dev/null @@ -1,51 +0,0 @@ -package by.esas.tools.logger - -import android.util.Log -import by.esas.tools.error_mapper.AppErrorStatusEnum - -class LoggerImpl : ILogger { - var baseTag: String = "LoggerImpl" - override fun setTag(tag: String) { - this.baseTag = tag - } - - override fun showMessage(msg: String, length: Int) { - - } - - override fun showMessage(msgRes: Int, length: Int) { - - } - - 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) - } - - override fun logError(throwable: Throwable) { - Log.e(baseTag, throwable.message, throwable) - } - - override fun logError(msg: String) { - Log.e(baseTag, msg) - } - - override fun logInfo(msg: String) { - Log.i(baseTag, msg) - } - - override fun sendLogs(msg: String) { - - } - - override fun logError(error: ErrorModel) { - Log.e(baseTag, error.statusEnum.name) - } -} \ 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..14a4e170 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/MainActivity.kt @@ -0,0 +1,168 @@ +package by.esas.tools.screens + +import android.os.Bundle +import android.view.Gravity +import android.view.MotionEvent +import android.widget.PopupMenu +import androidx.core.os.bundleOf +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.dialog_core.Config +import by.esas.tools.dialog_message.MessageDialog +import by.esas.tools.logger.Action +import by.esas.tools.screens.menu.MenuFragment +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 + lateinit var popupMenu: PopupMenu + 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) { + //REMEMBER we can register result listener for CLEAR_CASES_TEST_DATA_DIALOG + // in MenuFragment so it will receive the result right away + MainVM.CASE_STATUS_DIALOG -> { + FragmentResultListener { key, result -> + val actionName = result.getString(Config.DIALOG_USER_ACTION) + result.putString(MainVM.DIALOG_KEY, key) + if (!actionName.isNullOrBlank()) { + viewModel.handleAction(Action(actionName, result)) + } else { + viewModel.enableControls() + } + } + } + MainVM.CLEAR_CASES_TEST_DATA_DIALOG -> { + FragmentResultListener { _, result -> + val actionName = result.getString(Config.DIALOG_USER_ACTION) + if (actionName == MessageDialog.USER_ACTION_POSITIVE_CLICKED) + supportFragmentManager.setFragmentResult( + MenuFragment.MENU_UPDATE, + bundleOf(Pair(MenuFragment.MENU_UPDATE, MenuFragment.MENU_UPDATE_KEY_CLEAR)) + ) + } + } + 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) + popupMenu.show() + else + viewModel.openCaseStatusDialog(it.label.toString()) + } + } + }) + } + + override fun setupObservers() { + super.setupObservers() + + viewModel.updateMenuLive.observe(this) { + handleAction(Action(NEED_TO_UPDATE_MENU)) + } + } + + 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() { + popupMenu = PopupMenu(this, binding.aMainTopBar, Gravity.END) + popupMenu.inflate(R.menu.settings_menu) + popupMenu.setOnMenuItemClickListener { menuItem -> + return@setOnMenuItemClickListener when (menuItem.itemId) { + R.id.menuUiSettings -> { + navController.navigate(R.id.baseuiThemeFragment) + true + } + R.id.menuUpdateTest -> { + viewModel.openClearCasesTestDataDialog() + true + } + else -> false + } + } + } +} 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..de2d39c7 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/MainVM.kt @@ -0,0 +1,99 @@ +package by.esas.tools.screens + +import android.os.Bundle +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.base.AppVM +import by.esas.tools.dialog_message.MessageDialog +import by.esas.tools.entity.TestStatusEnum +import by.esas.tools.logger.Action +import javax.inject.Inject + +class MainVM @Inject constructor( + 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 == by.esas.tools.dialog_message.MessageDialog.USER_ACTION_ITEM_PICKED) { + getStatusAndUpdate(action.parameters) + } 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 + + fun openCaseStatusDialog(caseLabel: String) { + val dialog = by.esas.tools.dialog_message.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( + makeItemInfo(TestStatusEnum.CHECKED, R.string.case_status_checked), + makeItemInfo(TestStatusEnum.FAILED, R.string.case_status_failed), + makeItemInfo(TestStatusEnum.IN_PROCESS, R.string.case_status_in_process), + makeItemInfo(TestStatusEnum.UNCHECKED, R.string.case_status_unchecked) + ) + ) + + showDialog(dialog) + } + + fun openClearCasesTestDataDialog() { + val dialog = by.esas.tools.dialog_message.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) + + showDialog(dialog) + } + + private fun makeItemInfo(status: TestStatusEnum, resId: Int): by.esas.tools.dialog_message.MessageDialog.ItemInfo { + return by.esas.tools.dialog_message.MessageDialog.ItemInfo(status.name, App.appContext.getString(resId)) + } + + private fun getStatusAndUpdate(parameters: Bundle?) { + val code = parameters?.getString(by.esas.tools.dialog_message.MessageDialog.ITEM_CODE) ?: "" + val status = try { + TestStatusEnum.valueOf(code) + } catch (e: IllegalArgumentException) { + TestStatusEnum.UNCHECKED + } + //REMEMBER to execute usecase only in case that status is new to this case + addCaseStatus.caseId = currentCaseId + addCaseStatus.caseStatus = status + addCaseStatus.execute { + onComplete { + showMessage(R.string.case_status_is_changed) + updateMenuLive.postValue(true) + } + onError { handleError(it) } + } + } +} diff --git a/app/src/main/java/by/esas/tools/screens/access_container/AccessContainerFragment.kt b/app/src/main/java/by/esas/tools/screens/access_container/AccessContainerFragment.kt new file mode 100644 index 00000000..3af04d28 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/AccessContainerFragment.kt @@ -0,0 +1,89 @@ +package by.esas.tools.screens.access_container + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.FragmentResultListener +import androidx.lifecycle.ViewModelProvider +import by.esas.tools.R +import by.esas.tools.accesscontainer.entity.AuthType +import by.esas.tools.accesscontainer.entity.Token +import by.esas.tools.base.AppFragment +import by.esas.tools.biometric_decryption.checkBiometricSupport +import by.esas.tools.databinding.FMainRefresherBinding +import by.esas.tools.dialog_core.Config +import by.esas.tools.dialog_message.MessageDialog +import by.esas.tools.getErrorMessage +import by.esas.tools.logger.ILogger +import by.esas.tools.logger.handler.ErrorMessageHelper +import by.esas.tools.utils.logger.ErrorModel + +class AccessContainerFragment : AppFragment() { + + companion object { + + const val PREFERRED_TYPE_PICKER: String = "PREFERRED_TYPE_PICKER" + val DEFAULT_TOKEN: Token = Token("accessToken", "refreshToken") + } + + override val fragmentDestinationId = R.id.accessContainerFragment + + override fun provideLayoutId(): Int = R.layout.f_main_refresher + + override fun provideViewModel(): AccessContainerVM { + return ViewModelProvider( + this, + viewModelFactory.provideFactory() + ).get(AccessContainerVM::class.java) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.initialize() + viewModel.isBiometricAvailable.value = + activity?.applicationContext?.let { + checkBiometricSupport(it) { tag: String, msg: String -> + logger.logCategory(ILogger.CATEGORY_ERROR, tag, msg) + } + } ?: false + + viewModel.refresher.setActivity(requireActivity()) + viewModel.refresher.setUserId(viewModel.user) + viewModel.currentToken.value = DEFAULT_TOKEN.accessToken + viewModel.currentRefresh.value = DEFAULT_TOKEN.refreshToken + viewModel.provider.setToken(DEFAULT_TOKEN) + viewModel.refresher.setToken(DEFAULT_TOKEN) + } + + override fun provideRequestKeys(): List { + return super.provideRequestKeys().plus(listOf(PREFERRED_TYPE_PICKER)) + } + + override fun provideFragmentResultListener(requestKey: String): FragmentResultListener? { + return if (requestKey == PREFERRED_TYPE_PICKER) { + FragmentResultListener { key, result -> + val actionName = result.getString(Config.DIALOG_USER_ACTION) + if (actionName == MessageDialog.USER_ACTION_ITEM_PICKED) { + val name = result.getString(MessageDialog.ITEM_NAME) + val code = result.getString(MessageDialog.ITEM_CODE) ?: AuthType.NONE.name + viewModel.preferredTypeText.value = name + viewModel.preferredType = AuthType.valueOf(code) + viewModel.enableControls() + } else { + viewModel.enableControls() + } + } + } else { + super.provideFragmentResultListener(requestKey) + } + } + + override fun provideErrorStringHelper(): ErrorMessageHelper { + logger.order(TAG, "provideErrorHandler") + return object : ErrorMessageHelper { + + override fun getErrorMessage(error: ErrorModel): String { + return getErrorMessage(error.getStatusAsEnum(), null) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/AccessContainerVM.kt b/app/src/main/java/by/esas/tools/screens/access_container/AccessContainerVM.kt new file mode 100644 index 00000000..6d2d7634 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/AccessContainerVM.kt @@ -0,0 +1,236 @@ +package by.esas.tools.screens.access_container + +import androidx.lifecycle.MutableLiveData +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.accesscontainer.dialog.BiometricUserInfo +import by.esas.tools.accesscontainer.entity.AuthType +import by.esas.tools.accesscontainer.entity.Token +import by.esas.tools.accesscontainer.support.ITypeManager +import by.esas.tools.base.AppVM +import by.esas.tools.logger.ILogger +import by.esas.tools.screens.access_container.utility.Container +import by.esas.tools.screens.access_container.utility.Executor +import by.esas.tools.screens.access_container.utility.SecretProvider +import by.esas.tools.util.TAGk +import by.esas.tools.utils.logger.ErrorModel +import javax.inject.Inject + +class AccessContainerVM @Inject constructor() : AppVM() { + + lateinit var refresher: Container + lateinit var provider: SecretProvider + var user: String = "user" + var preferredType: AuthType = AuthType.PIN_AUTH + val authTypes = listOf(AuthType.NONE, AuthType.PIN_AUTH, AuthType.BIOMETRIC_AUTH) + + val preferredTypeText = MutableLiveData(getAuthTypeText(preferredType)) + val currentToken = MutableLiveData("") + val currentRefresh = MutableLiveData("") + val correctPassword = MutableLiveData("12345678") + val pinAvailable = MutableLiveData(false) + val biomAvailable = MutableLiveData(false) + val refreshExplicitly = MutableLiveData(false) + val isBiometricAvailable = MutableLiveData(false) + val noToken = MutableLiveData(false) + + val errorOnRefreshWithToken = MutableLiveData(false) + val errorOnGetSecrets = MutableLiveData(false) + val errorOnRefresh = MutableLiveData(false) + val errorOnRefreshWithSecret = MutableLiveData(false) + val errorOnCheckSecret = MutableLiveData(false) + val errorOnCreateSecret = MutableLiveData(false) + + private var userIv: ByteArray = ByteArray(16) + private val containerLogger = ILogger().apply { + setTag(Container::class.TAGk) + } + + fun getAuthTypeText(type: AuthType): String { + val resId = when (type) { + AuthType.PIN_AUTH -> R.string.label_pin + AuthType.BIOMETRIC_AUTH -> R.string.label_biom + AuthType.NONE -> R.string.label_password + } + return App.appContext.getString(resId) + } + + fun initialize() { + val executor = Executor(ILogger().apply { + setTag(Executor::class.TAGk) + }) + provider = executor.provider + refresher = Container.getInstance( + logger = containerLogger, + appMapper = mapper, + executor = executor, + typeManager = provideTypeManager(), + userInfo = provideUserInfo() + ) + } + + fun onPickPreferredType() { + disableControls() + val dialog: by.esas.tools.dialog_message.MessageDialog = + by.esas.tools.dialog_message.MessageDialog() + dialog.setRequestKey(AccessContainerFragment.PREFERRED_TYPE_PICKER) + dialog.setTitle(R.string.refresher_preferred_lable) + dialog.setItems(authTypes.map { + by.esas.tools.dialog_message.MessageDialog.ItemInfo( + it.name, + getAuthTypeText(it) + ) + }) + dialog.isCancelable = true + showDialog(dialog) + } + + fun onRefreshAccess() { + disableControls() + updateProvider() + refresher.refresh({ + currentToken.value = it + currentRefresh.value = refresher.getRefresh() + showMessage("Refresh access result: $it") + enableControls() + }, { + handleError(it) + }, { + showMessage("Refresh access cancelled") + enableControls() + }) + } + + fun onCheckAccess() { + disableControls() + updateProvider() + refresher.checkAccess(refreshExplicitly.value ?: false) { + onComplete { + currentToken.value = it + currentRefresh.value = refresher.getRefresh() + showMessage("Check access result: $it") + enableControls() + } + onError { + handleError(it) + } + onCancel { + showMessage("Check access cancelled") + enableControls() + } + onCancellation { + handleError(it) + } + } + } + + fun onGetSecret() { + disableControls() + updateProvider() + refresher.getSecret { + onComplete { + showMessage("Get secret result: $it") + enableControls() + } + onError { + handleError(it) + } + onCancel { + showMessage("Get secret cancelled") + enableControls() + } + onCancellation { + handleError(it) + } + } + } + + fun onSaveRefresh() { + disableControls() + updateProvider() + refresher.saveRefresh("newRefresh") { + onComplete { + currentToken.value = it + currentRefresh.value = refresher.getRefresh() + showMessage("Save refresh token \"newRefresh\" result: $it") + enableControls() + } + onError { + handleError(it) + } + onCancel { + showMessage("Save refresh token cancelled") + enableControls() + } + onCancellation { + handleError(it) + } + } + } + + private fun updateProvider() { + if (noToken.value == true) { + refresher.setToken(Token(currentToken.value ?: "", "")) + currentRefresh.value = "" + } else if (refresher.getRefresh().isBlank()) { + val token = provider.getCurrentToken() + refresher.setToken(token) + currentToken.value = token.accessToken + currentRefresh.value = token.refreshToken + } + + val list = mutableListOf() + if (pinAvailable.value == true) { + list.add(AuthType.PIN_AUTH) + } + if (biomAvailable.value == true) { + list.add(AuthType.BIOMETRIC_AUTH) + } + provider.authTypes = list + provider.correctPassword = correctPassword.value.orEmpty() + provider.errorOnRefreshWithToken = errorOnRefreshWithToken.value ?: false + provider.errorOnGetSecrets = errorOnGetSecrets.value ?: false + provider.errorOnRefresh = errorOnRefresh.value ?: false + provider.errorOnRefreshWithSecret = errorOnRefreshWithSecret.value ?: false + provider.errorOnCheckSecret = errorOnCheckSecret.value ?: false + provider.errorOnCreateSecret = errorOnCreateSecret.value ?: false + } + + private fun provideTypeManager(): ITypeManager { + return object : ITypeManager { + override fun getPreferredType(): AuthType { + // this way we can check refresher's behavior when there is not appropriate preferred type + return preferredType + } + + override fun putPreferredType(type: AuthType) { + preferredTypeText.value = getAuthTypeText(type) + preferredType = type + } + } + } + + private fun provideUserInfo(): BiometricUserInfo { + return object : BiometricUserInfo { + override fun getCurrentUser(): String { + return user + } + + override fun setCurrentUserIv(iv: ByteArray) { + userIv = iv + } + + override fun getCurrentUserIv(): ByteArray { + return userIv + } + } + } + + private fun showMessage(msg: String) { + val dialog: by.esas.tools.dialog_message.MessageDialog = + by.esas.tools.dialog_message.MessageDialog() + dialog.setMessage(msg) + dialog.isCancelable = true + showDialog(dialog) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/dialog/PasswordDialog.kt b/app/src/main/java/by/esas/tools/screens/access_container/dialog/PasswordDialog.kt new file mode 100644 index 00000000..e09c25a7 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/dialog/PasswordDialog.kt @@ -0,0 +1,159 @@ +package by.esas.tools.screens.access_container.dialog + +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.databinding.ObservableBoolean +import androidx.databinding.ObservableField +import by.esas.tools.BR +import by.esas.tools.R +import by.esas.tools.checker.Checker +import by.esas.tools.checker.Checking +import by.esas.tools.databinding.DfPasswordBinding +import by.esas.tools.dialog.BindingDialogFragment +import by.esas.tools.dialog_core.Config +import by.esas.tools.utils.checking.AppChecker + +class PasswordDialog : BindingDialogFragment() { + + companion object { + + const val USER_ACTION_PASSWORD_COMPLETED: String = "USER_ACTION_PASSWORD_COMPLETED" + const val RESULT_PASSWORD: String = "RESULT_PASSWORD" + const val USER_ACTION_ANOTHER_CLICKED: String = "USER_ACTION_ANOTHER_CLICKED" + const val USER_ACTION_FORGOT_CLICKED: String = "USER_ACTION_FORGOT_CLICKED" + } + + override fun provideLayoutId(): Int { + return R.layout.df_password + } + + override fun provideSwitchableList(): List { + return emptyList() + } + + override fun provideValidationList(): List { + return emptyList() + } + + override fun provideVariableId(): Int { + return BR.handler + } + + val showForgotPassword = ObservableBoolean(false) + val showAnotherBtn = ObservableBoolean(false) + val btnEnabled = ObservableBoolean(false) + val password = ObservableField("") + + private var titleRes: Int = R.string.label_password + private var showForgot: Boolean = false + private var showAnother: Boolean = false + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBoolean("btnEnabled", btnEnabled.get()) + outState.putString("password", password.get()) + outState.putBoolean("showForgot", showForgot) + outState.putBoolean("showAnother", showAnother) + outState.putInt("titleRes", titleRes) + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + if (savedInstanceState != null) { + btnEnabled.set(savedInstanceState.getBoolean("btnEnabled", true)) + showForgot = (savedInstanceState.getBoolean("showForgot", false)) + showAnother = (savedInstanceState.getBoolean("showAnother", false)) + password.set(savedInstanceState.getString("password", "") ?: "") + titleRes = (savedInstanceState.getInt("titleRes", R.string.label_password)) + updateScreen() + } + } + + override fun styleSettings() { + setStyle(STYLE_NO_FRAME, 0) + } + + override fun layoutSettings() { + dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + updateScreen() + enableControls() + } + + fun updateScreen() { + binding.dfPasswordTopBarTitle.setText(titleRes) + + showAnotherBtn.set(showAnother) + showForgotPassword.set(showForgot) + } + + fun onBackClick() { + disableControls() + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, Config.CANCEL_DIALOG) + dismiss() + } + + fun onAnotherClick() { + disableControls() + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, USER_ACTION_ANOTHER_CLICKED) + dismiss() + enableControls() + } + + fun onPasswordClick() { + disableControls() + validatePassword() + } + + fun onForgotClick() { + disableControls() + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, USER_ACTION_FORGOT_CLICKED) + dismiss() + enableControls() + } + + private fun validatePassword() { + AppChecker().setListener(object : Checker.CheckListener { + override fun onFailed() { + enableControls() + } + + override fun onSuccess() { + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, USER_ACTION_PASSWORD_COMPLETED) + resultBundle.putString(RESULT_PASSWORD, password.get() ?: "") + dismiss() + enableControls() + } + }).validate(provideValidationList()) + } + + override fun disableControls() { + super.disableControls() + btnEnabled.set(false) + } + + override fun enableControls() { + super.enableControls() + btnEnabled.set(true) + } + + fun showAnotherMethod(show: Boolean) { + showAnother = show + } + + fun setForgotPassword(enable: Boolean) { + showForgot = enable + } + + fun setTitle(resId: Int) { + titleRes = if (resId == -1) R.string.label_password else resId + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/dialog/PinDialog.kt b/app/src/main/java/by/esas/tools/screens/access_container/dialog/PinDialog.kt new file mode 100644 index 00000000..f2f13da6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/dialog/PinDialog.kt @@ -0,0 +1,230 @@ +package by.esas.tools.screens.access_container.dialog + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.databinding.ObservableBoolean +import androidx.databinding.ObservableField +import by.esas.tools.App +import by.esas.tools.BR +import by.esas.tools.R +import by.esas.tools.checker.Checking +import by.esas.tools.databinding.DfPinBinding +import by.esas.tools.dialog.BindingDialogFragment +import by.esas.tools.dialog_core.Config +import by.esas.tools.numpad.INumPadHandler +import com.google.android.material.textfield.TextInputEditText + +class PinDialog : BindingDialogFragment() { + + override val TAG: String = PinDialog::class.java.simpleName + + companion object { + + const val USER_ACTION_PIN_COMPLETED: String = "USER_ACTION_PIN_COMPLETED" + const val RESULT_PIN: String = "RESULT_PIN" + const val USER_ACTION_ANOTHER_CLICKED: String = "USER_ACTION_ANOTHER_CLICKED" + } + + override fun provideLayoutId(): Int { + return R.layout.df_pin + } + + override fun provideVariableId(): Int { + return BR.handler + } + + override fun provideSwitchableList(): List { + return emptyList() + } + + override fun provideValidationList(): List { + return emptyList() + } + + private val builder: StringBuilder = StringBuilder() + + private var compare: String = "" + private var titleRes: Int = R.string.dialog_pin_title + private var confirmBtnTextRes: Int = R.string.dialog_pin_create_btn + val btnEnabled = ObservableBoolean(true) + val confirmBtnEnable = ObservableBoolean(false) + val showPinError = ObservableBoolean(false) + val showAnotherBtn = ObservableBoolean(false) + val showConfirmBtn = ObservableBoolean(false) + val confirmBtnText = ObservableField("") + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putBoolean("btnEnabled", btnEnabled.get()) + outState.putBoolean("showAnotherBtn", showAnotherBtn.get()) + outState.putBoolean("showConfirmBtn", showConfirmBtn.get()) + outState.putBoolean("showPinError", showPinError.get()) + outState.putString("old", compare) + outState.putString("builder", builder.toString()) + outState.putInt("titleRes", titleRes) + outState.putInt("confirmBtnTextRes", confirmBtnTextRes) + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + if (savedInstanceState != null) { + btnEnabled.set(savedInstanceState.getBoolean("btnEnabled", true)) + showAnotherBtn.set(savedInstanceState.getBoolean("showAnotherBtn", false)) + showConfirmBtn.set(savedInstanceState.getBoolean("showConfirmBtn", false)) + showPinError.set(savedInstanceState.getBoolean("showPinError", false)) + compare = savedInstanceState.getString("old", "") ?: "" + builder.clear() + builder.append(savedInstanceState.getString("builder", "") ?: "") + titleRes = (savedInstanceState.getInt("titleRes", R.string.dialog_pin_title)) + confirmBtnTextRes = (savedInstanceState.getInt("confirmBtnTextRes", R.string.dialog_pin_create_btn)) + updateScreen() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + enableControls() + builder.clear() + + binding.dfPinNumPad.setNumpadHandler(object : INumPadHandler { + override fun onNumClick(num: Int) { + showPinError.set(false) + if (btnEnabled.get()) { + disableControls() + if (builder.length < 5) { + addPin(num) + } else if (builder.length < 6) { + addLastPin(num) + } else { + enableControls() + } + } + } + + private fun addPin(num: Int) { + binding.dfPinView.fillPin() + builder.append(num) + enableControls() + } + + private fun addLastPin(num: Int) { + binding.dfPinView.fillPin() + builder.append(num) + if (!showConfirmBtn.get()) { + binding.dfPinView.postOnAnimationDelayed({ + logger.i(builder.length.toString()) + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, USER_ACTION_PIN_COMPLETED) + resultBundle.putString(RESULT_PIN, builder.toString()) + builder.clear() + binding.dfPinView.clearPins() + dismiss() + enableControls() + }, 300L) + } else { + if (compare.isNotBlank() && compare != builder.toString()) { + showPinError.set(true) + } + enableControls() + } + } + + override fun onRightIconClick() { + disableControls() + showPinError.set(false) + if (builder.isNotEmpty()) { + val length = if (builder.length <= 1) 0 else builder.length - 1 + builder.setLength(length) + binding.dfPinView.unFillPin() + } + enableControls() + } + }) + /*restoreBtn?.setOnLongClickListener { + return@setOnLongClickListener if (btnEnabled.get()) { + disableControls() + showPinError.set(false) + builder.clear() + binding.dfPinView.clearPins() + enableControls() + true + } else + false + }*/ + updateScreen() + } + + private fun updateScreen() { + binding.dfPinTopBarTitle.setText(titleRes) + binding.dfPinView.setPin(builder.length) + confirmBtnText.set(App.appContext.getString(confirmBtnTextRes)) + } + + fun onBackClick() { + disableControls() + //full = false + builder.clear() + binding.dfPinView.clearPins() + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, Config.CANCEL_DIALOG) + dismiss() + } + + fun onAnotherClick() { + disableControls() + //full = false + builder.clear() + binding.dfPinView.clearPins() + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, USER_ACTION_ANOTHER_CLICKED) + dismiss() + enableControls() + } + + fun onConfirmClick() { + disableControls() + if (compare.isNotBlank() && compare != builder.toString()) { + Toast.makeText(requireContext(), R.string.error_app_not_equal, Toast.LENGTH_SHORT).show() + enableControls() + } else { + resultBundle.clear() + resultBundle.putString(Config.DIALOG_USER_ACTION, USER_ACTION_PIN_COMPLETED) + resultBundle.putString(RESULT_PIN, builder.toString()) + builder.clear() + binding.dfPinView.clearPins() + dismiss() + enableControls() + } + } + + override fun disableControls() { + btnEnabled.set(false) + confirmBtnEnable.set(false) + binding.dfPinNumPad.disableNumpadView() + super.disableControls() + } + + override fun enableControls() { + btnEnabled.set(true) + confirmBtnEnable.set(builder.length == 6) + binding.dfPinNumPad.enableNumpadView() + super.enableControls() + } + + fun setTitle(resId: Int) { + titleRes = if (resId == -1) R.string.dialog_pin_title else resId + } + + fun setConfirmBtn(showBtn: Boolean, textResId: Int = -1, compare: String = "") { + this.compare = compare + confirmBtnTextRes = if (textResId == -1) R.string.common_continue_button else textResId + showConfirmBtn.set(showBtn) + if (showBtn) showAnotherBtn.set(false) + } + + fun setHasAnother(value: Boolean) { + showAnotherBtn.set(value) + if (value) showConfirmBtn.set(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/utility/Container.kt b/app/src/main/java/by/esas/tools/screens/access_container/utility/Container.kt new file mode 100644 index 00000000..b8e5aad5 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/utility/Container.kt @@ -0,0 +1,81 @@ +package by.esas.tools.screens.access_container.utility + +import android.content.Context +import by.esas.tools.accesscontainer.Refresher +import by.esas.tools.accesscontainer.dialog.BiometricUserInfo +import by.esas.tools.accesscontainer.dialog.DialogProvider +import by.esas.tools.accesscontainer.error.IErrorMapper +import by.esas.tools.accesscontainer.support.IContainerExecutor +import by.esas.tools.accesscontainer.support.ITypeManager +import by.esas.tools.accesscontainer.support.supporter.IUtil +import by.esas.tools.accesscontainer.support.supporter.Supporter +import by.esas.tools.app_domain.error_mapper.AppErrorMapper +import by.esas.tools.logger.ILogger +import by.esas.tools.util.TAGk +import by.esas.tools.utils.logger.ErrorModel +import javax.crypto.SecretKey + +class Container( + logger: ILogger, + executor: IContainerExecutor, + typeManager: ITypeManager, + userInfo: BiometricUserInfo, + mapper: IErrorMapper, + dialogProvider: DialogProvider, + supporter: Supporter +) : Refresher(logger, executor, typeManager, userInfo, mapper, dialogProvider, supporter) { + + companion object { + + fun getInstance( + logger: ILogger, + executor: IContainerExecutor, + appMapper: AppErrorMapper, + typeManager: ITypeManager, + userInfo: BiometricUserInfo + ): Container { + val errorMapper = object : IErrorMapper { + private val mapper = appMapper + override fun mapError(e: Exception): ErrorModel { + return mapper.mapErrorException(TAGk, e) + } + + override fun createErrorModel(code: Int, enumName: String): ErrorModel { + return mapper.createModel(code, enumName) + } + } + + return Container( + logger = logger, + executor = executor, + typeManager = typeManager, + userInfo = userInfo, + mapper = errorMapper, + dialogProvider = DialogProviderImpl(logger), + supporter = Supporter(ResProviderImpl(), provideUtil()) + ) + } + + private fun provideUtil(): IUtil { + return object : IUtil { + + override fun generatePin(pin: String, userId: String, androidId: String): SecretKey? { + return by.esas.tools.biometric_decryption.generatePin(pin, userId, androidId) + } + + override fun checkBiometricSupport( + context: Context?, + logging: (String, String) -> Unit + ): Boolean { + return context?.let { + by.esas.tools.biometric_decryption.checkBiometricSupport( + context, + logging + ) + } + ?: false + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/utility/DialogProviderImpl.kt b/app/src/main/java/by/esas/tools/screens/access_container/utility/DialogProviderImpl.kt new file mode 100644 index 00000000..17ae1ab4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/utility/DialogProviderImpl.kt @@ -0,0 +1,425 @@ +package by.esas.tools.screens.access_container.utility + +import android.view.View +import androidx.biometric.BiometricPrompt +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentResultListener +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.accesscontainer.dialog.BiometricUserInfo +import by.esas.tools.accesscontainer.dialog.DialogProvider +import by.esas.tools.accesscontainer.dialog.IBaseDialog +import by.esas.tools.accesscontainer.dialog.IBiometric +import by.esas.tools.accesscontainer.dialog.setters.MenuDialogSetter +import by.esas.tools.accesscontainer.dialog.setters.MenuResultHandler +import by.esas.tools.accesscontainer.dialog.setters.PasswordResultHandler +import by.esas.tools.accesscontainer.dialog.setters.PinResultHandler +import by.esas.tools.accesscontainer.dialog.setters.SetBiometricDialog +import by.esas.tools.accesscontainer.dialog.setters.SetPasswordDialog +import by.esas.tools.accesscontainer.dialog.setters.SetPinDialog +import by.esas.tools.accesscontainer.entity.AuthType +import by.esas.tools.biometric_decryption.Biometric +import by.esas.tools.dialog_core.BaseDialogFragment +import by.esas.tools.dialog_core.Config +import by.esas.tools.dialog_core.Config.CANCEL_DIALOG +import by.esas.tools.dialog_message.MessageDialog +import by.esas.tools.dialog_message.MessageDialog.Companion.ITEM_CODE +import by.esas.tools.dialog_message.MessageDialog.Companion.USER_ACTION_ITEM_PICKED +import by.esas.tools.logger.ILogger +import by.esas.tools.screens.access_container.dialog.PasswordDialog +import by.esas.tools.screens.access_container.dialog.PinDialog +import by.esas.tools.utils.logger.ErrorModel +import javax.crypto.Cipher + +class DialogProviderImpl(val logger: ILogger) : DialogProvider() { + + override val passwordDialog: SetPasswordDialog = providePasswordDialog() + override val menuDialog: MenuDialogSetter = provideMenuDialog() + override val pinDialog: SetPinDialog = providePinDialog() + override val biom: SetBiometricDialog = provideBiomDialog() + + private fun show( + context: FragmentActivity?, + tag: String, + instance: BaseDialogFragment?, + resultListener: FragmentResultListener + ) { + logger.order("IBaseDialog $tag show instance != null ${instance != null}") + if (context != null) { + instance?.setRequestKey(tag) + + context.supportFragmentManager.clearFragmentResultListener(tag) + context.supportFragmentManager.setFragmentResultListener(tag, context, resultListener) + + try { + instance?.show(context.supportFragmentManager, tag) + } catch (e: java.lang.IllegalStateException) { + context.supportFragmentManager.clearFragmentResultListener(tag) + logger.e("IBaseDialog $tag IllegalStateException while dialog show") + } + } + } + + private fun provideMenuDialog(): MenuDialogSetter { + var dialog: MessageDialog? = null + + return object : MenuDialogSetter { + val list = mutableListOf>() + var menuHandler: MenuResultHandler? = null + + override fun createDialog() { + logger.order("MenuDialogSetter createDialog dialog!=null = ${dialog != null}") + dialog?.dismiss() + dialog = null + dialog = MessageDialog(false) + dialog?.isCancelable = false + } + + override fun setupParameterBundle( + isDecrypting: Boolean, + showPassword: Boolean, + showPin: Boolean, + showBiom: Boolean + ) { + logger.order("MenuDialogSetter setupParameterBundle isDecrypting=$isDecrypting, showPassword=$showPassword, showPin=$showPin, showBiom=$showBiom") + list.clear() + if (isDecrypting && showPassword) + list.add(Pair(App.appContext.getString(R.string.label_password), AuthType.NONE)) + if (showPin) + list.add(Pair(App.appContext.getString(R.string.label_pin), AuthType.PIN_AUTH)) + if (showBiom) + list.add(Pair(App.appContext.getString(R.string.label_biom), AuthType.BIOMETRIC_AUTH)) + dialog?.setItems( + list = list.map { MessageDialog.ItemInfo(it.second.name, it.first) }, + alignment = View.TEXT_ALIGNMENT_CENTER + ) + } + + override fun setupResultHandler(handler: MenuResultHandler) { + logger.order("MenuDialogSetter setupResultHandler") + this.menuHandler = handler + } + + override fun setTitle(titleRes: Int) { + logger.order("MenuDialogSetter setTitle dialog!=null = ${dialog != null}") + dialog?.setTitle(titleRes) + } + + override fun getDialog(): IBaseDialog { + logger.order("MenuDialogSetter getDialog dialog != null = ${dialog != null}") + return object : IBaseDialog { + var dialogInst: BaseDialogFragment? = dialog + + override fun dismiss() { + logger.order("IBaseDialog MessageDialog dismiss dialogInst != null = ${dialogInst != null}") + try { + dialogInst?.dismiss() + dialogInst = null + } catch (e: IllegalStateException) { + logger.throwable(e) + } + } + + override fun show(context: FragmentActivity?, tag: String) { + val listener = provideResultListener(tag) + show(context, tag, dialogInst, listener) + } + + override fun provideResultListener(tag: String): FragmentResultListener { + return FragmentResultListener { requestKey, result -> + if (requestKey == tag) { + val userAction = result.getString(Config.DIALOG_USER_ACTION) + logger.order("IBaseDialog $tag requestKey $requestKey userAction $userAction") + if (userAction == CANCEL_DIALOG) { + menuHandler?.onCancel() + } else if (userAction == USER_ACTION_ITEM_PICKED) { + val itemCode = result.getString(ITEM_CODE) ?: "" + val type: AuthType = try { + AuthType.valueOf(itemCode) + } catch (e: IllegalArgumentException) { + AuthType.NONE + } + when (type) { + AuthType.BIOMETRIC_AUTH -> menuHandler?.onBiomClick() + AuthType.PIN_AUTH -> menuHandler?.onPinClick() + AuthType.NONE -> menuHandler?.onPasswordClick() + } + } + } + } + } + } + } + + override fun clear(context: FragmentActivity?) { + logger.order("MenuDialogSetter clear dialog!=null = ${dialog != null}") + dialog?.getRequestKey()?.let { context?.supportFragmentManager?.clearFragmentResultListener(it) } + dialog?.dismiss() + dialog = null + } + } + } + + private fun providePasswordDialog(): SetPasswordDialog { + return object : SetPasswordDialog { + var dialog: PasswordDialog? = null + var resultHandler: PasswordResultHandler? = null + + override fun setupParameterBundle( + cancelBtnRes: Int, + anotherBtnRes: Int, + isCancellable: Boolean, + showAnother: Boolean, + forgotPasswordActionEnable: Boolean + ) { + logger.order("GetPasswordDialog setupParameterBundle") + dialog?.isCancelable = isCancellable + dialog?.showAnotherMethod(showAnother) + dialog?.setForgotPassword(forgotPasswordActionEnable) + } + + override fun setupResultHandler(handler: PasswordResultHandler) { + logger.order("GetPasswordDialog setupResultHandler") + this.resultHandler = handler + } + + override fun setTitle(titleRes: Int) { + logger.order("GetPasswordDialog setTitle dialog!=null = ${dialog != null}") + dialog?.setTitle(titleRes) + } + + override fun createDialog() { + logger.order("GetPasswordDialog createDialog dialog!=null = ${dialog != null}") + dialog?.dismiss() + dialog = null + dialog = PasswordDialog() + } + + override fun getDialog(): IBaseDialog { + logger.order("GetPasswordDialog getDialog dialog != null = ${dialog != null}") + return object : IBaseDialog { + var dialogInst: BaseDialogFragment? = dialog + override fun dismiss() { + logger.order("IBaseDialog PasswordDialog dismiss dialogInst != null = ${dialogInst != null}") + try { + dialogInst?.dismiss() + dialogInst = null + } catch (e: IllegalStateException) { + logger.throwable(e) + } + } + + override fun show(context: FragmentActivity?, tag: String) { + val listener = provideResultListener(tag) + show(context, tag, dialogInst, listener) + } + + override fun provideResultListener(tag: String): FragmentResultListener { + return FragmentResultListener { requestKey, result -> + if (requestKey == tag) { + val userAction = result.getString(Config.DIALOG_USER_ACTION) + logger.order("IBaseDialog $tag requestKey $requestKey userAction $userAction") + when (userAction) { + CANCEL_DIALOG -> { + resultHandler?.onCancel() + } + PasswordDialog.USER_ACTION_ANOTHER_CLICKED -> { + resultHandler?.onAnotherClicked() + } + PasswordDialog.USER_ACTION_PASSWORD_COMPLETED -> { + val password = result.getString(PasswordDialog.RESULT_PASSWORD) + resultHandler?.onComplete(password ?: "") + } + PasswordDialog.USER_ACTION_FORGOT_CLICKED -> { + resultHandler?.onForgotClicked() + } + } + } + } + } + } + } + + override fun clear(context: FragmentActivity?) { + logger.order("GetPasswordDialog clear dialog!=null = ${dialog != null}") + dialog?.getRequestKey()?.let { context?.supportFragmentManager?.clearFragmentResultListener(it) } + dialog?.dismiss() + dialog = null + } + } + } + + private fun providePinDialog(): SetPinDialog { + return object : SetPinDialog { + var dialog: PinDialog? = null + var resultHandler: PinResultHandler? = null + + override fun createDialog() { + logger.order("SetPinDialog createDialog dialog!=null = ${dialog != null}") + dialog?.dismiss() + dialog = null + dialog = PinDialog() + } + + override fun setTitle(titleRes: Int) { + logger.order("SetPinDialog setTitle dialog!=null = ${dialog != null}") + dialog?.setTitle(titleRes) + } + + override fun setupParameterBundle( + cancelBtnRes: Int, + anotherBtnRes: Int, + cancellable: Boolean, + hasAnother: Boolean + ) { + logger.order("SetPinDialog dialog!=null = ${dialog != null}") + logger.i("SetPinDialog setCancellable $cancellable") + dialog?.isCancelable = cancellable + logger.i("SetPinDialog hasAnother $hasAnother") + dialog?.setHasAnother(hasAnother) + } + + override fun setupResultHandler(handler: PinResultHandler) { + this.resultHandler = handler + } + + override fun getDialog(): IBaseDialog { + logger.order("SetPinDialog getDialog dialog != null = ${dialog != null}") + return object : IBaseDialog { + var dialogInst: BaseDialogFragment? = dialog + override fun dismiss() { + logger.order("IBaseDialog PinDialog dismiss dialogInst != null = ${dialogInst != null}") + try { + dialogInst?.dismiss() + dialogInst = null + } catch (e: IllegalStateException) { + logger.throwable(e) + } + } + + override fun show(context: FragmentActivity?, tag: String) { + val listener = provideResultListener(tag) + show(context, tag, dialogInst, listener) + } + + override fun provideResultListener(tag: String): FragmentResultListener { + return FragmentResultListener { requestKey, result -> + if (requestKey == tag) { + val userAction = result.getString(Config.DIALOG_USER_ACTION) + logger.order("IBaseDialog $tag requestKey $requestKey userAction $userAction") + when (userAction) { + CANCEL_DIALOG -> { + resultHandler?.onCancel() + } + PinDialog.USER_ACTION_ANOTHER_CLICKED -> { + resultHandler?.onAnotherClicked() + } + PinDialog.USER_ACTION_PIN_COMPLETED -> { + val pin = result.getString(PinDialog.RESULT_PIN) + resultHandler?.onComplete(pin ?: "") + } + } + } + } + } + } + } + + override fun clear(context: FragmentActivity?) { + logger.order("GetPasswordDialog clear dialog!=null = ${dialog != null}") + dialog?.getRequestKey()?.let { context?.supportFragmentManager?.clearFragmentResultListener(it) } + dialog?.dismiss() + dialog = null + } + } + } + + private fun provideBiomDialog(): SetBiometricDialog { + return object : SetBiometricDialog { + var biometricCallback: BiometricPrompt.AuthenticationCallback? = null + var builder: Biometric? = null + var prompt: BiometricPrompt.PromptInfo? = null + var dialog: BiometricPrompt? = null + + override fun onAuthenticationCallback(onError: (Boolean, String) -> Unit, onSuccess: (Cipher?) -> Unit) { + logger.order("SetBiometricDialog onAuthenticationCallback") + biometricCallback = object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + super.onAuthenticationError(errorCode, errString) + logger.order("SetBiometricDialog authentication Error") + onError(errorCode == BiometricPrompt.ERROR_CANCELED, errString.toString()) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + logger.order("SetBiometricDialog authentication Succeeded") + onSuccess(result.cryptoObject?.cipher) + } + + override fun onAuthenticationFailed() { + super.onAuthenticationFailed() + logger.order("SetBiometricDialog authentication Failed") + } + } + logger.order("SetBiometricDialog biometricCallback!=null = ${biometricCallback != null}") + } + + override fun create(activity: FragmentActivity, userInfo: BiometricUserInfo) { + logger.order("SetBiometricDialog create") + val info = object : Biometric.BiometricUserInfo { + override fun getCurrentUser(): String { + return userInfo.getCurrentUser() + } + + override fun setCurrentUserIv(iv: ByteArray) { + userInfo.setCurrentUserIv(iv) + } + + override fun getCurrentUserIv(): ByteArray { + return userInfo.getCurrentUserIv() + } + } + builder = biometricCallback?.let { callback -> + logger.order("SetBiometricDialog builder update") + Biometric(activity, callback, info) + } + logger.order("SetBiometricDialog builder!=null = ${builder != null}") + } + + override fun setInfo(title: String, negativeText: String) { + logger.order("SetBiometricDialog setInfo") + prompt = builder?.createInfo(title, negativeText) + logger.order("SetBiometricDialog prompt!=null = ${prompt != null}") + } + + override fun authenticate(isDecrypt: Boolean): IBiometric? { + logger.order("SetBiometricDialog authenticate isDecrypt = $isDecrypt") + prompt?.let { info -> + logger.order("SetBiometricDialog authenticate prompt let") + dialog = builder?.authenticate( + if (isDecrypt) Biometric.DialogMode.DECRYPT_MODE else Biometric.DialogMode.ENCRYPT_MODE, + info + ) + } + logger.order("SetBiometricDialog authenticate return IBiometric") + return object : IBiometric { + private var dialogInst: BiometricPrompt? = dialog + override fun cancelAuthentication() { + logger.order("IBiometric cancelAuthentication dialogInst!=null ${dialogInst != null}") + dialogInst?.cancelAuthentication() + dialogInst = null + } + } + } + + override fun clear() { + logger.order("SetBiometricDialog clear") + biometricCallback = null + builder = null + prompt = null + dialog?.cancelAuthentication() + dialog = null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/utility/Executor.kt b/app/src/main/java/by/esas/tools/screens/access_container/utility/Executor.kt new file mode 100644 index 00000000..9af243a4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/utility/Executor.kt @@ -0,0 +1,158 @@ +package by.esas.tools.screens.access_container.utility + +import by.esas.tools.accesscontainer.ContainerRequest +import by.esas.tools.accesscontainer.entity.AuthType +import by.esas.tools.accesscontainer.entity.Token +import by.esas.tools.accesscontainer.support.IContainerExecutor +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import by.esas.tools.logger.ILogger +import by.esas.tools.utils.logger.ErrorModel +import javax.crypto.Cipher +import javax.crypto.SecretKey + +class Executor( + private val logger: ILogger +) : IContainerExecutor { + + val TAG: String = Executor::class.java.simpleName + + var provider: SecretProvider = SecretProvider() + + override fun refreshAccessUC( + refreshToken: String, + block: ContainerRequest.() -> Unit + ) { + logger.order("refreshAccessUC") + + val response = ContainerRequest().apply { block() } + provider.refreshToken().let { + if (it.type == Result.SUCCESS_RESULT) { + response(it.result) + } else { + response(ErrorModel(0, AppErrorStatusEnum.ACCESS_DENIED)) + } + } + } + + override fun getSecretsUC(block: ContainerRequest, ErrorModel>.() -> Unit) { + logger.order("getSecretsUC") + + val response = ContainerRequest, ErrorModel>().apply { block() } + provider.getSecrets().let { + if (it.type == Result.SUCCESS_RESULT) { + response(it.result!!) + } else { + response(ErrorModel(0, AppErrorStatusEnum.APP_USER_HAS_NO_SECRETS)) + } + } + } + + override fun refreshWithSecretUC( + pinKey: SecretKey?, + cipher: Cipher?, + block: ContainerRequest.() -> Unit + ) { + logger.order("refreshWithSecretUC pinKey!=null = ${pinKey != null}, cipher!=null = ${cipher != null}") + + val response = ContainerRequest().apply { block() } + provider.refreshWithSecret().let { + if (it.type == Result.SUCCESS_RESULT) { + response(it.result!!) + } else { + val status = + if (pinKey != null) AppErrorStatusEnum.APP_PIN_DECRYPTION_FAILED + else AppErrorStatusEnum.APP_BIOMETRIC_DECRYPTION_FAILED + response(ErrorModel(0, status)) + } + } + } + + override fun checkSecretUC( + pinKey: SecretKey?, + cipher: Cipher?, + block: ContainerRequest, ErrorModel>.() -> Unit + ) { + logger.order("checkSecretUC pinKey!=null = ${pinKey != null}, cipher!=null = ${cipher != null}") + + val response = ContainerRequest, ErrorModel>().apply { block() } + provider.checkSecret().let { + if (it.type == Result.SUCCESS_RESULT) { + response(it.result!!) + } else { + response(ErrorModel(0, AppErrorStatusEnum.SECRET_CHECK_NOT_MATCH)) + } + } + } + + override fun refreshWithSecretUC( + secret: String, + block: ContainerRequest.() -> Unit + ) { + logger.order("refreshWithSecretUC secret not empty ${secret.isNotBlank()}") + + val response = ContainerRequest().apply { block() } + provider.refreshWithSecretText().let { + if (it.type == Result.SUCCESS_RESULT) { + response(it.result!!) + } else { + response(ErrorModel(0, AppErrorStatusEnum.APP_REFRESH_TOKEN_DECRYPTION_FAILED)) + } + } + } + + override fun authorizationInSystemUC( + password: String, + block: ContainerRequest.() -> Unit + ) { + logger.order("authorizationInSystemUC password not empty ${password.isNotBlank()}") + + val response = ContainerRequest().apply { block() } + provider.authorizationInSystem(password).let { + if (it.type == Result.SUCCESS_RESULT) { + response(it.result!!) + } else { + response(ErrorModel(400, AppErrorStatusEnum.API_WRONG_PASSWORD)) + } + } + } + + override fun createSecretUC( + refreshToken: String, + cipher: Cipher?, + pinKey: SecretKey?, + block: ContainerRequest.() -> Unit + ) { + logger.order("createSecretUC pinKey!=null = ${pinKey != null}, cipher!=null = ${cipher != null}") + + val response = ContainerRequest().apply { block() } + provider.createSecret(refreshToken).let { + if (it.type == Result.SUCCESS_RESULT) { + response(it.result!!) + } else { + response(ErrorModel(400, AppErrorStatusEnum.API_WRONG_PASSWORD)) + } + } + response(true) + } + + override fun deleteSecretTypeUC( + type: AuthType, + block: ContainerRequest.() -> Unit + ) { + logger.order("deleteSecretTypeUC type=$type") + + val response = ContainerRequest().apply { block() } + response(true) + } + + override fun deleteSecretsUC(block: ContainerRequest.() -> Unit) { + logger.order("deleteSecretsUC") + + val response = ContainerRequest().apply { block() } + response(true) + } + + override fun unsubscribe() { + logger.order("unsubscribe") + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/utility/ResProviderImpl.kt b/app/src/main/java/by/esas/tools/screens/access_container/utility/ResProviderImpl.kt new file mode 100644 index 00000000..1744b314 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/utility/ResProviderImpl.kt @@ -0,0 +1,26 @@ +package by.esas.tools.screens.access_container.utility + +import android.content.Context +import by.esas.tools.App +import by.esas.tools.R +import by.esas.tools.accesscontainer.support.supporter.ResourceStrProvider + +class ResProviderImpl : ResourceStrProvider { + + override fun requireContext(): Context { + return App.appContext + } + + override fun getString(resId: Int): String { + return App.appContext.getString(resId) + } + + override fun provideAlterCancelStr(): Int = R.string.dialog_change_btn + override fun provideCancelStr(): Int { + return R.string.common_cancel + } + + override fun provideAccessStr(): Int = R.string.dialog_auth_title + override fun provideAccessConfirmStr(): Int = R.string.dialog_auth_title_confirm + override fun provideEncryptTitle(): Int = R.string.dialog_auth_encrypt_title +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/utility/SecretProvider.kt b/app/src/main/java/by/esas/tools/screens/access_container/utility/SecretProvider.kt new file mode 100644 index 00000000..89ed6e0b --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/utility/SecretProvider.kt @@ -0,0 +1,110 @@ +package by.esas.tools.screens.access_container.utility + +import by.esas.tools.accesscontainer.entity.AuthType +import by.esas.tools.accesscontainer.entity.Token + +open class SecretProvider { + + var errorOnRefreshWithToken: Boolean = true + var errorOnGetSecrets: Boolean = true + var errorOnRefresh: Boolean = true + var errorOnRefreshWithSecret: Boolean = true + var errorOnCheckSecret: Boolean = true + var errorOnCreateSecret: Boolean = true + + var authTypes: List = emptyList() + var correctPassword = "12345678" + private val secret: String = "secret" + private var currentRefresh: String = "refreshToken" + private var currentToken: String = "token" + + fun refreshToken(): SecretResult { + return if (errorOnRefreshWithToken) { + SecretResult(type = Result.ERROR_RESULT) + } else { + val random = Math.random() + currentRefresh = "refreshToken_$random" + currentToken = "token_$random" + SecretResult(type = Result.SUCCESS_RESULT, getToken()) + } + } + + fun getSecrets(): SecretResult> { + return if (errorOnGetSecrets) { + SecretResult(type = Result.ERROR_RESULT) + } else { + SecretResult(type = Result.SUCCESS_RESULT, authTypes) + } + } + + fun refreshWithSecret(): SecretResult { + return if (errorOnRefresh) { + SecretResult(type = Result.ERROR_RESULT) + } else { + val random = Math.random() + currentRefresh = "refreshToken_$random" + currentToken = "token_$random" + SecretResult(type = Result.SUCCESS_RESULT, getToken()) + } + } + + fun refreshWithSecretText(): SecretResult { + return if (errorOnRefreshWithSecret) { + SecretResult(type = Result.ERROR_RESULT) + } else { + val random = Math.random() + currentRefresh = "refreshToken_$random" + currentToken = "token_$random" + SecretResult(type = Result.SUCCESS_RESULT, getToken()) + } + } + + fun checkSecret(): SecretResult> { + return if (errorOnCheckSecret) { + SecretResult(type = Result.ERROR_RESULT) + } else { + SecretResult(type = Result.SUCCESS_RESULT, Pair(true, secret)) + } + } + + fun authorizationInSystem(password: String): SecretResult { + return if (password == correctPassword) { + val random = Math.random() + currentRefresh = "refreshToken_$random" + currentToken = "token_$random" + SecretResult(type = Result.SUCCESS_RESULT, getToken()) + } else { + SecretResult(type = Result.ERROR_RESULT) + } + } + + fun createSecret(refreshToken: String): SecretResult { + return if (errorOnCreateSecret) { + SecretResult(type = Result.ERROR_RESULT) + } else { + currentRefresh = refreshToken + SecretResult(type = Result.SUCCESS_RESULT, true) + } + } + + fun deleteSecretType(type: AuthType): SecretResult { + return SecretResult(type = Result.SUCCESS_RESULT, true) + } + + fun deleteSecrets(): SecretResult { + return SecretResult(type = Result.SUCCESS_RESULT, true) + } + + private fun getToken(): Token { + return Token(accessToken = currentToken, refreshToken = currentRefresh) + } + + fun setToken(token: Token) { + currentToken = token.accessToken + currentRefresh = token.refreshToken ?: "" + } + + fun getCurrentToken(): Token { + return Token(currentToken, currentRefresh) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/screens/access_container/utility/SecretResult.kt b/app/src/main/java/by/esas/tools/screens/access_container/utility/SecretResult.kt new file mode 100644 index 00000000..31d193fe --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/access_container/utility/SecretResult.kt @@ -0,0 +1,11 @@ +package by.esas.tools.screens.access_container.utility + +class SecretResult( + val type: Result, + val result: T? = null +) + +enum class Result { + SUCCESS_RESULT, + ERROR_RESULT +} \ No newline at end of file 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..4334cb24 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationFragment.kt @@ -0,0 +1,67 @@ +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() { + viewModel.enableControls() + } + + override fun onSuccess() { + viewModel.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..20c01fe4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationSecondFragment.kt @@ -0,0 +1,67 @@ +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() { + viewModel.enableControls() + } + + override fun onSuccess() { + viewModel.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..05141e05 --- /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..dfe797fd --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/navigation/BaseUINavigationVM.kt @@ -0,0 +1,15 @@ +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..eee18da7 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/other/BaseUIFunctionalityFragment.kt @@ -0,0 +1,198 @@ +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.logger.Action +import by.esas.tools.logger.handler.ErrorAction +import by.esas.tools.logger.handler.ShowErrorType +import by.esas.tools.util_ui.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: by.esas.tools.util_ui.SwitchManager = object : by.esas.tools.util_ui.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 onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fBaseuiFunctionalityMessageDialogFragmentBtn.setOnClickListener { + val dialog = by.esas.tools.dialog_message.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) viewModel.enableControls() + else viewModel.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 + ) + ) + } + } + + override fun switchControlsOn() { + super.switchControlsOn() + + binding.fBaseuiSwitcher.isChecked = true + } +} 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..292c1028 --- /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_message.MessageDialog +import javax.inject.Inject + +class BaseUIFunctionalityVM @Inject constructor() : AppVM() { + + fun showMessageDialog() { + val dialog = by.esas.tools.dialog_message.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..4ab04cb5 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/baseui/ui/BaseUIThemeFragment.kt @@ -0,0 +1,74 @@ +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_ui.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) { + by.esas.tools.util_ui.configs.UiModeType.SYSTEM -> binding.fBaseuiThemeModeRadioSystem.isChecked = true + by.esas.tools.util_ui.configs.UiModeType.DAY -> binding.fBaseuiThemeModeRadioLight.isChecked = true + by.esas.tools.util_ui.configs.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(): by.esas.tools.util_ui.configs.UiModeType { + val selectedItem = binding.fBaseuiThemeModeRadioGroup.checkedRadioButtonId + return when (selectedItem) { + R.id.f_baseui_theme_mode_radio_light -> by.esas.tools.util_ui.configs.UiModeType.DAY + R.id.f_baseui_theme_mode_radio_night -> by.esas.tools.util_ui.configs.UiModeType.NIGHT + else -> by.esas.tools.util_ui.configs.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..2de9ed55 --- /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..c1a73836 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionFragment.kt @@ -0,0 +1,64 @@ +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.e(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..9dc59a8d --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/biometric/BiometricDecryptionVM.kt @@ -0,0 +1,60 @@ +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.i(errString.toString()) + messageLive.postValue(errString.toString()) + } + + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + super.onAuthenticationSucceeded(result) + logger.i(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.i(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..11de3fd1 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/cardline/CardlineFragment.kt @@ -0,0 +1,41 @@ +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..07417fe0 --- /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..58ba3812 --- /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..ea414f92 --- /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..d181a5cb --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/checker/CheckerInputFieldFragment.kt @@ -0,0 +1,159 @@ +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.* + +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() { + viewModel.enableControls() + } + + override fun onSuccess() { + viewModel.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..97f03432 --- /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..f0e006ff --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchFragment.kt @@ -0,0 +1,143 @@ +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_ui.SwitchManager +import by.esas.tools.util_ui.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: by.esas.tools.util_ui.SwitchManager = object : by.esas.tools.util_ui.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()) + by.esas.tools.util_ui.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) { + if (isChecked) viewModel.enableControls() + else viewModel.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..a6e8b7f1 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/custom_switch/CustomSwitchVM.kt @@ -0,0 +1,14 @@ +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..08d2ebdd --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/custom_switch/program/CustomSwitchProgramFragment.kt @@ -0,0 +1,130 @@ +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..59962730 --- /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..40d58440 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogFragment.kt @@ -0,0 +1,222 @@ +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_message.MessageDialog +import by.esas.tools.dialog_message.MessageDialog.Companion.ITEM_CODE +import by.esas.tools.dialog_message.MessageDialog.Companion.ITEM_NAME +import by.esas.tools.dialog_message.MessageDialog.Companion.USER_ACTION_ITEM_PICKED +import by.esas.tools.dialog_message.MessageDialog.Companion.USER_ACTION_NEGATIVE_CLICKED +import by.esas.tools.dialog_message.MessageDialog.Companion.USER_ACTION_NEUTRAL_CLICKED +import by.esas.tools.dialog_message.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_core.Config.DIALOG_USER_ACTION) + if (!actionName.isNullOrBlank()) { + handleAction(Action(actionName, result)) + } else { + viewModel.enableControls() + } + } + } 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 -> { + val code: String = action.parameters?.getString(ITEM_CODE) ?: "" + val name: String = action.parameters?.getString(ITEM_NAME) ?: "" + Toast.makeText( + requireContext(), + "$code - $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(): by.esas.tools.dialog_message.MessageDialog { + val dialog = by.esas.tools.dialog_message.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(): by.esas.tools.dialog_message.MessageDialog.ButtonAppearance? { + return if (binding.fDynamicMessageDialogBtnPositiveCheck.isChecked) + by.esas.tools.dialog_message.MessageDialog.ButtonAppearance( + getTextStyle(binding.fDynamicMessageDialogBtnPositiveSpinner.selectedItem.toString()), + getColorFromRadioGroup(binding.fDynamicMessageDialogBtnPositiveColor as RadioGroup), + binding.fDynamicMessageDialogBtnPositiveCaps.isChecked + ) else + null + } + + private fun getNegativeButtonAppearance(): by.esas.tools.dialog_message.MessageDialog.ButtonAppearance? { + return if (binding.fDynamicMessageDialogBtnNegativeCheck.isChecked) + by.esas.tools.dialog_message.MessageDialog.ButtonAppearance( + getTextStyle(binding.fDynamicMessageDialogBtnNegativeSpinner.selectedItem.toString()), + getColorFromRadioGroup(binding.fDynamicMessageDialogBtnNegativeColor as RadioGroup), + binding.fDynamicMessageDialogBtnNegativeCaps.isChecked + ) else + null + } + + private fun getNeutralButtonAppearance(): by.esas.tools.dialog_message.MessageDialog.ButtonAppearance? { + return if (binding.fDynamicMessageDialogBtnNeutralCheck.isChecked) + by.esas.tools.dialog_message.MessageDialog.ButtonAppearance( + getTextStyle(binding.fDynamicMessageDialogBtnNeutralSpinner.selectedItem.toString()), + getColorFromRadioGroup(binding.fDynamicMessageDialogBtnNeutralColor as RadioGroup), + binding.fDynamicMessageDialogBtnNeutralCaps.isChecked + ) else + null + } + + private fun setDialogItems(dialog: by.esas.tools.dialog_message.MessageDialog) { + if (binding.fDynamicMessageDialogItemsListCheck.isChecked) + dialog.setItems( + listOf( + by.esas.tools.dialog_message.MessageDialog.ItemInfo("1", resources.getString(R.string.item_1)), + by.esas.tools.dialog_message.MessageDialog.ItemInfo("2", resources.getString(R.string.item_2)), + by.esas.tools.dialog_message.MessageDialog.ItemInfo("3", 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..d793dede --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/dialog/DynamicMessageDialogVM.kt @@ -0,0 +1,12 @@ +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..7312f594 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/dialog/bottom_dialog/BottomDialogFragment.kt @@ -0,0 +1,75 @@ +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_core.Config +import by.esas.tools.dialog_core.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 { + viewModel.enableControls() + } + } + } 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..93d208a6 --- /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..2680dd8d --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/domain/DomainCaseFragment.kt @@ -0,0 +1,103 @@ +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.ErrorMessageHelper +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 provideErrorStringHelper(): ErrorMessageHelper { + return object : ErrorMessageHelper { + + override fun getErrorMessage(error: ErrorModel): String { + return resources.getString(R.string.domain_case_fail) + } + } + } + + @SuppressLint("SetTextI18n") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.fDomainCasePlayBtn.setOnClickListener { + AppChecker().setListener(object : Checker.CheckListener { + override fun onFailed() { + viewModel.enableControls() + } + + override fun onSuccess() { + viewModel.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() { + viewModel.enableControls() + } + + override fun onSuccess() { + viewModel.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() { + viewModel.enableControls() + } + + override fun onSuccess() { + viewModel.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..4389774a --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/domain/DomainCaseVM.kt @@ -0,0 +1,53 @@ +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.util.decryptAES +import by.esas.tools.util.decryptRSA +import by.esas.tools.util.encryptAES +import by.esas.tools.util.generateKey +import by.esas.tools.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 = by.esas.tools.dialog_message.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.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..d8c171f4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/end_icon/InputfieldViewEndIconFragment.kt @@ -0,0 +1,111 @@ +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..a5aae22f --- /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..c7767cfb --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/input_field/InputfieldViewFragment.kt @@ -0,0 +1,98 @@ +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..4b4e73b4 --- /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..bb1c11e6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/inputfield_view/start_icon/InputfieldViewStartIconFragment.kt @@ -0,0 +1,84 @@ +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..3cd5f195 --- /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..23dad0c3 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/listheader/ListheaderFragment.kt @@ -0,0 +1,107 @@ +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_ui.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 : by.esas.tools.util_ui.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) + viewModel.enableControls() + else + viewModel.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..2fcbfa6a --- /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..6b20e7d6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/listheader/dynamic/DynamicListheaderFragment.kt @@ -0,0 +1,155 @@ +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..f4f9c113 --- /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..967bda32 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/LoggerFragment.kt @@ -0,0 +1,144 @@ +package by.esas.tools.screens.logger + +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.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 fun getTag(): String = "CaseLoggerImpl" + + override fun logLocally(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 throwable(throwable: Throwable) { + setLog(LogItem("", ILogger.CATEGORY_ERROR, throwable.message.toString())) + } + + override fun errorModel(error: ErrorModel) { + setLog(LogItem("", ILogger.CATEGORY_ERROR, error.status)) + } + } + + 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.i(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) + ) + ) + } + } + + 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..f317068a --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/LoggerVM.kt @@ -0,0 +1,85 @@ +package by.esas.tools.screens.logger + +import androidx.databinding.ObservableField +import androidx.lifecycle.MutableLiveData +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 fun getTag(): String = "CaseLoggerVMImpl" + + override fun logLocally(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 throwable(throwable: Throwable) { + setLog(LogItem("", ILogger.CATEGORY_ERROR, throwable.message.toString())) + } + + override fun errorModel(error: ErrorModel) { + setLog(LogItem("", ILogger.CATEGORY_ERROR, error.status)) + } + } + + fun logTag(checks: List) { + AppChecker().setListener(object : Checker.CheckListener { + override fun onSuccess() { + logger.i(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..d864b2b6 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemAdapter.kt @@ -0,0 +1,16 @@ +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..98667cbe --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/logger/recycler/LogItemViewHolder.kt @@ -0,0 +1,27 @@ +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..cdd62181 --- /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 + +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..df751f77 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/MenuFragment.kt @@ -0,0 +1,181 @@ +package by.esas.tools.screens.menu + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +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_ui.defocusAndHideKeyboard +import com.google.android.material.chip.Chip + +class MenuFragment : AppFragment() { + + companion object { + const val MENU_UPDATE = "MENU_UPDATE" + const val MENU_UPDATE_KEY_CLEAR = "MENU_UPDATE_KEY_CLEAR" + } + + private val caseAdapter = CaseAdapter( + onClick = { item -> + logger.i("${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 { + viewModel.enableControls() + } + } + } else + super.provideFragmentResultListener(requestKey) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + requireActivity().supportFragmentManager.setFragmentResultListener(MENU_UPDATE, viewLifecycleOwner) { _, bundle -> + val actionName = bundle.getString(MENU_UPDATE) + if (actionName == MENU_UPDATE_KEY_CLEAR) + viewModel.clearCaseStatuses() + } + + return super.onCreateView(inflater, container, savedInstanceState) + } + + 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() { + by.esas.tools.util_ui.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..78947f98 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/MenuVM.kt @@ -0,0 +1,124 @@ +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.ClearAllSavedTestStatusesUseCase +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 javax.inject.Inject + +class MenuVM @Inject constructor( + private val searchCase: SearchCaseUseCase, + private val filterCase: FilterCaseUseCase, + private val getCaseItems: GetCaseItemsUseCase, + private val clearAllCaseStatuses: ClearAllSavedTestStatusesUseCase +) : 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 clearCaseStatuses() { + clearAllCaseStatuses.execute { + onComplete { + setCases() + } + onError { + handleError(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..793b9db0 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewHolder.kt @@ -0,0 +1,27 @@ +package by.esas.tools.screens.menu.recycler + +import android.view.LayoutInflater +import android.view.ViewGroup +import by.esas.tools.BR +import by.esas.tools.databinding.ICaseBinding +import by.esas.tools.entity.CaseItemInfo +import by.esas.tools.recycler.BaseViewHolder + +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..683cb182 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/menu/recycler/CaseViewModel.kt @@ -0,0 +1,20 @@ +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..94c0f396 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/numpad/NumpadImageFragment.kt @@ -0,0 +1,105 @@ +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_ui.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: by.esas.tools.util_ui.SwitchManager = object : by.esas.tools.util_ui.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) + viewModel.enableControls() + else + viewModel.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..3b36e74f --- /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..f4d36cb5 --- /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.dpToPx +import by.esas.tools.numpad.INumPadHandler +import by.esas.tools.util_ui.SwitchManager +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: by.esas.tools.util_ui.SwitchManager = object : by.esas.tools.util_ui.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) + viewModel.enableControls() + else + viewModel.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..80408cfb --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/numpad/numpad_text/NumpadTextVM.kt @@ -0,0 +1,80 @@ +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..3285fcb4 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/pin_view/dynamic/DynamicPinViewFragment.kt @@ -0,0 +1,109 @@ +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..6dbf6703 --- /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/RecyclerEntity.kt b/app/src/main/java/by/esas/tools/screens/recycler/RecyclerEntity.kt new file mode 100644 index 00000000..b04bf5be --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/RecyclerEntity.kt @@ -0,0 +1,11 @@ +package by.esas.tools.screens.recycler + +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemModel + +class RecyclerEntity( + val name: String +) + +fun RecyclerEntity.mapToSimple(isLast: Boolean, alignment: Int): SimpleItemModel { + return SimpleItemModel(name = name, code = 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..19efaeda --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/RecyclerLists.kt @@ -0,0 +1,45 @@ +package by.esas.tools.screens.recycler + +object RecyclerLists { + + val firstList: List = listOf( + RecyclerEntity("Alpha"), + RecyclerEntity("Beta"), + RecyclerEntity("Gamma"), + RecyclerEntity("Delta"), + RecyclerEntity("Epsilon"), + RecyclerEntity("Zeta"), + RecyclerEntity("Eta"), + RecyclerEntity("Theta"), + RecyclerEntity("Iota"), + RecyclerEntity("Cappa"), + RecyclerEntity("Lambda"), + RecyclerEntity("Mi"), + RecyclerEntity("Ni"), + RecyclerEntity("Csi"), + RecyclerEntity("Omicron"), + RecyclerEntity("Pi"), + RecyclerEntity("Rho"), + RecyclerEntity("Sigma"), + RecyclerEntity("Tau"), + RecyclerEntity("Ipsilon"), + RecyclerEntity("Phi"), + RecyclerEntity("Chi"), + RecyclerEntity("Psi"), + RecyclerEntity("Omega") + ) + val secondList: List = listOf( + RecyclerEntity("One"), + RecyclerEntity("Two"), + RecyclerEntity("Three"), + RecyclerEntity("Four"), + RecyclerEntity("Five"), + RecyclerEntity("Six"), + RecyclerEntity("Seven"), + RecyclerEntity("Eight"), + RecyclerEntity("Nine"), + RecyclerEntity("Ten"), + RecyclerEntity("Eleven"), + RecyclerEntity("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..fc9226e4 --- /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.RecyclerEntity +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(RecyclerEntity(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..0b7b69d2 --- /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.RecyclerEntity + +class FirstAdapter( + onClick: (RecyclerEntity) -> Unit, + onClickPosition: (Int, RecyclerEntity) -> Unit, + onLongClick: (RecyclerEntity) -> Unit, + onLongClickPosition: (Int, RecyclerEntity) -> 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..31b2a7c5 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstItemVM.kt @@ -0,0 +1,24 @@ +package by.esas.tools.screens.recycler.base.adapter + +import androidx.databinding.ObservableField +import by.esas.tools.recycler.BaseItemViewModel +import by.esas.tools.screens.recycler.RecyclerEntity + +class FirstItemVM : BaseItemViewModel() { + + val text = ObservableField("") + val counter = ObservableField("") + private var innerCounter = 0 + + override fun bindItem(item: RecyclerEntity, 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..c99fc030 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/base/adapter/FirstViewHolder.kt @@ -0,0 +1,25 @@ +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.RecyclerEntity + +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..959abc13 --- /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, + code = it.code, + 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..9cfd47a3 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/custom/CustomRecyclerVM.kt @@ -0,0 +1,96 @@ +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.simpleItemAdapter.SimpleItemAdapter +import by.esas.tools.recycler.simpleItemAdapter.SimpleItemModel +import by.esas.tools.screens.recycler.RecyclerLists +import by.esas.tools.screens.recycler.mapToSimple +import javax.inject.Inject + +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, + code = 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..27616d50 --- /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, + code = it.code, + 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..4b775c31 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/simple/SimpleRecyclerVM.kt @@ -0,0 +1,94 @@ +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.RecyclerLists +import by.esas.tools.screens.recycler.mapToSimple +import javax.inject.Inject + +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, + code = 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..66454e19 --- /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.RecyclerEntity +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(RecyclerEntity(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..dbb755e3 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseCompareData.kt @@ -0,0 +1,19 @@ +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..0704b4a3 --- /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.RecyclerEntity + +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: RecyclerEntity?): 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..6a836f9b --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyHeader.kt @@ -0,0 +1,38 @@ +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.RecyclerEntity + +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..82da8601 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyItemVM.kt @@ -0,0 +1,30 @@ +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.RecyclerEntity + +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..34468c06 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/recycler/sticky_case/sticky_adapter/CaseStickyViewHolder.kt @@ -0,0 +1,45 @@ +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.RecyclerEntity + +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..1c7da6ca --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/saved_state_vm/SavedStateVM.kt @@ -0,0 +1,23 @@ +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..45483a90 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/timeparser/TimeParserFragment.kt @@ -0,0 +1,102 @@ +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.ErrorMessageHelper +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.* + +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 provideErrorStringHelper(): ErrorMessageHelper { + return object : ErrorMessageHelper { + + 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 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..9362a0e6 --- /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.util.* +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..7ac1aa02 --- /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.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.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..92aa97c7 --- /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..05b62afb --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/UtilKeyboardFragment.kt @@ -0,0 +1,39 @@ +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_ui.defocusAndHideKeyboard +import by.esas.tools.util_ui.focusAndShowKeyboard +import by.esas.tools.util_ui.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) + + by.esas.tools.util_ui.focusAndShowKeyboard(activity, binding.fMainUtilCase1Edit) + binding.fMainUtilCase1Button.setOnClickListener { + by.esas.tools.util_ui.defocusAndHideKeyboard(activity) + } + binding.fMainUtilCase2ButtonShow.setOnClickListener { + by.esas.tools.util_ui.showKeyboard(activity, false) + } + binding.fMainUtilCase2ButtonHide.setOnClickListener { + by.esas.tools.util_ui.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..d59d16e5 --- /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..0a18fb71 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/switchManager/UtilSwitchManagerFragment.kt @@ -0,0 +1,112 @@ +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_ui.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) + viewModel.enableControls() + else + viewModel.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(): by.esas.tools.util_ui.SwitchManager { + return object : by.esas.tools.util_ui.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..d00ad412 --- /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..c46665f9 --- /dev/null +++ b/app/src/main/java/by/esas/tools/screens/util/utils/UtilsFragment.kt @@ -0,0 +1,60 @@ +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.getCardType +import by.esas.tools.util.getOnlyNumbers +import by.esas.tools.util.luhnCheck +import by.esas.tools.util.toFormattedInput +import by.esas.tools.util.toFormattedString + +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..8d42b271 --- /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/usecase/GetDefaultCardUseCase.kt b/app/src/main/java/by/esas/tools/usecase/GetDefaultCardUseCase.kt deleted file mode 100644 index 48c42283..00000000 --- a/app/src/main/java/by/esas/tools/usecase/GetDefaultCardUseCase.kt +++ /dev/null @@ -1,17 +0,0 @@ -package by.esas.tools.usecase - -import by.esas.tools.error_mapper.AppErrorMapper -import by.esas.tools.domain.exception.BaseException -import by.esas.tools.domain.exception.BaseStatusEnum -import kotlin.coroutines.CoroutineContext - -class GetDefaultCardUseCase constructor( - errorUtil: AppErrorMapper, - foregroundContext: CoroutineContext -) : BaseUseCase(errorUtil, null) { - override val TAG: String = GetDefaultCardUseCase::class.java.simpleName - - override suspend fun executeOnBackground(): String { - throw BaseException(BaseStatusEnum.UNKNOWN_ERROR) - } -} \ 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/Utils.kt b/app/src/main/java/by/esas/tools/utils/Utils.kt new file mode 100644 index 00000000..13a29623 --- /dev/null +++ b/app/src/main/java/by/esas/tools/utils/Utils.kt @@ -0,0 +1,107 @@ +package by.esas.tools + +import android.app.Activity +import android.content.Context +import android.content.res.Resources +import android.view.View +import by.esas.tools.app_domain.error_mapper.AppErrorStatusEnum +import java.util.* + +fun getLocale(context: Context): Locale { + return Locale.getDefault() +} + +/* Standard */ +fun hideSystemUIApp(activity: Activity?) { + // Enables regular immersive mode. + // For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE. + // Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY + if (activity != null) { + activity.window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + // Set the content to appear under the system bars so that the + // content doesn't resize when the system bars hide and show. + or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + //or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + // Hide the nav bar and status bar + 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() +} + +fun getErrorMessage(status: AppErrorStatusEnum, message: String? = null): String { + val stringRes: Int = when (status) { + AppErrorStatusEnum.OK -> R.string.error_ok + AppErrorStatusEnum.NOT_SET -> R.string.error_unknown_happened + AppErrorStatusEnum.UNKNOWN_ERROR -> R.string.error_unknown_happened + AppErrorStatusEnum.CLIENT_ERROR -> R.string.error_unknown_happened + AppErrorStatusEnum.SERVER_ERROR -> R.string.error_server_error + //Authorization + AppErrorStatusEnum.UNAUTHORIZED -> R.string.error_unauthenticated + AppErrorStatusEnum.ACCESS_DENIED -> R.string.error_access_denied + AppErrorStatusEnum.INVALID_GRANT -> R.string.error_invalid_refresh + //NET + AppErrorStatusEnum.NET_NO_CONNECTION -> R.string.error_net_no_connection + AppErrorStatusEnum.NET_UNAVAILABLE -> R.string.error_net_server_unavailable + AppErrorStatusEnum.NET_TIMEOUT -> R.string.error_net_timeout + AppErrorStatusEnum.NET_SSL_HANDSHAKE -> R.string.error_net_ssl_handshake + AppErrorStatusEnum.NET_UNKNOWN_HOST -> R.string.error_net_unknown_host + //APP + AppErrorStatusEnum.APP_UNPREDICTED_ERROR -> R.string.error_app_unpredicted + AppErrorStatusEnum.APP_DATE_IN_UNEXPECTED_FORMAT -> R.string.error_date_unexpected_format + AppErrorStatusEnum.APP_NO_ACCESS_TOKEN -> R.string.error_app_no_access_token + AppErrorStatusEnum.APP_CONTEXT_PARAMETERS_EMPTY -> R.string.error_app_context_parameters_empty + AppErrorStatusEnum.APP_USER_HAS_NO_SECRETS -> R.string.error_app_user_has_no_secrets + //Decryption + AppErrorStatusEnum.BIOMETRIC_UNAVAILABLE -> R.string.error_decryption_biom + AppErrorStatusEnum.SECRET_CHECK_NOT_MATCH -> R.string.error_app_secret_check_failed + AppErrorStatusEnum.DECRYPTION_FAILED -> R.string.error_decryption + AppErrorStatusEnum.APP_REFRESH_TOKEN_DECRYPTION_FAILED -> R.string.error_decryption + AppErrorStatusEnum.APP_PIN_DECRYPTION_FAILED -> R.string.error_decryption_pin + AppErrorStatusEnum.APP_BIOMETRIC_DECRYPTION_FAILED -> R.string.error_decryption_biom + AppErrorStatusEnum.APP_PRIVATE_KEY_DECRYPTION_FAILED -> R.string.error_decryption_private_key + AppErrorStatusEnum.APP_BIOMETRIC_KEY_DECRYPTION_FAILED -> R.string.error_decryption + AppErrorStatusEnum.APP_SECRET_KEY_DECRYPTION_FAILED -> R.string.error_decryption_secret_key + //API + AppErrorStatusEnum.API_REDIRECTION -> R.string.error_unknown_happened + AppErrorStatusEnum.API_USER_DOES_NOT_EXIST -> R.string.error_api_user_not_exist_message + AppErrorStatusEnum.API_USER_ALREADY_EXISTS -> R.string.error_api_user_exists + AppErrorStatusEnum.API_USER_NOT_ACTIVATED -> R.string.error_api_user_not_active + AppErrorStatusEnum.API_PASSWORD_RECOVERY_FAILED -> R.string.error_api_password_recovery_failed + AppErrorStatusEnum.API_ACTIVATION_FAILED -> R.string.error_api_activation_failed + AppErrorStatusEnum.API_REQUEST_ERROR -> R.string.error_api_request + AppErrorStatusEnum.API_WRONG_PASSWORD -> R.string.error_api_wrong_password + AppErrorStatusEnum.API_USER_LOCKED_OUT -> R.string.error_api_user_locked_out + AppErrorStatusEnum.API_SCOPE_ERROR -> R.string.error_unknown_happened + AppErrorStatusEnum.API_ALREADY_ACTIVATED -> R.string.error_api_already_activated + AppErrorStatusEnum.API_REACTIVATE_ERROR -> R.string.error_api_reactivate_error + AppErrorStatusEnum.API_REGISTER_ERROR -> R.string.error_api_register_error + AppErrorStatusEnum.API_PHONE_TAKEN -> R.string.error_api_phone_taken + AppErrorStatusEnum.API_EMAIL_NUMBER_NOT_CONFIRMED -> R.string.error_api_email_not_confirmed + AppErrorStatusEnum.API_EMAIL_TAKEN -> R.string.error_api_email_taken + AppErrorStatusEnum.API_EMAIL_INVALID -> R.string.error_api_email_invalid + AppErrorStatusEnum.API_USER_NAME_TAKEN -> R.string.error_api_user_name_taken + AppErrorStatusEnum.API_USER_NAME_INVALID -> R.string.error_api_user_name_invalid + AppErrorStatusEnum.API_INCORRECT_PASSWORD_FORMAT -> R.string.error_api_incorrect_password_format + AppErrorStatusEnum.API_FORBIDDEN -> R.string.error_api_forbidden + AppErrorStatusEnum.API_ADD_EMAIL_ERROR -> R.string.error_api_add_email_error + AppErrorStatusEnum.API_VERIFY_EMAIL_ERROR -> R.string.error_api_verify_email_error + AppErrorStatusEnum.API_DELETE_EMAIL_ERROR -> R.string.error_api_delete_email_error + AppErrorStatusEnum.API_EMAIL_ALREADY_SET -> R.string.error_api_email_already_set + AppErrorStatusEnum.API_ADD_PHONE_ERROR -> R.string.error_api_add_phone_error + AppErrorStatusEnum.API_VERIFY_PHONE_ERROR -> R.string.error_api_verify_phone_error + AppErrorStatusEnum.API_DELETE_PHONE_ERROR -> R.string.error_api_delete_phone_error + AppErrorStatusEnum.API_PHONE_NUMBER_NOT_CONFIRMED -> R.string.error_api_phone_not_confirmed + AppErrorStatusEnum.API_PHONE_ALREADY_SET -> R.string.error_api_phone_already_set + AppErrorStatusEnum.API_CODE_INVALID_OR_EXPIRED -> R.string.error_api_wrong_verify_code + AppErrorStatusEnum.APP_ILLEGAL_PATTERN_CHARACTER -> R.string.error_date_unexpected_format + } + return if (stringRes == -1) { + message ?: App.appContext.getString(R.string.error_unknown_happened) + } else + App.appContext.getString(stringRes) +} \ 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 93% 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..b01e4da2 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 @@ -6,6 +6,7 @@ import by.esas.tools.checker.Checking import by.esas.tools.checker.IFocusableChecking open class AppChecker : Checker() { + override var getter: BaseCheck.ErrorGetter? = object : BaseCheck.ErrorGetter { override fun getErrorMsgFromException(exception: Exception?): String { return super.getErrorMsgFromException(exception) @@ -29,12 +30,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..bb10049d 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 @@ -9,6 +9,7 @@ import by.esas.tools.inputfieldview.InputFieldView import java.lang.ref.WeakReference class FieldChecking(view: InputFieldView) : Checking(), IFocusableChecking { + private val field: WeakReference = WeakReference(view) override var notEmptyRule: NotEmptyCheck? = NotEmptyCheck("Can\'t be empty") override var checkEmpty: Boolean = true 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 87% 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..b77fbed1 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 @@ -6,10 +6,14 @@ import by.esas.tools.checker.Checking import by.esas.tools.checker.checks.NotEmptyCheck class ValueChecking(val field: MutableLiveData, val error: MutableLiveData) : Checking() { + protected override var checkEmpty: Boolean = true protected override var notEmptyRule: NotEmptyCheck? = NotEmptyCheck("Can\'t be empty") - constructor(field: MutableLiveData, error: MutableLiveData, checkEmpty: Boolean) : this(field, error) { + constructor(field: MutableLiveData, error: MutableLiveData, checkEmpty: Boolean) : this( + field, + error + ) { this.checkEmpty = checkEmpty } @@ -22,14 +26,15 @@ class ValueChecking(val field: MutableLiveData, val error: MutableLiveDa notEmptyRule?.let { list.add(it) } else if (text.isEmpty()) return true - else{ + else { list.addAll(checks) list.forEach { rule -> if (!rule.check(text)) { if (setError) error.postValue(rule.getException()) return false } - }} + } + } } return true } 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..c35cfff9 --- /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(status) + } +} \ No newline at end of file diff --git a/app/src/main/java/by/esas/tools/utils/logger/LoggerImpl.kt b/app/src/main/java/by/esas/tools/utils/logger/LoggerImpl.kt new file mode 100644 index 00000000..cae0ab4e --- /dev/null +++ b/app/src/main/java/by/esas/tools/utils/logger/LoggerImpl.kt @@ -0,0 +1,7 @@ +package by.esas.tools.utils.logger + +import by.esas.tools.logger.ILogger + +object LoggerImpl : ILogger() { + +} \ 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..f0be7426 --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_custom_color.xml @@ -0,0 +1,5 @@ + + + + + \ 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..3cbf8648 --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_icon_color.xml @@ -0,0 +1,5 @@ + + + + + \ 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..ad383615 --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_stroke_color.xml @@ -0,0 +1,5 @@ + + + + + \ 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..e95651c6 --- /dev/null +++ b/app/src/main/res/color/inputfieldview_selector_text_color.xml @@ -0,0 +1,5 @@ + + + + + \ 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/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/card.png b/app/src/main/res/drawable/card.png new file mode 100644 index 00000000..5e161992 Binary files /dev/null and b/app/src/main/res/drawable/card.png differ 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..a9503fd3 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,10 @@ + + + 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..1686755f --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,11 @@ + + + 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..31b1796f --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,10 @@ + + + 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..ba06cc3e --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_downward.xml @@ -0,0 +1,10 @@ + + + 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..683f8d68 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_down.xml @@ -0,0 +1,10 @@ + + + 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..1257937c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_up.xml @@ -0,0 +1,10 @@ + + + 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..dbad8e23 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,10 @@ + + + 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..fa5681a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_upward.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_build_24.xml b/app/src/main/res/drawable/ic_baseline_build_24.xml index b1a3035d..3e8af45d 100644 --- a/app/src/main/res/drawable/ic_baseline_build_24.xml +++ b/app/src/main/res/drawable/ic_baseline_build_24.xml @@ -1,10 +1,10 @@ + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" /> 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..8aa73a6b --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,10 @@ + + + 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_check_box_filled.xml b/app/src/main/res/drawable/ic_check_box_filled.xml new file mode 100644 index 00000000..f8a66154 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_box_unfilled.xml b/app/src/main/res/drawable/ic_check_box_unfilled.xml new file mode 100644 index 00000000..30c54718 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_unfilled.xml @@ -0,0 +1,9 @@ + + + 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..35b13485 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + 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..8bc26cdf --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud.xml @@ -0,0 +1,10 @@ + + + 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..b2979c7b --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_queue.xml @@ -0,0 +1,10 @@ + + + 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..69973acf --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,10 @@ + + + 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..4137d80c --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite.xml @@ -0,0 +1,10 @@ + + + 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..7dd56cb9 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_border.xml @@ -0,0 +1,10 @@ + + + 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..92074d26 --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,10 @@ + + + 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..e3e15979 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,10 @@ + + + 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..0e39f499 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_star_filled.xml @@ -0,0 +1,10 @@ + + + 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..dab2a0df --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_star_unfilled.xml @@ -0,0 +1,10 @@ + + + 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..19bfc972 --- /dev/null +++ b/app/src/main/res/drawable/ic_remove.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 00000000..3bd9249a --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,18 @@ + + + + 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..b0a938c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,10 @@ + + + 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..e6b67ee7 --- /dev/null +++ b/app/src/main/res/drawable/ic_star.xml @@ -0,0 +1,10 @@ + + + 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..73eb3512 --- /dev/null +++ b/app/src/main/res/drawable/number_0.xml @@ -0,0 +1,12 @@ + + + + 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..2ad46d05 --- /dev/null +++ b/app/src/main/res/drawable/number_1.xml @@ -0,0 +1,9 @@ + + + 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..93af0dbd --- /dev/null +++ b/app/src/main/res/drawable/number_2.xml @@ -0,0 +1,9 @@ + + + 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..da15d1c6 --- /dev/null +++ b/app/src/main/res/drawable/number_3.xml @@ -0,0 +1,9 @@ + + + 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..54ec67c7 --- /dev/null +++ b/app/src/main/res/drawable/number_4.xml @@ -0,0 +1,9 @@ + + + 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..9e12e895 --- /dev/null +++ b/app/src/main/res/drawable/number_5.xml @@ -0,0 +1,9 @@ + + + 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..5910c47f --- /dev/null +++ b/app/src/main/res/drawable/number_6.xml @@ -0,0 +1,9 @@ + + + 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..bf875747 --- /dev/null +++ b/app/src/main/res/drawable/number_7.xml @@ -0,0 +1,9 @@ + + + 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..c297702b --- /dev/null +++ b/app/src/main/res/drawable/number_8.xml @@ -0,0 +1,9 @@ + + + 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..1130b267 --- /dev/null +++ b/app/src/main/res/drawable/number_9.xml @@ -0,0 +1,9 @@ + + + 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..9a7a55ba --- /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..31ee34f4 --- /dev/null +++ b/app/src/main/res/drawable/numpad_cancel.xml @@ -0,0 +1,12 @@ + + + + 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..01746d40 --- /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..0f4c18bf --- /dev/null +++ b/app/src/main/res/drawable/radio_background_checked.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ 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..df32a745 --- /dev/null +++ b/app/src/main/res/drawable/radio_background_unchecked.xml @@ -0,0 +1,15 @@ + + + + + + + + \ 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..5d11e722 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,242 +7,47 @@ - + type="by.esas.tools.screens.MainVM" /> - - - - - - - - - - - - - - - - - - - + android:background="@color/colorBackground" + android:descendantFocusability="beforeDescendants" + android:focusableInTouchMode="true"> - - - - - - - - - - - - - - - - - - + + - - - + android:layout_height="0dp" + app:defaultNavHost="true" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/a_main_top_bar" + app:navGraph="@navigation/n_main" /> + + \ 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..5b3b2abf --- /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..a8a21301 --- /dev/null +++ b/app/src/main/res/layout/df_custom_bottom.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + +