diff --git a/app/build.gradle b/app/build.gradle index 3acca943..bdbd18c5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { applicationId "by.alexandr7035.affinidi_id" minSdk 21 targetSdk 31 - versionCode 500 - versionName "3.0" + versionCode 600 + versionName "4.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -62,10 +62,10 @@ dependencies { implementation project(path:":domain") implementation project(path: ":data") - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.1' - implementation 'com.google.android.material:material:1.5.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.core:core-ktx:1.8.0' + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' @@ -81,21 +81,21 @@ dependencies { // Retrofit implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation 'com.squareup.retrofit2:converter-gson:2.9.0' - implementation "com.squareup.okhttp3:logging-interceptor:4.9.1" + implementation "com.squareup.okhttp3:logging-interceptor:4.9.3" // Room - def room_version = "2.4.1" + def room_version = "2.4.2" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" // Navigation - def nav_version = "2.4.1" + def nav_version = "2.5.0" implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" implementation "androidx.navigation:navigation-ui-ktx:$nav_version" // ViewBinding delegate - def viewbindingdelegate_version = "1.5.3" + def viewbindingdelegate_version = "1.5.6" implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:$viewbindingdelegate_version" // Coil @@ -109,5 +109,5 @@ dependencies { implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } implementation 'com.google.zxing:core:3.3.0' - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/Context.kt b/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/Context.kt index 0390b8f4..f3bc3b3c 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/Context.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/Context.kt @@ -6,6 +6,7 @@ import android.net.Uri import android.os.Build import android.os.VibrationEffect import android.os.Vibrator +import android.os.VibratorManager import android.provider.Settings import android.widget.Toast import by.alexandr7035.affinidi_id.presentation.common.VibrationMode @@ -23,10 +24,11 @@ fun Context.vibrate(vibrationMode: VibrationMode) { }.toLong() val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - this.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) + val vibratorManager = this.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager + vibratorManager.defaultVibrator } else { - this.getSystemService(Context.VIBRATOR_SERVICE) - } as Vibrator + this.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { vibrator.vibrate(VibrationEffect.createOneShot(vibrationTimeMills, VibrationEffect.DEFAULT_AMPLITUDE)) diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/String.kt b/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/String.kt index d5197ecd..7cb52ad9 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/String.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/core/extensions/String.kt @@ -7,6 +7,8 @@ import android.text.TextPaint import android.text.style.ClickableSpan import android.text.style.StyleSpan import android.view.View +import timber.log.Timber +import java.lang.Exception fun String.getClickableSpannable( clickableText: String, @@ -49,3 +51,23 @@ fun String.getClickableSpannable( return spannableString } + + +fun String.getPrettifiedDid(): String { + return try { + Timber.debug(this) + + val didSplit = this.split(":") + val didValue = didSplit[2] + + Timber.debug(didValue) + Timber.debug(didValue.takeLast(4)) + + val prettifiedValue = "${didSplit[0]}:${didSplit[1]}:${didValue.take(4)}...${didValue.takeLast(4)}" + prettifiedValue + } + // In case of any problem retur initial value + catch (e: Exception) { + this + } +} diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/core/livedata/SingleLiveEvent.kt b/app/src/main/java/by/alexandr7035/affinidi_id/core/livedata/SingleLiveEvent.kt index 399f1773..d880a533 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/core/livedata/SingleLiveEvent.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/core/livedata/SingleLiveEvent.kt @@ -17,11 +17,11 @@ class SingleLiveEvent : MutableLiveData() { } // Observe the internal MutableLiveData - super.observe(owner, { t -> + super.observe(owner) { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } - }) + } } @MainThread diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/di/PresentationModule.kt b/app/src/main/java/by/alexandr7035/affinidi_id/di/PresentationModule.kt index d325477f..b4a1fe04 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/di/PresentationModule.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/di/PresentationModule.kt @@ -1,10 +1,8 @@ package by.alexandr7035.affinidi_id.di import android.content.Context -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialToDetailsModelMapper -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialToDetailsModelMapperImpl -import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_metadata.CredentialMetadataToFieldsMapper -import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_metadata.CredentialMetadataToFieldsMapperImpl +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardMapper +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardMapperImpl import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_proof.CredentialProofToFieldsMapper import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_proof.CredentialProofToFieldsMapperImpl import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusMapper @@ -21,8 +19,10 @@ import by.alexandr7035.affinidi_id.presentation.common.permissions.PermissionsPr import by.alexandr7035.affinidi_id.presentation.common.permissions.PermissionsPreferencesImpl import by.alexandr7035.affinidi_id.presentation.common.resources.ResourceProvider import by.alexandr7035.affinidi_id.presentation.common.resources.ResourceProviderImpl -import by.alexandr7035.affinidi_id.presentation.credentials_list.CredentialsListMapper -import by.alexandr7035.affinidi_id.presentation.credentials_list.CredentialsListMapperImpl +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialToDetailsModelMapper +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialToDetailsModelMapperImpl +import by.alexandr7035.affinidi_id.presentation.credentials_list.model.CredentialsListMapper +import by.alexandr7035.affinidi_id.presentation.credentials_list.model.CredentialsListMapperImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -50,21 +50,25 @@ object PresentationModule { @Provides fun provideCredentialsListMapper( - resourceProvider: ResourceProvider, - credentialStatusMapper: CredentialStatusMapper, - errorTypeMapper: ErrorTypeMapper + errorTypeMapper: ErrorTypeMapper, + credentialCardMapper: CredentialCardMapper ): CredentialsListMapper { - return CredentialsListMapperImpl(resourceProvider, credentialStatusMapper, errorTypeMapper) + return CredentialsListMapperImpl(credentialCardMapper, errorTypeMapper) + } + + @Provides + fun provideCredentialCardMapper(resourceProvider: ResourceProvider, credentialStatusMapper: CredentialStatusMapper): CredentialCardMapper { + return CredentialCardMapperImpl(resourceProvider, credentialStatusMapper) } @Provides fun provideCredentialToDetailsModelMapper( - statusMapper: CredentialStatusMapper, + credentialCardMapper: CredentialCardMapper, credentialSubjectMapper: CredentialSubjectToFieldsMapper, - metadataMapper: CredentialMetadataToFieldsMapper, - proofMapper: CredentialProofToFieldsMapper - ): CredentialToDetailsModelMapper { - return CredentialToDetailsModelMapperImpl(statusMapper, credentialSubjectMapper, metadataMapper, proofMapper) + proofMapper: CredentialProofToFieldsMapper, + statusMapper: CredentialStatusMapper, + ): CredentialToDetailsModelMapper { + return CredentialToDetailsModelMapperImpl(credentialCardMapper, statusMapper, credentialSubjectMapper, proofMapper) } @@ -78,11 +82,6 @@ object PresentationModule { return CredentialSubjectToFieldsMapperImpl() } - @Provides - fun provideCredentialMetadataToFieldsMapper(resourceProvider: ResourceProvider): CredentialMetadataToFieldsMapper { - return CredentialMetadataToFieldsMapperImpl(resourceProvider) - } - @Provides fun provideCredentialProofToFieldsMapper(resourceProvider: ResourceProvider): CredentialProofToFieldsMapper { return CredentialProofToFieldsMapperImpl(resourceProvider) diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/MainActivity.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/MainActivity.kt index e2334a99..7450cbee 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/MainActivity.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/MainActivity.kt @@ -62,7 +62,7 @@ class MainActivity : AppCompatActivity() { } // navigateSafe method allow us not check current destination - viewModel.getAuthCheckLiveData().observe(this, { authCheckResult -> + viewModel.getAuthCheckLiveData().observe(this) { authCheckResult -> binding.progressView.root.isVisible = false when (authCheckResult) { @@ -108,7 +108,7 @@ class MainActivity : AppCompatActivity() { } } } - }) + } } diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialDetailsUiModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialDetailsUiModel.kt deleted file mode 100644 index 1f352f2a..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialDetailsUiModel.kt +++ /dev/null @@ -1,20 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.common.credentials - -import by.alexandr7035.affinidi_id.domain.core.ErrorType -import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusUi - -sealed class CredentialDetailsUiModel { - data class Success( - val credentialId: String, - val credentialType: String, - val rawVcDataPrettyFormatted: String, - val credentialSubjectItems: List, - val metadataItems: List, - val proofItems: List, - val credentialStatus: CredentialStatusUi - ): CredentialDetailsUiModel() - - data class Fail(val errorType: ErrorType): CredentialDetailsUiModel() - - object Loading: CredentialDetailsUiModel() -} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialToDetailsModelMapper.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardMapper.kt similarity index 57% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialToDetailsModelMapper.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardMapper.kt index 34032d8f..4b5f02b0 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialToDetailsModelMapper.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardMapper.kt @@ -1,7 +1,7 @@ -package by.alexandr7035.affinidi_id.presentation.common.credentials +package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.Credential -interface CredentialToDetailsModelMapper { - fun map(credential: Credential): CredentialDetailsUiModel.Success +interface CredentialCardMapper { + fun map(credential: Credential): CredentialCardUi } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardMapperImpl.kt new file mode 100644 index 00000000..d6049b9a --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardMapperImpl.kt @@ -0,0 +1,69 @@ +package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card + +import by.alexandr7035.affinidi_id.R +import by.alexandr7035.affinidi_id.core.extensions.getStringDateFromLong +import by.alexandr7035.affinidi_id.core.extensions.getPrettifiedDid +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.Credential +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialStatus +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusMapper +import by.alexandr7035.affinidi_id.presentation.common.resources.ResourceProvider +import javax.inject.Inject + +class CredentialCardMapperImpl @Inject constructor( + private val resourceProvider: ResourceProvider, + private val credentialStatusMapper: CredentialStatusMapper +) : CredentialCardMapper { + override fun map(credential: Credential): CredentialCardUi { + + val credentialExpirationText = if (credential.expirationDate != null) { + + when (credential.credentialStatus) { + CredentialStatus.ACTIVE -> { + resourceProvider.getString( + R.string.credential_active_until_template, + credential.expirationDate!!.getStringDateFromLong(CARD_DATE_FORMAT) + ) + } + + CredentialStatus.EXPIRED -> { + resourceProvider.getString( + R.string.credential_expired_at_template, + credential.expirationDate!!.getStringDateFromLong(CARD_DATE_FORMAT) + ) + } + } + + } else { + resourceProvider.getString( + R.string.no_expiration + ) + } + + val issuanceDate = resourceProvider.getString( + R.string.credential_issued_on_template, + credential.issuanceDate.getStringDateFromLong(CARD_DATE_FORMAT) + ) + + val formattedIssuerDid = credential.issuerDid.split(";").first() + val prettifiedIssuerDid = formattedIssuerDid.getPrettifiedDid() + + val formattedHolderDid = credential.holderDid.split(";").first() + val prettifiedHolderDid = formattedHolderDid.getPrettifiedDid() + + val credentialStatusUi = credentialStatusMapper.map(credential.credentialStatus) + + return CredentialCardUi( + id = credential.id, + issuerDid = prettifiedHolderDid, + holderDid = prettifiedHolderDid, + issuanceDateText = issuanceDate, + credentialStatusUi = credentialStatusUi, + credentialExpirationText = credentialExpirationText, + credentialTypeText = credential.vcType, + ) + } + + companion object { + private const val CARD_DATE_FORMAT = "dd/MMM/YYYY" + } +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardUi.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardUi.kt new file mode 100644 index 00000000..e7a9e146 --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_card/CredentialCardUi.kt @@ -0,0 +1,13 @@ +package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card + +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusUi + +data class CredentialCardUi( + val id: String, + val holderDid: String, + val issuerDid: String, + val issuanceDateText: String, + val credentialStatusUi: CredentialStatusUi, + val credentialTypeText: String, + val credentialExpirationText: String, +) \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_metadata/CredentialMetadataToFieldsMapper.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_metadata/CredentialMetadataToFieldsMapper.kt deleted file mode 100644 index a752bf57..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_metadata/CredentialMetadataToFieldsMapper.kt +++ /dev/null @@ -1,8 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_metadata - -import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.Credential -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDataItem - -interface CredentialMetadataToFieldsMapper { - fun map(credential: Credential): List -} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_metadata/CredentialMetadataToFieldsMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_metadata/CredentialMetadataToFieldsMapperImpl.kt deleted file mode 100644 index d90c85c2..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_metadata/CredentialMetadataToFieldsMapperImpl.kt +++ /dev/null @@ -1,48 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_metadata - -import by.alexandr7035.affinidi_id.R -import by.alexandr7035.affinidi_id.domain.core.extensions.getStringDateFromLong -import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.Credential -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDataItem -import by.alexandr7035.affinidi_id.presentation.common.resources.ResourceProvider -import javax.inject.Inject - -class CredentialMetadataToFieldsMapperImpl @Inject constructor(private val resourceProvider: ResourceProvider): - CredentialMetadataToFieldsMapper { - override fun map(credential: Credential): List { - - // Cut DID after ";" (delete initial state, etc.) - val formattedIssuerDid = credential.issuerDid.split(";").first() - - return listOf( - CredentialDataItem.Field( - name = resourceProvider.getString(R.string.credential_id), - value = credential.id - ), - CredentialDataItem.Field( - name = resourceProvider.getString(R.string.issuer), - value = formattedIssuerDid - ), - - CredentialDataItem.Field( - name = resourceProvider.getString(R.string.holder), - value = credential.holderDid - ), - - CredentialDataItem.Field( - name = resourceProvider.getString(R.string.issuance_date), - value = credential.issuanceDate.getStringDateFromLong(DATE_FORMAT) - ), - - CredentialDataItem.Field( - name = resourceProvider.getString(R.string.expiration_date), - value = credential.expirationDate?.getStringDateFromLong(DATE_FORMAT) - ?: resourceProvider.getString(R.string.no_expiration) - ) - ) - } - - companion object { - private const val DATE_FORMAT = "dd MMM yyyy HH:mm:ss" - } -} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapper.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapper.kt index c484ea79..38b11b84 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapper.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapper.kt @@ -1,8 +1,8 @@ package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_proof import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialProof -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDataItem +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialFieldUi interface CredentialProofToFieldsMapper { - fun map(credentialProof: CredentialProof): List + fun map(credentialProof: CredentialProof): List } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapperImpl.kt index 2dd32865..0b3cac53 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapperImpl.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_proof/CredentialProofToFieldsMapperImpl.kt @@ -3,38 +3,38 @@ package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_p import by.alexandr7035.affinidi_id.R import by.alexandr7035.affinidi_id.core.extensions.getStringDateFromLong import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialProof -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDataItem import by.alexandr7035.affinidi_id.presentation.common.resources.ResourceProvider +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialFieldUi import javax.inject.Inject class CredentialProofToFieldsMapperImpl @Inject constructor(private val resourceProvider: ResourceProvider) : CredentialProofToFieldsMapper { - override fun map(credentialProof: CredentialProof): List { + override fun map(credentialProof: CredentialProof): List { val formattedCreationDate = credentialProof.creationDate.getStringDateFromLong(PROOF_DATE_FORMAT) return listOf( - CredentialDataItem.Field( + CredentialFieldUi.Field( name = resourceProvider.getString(R.string.created), value = formattedCreationDate, ), - CredentialDataItem.Field( + CredentialFieldUi.Field( name = resourceProvider.getString(R.string.type), value = credentialProof.type, ), - CredentialDataItem.Field( + CredentialFieldUi.Field( name = resourceProvider.getString(R.string.proof_purpose), value = credentialProof.proofPurpose, ), - CredentialDataItem.Field( + CredentialFieldUi.Field( name = resourceProvider.getString(R.string.verification_method), value = credentialProof.verificationMethod, ), - CredentialDataItem.Field( + CredentialFieldUi.Field( name = resourceProvider.getString(R.string.jws), value = credentialProof.jws, ) diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusMapperImpl.kt index c0c2b49e..1b1f16d9 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusMapperImpl.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusMapperImpl.kt @@ -25,6 +25,10 @@ class CredentialStatusMapperImpl @Inject constructor(private val resourceProvide } } - return CredentialStatusUi(status = stringStatus, statusColor = statusColor) + return CredentialStatusUi( + statusText = stringStatus, + statusColor = statusColor, + domainStatus = credentialStatus + ) } } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusUi.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusUi.kt index 736ae03d..3b4dcefb 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusUi.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_status/CredentialStatusUi.kt @@ -1,6 +1,9 @@ package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialStatus + data class CredentialStatusUi( - val status: String, + val domainStatus: CredentialStatus, + val statusText: String, val statusColor: Int ) \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapper.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapper.kt index d1a63222..4674c885 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapper.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapper.kt @@ -1,8 +1,8 @@ package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_subject -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDataItem +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialFieldUi import com.google.gson.JsonObject interface CredentialSubjectToFieldsMapper { - fun map(jsonObject: JsonObject, offsetLevel: Int = 0): List + fun map(jsonObject: JsonObject, offsetLevel: Int = 0): List } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapperImpl.kt index c5e4e228..2b574ac3 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapperImpl.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/credential_subject/CredentialSubjectToFieldsMapperImpl.kt @@ -1,13 +1,13 @@ package by.alexandr7035.affinidi_id.presentation.common.credentials.credential_subject -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDataItem +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialFieldUi import com.google.gson.JsonObject // See also unit tests which also may explain clearly how VC data is converted to ui models class CredentialSubjectToFieldsMapperImpl: CredentialSubjectToFieldsMapper { - override fun map(jsonObject: JsonObject, offsetLevel: Int): List { - val fields = ArrayList() + override fun map(jsonObject: JsonObject, offsetLevel: Int): List { + val fields = ArrayList() val keys = jsonObject.keySet() @@ -17,12 +17,12 @@ class CredentialSubjectToFieldsMapperImpl: CredentialSubjectToFieldsMapper { // Primitive (string, int, boolean, etc.) when { value.isJsonPrimitive -> { - fields.add(CredentialDataItem.Field(name = key, value = value.asString, offsetLevel = offsetLevel)) + fields.add(CredentialFieldUi.Field(name = key, value = value.asString, offsetLevel = offsetLevel)) } // Internal json object value.isJsonObject -> { // Add field name - fields.add(CredentialDataItem.TitleOnly(name = key, offsetLevel = offsetLevel)) + fields.add(CredentialFieldUi.TitleOnly(name = key, offsetLevel = offsetLevel)) // Increase offset level (to add margin) val increasedOffset = offsetLevel + 1 @@ -34,11 +34,11 @@ class CredentialSubjectToFieldsMapperImpl: CredentialSubjectToFieldsMapper { value.isJsonArray -> { // JsonArray.asString raises exception when contains more than 1 element // So use toString() - fields.add(CredentialDataItem.Field(name = key, value = value.toString())) + fields.add(CredentialFieldUi.Field(name = key, value = value.toString())) } // Null values and other unexpected cases. Just stringify value.isJsonNull -> { - fields.add(CredentialDataItem.Field(name = key, value = value.toString())) + fields.add(CredentialFieldUi.Field(name = key, value = value.toString())) } } } diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProvider.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProvider.kt index e39df2aa..90ccc907 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProvider.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProvider.kt @@ -6,5 +6,7 @@ import androidx.annotation.StringRes interface ResourceProvider { fun getString(@StringRes id: Int): String + fun getString(@StringRes id: Int, vararg args: Any): String + fun getColor(@ColorRes id: Int): Int } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProviderImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProviderImpl.kt index 3be7ac11..c7938b02 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProviderImpl.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/resources/ResourceProviderImpl.kt @@ -8,6 +8,10 @@ class ResourceProviderImpl(private val context: Context): ResourceProvider { return context.getString(id) } + override fun getString(id: Int, vararg args: Any): String { + return context.getString(id, *args) + } + override fun getColor(id: Int): Int { return ContextCompat.getColor(context, id) } diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDataAdapter.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialFieldsAdapter.kt similarity index 70% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDataAdapter.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialFieldsAdapter.kt index 7f7b3da0..630b48ba 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDataAdapter.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialFieldsAdapter.kt @@ -5,27 +5,29 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding +import by.alexandr7035.affinidi_id.core.extensions.copyToClipboard +import by.alexandr7035.affinidi_id.core.extensions.showToast import by.alexandr7035.affinidi_id.databinding.ViewVcDetailFieldBinding import by.alexandr7035.affinidi_id.databinding.ViewVcDetailTitleOnlyBinding -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDataItem +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialFieldUi -class CredentialDataAdapter : RecyclerView.Adapter() { +class CredentialFieldsAdapter : RecyclerView.Adapter() { - private var items: List = emptyList() + private var items: List = emptyList() @SuppressLint("NotifyDataSetChanged") - fun setItems(items: List) { + fun setItems(items: List) { this.items = items notifyDataSetChanged() } override fun getItemViewType(position: Int): Int { return when (items[position]) { - is CredentialDataItem.Field -> { + is CredentialFieldUi.Field -> { FIELD_ITEM_TYPE } - is CredentialDataItem.TitleOnly -> { + is CredentialFieldUi.TitleOnly -> { TITLE_ITEM_TYPE } } @@ -64,20 +66,27 @@ class CredentialDataAdapter : RecyclerView.Adapter { + CredentialClaimsFragment() + } + + 1 -> { + CredentialProofFragment() + } + + else -> throw IllegalStateException("Too many pages, some not implemented. Check viewpager & adapter") + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDetailsViewModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/LoadCredentialDetailsViewModel.kt similarity index 52% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDetailsViewModel.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/LoadCredentialDetailsViewModel.kt index e676ea1d..2ef9f1bc 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDetailsViewModel.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/LoadCredentialDetailsViewModel.kt @@ -4,35 +4,29 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import by.alexandr7035.affinidi_id.core.livedata.SingleLiveEvent +import by.alexandr7035.affinidi_id.domain.model.credentials.get_from_qr_code.ObtainVcFromQrCodeReqModel +import by.alexandr7035.affinidi_id.domain.model.credentials.get_from_qr_code.ObtainVcFromQrCodeResModel import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.GetCredentialByIdReqModel import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.GetCredentialByIdResModel -import by.alexandr7035.affinidi_id.domain.model.credentials.verify_vc.VerifyVcReqModel import by.alexandr7035.affinidi_id.domain.usecase.credentials.GetCredentialByIdUseCase -import by.alexandr7035.affinidi_id.domain.usecase.credentials.VerifyCredentialUseCase -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDetailsUiModel -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialToDetailsModelMapper -import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationModelUi -import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationResultToUiMapper +import by.alexandr7035.affinidi_id.domain.usecase.credentials.ObtainCredentialWithQrCodeUseCase +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialDetailsUi +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialToDetailsModelMapper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @HiltViewModel -class CredentialDetailsViewModel @Inject constructor( +class LoadCredentialDetailsViewModel @Inject constructor( private val getCredentialByIdUseCase: GetCredentialByIdUseCase, - private val verifyCredentialUseCase: VerifyCredentialUseCase, - private val verificationResultToUiMapper: VerificationResultToUiMapper, + private val obtainCredentialWithQrCodeUseCase: ObtainCredentialWithQrCodeUseCase, private val credentialToDetailsModelMapper: CredentialToDetailsModelMapper, ) : ViewModel() { + private val credentialLiveData = MutableLiveData() - private val credentialLiveData = MutableLiveData() - private val verificationLiveData = SingleLiveEvent() - - fun loadCredential(credentialId: String) { + fun loadCredentialById(credentialId: String) { viewModelScope.launch(Dispatchers.IO) { // Get credential getCredentialByIdUseCase.execute( @@ -44,11 +38,11 @@ class CredentialDetailsViewModel @Inject constructor( credentialToDetailsModelMapper.map(credential = res.credential) } is GetCredentialByIdResModel.Loading -> { - CredentialDetailsUiModel.Loading + CredentialDetailsUi.Loading } is GetCredentialByIdResModel.Fail -> { - CredentialDetailsUiModel.Fail(res.errorType) + CredentialDetailsUi.Fail(res.errorType) } } @@ -59,22 +53,25 @@ class CredentialDetailsViewModel @Inject constructor( } } - fun verifyCredential(rawVc: String) { + fun obtainCredential(credentialLink: String) { viewModelScope.launch(Dispatchers.IO) { - val res = verifyCredentialUseCase.execute( - VerifyVcReqModel( - rawVc = rawVc - ) - ) + val res = obtainCredentialWithQrCodeUseCase.execute(ObtainVcFromQrCodeReqModel(credentialLink)) + + val uiModel = when (res) { + is ObtainVcFromQrCodeResModel.Success -> { + credentialToDetailsModelMapper.map(credential = res.credential) + } + + is ObtainVcFromQrCodeResModel.Fail -> { + CredentialDetailsUi.Fail(res.errorType) + } + } withContext(Dispatchers.Main) { - // Map domain result to ui model for verification snackbar - verificationLiveData.value = verificationResultToUiMapper.map(res) + credentialLiveData.value = uiModel } } } - fun getCredentialLiveData(): LiveData = credentialLiveData - - fun getVerificationLiveData(): LiveData = verificationLiveData + fun getCredentialLiveData(): LiveData = credentialLiveData } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/claims/CredentialClaimsFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/claims/CredentialClaimsFragment.kt new file mode 100644 index 00000000..9edd8e6b --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/claims/CredentialClaimsFragment.kt @@ -0,0 +1,70 @@ +package by.alexandr7035.affinidi_id.presentation.credential_details.claims + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import by.alexandr7035.affinidi_id.R +import by.alexandr7035.affinidi_id.databinding.FragmentCredentialClaimsBinding +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialDetailsUi +import by.alexandr7035.affinidi_id.presentation.credential_details.LoadCredentialDetailsViewModel +import by.alexandr7035.affinidi_id.presentation.credential_details.CredentialFieldsAdapter +import by.kirich1409.viewbindingdelegate.viewBinding + + +class CredentialClaimsFragment : Fragment() { + +// private val viewModel by lazy { +// ViewModelProvider(requireActivity()).get(CredentialDetailsViewModel::class.java) +// } + + private val binding by viewBinding(FragmentCredentialClaimsBinding::bind) + private val sharedViewModel by viewModels( + ownerProducer = { requireParentFragment() } + ) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_credential_claims, container, false) + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val claimsAdapter = CredentialFieldsAdapter() + binding.claimsRecycler.adapter = claimsAdapter + binding.claimsRecycler.layoutManager = LinearLayoutManager(requireContext()) + + sharedViewModel.getCredentialLiveData().observe(viewLifecycleOwner) { credentialDetails -> + + when (credentialDetails) { + is CredentialDetailsUi.Success -> { + binding.credentialCard.credentialId.text = credentialDetails.credentialCardUi.id + binding.credentialCard.credentialTypeView.text = credentialDetails.credentialCardUi.credentialTypeText + binding.credentialCard.issuanceDate.text = credentialDetails.credentialCardUi.issuanceDateText + + claimsAdapter.setItems(credentialDetails.credentialSubjectItems) + + binding.credentialCard.issuer.text = credentialDetails.credentialCardUi.issuerDid + binding.credentialCard.statusMark.setColorFilter(credentialDetails.credentialStatus.statusColor) + binding.credentialCard.statusLabel.text = credentialDetails.credentialStatus.statusText + binding.credentialCard.statusValue.text = credentialDetails.credentialCardUi.credentialExpirationText + } + + is CredentialDetailsUi.Loading -> { + // Handled in parent fragment + } + + is CredentialDetailsUi.Fail -> { + // Handled in parent fragment + } + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialDetailsUi.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialDetailsUi.kt new file mode 100644 index 00000000..2ecef8e6 --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialDetailsUi.kt @@ -0,0 +1,19 @@ +package by.alexandr7035.affinidi_id.presentation.credential_details.model + +import by.alexandr7035.affinidi_id.domain.core.ErrorType +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardUi +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusUi + +sealed class CredentialDetailsUi { + data class Success( + val credentialCardUi: CredentialCardUi, + val credentialSubjectItems: List, + val proofItems: List, + val credentialStatus: CredentialStatusUi, + val rawVcDataPrettyFormatted: String + ) : CredentialDetailsUi() + + data class Fail(val errorType: ErrorType) : CredentialDetailsUi() + + object Loading : CredentialDetailsUi() +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialDataItem.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialFieldUi.kt similarity index 69% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialDataItem.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialFieldUi.kt index 97d4a9d3..6bca695c 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialDataItem.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialFieldUi.kt @@ -1,7 +1,6 @@ -package by.alexandr7035.affinidi_id.presentation.common.credentials - -sealed class CredentialDataItem { +package by.alexandr7035.affinidi_id.presentation.credential_details.model +sealed class CredentialFieldUi { // Determines left margin in recyclerview item // Use it for inner fields (e.g. for internal jsonObject in json) open val offsetLevel: Int = 0 @@ -10,11 +9,11 @@ sealed class CredentialDataItem { val name: String, val value: String, override val offsetLevel: Int = 0 - ): CredentialDataItem() + ): CredentialFieldUi() data class TitleOnly( val name: String, override val offsetLevel: Int = 0 - ): CredentialDataItem() + ): CredentialFieldUi() } diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialToDetailsModelMapper.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialToDetailsModelMapper.kt new file mode 100644 index 00000000..229e433f --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialToDetailsModelMapper.kt @@ -0,0 +1,7 @@ +package by.alexandr7035.affinidi_id.presentation.credential_details.model + +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.Credential + +interface CredentialToDetailsModelMapper { + fun map(credential: Credential): CredentialDetailsUi.Success +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialToDetailsModelMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialToDetailsModelMapperImpl.kt similarity index 75% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialToDetailsModelMapperImpl.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialToDetailsModelMapperImpl.kt index a1c620a6..35a64c01 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/common/credentials/CredentialToDetailsModelMapperImpl.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/model/CredentialToDetailsModelMapperImpl.kt @@ -1,44 +1,41 @@ -package by.alexandr7035.affinidi_id.presentation.common.credentials +package by.alexandr7035.affinidi_id.presentation.credential_details.model import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.Credential -import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_metadata.CredentialMetadataToFieldsMapper +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardMapper import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_proof.CredentialProofToFieldsMapper -import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_subject.CredentialSubjectToFieldsMapper import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusMapper +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_subject.CredentialSubjectToFieldsMapper import com.google.gson.GsonBuilder import com.google.gson.JsonObject import javax.inject.Inject class CredentialToDetailsModelMapperImpl @Inject constructor( + private val credentialCardMapper: CredentialCardMapper, private val credentialStatusMapper: CredentialStatusMapper, private val credentialSubjectToFieldsMapper: CredentialSubjectToFieldsMapper, - private val credentialMetadataToFieldsMapper: CredentialMetadataToFieldsMapper, private val credentialProofToFieldsMapper: CredentialProofToFieldsMapper ): CredentialToDetailsModelMapper { - override fun map(credential: Credential): CredentialDetailsUiModel.Success { + override fun map(credential: Credential): CredentialDetailsUi.Success { + // Credential card with primary fields + val credentialCardData = credentialCardMapper.map(credential) val credentialStatusUi = credentialStatusMapper.map(credential.credentialStatus) -// val credentialType = credentialTypeMapper.map(credential.vcType) - - val metadataItems = credentialMetadataToFieldsMapper.map(credential) - val credentialProofItems = credentialProofToFieldsMapper.map(credential.credentialProof) - // FIXME domain abstraction + // CredentialSubject (VC fields) + // TODO handle errors inside val gson = GsonBuilder().setPrettyPrinting().create() - val jsonObject = gson.fromJson(credential.credentialSubjectData, JsonObject::class.java) - // TODO handle errors inside val credentialSubjectItems = credentialSubjectToFieldsMapper.map(jsonObject) val json = gson.fromJson(credential.rawVCData, JsonObject::class.java) val prettyFormattedVC = gson.toJson(json, JsonObject::class.java) + // Credential proof + val credentialProofItems = credentialProofToFieldsMapper.map(credential.credentialProof) - return CredentialDetailsUiModel.Success( - metadataItems = metadataItems, + return CredentialDetailsUi.Success( + credentialCardUi = credentialCardData, credentialSubjectItems = credentialSubjectItems, - credentialType = credential.vcType, - credentialId = credential.id, rawVcDataPrettyFormatted = prettyFormattedVC, proofItems = credentialProofItems, credentialStatus = credentialStatusUi diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/proof/CredentialProofFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/proof/CredentialProofFragment.kt new file mode 100644 index 00000000..98db1533 --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/proof/CredentialProofFragment.kt @@ -0,0 +1,54 @@ +package by.alexandr7035.affinidi_id.presentation.credential_details.proof + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import by.alexandr7035.affinidi_id.R +import by.alexandr7035.affinidi_id.databinding.FragmentCredentialProofBinding +import by.alexandr7035.affinidi_id.presentation.credential_details.LoadCredentialDetailsViewModel +import by.alexandr7035.affinidi_id.presentation.credential_details.CredentialFieldsAdapter +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialDetailsUi +import by.kirich1409.viewbindingdelegate.viewBinding + + +class CredentialProofFragment : Fragment() { + + private val sharedViewModel by viewModels( + ownerProducer = { requireParentFragment() } + ) + private val binding by viewBinding(FragmentCredentialProofBinding::bind) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_credential_proof, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val proofAdapter = CredentialFieldsAdapter() + binding.proofRecycler.adapter = proofAdapter + binding.proofRecycler.layoutManager = LinearLayoutManager(requireContext()) + + sharedViewModel.getCredentialLiveData().observe(viewLifecycleOwner) { credentialDetails -> + when (credentialDetails) { + is CredentialDetailsUi.Success -> { + proofAdapter.setItems(credentialDetails.proofItems) + } + + is CredentialDetailsUi.Loading -> { + // Handled in parent fragment + } + + is CredentialDetailsUi.Fail -> { + // Handled in parent fragment + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScannedCredentialFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/scanned_credential/ScannedCredentialFragment.kt similarity index 60% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScannedCredentialFragment.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/scanned_credential/ScannedCredentialFragment.kt index 7342c3fb..a7fe44ac 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScannedCredentialFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/scanned_credential/ScannedCredentialFragment.kt @@ -1,4 +1,4 @@ -package by.alexandr7035.affinidi_id.presentation.verify_credential_qr +package by.alexandr7035.affinidi_id.presentation.credential_details.scanned_credential import android.os.Bundle import android.view.LayoutInflater @@ -9,26 +9,28 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.LinearLayoutManager import by.alexandr7035.affinidi_id.R import by.alexandr7035.affinidi_id.core.extensions.showErrorDialog import by.alexandr7035.affinidi_id.core.extensions.showSnackBar import by.alexandr7035.affinidi_id.core.extensions.vibrate import by.alexandr7035.affinidi_id.databinding.FragmentScannedCredentialBinding import by.alexandr7035.affinidi_id.domain.core.ErrorType -import by.alexandr7035.affinidi_id.presentation.common.SnackBarMode import by.alexandr7035.affinidi_id.presentation.common.VibrationMode -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDetailsUiModel import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationModelUi -import by.alexandr7035.affinidi_id.presentation.credential_details.CredentialDataAdapter +import by.alexandr7035.affinidi_id.presentation.credential_details.LoadCredentialDetailsViewModel +import by.alexandr7035.affinidi_id.presentation.credential_details.CredentialViewPagerAdapter +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialDetailsUi +import by.alexandr7035.affinidi_id.presentation.verify_credential.VerificationViewModel import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.Snackbar +import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class ScannedCredentialFragment : Fragment() { - private val viewModel by viewModels() + private val detailsViewModel by viewModels() + private val verifyViewModel by viewModels() private val safeArgs by navArgs() private val binding by viewBinding(FragmentScannedCredentialBinding::bind) @@ -44,52 +46,57 @@ class ScannedCredentialFragment : Fragment() { findNavController().navigateUp() } - val credentialSubjectAdapter = CredentialDataAdapter() - binding.credentialSubjectRecycler.adapter = credentialSubjectAdapter - binding.credentialSubjectRecycler.layoutManager = LinearLayoutManager(requireContext()) - val metadataAdapter = CredentialDataAdapter() - binding.metadataRecycler.adapter = metadataAdapter - binding.metadataRecycler.layoutManager = LinearLayoutManager(requireContext()) - - val proofAdapter = CredentialDataAdapter() - binding.proofRecycler.adapter = proofAdapter - binding.proofRecycler.layoutManager = LinearLayoutManager(requireContext()) - - viewModel.getCredentialLiveData().observe(viewLifecycleOwner, { credentialData -> + detailsViewModel.getCredentialLiveData().observe(viewLifecycleOwner) { credentialData -> binding.progressView.root.isVisible = false when (credentialData) { - is CredentialDetailsUiModel.Success -> { - binding.dataContainer.isVisible = true + is CredentialDetailsUi.Success -> { + + val pagerAdapter = CredentialViewPagerAdapter( + parentFragment = this, + tabsCount = 2 + ) - // Set fields to cards - credentialSubjectAdapter.setItems(credentialData.credentialSubjectItems) - metadataAdapter.setItems(credentialData.metadataItems) - proofAdapter.setItems(credentialData.proofItems) + binding.viewPager.adapter = pagerAdapter - binding.credentialType.text = credentialData.credentialType - binding.statusMark.setColorFilter(credentialData.credentialStatus.statusColor) - binding.statusLabel.text = credentialData.credentialStatus.status + val tabTitles = listOf( + getString(R.string.claims), + getString(R.string.proof) + ) + + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = tabTitles[position] + }.attach() binding.verifyBtn.setOnClickListener { binding.progressView.root.isVisible = true - viewModel.verifyCredential(credentialData.rawVcDataPrettyFormatted) + verifyViewModel.verifyCredential(credentialData.rawVcDataPrettyFormatted) } + + binding.holderLabel.text = getString( + R.string.holder_did_template, + credentialData.credentialCardUi.holderDid + ) } - is CredentialDetailsUiModel.Fail -> { - binding.root.showSnackBar( - getString(R.string.credential_scan_error), - SnackBarMode.Negative, - Snackbar.LENGTH_LONG + is CredentialDetailsUi.Loading -> { + binding.progressView.root.isVisible = true + } + + is CredentialDetailsUi.Fail -> { + // Show unknown error always + // Connection error is unlikely to be thrown as credential is already cached + showErrorDialog( + getString(R.string.error_unknown_title), + getString(R.string.error_unknown) ) - findNavController().navigateUp() } } - }) + } - viewModel.getVerificationLiveData().observe(viewLifecycleOwner, { verificationResult -> + + verifyViewModel.getVerificationLiveData().observe(viewLifecycleOwner) { verificationResult -> binding.progressView.root.isVisible = false when (verificationResult) { @@ -121,10 +128,11 @@ class ScannedCredentialFragment : Fragment() { } } } - }) + } // Load credential from QR code binding.progressView.root.isVisible = true - viewModel.obtainCredential(safeArgs.qrLink) + detailsViewModel.obtainCredential(safeArgs.qrLink) } + } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDetailsFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/stored_credential/CredentialDetailsFragment.kt similarity index 72% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDetailsFragment.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/stored_credential/CredentialDetailsFragment.kt index 9d05b974..58856037 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/CredentialDetailsFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credential_details/stored_credential/CredentialDetailsFragment.kt @@ -1,4 +1,4 @@ -package by.alexandr7035.affinidi_id.presentation.credential_details +package by.alexandr7035.affinidi_id.presentation.credential_details.stored_credential import android.os.Bundle import android.view.LayoutInflater @@ -9,7 +9,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.LinearLayoutManager import by.alexandr7035.affinidi_id.R import by.alexandr7035.affinidi_id.core.extensions.navigateSafe import by.alexandr7035.affinidi_id.core.extensions.showErrorDialog @@ -18,10 +17,14 @@ import by.alexandr7035.affinidi_id.core.extensions.vibrate import by.alexandr7035.affinidi_id.databinding.FragmentCredentialDetailsBinding import by.alexandr7035.affinidi_id.domain.core.ErrorType import by.alexandr7035.affinidi_id.presentation.common.VibrationMode -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDetailsUiModel import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationModelUi +import by.alexandr7035.affinidi_id.presentation.credential_details.LoadCredentialDetailsViewModel +import by.alexandr7035.affinidi_id.presentation.credential_details.CredentialViewPagerAdapter +import by.alexandr7035.affinidi_id.presentation.credential_details.model.CredentialDetailsUi +import by.alexandr7035.affinidi_id.presentation.verify_credential.VerificationViewModel import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.Snackbar +import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint @@ -29,7 +32,8 @@ import dagger.hilt.android.AndroidEntryPoint class CredentialDetailsFragment : Fragment() { private val binding by viewBinding(FragmentCredentialDetailsBinding::bind) - private val viewModel by viewModels() + private val detailsViewModel by viewModels() + private val verifyViewModel by viewModels() private val safeArgs by navArgs() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { @@ -44,35 +48,31 @@ class CredentialDetailsFragment : Fragment() { findNavController().navigateUp() } - val credentialSubjectAdapter = CredentialDataAdapter() - binding.credentialSubjectRecycler.adapter = credentialSubjectAdapter - binding.credentialSubjectRecycler.layoutManager = LinearLayoutManager(requireContext()) + detailsViewModel.getCredentialLiveData().observe(viewLifecycleOwner) { credentialData -> + binding.progressView.root.isVisible = false - val metadataAdapter = CredentialDataAdapter() - binding.metadataRecycler.adapter = metadataAdapter - binding.metadataRecycler.layoutManager = LinearLayoutManager(requireContext()) + when (credentialData) { + is CredentialDetailsUi.Success -> { - val proofAdapter = CredentialDataAdapter() - binding.proofRecycler.adapter = proofAdapter - binding.proofRecycler.layoutManager = LinearLayoutManager(requireContext()) + val pagerAdapter = CredentialViewPagerAdapter( + parentFragment = this, + tabsCount = 2 + ) - viewModel.getCredentialLiveData().observe(viewLifecycleOwner, { credentialData -> - binding.progressView.root.isVisible = false + binding.viewPager.adapter = pagerAdapter - when (credentialData) { - is CredentialDetailsUiModel.Success -> { - // Set fields to cards - credentialSubjectAdapter.setItems(credentialData.credentialSubjectItems) - metadataAdapter.setItems(credentialData.metadataItems) - proofAdapter.setItems(credentialData.proofItems) + val tabTitles = listOf( + getString(R.string.claims), + getString(R.string.proof) + ) - binding.credentialType.text = credentialData.credentialType - binding.statusMark.setColorFilter(credentialData.credentialStatus.statusColor) - binding.statusLabel.text = credentialData.credentialStatus.status + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = tabTitles[position] + }.attach() binding.verifyBtn.setOnClickListener { binding.progressView.root.isVisible = true - viewModel.verifyCredential(credentialData.rawVcDataPrettyFormatted) + verifyViewModel.verifyCredential(credentialData.rawVcDataPrettyFormatted) } binding.toolbar.setOnMenuItemClickListener { @@ -102,11 +102,11 @@ class CredentialDetailsFragment : Fragment() { } - is CredentialDetailsUiModel.Loading -> { + is CredentialDetailsUi.Loading -> { binding.progressView.root.isVisible = true } - is CredentialDetailsUiModel.Fail -> { + is CredentialDetailsUi.Fail -> { // Show unknown error always // Connection error is unlikely to be thrown as credential is already cached showErrorDialog( @@ -115,14 +115,13 @@ class CredentialDetailsFragment : Fragment() { ) } } - - }) + } - // Load credential data +// // Load credential data load(safeArgs.credentialId) - viewModel.getVerificationLiveData().observe(viewLifecycleOwner, { verificationResult -> + verifyViewModel.getVerificationLiveData().observe(viewLifecycleOwner) { verificationResult -> binding.progressView.root.isVisible = false when (verificationResult) { @@ -154,11 +153,11 @@ class CredentialDetailsFragment : Fragment() { } } } - }) + } } private fun load(credentialId: String) { binding.progressView.progressView.isVisible = true - viewModel.loadCredential(credentialId) + detailsViewModel.loadCredentialById(credentialId) } } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialClickListener.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialClickListener.kt deleted file mode 100644 index deb114d6..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialClickListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.credentials_list - -interface CredentialClickListener { - fun onCredentialClicked(credentialId: String) -} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialItemUiModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialItemUiModel.kt deleted file mode 100644 index 0c13cb8d..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialItemUiModel.kt +++ /dev/null @@ -1,10 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.credentials_list - -import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusUi - -data class CredentialItemUiModel( - val id: String, - val credentialTypeString: String, - val expirationDate: String, - val credentialStatus: CredentialStatusUi, -) \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsAdapter.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsAdapter.kt index a3144fe5..3fb2d584 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsAdapter.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsAdapter.kt @@ -3,18 +3,33 @@ package by.alexandr7035.affinidi_id.presentation.credentials_list import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding +import by.alexandr7035.affinidi_id.R import by.alexandr7035.affinidi_id.databinding.ViewCredentialItemBinding +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialStatus +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardUi +import java.lang.IllegalStateException -class CredentialsAdapter(private val credentialClickListener: CredentialClickListener) : +class CredentialsAdapter(private val credentialClickCallback: (credentialId: String) -> Unit) : RecyclerView.Adapter() { - private var items: List = emptyList() + private var items: List = emptyList() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CredentialViewHolder { val binding = ViewCredentialItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return CredentialViewHolder.NormalVCViewHolder(binding, credentialClickListener) + + return when (viewType) { + ACTIVE_CREDENTIAL -> { + CredentialViewHolder.NormalVCViewHolder(binding, credentialClickCallback) + } + NON_ACTIVE_CREDENTIAL -> { + CredentialViewHolder.ExpiredVCViewHolder(binding, credentialClickCallback) + } + + else -> throw IllegalStateException("View type not implemented") + } } override fun onBindViewHolder(holder: CredentialViewHolder, position: Int) { @@ -25,33 +40,64 @@ class CredentialsAdapter(private val credentialClickListener: CredentialClickLis return items.size } + override fun getItemViewType(position: Int): Int { + return if (items[position].credentialStatusUi.domainStatus == CredentialStatus.ACTIVE) { + ACTIVE_CREDENTIAL + } else { + NON_ACTIVE_CREDENTIAL + } + } + @SuppressLint("NotifyDataSetChanged") - fun setItems(items: List) { + fun setItems(items: List) { this.items = items notifyDataSetChanged() } abstract class CredentialViewHolder(open val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) { - abstract fun bind(item: CredentialItemUiModel) + abstract fun bind(item: CredentialCardUi) class NormalVCViewHolder( override val binding: ViewCredentialItemBinding, - private val credentialClickListener: CredentialClickListener + private val credentialClickCallback: (credentialId: String) -> Unit + ) : CredentialViewHolder(binding) { + override fun bind(item: CredentialCardUi) { + binding.credentialId.text = item.id + binding.credentialTypeView.text = item.credentialTypeText + binding.issuanceDate.text = item.issuanceDateText + binding.root.setOnClickListener { + credentialClickCallback.invoke(item.id) + } + } + } + + + class ExpiredVCViewHolder( + override val binding: ViewCredentialItemBinding, + private val credentialClickCallback: (credentialId: String) -> Unit ) : CredentialViewHolder(binding) { - override fun bind(item: CredentialItemUiModel) { + override fun bind(item: CredentialCardUi) { binding.credentialId.text = item.id - binding.credentialTypeView.text = item.credentialTypeString - binding.credentialExpires.text = item.expirationDate - binding.statusLabel.text = item.credentialStatus.status - binding.statusMark.setColorFilter(item.credentialStatus.statusColor) + binding.credentialTypeView.text = item.credentialTypeText + binding.issuanceDate.text = item.issuanceDateText binding.root.setOnClickListener { - credentialClickListener.onCredentialClicked(item.id) + credentialClickCallback.invoke(item.id) } + + // Set other background + binding.root.background = ContextCompat.getDrawable( + binding.root.context, + R.drawable.background_credential_item_secondary + ) } } - // TODO other types here + } + + companion object { + private const val ACTIVE_CREDENTIAL = 1 + private const val NON_ACTIVE_CREDENTIAL = 2 } } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListFragment.kt index 1f24ab6d..1cd5d2b4 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListFragment.kt @@ -15,13 +15,16 @@ import by.alexandr7035.affinidi_id.R import by.alexandr7035.affinidi_id.core.extensions.debug import by.alexandr7035.affinidi_id.core.extensions.navigateSafe import by.alexandr7035.affinidi_id.databinding.FragmentCredentialsListBinding +import by.alexandr7035.affinidi_id.presentation.credentials_list.filters.CredentialFilters +import by.alexandr7035.affinidi_id.presentation.credentials_list.model.CredentialListUiModel import by.kirich1409.viewbindingdelegate.viewBinding +import com.google.android.material.tabs.TabLayout import dagger.hilt.android.AndroidEntryPoint import timber.log.Timber @AndroidEntryPoint -class CredentialsListFragment : Fragment(), CredentialClickListener { +class CredentialsListFragment : Fragment() { private val binding by viewBinding(FragmentCredentialsListBinding::bind) private val viewModel by viewModels() @@ -36,7 +39,12 @@ class CredentialsListFragment : Fragment(), CredentialClickListener { Timber.debug("onViewCreated creds list") - val adapter = CredentialsAdapter(credentialClickListener = this) + val adapter = CredentialsAdapter(credentialClickCallback = { credentialId -> + findNavController().navigateSafe(CredentialsListFragmentDirections + .actionCredentialsListFragmentToCredentialDetailsFragment(credentialId)) + }) + + val layoutManager = LinearLayoutManager(requireContext()) binding.recycler.layoutManager = layoutManager binding.recycler.adapter = adapter @@ -51,7 +59,7 @@ class CredentialsListFragment : Fragment(), CredentialClickListener { } binding.recycler.addItemDecoration(decoration) - viewModel.getCredentialsLiveData().observe(viewLifecycleOwner, { + viewModel.getCredentialsLiveData().observe(viewLifecycleOwner) { // Hide all before state update binding.progressView.root.isVisible = false binding.recycler.isVisible = false @@ -79,27 +87,48 @@ class CredentialsListFragment : Fragment(), CredentialClickListener { binding.errorView.errorText.text = it.errorUi.message } } - }) + } - loadData() + // Initial load + loadData(CredentialFilters.All) + // Retry load binding.errorView.retryBtn.setOnClickListener { - loadData() + loadData(CredentialFilters.All) } + binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + loadData(getFiltersForTab(tab.position)) + } + + override fun onTabUnselected(tab: TabLayout.Tab) { + + } + + override fun onTabReselected(tab: TabLayout.Tab) { + loadData(getFiltersForTab(tab.position)) + } + }) + + binding.addCredentialBtn.setOnClickListener { findNavController().navigateSafe(CredentialsListFragmentDirections .actionCredentialsListFragmentToIssueCredentialFragment()) } } - private fun loadData() { + private fun loadData(credentialFilters: CredentialFilters) { binding.errorView.root.isVisible = false - viewModel.load() + viewModel.load(credentialFilters) } - override fun onCredentialClicked(credentialId: String) { - findNavController().navigateSafe(CredentialsListFragmentDirections - .actionCredentialsListFragmentToCredentialDetailsFragment(credentialId)) + private fun getFiltersForTab(tabPosition: Int): CredentialFilters { + return when (tabPosition) { + 0 -> CredentialFilters.All + 1 -> CredentialFilters.Active + 2 -> CredentialFilters.Expired + else -> throw RuntimeException("No such tab implemented") + } } } \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListMapper.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListMapper.kt deleted file mode 100644 index 1fd58743..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListMapper.kt +++ /dev/null @@ -1,7 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.credentials_list - -import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialsListResModel - -interface CredentialsListMapper { - fun map(domainCredentials: CredentialsListResModel): CredentialListUiModel -} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListMapperImpl.kt deleted file mode 100644 index 6b359a85..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListMapperImpl.kt +++ /dev/null @@ -1,60 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.credentials_list - -import by.alexandr7035.affinidi_id.R -import by.alexandr7035.affinidi_id.core.extensions.getStringDateFromLong -import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialsListResModel -import by.alexandr7035.affinidi_id.presentation.common.errors.ErrorTypeMapper -import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_status.CredentialStatusMapper -import by.alexandr7035.affinidi_id.presentation.common.resources.ResourceProvider -import javax.inject.Inject - -class CredentialsListMapperImpl @Inject constructor( - private val resourceProvider: ResourceProvider, - private val credentialStatusMapper: CredentialStatusMapper, - private val errorTypeMapper: ErrorTypeMapper -) : CredentialsListMapper { - - override fun map(domainCredentials: CredentialsListResModel): CredentialListUiModel { - return when (domainCredentials) { - is CredentialsListResModel.Success -> { - - val uiCredentials: List = domainCredentials.credentials.map { - - val textExpirationDate = - it.expirationDate?.getStringDateFromLong(CARD_EXPIRATION_DATE_FORMAT) ?: resourceProvider.getString( - R.string.no_expiration - ) - - // Map domain fields to UI - val credentialStatus = credentialStatusMapper.map(it.credentialStatus) - - CredentialItemUiModel( - id = it.id, - expirationDate = textExpirationDate, - credentialTypeString = it.vcType, - credentialStatus = credentialStatus, - ) - } - - if (uiCredentials.isEmpty()) { - CredentialListUiModel.NoCredentials - } - else { - CredentialListUiModel.Success(uiCredentials) - } - } - - is CredentialsListResModel.Fail -> { - CredentialListUiModel.Fail(errorUi = errorTypeMapper.map(errorType = domainCredentials.errorType)) - } - - is CredentialsListResModel.Loading -> { - CredentialListUiModel.Loading - } - } - } - - companion object { - private const val CARD_EXPIRATION_DATE_FORMAT = "dd.MM.YYYY HH:mm" - } -} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListViewModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListViewModel.kt index ac2744d9..39c21fa2 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListViewModel.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialsListViewModel.kt @@ -5,9 +5,11 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import by.alexandr7035.affinidi_id.domain.usecase.credentials.GetCredentialsListUseCase +import by.alexandr7035.affinidi_id.presentation.credentials_list.filters.CredentialFilters +import by.alexandr7035.affinidi_id.presentation.credentials_list.model.CredentialListUiModel +import by.alexandr7035.affinidi_id.presentation.credentials_list.model.CredentialsListMapper import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -20,10 +22,11 @@ class CredentialsListViewModel @Inject constructor( private val credentialsLiveData = MutableLiveData() - fun load() { + fun load(credentialFilters: CredentialFilters) { viewModelScope.launch(Dispatchers.IO) { getCredentialsListUseCase.execute().collect { res -> - val credentialListUiModel = mapper.map(res) + // Map credentials to ui. Apply filters + val credentialListUiModel = mapper.map(res, credentialFilters) withContext(Dispatchers.Main) { credentialsLiveData.value = credentialListUiModel diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/filters/CredentialFilters.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/filters/CredentialFilters.kt new file mode 100644 index 00000000..78f2b636 --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/filters/CredentialFilters.kt @@ -0,0 +1,9 @@ +package by.alexandr7035.affinidi_id.presentation.credentials_list.filters + +sealed class CredentialFilters { + object All: CredentialFilters() + + object Active: CredentialFilters() + + object Expired: CredentialFilters() +} diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialListUiModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialListUiModel.kt similarity index 64% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialListUiModel.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialListUiModel.kt index 34e6021b..9d8e17d9 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/CredentialListUiModel.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialListUiModel.kt @@ -1,9 +1,10 @@ -package by.alexandr7035.affinidi_id.presentation.credentials_list +package by.alexandr7035.affinidi_id.presentation.credentials_list.model +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardUi import by.alexandr7035.affinidi_id.presentation.common.errors.DetailedErrorUi sealed class CredentialListUiModel { - class Success(val credentials: List): CredentialListUiModel() + class Success(val credentials: List): CredentialListUiModel() class Fail(val errorUi: DetailedErrorUi): CredentialListUiModel() diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialsListMapper.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialsListMapper.kt new file mode 100644 index 00000000..abc07fcf --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialsListMapper.kt @@ -0,0 +1,11 @@ +package by.alexandr7035.affinidi_id.presentation.credentials_list.model + +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialsListResModel +import by.alexandr7035.affinidi_id.presentation.credentials_list.filters.CredentialFilters + +interface CredentialsListMapper { + fun map( + domainCredentials: CredentialsListResModel, + credentialFilters: CredentialFilters + ): CredentialListUiModel +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialsListMapperImpl.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialsListMapperImpl.kt new file mode 100644 index 00000000..f4c9f3be --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/credentials_list/model/CredentialsListMapperImpl.kt @@ -0,0 +1,58 @@ +package by.alexandr7035.affinidi_id.presentation.credentials_list.model + +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialStatus +import by.alexandr7035.affinidi_id.domain.model.credentials.stored_credentials.CredentialsListResModel +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardMapper +import by.alexandr7035.affinidi_id.presentation.common.credentials.credential_card.CredentialCardUi +import by.alexandr7035.affinidi_id.presentation.common.errors.ErrorTypeMapper +import by.alexandr7035.affinidi_id.presentation.credentials_list.filters.CredentialFilters +import javax.inject.Inject + +class CredentialsListMapperImpl @Inject constructor( + private val credentialCardMapper: CredentialCardMapper, + private val errorTypeMapper: ErrorTypeMapper +) : CredentialsListMapper { + + override fun map(domainCredentials: CredentialsListResModel, credentialFilters: CredentialFilters): CredentialListUiModel { + return when (domainCredentials) { + is CredentialsListResModel.Success -> { + + val uiCredentialsUnfiltered: List = domainCredentials.credentials.map { + credentialCardMapper.map(it) + } + + val uiCredentialsFiltered = when (credentialFilters) { + CredentialFilters.All -> { + uiCredentialsUnfiltered + } + + CredentialFilters.Active -> { + uiCredentialsUnfiltered.filter { + it.credentialStatusUi.domainStatus == CredentialStatus.ACTIVE + } + } + + CredentialFilters.Expired -> { + uiCredentialsUnfiltered.filter { + it.credentialStatusUi.domainStatus == CredentialStatus.EXPIRED + } + } + } + + if (uiCredentialsFiltered.isEmpty()) { + CredentialListUiModel.NoCredentials + } else { + CredentialListUiModel.Success(uiCredentialsFiltered) + } + } + + is CredentialsListResModel.Fail -> { + CredentialListUiModel.Fail(errorUi = errorTypeMapper.map(errorType = domainCredentials.errorType)) + } + + is CredentialsListResModel.Loading -> { + CredentialListUiModel.Loading + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/delete_credential/DeleteCredentialFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/delete_credential/DeleteCredentialFragment.kt index 18a8ace4..7fb50d1b 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/delete_credential/DeleteCredentialFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/delete_credential/DeleteCredentialFragment.kt @@ -33,13 +33,15 @@ class DeleteCredentialFragment : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.getDeleteVcLiveData().observe(viewLifecycleOwner, { result -> + viewModel.getDeleteVcLiveData().observe(viewLifecycleOwner) { result -> binding.progressView.progressView.isVisible = false when (result) { is DeleteVcResModel.Success -> { - findNavController().navigateSafe(DeleteCredentialFragmentDirections - .actionDeleteCredentialFragmentToCredentialsListFragment()) + findNavController().navigateSafe( + DeleteCredentialFragmentDirections + .actionDeleteCredentialFragmentToCredentialsListFragment() + ) } is DeleteVcResModel.Fail -> { when (result.errorType) { @@ -60,7 +62,7 @@ class DeleteCredentialFragment : BottomSheetDialogFragment() { } } - }) + } binding.confirmDeleteBtn.setOnClickListener { binding.progressView.progressView.isVisible = true diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/issue_credential/IssueCredentialFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/issue_credential/IssueCredentialFragment.kt index dd810941..139c22b7 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/issue_credential/IssueCredentialFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/issue_credential/IssueCredentialFragment.kt @@ -57,14 +57,14 @@ class IssueCredentialFragment : Fragment(), CredentialClickListener { } binding.recycler.addItemDecoration(decoration) - viewModel.getAvailableVCsLiveData().observe(viewLifecycleOwner, { + viewModel.getAvailableVCsLiveData().observe(viewLifecycleOwner) { adapter.setItems(it) - }) + } viewModel.loadAvailableVCs() - viewModel.getIssueCredentialLiveData().observe(viewLifecycleOwner, { result -> + viewModel.getIssueCredentialLiveData().observe(viewLifecycleOwner) { result -> binding.progressView.root.isVisible = false when (result) { @@ -101,7 +101,7 @@ class IssueCredentialFragment : Fragment(), CredentialClickListener { } } - }) + } } diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/login/LoginFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/login/LoginFragment.kt index 6de84ca6..b3b2daef 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/login/LoginFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/login/LoginFragment.kt @@ -67,7 +67,7 @@ class LoginFragment : Fragment() { } - viewModel.signInLiveData.observe(viewLifecycleOwner, { response -> + viewModel.signInLiveData.observe(viewLifecycleOwner) { response -> binding.progressView.root.isVisible = false @@ -105,7 +105,7 @@ class LoginFragment : Fragment() { } } } - }) + } val goToSignUpText = getString(R.string.go_to_sign_up) diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/logout/LogoutFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/logout/LogoutFragment.kt index 8f636251..529f5a39 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/logout/LogoutFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/logout/LogoutFragment.kt @@ -31,7 +31,7 @@ class LogoutFragment : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.logOutLiveData.observe(viewLifecycleOwner, { logout -> + viewModel.logOutLiveData.observe(viewLifecycleOwner) { logout -> binding.progressView.root.isVisible = false @@ -57,7 +57,7 @@ class LogoutFragment : BottomSheetDialogFragment() { } } } - }) + } binding.logoutBtn.setOnClickListener { binding.progressView.root.isVisible = true diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/main_menu/MainMenuFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/main_menu/MainMenuFragment.kt index 59fc1e1d..748aa0c1 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/main_menu/MainMenuFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/main_menu/MainMenuFragment.kt @@ -73,12 +73,12 @@ class MainMenuFragment : Fragment() { .build() viewModel.init() - viewModel.userProfileLiveData.observe(viewLifecycleOwner, { profile -> + viewModel.userProfileLiveData.observe(viewLifecycleOwner) { profile -> binding.profileImageView.load( uri = profile.imageUrl, imageLoader = imageLoader ) - }) + } binding.toolbar.setOnMenuItemClickListener { diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/ProfileFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/ProfileFragment.kt index 46e3ad7c..8fee23e3 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/ProfileFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/ProfileFragment.kt @@ -39,7 +39,7 @@ class ProfileFragment : Fragment() { .build() - viewModel.userProfileLiveData.observe(viewLifecycleOwner, { profile -> + viewModel.userProfileLiveData.observe(viewLifecycleOwner) { profile -> binding.userNameView.text = profile.userName // TODO profile ui model val formattedDid = profile.userDid.split(";").first() @@ -49,7 +49,7 @@ class ProfileFragment : Fragment() { uri = profile.imageUrl, imageLoader = imageLoader ) - }) + } viewModel.init() diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/edit_password/ChangePasswordFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/edit_password/ChangePasswordFragment.kt index 16c14893..4e9e590a 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/edit_password/ChangePasswordFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/profile/edit_password/ChangePasswordFragment.kt @@ -65,7 +65,7 @@ class ChangePasswordFragment : Fragment() { } } - viewModel.changePasswordLiveData.observe(viewLifecycleOwner, { result -> + viewModel.changePasswordLiveData.observe(viewLifecycleOwner) { result -> binding.progressView.root.isVisible = false when (result) { @@ -98,7 +98,7 @@ class ChangePasswordFragment : Fragment() { } } } - }) + } } private fun chekIfFormIsValid(): Boolean { diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/registration/RegistrationFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/registration/RegistrationFragment.kt index 8c3dff80..bd1e2d95 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/registration/RegistrationFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/registration/RegistrationFragment.kt @@ -55,7 +55,7 @@ class RegistrationFragment : Fragment() { } - viewModel.signUpLiveData.observe(viewLifecycleOwner, { signUpResult -> + viewModel.signUpLiveData.observe(viewLifecycleOwner) { signUpResult -> when (signUpResult) { is SignUpResponseModel.Success -> { @@ -89,7 +89,7 @@ class RegistrationFragment : Fragment() { } } } - }) + } binding.userNameEditText.doOnTextChanged { text, start, before, count -> @@ -127,7 +127,7 @@ class RegistrationFragment : Fragment() { } - viewModel.signUpConfirmationLiveData.observe(viewLifecycleOwner, { signUpConfirmationResult -> + viewModel.signUpConfirmationLiveData.observe(viewLifecycleOwner) { signUpConfirmationResult -> when (signUpConfirmationResult) { is ConfirmSignUpResponseModel.Success -> { @@ -165,7 +165,7 @@ class RegistrationFragment : Fragment() { } } } - }) + } } private fun chekIfFormIsValid(): Boolean { diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordConfirmationFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordConfirmationFragment.kt index e0c75566..aec82ff0 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordConfirmationFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordConfirmationFragment.kt @@ -55,13 +55,15 @@ class ResetPasswordConfirmationFragment : Fragment() { } - viewModel.confirmPasswordResetLiveData.observe(viewLifecycleOwner, { result -> + viewModel.confirmPasswordResetLiveData.observe(viewLifecycleOwner) { result -> binding.progressView.root.isVisible = false when (result) { is ConfirmPasswordResetResponseModel.Success -> { - findNavController().navigateSafe(ResetPasswordConfirmationFragmentDirections - .actionGlobalLoginFragment()) + findNavController().navigateSafe( + ResetPasswordConfirmationFragmentDirections + .actionGlobalLoginFragment() + ) binding.root.showSnackBar( message = getString(R.string.password_changed_successfully), @@ -92,7 +94,7 @@ class ResetPasswordConfirmationFragment : Fragment() { } } } - }) + } binding.toolbar.setNavigationOnClickListener { findNavController().navigateUp() diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordSetUsernameFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordSetUsernameFragment.kt index 101bd040..b8478683 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordSetUsernameFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/reset_password/ResetPasswordSetUsernameFragment.kt @@ -54,13 +54,15 @@ class ResetPasswordSetUsernameFragment : Fragment() { } } - viewModel.initializePasswordResetLiveData.observe(viewLifecycleOwner, { result -> + viewModel.initializePasswordResetLiveData.observe(viewLifecycleOwner) { result -> binding.progressView.root.isVisible = false when (result) { is InitializePasswordResetResponseModel.Success -> { - findNavController().navigateSafe(ResetPasswordSetUsernameFragmentDirections - .actionResetPasswordSetUsernameFragmentToResetPasswordSetPasswordFragment(result.userName)) + findNavController().navigateSafe( + ResetPasswordSetUsernameFragmentDirections + .actionResetPasswordSetUsernameFragmentToResetPasswordSetPasswordFragment(result.userName) + ) } is InitializePasswordResetResponseModel.Fail -> { @@ -84,7 +86,7 @@ class ResetPasswordSetUsernameFragment : Fragment() { } } } - }) + } } private fun chekIfFormIsValid(): Boolean { diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScanCredentialQrCodeFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/scan_credential_qr/ScanCredentialQrCodeFragment.kt similarity index 98% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScanCredentialQrCodeFragment.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/scan_credential_qr/ScanCredentialQrCodeFragment.kt index a6e16385..9e069214 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScanCredentialQrCodeFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/scan_credential_qr/ScanCredentialQrCodeFragment.kt @@ -1,4 +1,4 @@ -package by.alexandr7035.affinidi_id.presentation.verify_credential_qr +package by.alexandr7035.affinidi_id.presentation.scan_credential_qr import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScanCredentialQrViewModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/scan_credential_qr/ScanCredentialQrViewModel.kt similarity index 91% rename from app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScanCredentialQrViewModel.kt rename to app/src/main/java/by/alexandr7035/affinidi_id/presentation/scan_credential_qr/ScanCredentialQrViewModel.kt index 96daa057..4a07cc89 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/ScanCredentialQrViewModel.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/scan_credential_qr/ScanCredentialQrViewModel.kt @@ -1,4 +1,4 @@ -package by.alexandr7035.affinidi_id.presentation.verify_credential_qr +package by.alexandr7035.affinidi_id.presentation.scan_credential_qr import android.Manifest import android.app.Activity diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/share_credential/ShareCredentialFragment.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/share_credential/ShareCredentialFragment.kt index b2ac3da7..305b4385 100644 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/share_credential/ShareCredentialFragment.kt +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/share_credential/ShareCredentialFragment.kt @@ -36,7 +36,7 @@ class ShareCredentialFragment : BottomSheetDialogFragment() { binding.progressView.root.isVisible = true - viewModel.getShareCredentialLiveData().observe(viewLifecycleOwner, { + viewModel.getShareCredentialLiveData().observe(viewLifecycleOwner) { binding.progressView.root.isVisible = false when (it) { @@ -64,7 +64,7 @@ class ShareCredentialFragment : BottomSheetDialogFragment() { } } - }) + } viewModel.load(safeArgs.credentialId) } diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential/VerificationViewModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential/VerificationViewModel.kt new file mode 100644 index 00000000..a5a250af --- /dev/null +++ b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential/VerificationViewModel.kt @@ -0,0 +1,41 @@ +package by.alexandr7035.affinidi_id.presentation.verify_credential + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import by.alexandr7035.affinidi_id.core.livedata.SingleLiveEvent +import by.alexandr7035.affinidi_id.domain.model.credentials.verify_vc.VerifyVcReqModel +import by.alexandr7035.affinidi_id.domain.usecase.credentials.VerifyCredentialUseCase +import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationModelUi +import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationResultToUiMapper +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +@HiltViewModel +class VerificationViewModel @Inject constructor( + private val verifyCredentialUseCase: VerifyCredentialUseCase, + private val verificationResultToUiMapper: VerificationResultToUiMapper, +): ViewModel() { + + private val verificationLiveData = SingleLiveEvent() + + fun verifyCredential(rawVc: String) { + viewModelScope.launch(Dispatchers.IO) { + val res = verifyCredentialUseCase.execute( + VerifyVcReqModel( + rawVc = rawVc + ) + ) + + withContext(Dispatchers.Main) { + // Map domain result to ui model for verification snackbar + verificationLiveData.value = verificationResultToUiMapper.map(res) + } + } + } + + fun getVerificationLiveData(): LiveData = verificationLiveData +} \ No newline at end of file diff --git a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/VerifyCredentialQrViewModel.kt b/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/VerifyCredentialQrViewModel.kt deleted file mode 100644 index d74408f2..00000000 --- a/app/src/main/java/by/alexandr7035/affinidi_id/presentation/verify_credential_qr/VerifyCredentialQrViewModel.kt +++ /dev/null @@ -1,73 +0,0 @@ -package by.alexandr7035.affinidi_id.presentation.verify_credential_qr - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import by.alexandr7035.affinidi_id.core.livedata.SingleLiveEvent -import by.alexandr7035.affinidi_id.domain.model.credentials.get_from_qr_code.ObtainVcFromQrCodeReqModel -import by.alexandr7035.affinidi_id.domain.model.credentials.get_from_qr_code.ObtainVcFromQrCodeResModel -import by.alexandr7035.affinidi_id.domain.model.credentials.verify_vc.VerifyVcReqModel -import by.alexandr7035.affinidi_id.domain.usecase.credentials.ObtainCredentialWithQrCodeUseCase -import by.alexandr7035.affinidi_id.domain.usecase.credentials.VerifyCredentialUseCase -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialDetailsUiModel -import by.alexandr7035.affinidi_id.presentation.common.credentials.CredentialToDetailsModelMapper -import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationModelUi -import by.alexandr7035.affinidi_id.presentation.common.credentials.verification.VerificationResultToUiMapper -import by.alexandr7035.affinidi_id.presentation.common.resources.ResourceProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import javax.inject.Inject - -@HiltViewModel -class VerifyCredentialQrViewModel @Inject constructor( - private val obtainCredentialWithQrCodeUseCase: ObtainCredentialWithQrCodeUseCase, - private val credentialToDetailsModelMapper: CredentialToDetailsModelMapper, - private val verifyCredentialUseCase: VerifyCredentialUseCase, - private val verificationResultToUiMapper: VerificationResultToUiMapper, -) : ViewModel() { - - private val credentialLiveData = MutableLiveData() - private val verificationLiveData = SingleLiveEvent() - - fun obtainCredential(credentialLink: String) { - viewModelScope.launch(Dispatchers.IO) { - val res = obtainCredentialWithQrCodeUseCase.execute(ObtainVcFromQrCodeReqModel(credentialLink)) - - val uiModel = when (res) { - is ObtainVcFromQrCodeResModel.Success -> { - credentialToDetailsModelMapper.map(credential = res.credential) - } - - is ObtainVcFromQrCodeResModel.Fail -> { - CredentialDetailsUiModel.Fail(res.errorType) - } - } - - withContext(Dispatchers.Main) { - credentialLiveData.value = uiModel - } - } - } - - fun verifyCredential(rawVc: String) { - viewModelScope.launch(Dispatchers.IO) { - val res = verifyCredentialUseCase.execute( - VerifyVcReqModel( - rawVc = rawVc - ) - ) - - withContext(Dispatchers.Main) { - // Map domain result to ui model for verification snackbar - verificationLiveData.value = verificationResultToUiMapper.map(res) - } - } - } - - fun getCredentialLiveData(): LiveData = credentialLiveData - - fun getVerificationLiveData(): LiveData = verificationLiveData -} \ No newline at end of file diff --git a/app/src/main/res/drawable/background_credential_item_secondary.xml b/app/src/main/res/drawable/background_credential_item_secondary.xml new file mode 100644 index 00000000..c3e91f4e --- /dev/null +++ b/app/src/main/res/drawable/background_credential_item_secondary.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_credential_item_expanded.xml b/app/src/main/res/drawable/bg_credential_item_expanded.xml new file mode 100644 index 00000000..724ee8cd --- /dev/null +++ b/app/src/main/res/drawable/bg_credential_item_expanded.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_tab.xml b/app/src/main/res/drawable/bg_tab.xml new file mode 100644 index 00000000..e69e8219 --- /dev/null +++ b/app/src/main/res/drawable/bg_tab.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_credential_mark.png b/app/src/main/res/drawable/ic_credential_mark.png new file mode 100644 index 00000000..3275e666 Binary files /dev/null and b/app/src/main/res/drawable/ic_credential_mark.png differ diff --git a/app/src/main/res/layout/fragment_credential_claims.xml b/app/src/main/res/layout/fragment_credential_claims.xml new file mode 100644 index 00000000..547776e0 --- /dev/null +++ b/app/src/main/res/layout/fragment_credential_claims.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_credential_details.xml b/app/src/main/res/layout/fragment_credential_details.xml index 5dd13052..f981d111 100644 --- a/app/src/main/res/layout/fragment_credential_details.xml +++ b/app/src/main/res/layout/fragment_credential_details.xml @@ -16,132 +16,38 @@ app:menu="@menu/menu_credential_info_toolbar" app:title="@string/credential_info" /> - + app:layout_constraintEnd_toEndOf="parent"> - - - - - - - - - - - - - - - - - + android:text="@string/claims" /> - - - - - - - - - - - - - - + - + +