From 014c002ebe9ac7bb353e145c7b94aa08b4b1586a Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Sat, 13 Jun 2026 17:40:09 +0500 Subject: [PATCH] add BML and MIB card freeze/unfreeze --- .kotlin/errors/errors-1781348419776.log | 231 ++++++++++++++++++ .../sh/sar/basedbank/api/bml/BmlCardClient.kt | 55 +++++ .../sar/basedbank/api/mib/MibCardsClient.kt | 47 +++- .../sh/sar/basedbank/api/mib/MibModels.kt | 3 +- .../basedbank/ui/home/DashboardFragment.kt | 2 +- .../sh/sar/basedbank/ui/home/HomeActivity.kt | 8 +- .../basedbank/ui/home/PayWithCardFragment.kt | 211 +++++++++++++++- .../java/sh/sar/basedbank/util/CardsCache.kt | 4 +- app/src/main/res/values/strings.xml | 10 + 9 files changed, 552 insertions(+), 19 deletions(-) create mode 100644 .kotlin/errors/errors-1781348419776.log create mode 100644 app/src/main/java/sh/sar/basedbank/api/bml/BmlCardClient.kt diff --git a/.kotlin/errors/errors-1781348419776.log b/.kotlin/errors/errors-1781348419776.log new file mode 100644 index 0000000..f129000 --- /dev/null +++ b/.kotlin/errors/errors-1781348419776.log @@ -0,0 +1,231 @@ +kotlin version: 2.1.21 +error message: org.jetbrains.kotlin.util.FileAnalysisException: While analysing /home/shihaam/git/sargit/thijooree/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt:701:9: java.io.FileNotFoundException: /home/shihaam/git/sargit/thijooree/app/build/tmp/kotlin-classes/debug/sh/sar/basedbank/nfc/BmlHostCardEmulatorService.class (No such file or directory) + at org.jetbrains.kotlin.util.AnalysisExceptionsKt.wrapIntoFileAnalysisExceptionIfNeeded(AnalysisExceptions.kt:57) + at org.jetbrains.kotlin.fir.FirCliExceptionHandler.handleExceptionOnFileAnalysis(Utils.kt:251) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformFile(FirDeclarationsResolveTransformer.kt:1757) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformFile(FirAbstractBodyResolveTransformerDispatcher.kt:57) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformFile(FirAbstractBodyResolveTransformerDispatcher.kt:24) + at org.jetbrains.kotlin.fir.declarations.FirFile.transform(FirFile.kt:46) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirBodyResolveTransformerAdapter.transformFile(FirBodyResolveTransformerAdapters.kt:41) + at org.jetbrains.kotlin.fir.declarations.FirFile.transform(FirFile.kt:46) + at org.jetbrains.kotlin.fir.resolve.transformers.FirTransformerBasedResolveProcessor.processFile(FirResolveProcessor.kt:48) + at org.jetbrains.kotlin.fir.resolve.transformers.FirTotalResolveProcessor.process(FirTotalResolveProcessor.kt:36) + at org.jetbrains.kotlin.fir.pipeline.AnalyseKt.runResolution(analyse.kt:24) + at org.jetbrains.kotlin.fir.pipeline.FirUtilsKt.resolveAndCheckFir(firUtils.kt:76) + at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFrontendPipelinePhase.executePhase(JvmFrontendPipelinePhase.kt:167) + at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFrontendPipelinePhase.executePhase(JvmFrontendPipelinePhase.kt:56) + at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:68) + at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:58) + at org.jetbrains.kotlin.config.phaser.SimpleNamedCompilerPhase.phaseBody(CompilerPhase.kt:215) + at org.jetbrains.kotlin.config.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:111) + at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:28) + at org.jetbrains.kotlin.config.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:62) + at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.runPhasedPipeline(AbstractCliPipeline.kt:106) + at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.execute(AbstractCliPipeline.kt:65) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:61) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:36) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:80) + at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:337) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:466) + at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:75) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:514) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:431) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally$lambda$10$compile(IncrementalCompilerRunner.kt:258) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:276) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:128) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:678) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1805) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:714) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:400) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) + at java.base/java.lang.Thread.run(Thread.java:1583) +Caused by: java.io.FileNotFoundException: /home/shihaam/git/sargit/thijooree/app/build/tmp/kotlin-classes/debug/sh/sar/basedbank/nfc/BmlHostCardEmulatorService.class (No such file or directory) + at java.base/java.io.FileInputStream.open0(Native Method) + at java.base/java.io.FileInputStream.open(FileInputStream.java:213) + at java.base/java.io.FileInputStream.(FileInputStream.java:152) + at org.jetbrains.kotlin.com.intellij.openapi.util.io.FileUtil.loadFileBytes(FileUtil.java:211) + at org.jetbrains.kotlin.cli.common.localfs.KotlinLocalVirtualFile.contentsToByteArray(KotlinLocalVirtualFile.kt:108) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinCliJavaFileManagerImpl.findClass(KotlinCliJavaFileManagerImpl.kt:141) + at org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade$CliFinder.findClass(KotlinJavaPsiFacade.java:538) + at org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade.findClass(KotlinJavaPsiFacade.java:181) + at org.jetbrains.kotlin.load.java.JavaClassFinderImpl.findClass(JavaClassFinderImpl.kt:46) + at org.jetbrains.kotlin.fir.java.FirJavaFacade.findClass(FirJavaFacade.kt:69) + at org.jetbrains.kotlin.fir.java.deserialization.JvmClassFileBasedSymbolProvider.extractClassMetadata(JvmClassFileBasedSymbolProvider.kt:174) + at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.findAndDeserializeClass(AbstractFirDeserializedSymbolProvider.kt:229) + at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.classCache$lambda$5(AbstractFirDeserializedSymbolProvider.kt:163) + at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCacheWithPostCompute.getValue(FirThreadUnsafeCachesFactory.kt:75) + at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClass(AbstractFirDeserializedSymbolProvider.kt:315) + at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClass$default(AbstractFirDeserializedSymbolProvider.kt:298) + at org.jetbrains.kotlin.fir.deserialization.AbstractFirDeserializedSymbolProvider.getClassLikeSymbolByClassId(AbstractFirDeserializedSymbolProvider.kt:372) + at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.computeClass(FirCachingCompositeSymbolProvider.kt:147) + at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.access$computeClass(FirCachingCompositeSymbolProvider.kt:27) + at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:149) + at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider$special$$inlined$createCache$1.invoke(FirCachesFactory.kt:147) + at org.jetbrains.kotlin.fir.caches.FirThreadUnsafeCache.getValue(FirThreadUnsafeCachesFactory.kt:57) + at org.jetbrains.kotlin.fir.resolve.providers.impl.FirCachingCompositeSymbolProvider.getClassLikeSymbolByClassId(FirCachingCompositeSymbolProvider.kt:174) + at org.jetbrains.kotlin.fir.scopes.impl.FirAbstractImportingScope.processClassifiersFromImportsByName(FirAbstractImportingScope.kt:53) + at org.jetbrains.kotlin.fir.scopes.impl.FirAbstractSimpleImportingScope.processClassifiersByNameWithSubstitution(FirAbstractSimpleImportingScope.kt:30) + at org.jetbrains.kotlin.fir.resolve.calls.tower.ScopeBasedTowerLevel.processObjectsByName(TowerLevels.kt:538) + at org.jetbrains.kotlin.fir.resolve.calls.tower.TowerLevelHandler.handleLevel(TowerLevelHandler.kt:57) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirBaseTowerResolveTask.processLevel(FirTowerResolveTask.kt:206) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirBaseTowerResolveTask.access$processLevel(FirTowerResolveTask.kt:63) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirTowerResolveTask.runResolverForNoReceiver(FirTowerResolveTask.kt:635) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirTowerResolveTask.runResolverForNoReceiver$default(FirTowerResolveTask.kt:338) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirTowerResolver$enqueueResolutionTasks$2.invokeSuspend(FirTowerResolver.kt:79) + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) + at org.jetbrains.kotlin.fir.resolve.calls.tower.TowerResolveManager.resumeTask(TowerResolveManager.kt:77) + at org.jetbrains.kotlin.fir.resolve.calls.tower.TowerResolveManager.runTasks(TowerResolveManager.kt:83) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirTowerResolver.runResolver(FirTowerResolver.kt:53) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirTowerResolver.runResolver$default(FirTowerResolver.kt:42) + at org.jetbrains.kotlin.fir.resolve.calls.tower.FirTowerResolver.runResolver(FirTowerResolver.kt:39) + at org.jetbrains.kotlin.fir.resolve.calls.FirCallResolver.collectCandidates(FirCallResolver.kt:209) + at org.jetbrains.kotlin.fir.resolve.calls.FirCallResolver.collectCandidates$default(FirCallResolver.kt:174) + at org.jetbrains.kotlin.fir.resolve.calls.FirCallResolver.resolveVariableAccessAndSelectCandidateImpl$lambda$7(FirCallResolver.kt:282) + at kotlin.UnsafeLazyImpl.getValue(Lazy.kt:98) + at org.jetbrains.kotlin.fir.resolve.calls.FirCallResolver.resolveVariableAccessAndSelectCandidateImpl$lambda$8(FirCallResolver.kt:281) + at org.jetbrains.kotlin.fir.resolve.calls.FirCallResolver.resolveVariableAccessAndSelectCandidateImpl(FirCallResolver.kt:311) + at org.jetbrains.kotlin.fir.resolve.calls.FirCallResolver.resolveVariableAccessAndSelectCandidate(FirCallResolver.kt:258) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.resolveQualifiedAccessAndSelectCandidate(FirExpressionsResolveTransformer.kt:281) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformQualifiedAccessExpression(FirExpressionsResolveTransformer.kt:187) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformAsExplicitReceiver(FirExpressionsResolveTransformer.kt:260) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformExplicitReceiverOf(FirExpressionsResolveTransformer.kt:248) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformFunctionCallInternal$resolve(FirExpressionsResolveTransformer.kt:508) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformFunctionCall(FirExpressionsResolveTransformer.kt:453) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformFunctionCall(FirAbstractBodyResolveTransformerDispatcher.kt:174) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformFunctionCall(FirAbstractBodyResolveTransformerDispatcher.kt:24) + at org.jetbrains.kotlin.fir.expressions.FirFunctionCall.transform(FirFunctionCall.kt:45) + at org.jetbrains.kotlin.fir.expressions.FirExpressionUtilKt.transformStatementsIndexed(FirExpressionUtil.kt:198) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformBlockInCurrentScope$resolve(FirExpressionsResolveTransformer.kt:685) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirExpressionsResolveTransformer.transformBlock(FirExpressionsResolveTransformer.kt:676) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformBlock(FirAbstractBodyResolveTransformerDispatcher.kt:201) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformBlock(FirAbstractBodyResolveTransformerDispatcher.kt:24) + at org.jetbrains.kotlin.fir.expressions.FirBlock.transform(FirBlock.kt:32) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirPartialBodyResolveTransformer.transformElement(FirPartialBodyResolveTransformer.kt:36) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirPartialBodyResolveTransformer.transformElement(FirPartialBodyResolveTransformer.kt:13) + at org.jetbrains.kotlin.fir.visitors.FirTransformer.transformBlock(FirTransformer.kt:183) + at org.jetbrains.kotlin.fir.expressions.FirBlock.transform(FirBlock.kt:32) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.transformBody(FirSimpleFunctionImpl.kt:114) + at org.jetbrains.kotlin.fir.declarations.impl.FirSimpleFunctionImpl.transformBody(FirSimpleFunctionImpl.kt:32) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformFunction(FirDeclarationsResolveTransformer.kt:1004) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformFunctionWithGivenSignature(FirDeclarationsResolveTransformer.kt:950) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.access$transformFunctionWithGivenSignature(FirDeclarationsResolveTransformer.kt:63) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformSimpleFunction$lambda$63$lambda$62$lambda$61(FirDeclarationsResolveTransformer.kt:942) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext.forFunctionBody(BodyResolveContext.kt:1320) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformSimpleFunction(FirDeclarationsResolveTransformer.kt:940) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformSimpleFunction(FirAbstractBodyResolveTransformerDispatcher.kt:549) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformSimpleFunction(FirAbstractBodyResolveTransformerDispatcher.kt:24) + at org.jetbrains.kotlin.fir.declarations.FirSimpleFunction.transform(FirSimpleFunction.kt:55) + at org.jetbrains.kotlin.fir.visitors.FirTransformerUtilKt.transformInplace(FirTransformerUtil.kt:20) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.transformDeclarations(FirRegularClassImpl.kt:91) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.transformChildren(FirRegularClassImpl.kt:73) + at org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl.transformChildren(FirRegularClassImpl.kt:30) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformElement(FirAbstractBodyResolveTransformerDispatcher.kt:89) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformDeclarationContent(FirAbstractBodyResolveTransformerDispatcher.kt:441) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformDeclarationContent(FirDeclarationsResolveTransformer.kt:83) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.doTransformRegularClass$lambda$55(FirDeclarationsResolveTransformer.kt:869) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.withRegularClass$lambda$56(FirDeclarationsResolveTransformer.kt:878) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext.withRegularClass$lambda$17$lambda$16(BodyResolveContext.kt:1673) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext.withScopesForClass(BodyResolveContext.kt:570) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext.withRegularClass(BodyResolveContext.kt:468) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.withRegularClass(FirDeclarationsResolveTransformer.kt:877) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.doTransformRegularClass(FirDeclarationsResolveTransformer.kt:868) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformRegularClass(FirDeclarationsResolveTransformer.kt:769) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformRegularClass(FirAbstractBodyResolveTransformerDispatcher.kt:522) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformRegularClass(FirAbstractBodyResolveTransformerDispatcher.kt:24) + at org.jetbrains.kotlin.fir.declarations.FirRegularClass.transform(FirRegularClass.kt:52) + at org.jetbrains.kotlin.fir.visitors.FirTransformerUtilKt.transformInplace(FirTransformerUtil.kt:20) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.transformDeclarations(FirFileImpl.kt:80) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.transformChildren(FirFileImpl.kt:65) + at org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl.transformChildren(FirFileImpl.kt:29) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformElement(FirAbstractBodyResolveTransformerDispatcher.kt:89) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirAbstractBodyResolveTransformerDispatcher.transformDeclarationContent(FirAbstractBodyResolveTransformerDispatcher.kt:441) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformDeclarationContent(FirDeclarationsResolveTransformer.kt:83) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.doTransformFile$lambda$53(FirDeclarationsResolveTransformer.kt:841) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.withFile(FirDeclarationsResolveTransformer.kt:854) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.doTransformFile(FirDeclarationsResolveTransformer.kt:840) + at org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformFile(FirDeclarationsResolveTransformer.kt:747) + ... 48 more + + +error message: Daemon compilation failed: null +java.lang.Exception + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69) + at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159) + at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111) + at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:76) + at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62) + at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44) + at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209) + at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66) + at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166) + at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59) + at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53) + at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41) + at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59) + at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572) + at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) + at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64) + at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) + at java.base/java.lang.Thread.run(Thread.java:1583) +Caused by: java.nio.file.NoSuchFileException: /tmp/kotlin-backups1029090002252849674/278.backup -> /home/shihaam/git/sargit/thijooree/app/build/tmp/kotlin-classes/debug/sh/sar/basedbank/ui/home/TransferFragment$lookupAccount$1.class + at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) + at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106) + at java.base/sun.nio.fs.UnixFileSystem.move(UnixFileSystem.java:939) + at java.base/sun.nio.fs.UnixFileSystemProvider.move(UnixFileSystemProvider.java:309) + at java.base/java.nio.file.Files.move(Files.java:1431) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.revertChanges(CompilationTransaction.kt:231) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.close(CompilationTransaction.kt:256) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:762) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:128) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:678) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1805) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) + at java.base/java.lang.reflect.Method.invoke(Method.java:580) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) + at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:714) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721) + at java.base/java.security.AccessController.doPrivileged(AccessController.java:400) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720) + ... 3 more + + diff --git a/app/src/main/java/sh/sar/basedbank/api/bml/BmlCardClient.kt b/app/src/main/java/sh/sar/basedbank/api/bml/BmlCardClient.kt new file mode 100644 index 0000000..0bce14d --- /dev/null +++ b/app/src/main/java/sh/sar/basedbank/api/bml/BmlCardClient.kt @@ -0,0 +1,55 @@ +package sh.sar.basedbank.api.bml + +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import sh.sar.basedbank.api.models.BankServerException + +data class BmlCardActionResult( + val success: Boolean, + val message: String +) + +class BmlCardClient { + + private val client = newBmlApiClient() + + /** + * Freezes or unfreezes a BML card. + * @param cardId BML card UUID (BankAccount.internalId) + * @param action "freeze" or "unfreeze" + */ + fun setCardFreezeState(session: BmlSession, cardId: String, action: String): BmlCardActionResult { + val body = JSONObject().apply { + put("card", cardId) + put("action", action) + }.toString().toRequestBody("application/json".toMediaType()) + + val request = Request.Builder() + .url("$BML_BASE_URL/api/mobile/services/card/freeze") + .post(body) + .header("Authorization", "Bearer ${session.accessToken}") + .header("User-Agent", BML_USER_AGENT) + .header("x-app-version", BML_APP_VERSION) + .header("accept", "application/json") + .build() + + val resp = client.newCall(request).execute() + val code = resp.code + val responseBody = resp.body?.string() + resp.close() + if (code == 401 || code == 419) throw AuthExpiredException() + if (code in 500..599) throw BankServerException("BML") + return try { + val json = JSONObject(responseBody ?: "") + val ok = json.optBoolean("success") && json.optInt("code") == 0 + BmlCardActionResult( + success = ok, + message = json.optString("payload").ifBlank { json.optString("message") } + ) + } catch (_: Exception) { + BmlCardActionResult(success = false, message = "") + } + } +} diff --git a/app/src/main/java/sh/sar/basedbank/api/mib/MibCardsClient.kt b/app/src/main/java/sh/sar/basedbank/api/mib/MibCardsClient.kt index b66f83b..b81788e 100644 --- a/app/src/main/java/sh/sar/basedbank/api/mib/MibCardsClient.kt +++ b/app/src/main/java/sh/sar/basedbank/api/mib/MibCardsClient.kt @@ -7,10 +7,18 @@ import okhttp3.Request import org.json.JSONObject import java.util.concurrent.TimeUnit +data class MibCardActionResult( + val success: Boolean, + val message: String, + val currentStatusCode: String +) + class MibCardsClient { private val BASE_WV_URL = "https://faisamobilex-wv.mib.com.mv" + private val USER_AGENT = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36" + private val client = OkHttpClient.Builder() .connectTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) @@ -20,7 +28,7 @@ class MibCardsClient { "mbmodel=IOS-1.0; xxid=${session.xxid}; IBSID=${session.xxid}; " + "mbnonce=${session.nonceGenerator}; time-tracker=597" - fun fetchCards(session: MibSession, loginTag: String): List { + fun fetchCards(session: MibSession, loginTag: String, profileId: String = ""): List { val body = FormBody.Builder() .add("name", "") .add("start", "1") @@ -32,7 +40,7 @@ class MibCardsClient { .url("$BASE_WV_URL/ajaxDebitCard/fetchCardInfos") .post(body) .header("Cookie", cookieHeader(session)) - .header("User-Agent", "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/129.0.6668.70 Mobile Safari/537.36") + .header("User-Agent", USER_AGENT) .header("X-Requested-With", "XMLHttpRequest") .header("Accept", "*/*") .header("Origin", BASE_WV_URL) @@ -55,9 +63,42 @@ class MibCardsClient { customerId = item.optString("customerId"), phoneNumber = item.optString("phoneNumber"), cardHolderName = item.optString("cardHolderName"), - loginTag = loginTag + loginTag = loginTag, + profileId = profileId ) } } } + + /** Freezes a MIB card. action = "freeze" or "unfreeze". */ + fun setCardFreezeState(session: MibSession, cardId: String, action: String, comments: String): MibCardActionResult { + val body = FormBody.Builder() + .add("cardId", cardId) + .add("comments", comments) + .build() + + val request = Request.Builder() + .url("$BASE_WV_URL/ajaxDebitCard/$action") + .post(body) + .header("Cookie", cookieHeader(session)) + .header("User-Agent", USER_AGENT) + .header("X-Requested-With", "XMLHttpRequest") + .header("Accept", "*/*") + .header("Origin", BASE_WV_URL) + .header("Referer", "$BASE_WV_URL//debitCards/manage?cardId=$cardId&dashurl=1") + .build() + + return client.newCall(request).execute().use { response -> + val bodyStr = response.body?.string() + ?: return MibCardActionResult(false, "", "") + val json = try { JSONObject(bodyStr) } catch (_: Exception) { + return MibCardActionResult(false, "", "") + } + MibCardActionResult( + success = json.optBoolean("success"), + message = json.optString("reasonText"), + currentStatusCode = json.optString("currentStatusCode") + ) + } + } } diff --git a/app/src/main/java/sh/sar/basedbank/api/mib/MibModels.kt b/app/src/main/java/sh/sar/basedbank/api/mib/MibModels.kt index edb4c73..d666bc2 100644 --- a/app/src/main/java/sh/sar/basedbank/api/mib/MibModels.kt +++ b/app/src/main/java/sh/sar/basedbank/api/mib/MibModels.kt @@ -55,7 +55,8 @@ data class MibCard( val customerId: String, val phoneNumber: String, val cardHolderName: String, - val loginTag: String + val loginTag: String, + val profileId: String = "" ) data class MibFinanceDeal( diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt index c030a67..28c322b 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/DashboardFragment.kt @@ -116,7 +116,7 @@ class DashboardFragment : Fragment() { val credStore = CredentialStore(requireContext()) val hidden = credStore.getHiddenDashboardCardNumbers() val mibItems = (viewModel.mibCards.value ?: emptyList()) - .filter { !hidden.contains(it.maskedCardNumber) } + .filter { CardsFragment.isMibCardActive(it.cardStatus) && !hidden.contains(it.maskedCardNumber) } .map { CardItem.Mib(it) } val bmlItems = (viewModel.accounts.value ?: emptyList()) .filter { (it.profileType == "BML_PREPAID" || it.profileType == "BML_CREDIT" || it.profileType == "BML_DEBIT") && it.statusDesc.equals("Active", ignoreCase = true) && !hidden.contains(it.accountNumber) } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index f21729d..233c5d3 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -1130,14 +1130,14 @@ fun applyNavLabelVisibility() { val fresh = withContext(Dispatchers.IO) { val sess = app.bmlSessionFor(src) ?: return@withContext null try { - val accounts = BmlAccountClient().fetchAccounts(sess, src.loginTag) + val accounts = BmlAccountClient().fetchAccounts(sess, src.loginTag, src.profileName, src.profileId) AccountCache.saveBml(this@HomeActivity, loginId, accounts) - val otherBml = app.bmlAccounts.filter { it.loginTag != src.loginTag } + val otherBml = app.bmlAccounts.filter { it.loginTag != src.loginTag || it.profileId != src.profileId } app.bmlAccounts = otherBml + accounts accounts } catch (_: Exception) { null } } ?: return@launch - val otherAccounts = current.filter { it.bank != "BML" || it.loginTag != src.loginTag } + val otherAccounts = current.filter { it.bank != "BML" || it.loginTag != src.loginTag || it.profileId != src.profileId } viewModel.accounts.postValue(otherAccounts + fresh) } else { val loginId = src.loginTag.removePrefix("mib_") @@ -1220,7 +1220,7 @@ fun applyNavLabelVisibility() { for (profile in profiles) { try { flow.switchProfile(session, profile) - for (card in client.fetchCards(session, "mib_$loginId")) { + for (card in client.fetchCards(session, "mib_$loginId", profile.profileId)) { if (seen.add(card.cardId)) result += card } } catch (_: Exception) { } diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt index f9c4041..70c4692 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/PayWithCardFragment.kt @@ -37,12 +37,19 @@ import com.google.android.material.button.MaterialButton import com.google.android.material.color.MaterialColors import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import sh.sar.basedbank.BasedBankApp import sh.sar.basedbank.R +import sh.sar.basedbank.api.bml.BmlCardClient import sh.sar.basedbank.api.bml.BmlTapToPayClient +import sh.sar.basedbank.api.mib.MibCardsClient import sh.sar.basedbank.nfc.BmlHostCardEmulatorService import sh.sar.basedbank.api.mib.MibCard +import android.text.InputType +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout import sh.sar.basedbank.databinding.FragmentCardsBinding import sh.sar.basedbank.util.CardsCache import sh.sar.basedbank.util.CredentialStore @@ -63,6 +70,8 @@ class CardsFragment : Fragment() { private var cardWidth: Int = 0 private var pendingQrCardNumber: String? = null private var isManageMode: Boolean = false + private var managedCardKey: String? = null + private var freezeInFlight: Boolean = false private val qrLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult @@ -155,8 +164,14 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets -> insets } - viewModel.mibCards.observe(viewLifecycleOwner) { rebuildCards() } - viewModel.accounts.observe(viewLifecycleOwner) { rebuildCards() } + viewModel.mibCards.observe(viewLifecycleOwner) { + rebuildCards() + rebindManagedCardIfNeeded() + } + viewModel.accounts.observe(viewLifecycleOwner) { + rebuildCards() + rebindManagedCardIfNeeded() + } val cached = CardsCache.load(requireContext()) if (cached.isNotEmpty()) { @@ -247,20 +262,161 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets -> Toast.makeText(requireContext(), R.string.work_in_progress, Toast.LENGTH_SHORT).show() } binding.btnChangePin.setOnClickListener(wip) - binding.btnFreeze.setOnClickListener(wip) + binding.btnFreeze.setOnClickListener { + when (val item = cards.getOrNull(currentCardPosition)) { + is CardItem.Bml -> confirmBmlFreezeToggle(item) + is CardItem.Mib -> confirmMibFreezeToggle(item) + null -> {} + } + } binding.btnBlock.setOnClickListener(wip) } + private fun confirmBmlFreezeToggle(item: CardItem.Bml) { + if (freezeInFlight) return + val frozen = isBmlFrozen(item.account.statusDesc) + val titleRes = if (frozen) R.string.card_unfreeze_confirm_title else R.string.card_freeze_confirm_title + val messageRes = if (frozen) R.string.card_unfreeze_confirm_message else R.string.card_freeze_confirm_message + val confirmRes = if (frozen) R.string.card_action_unfreeze else R.string.card_action_freeze + MaterialAlertDialogBuilder(requireContext()) + .setTitle(titleRes) + .setMessage(messageRes) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(confirmRes) { _, _ -> performBmlFreezeToggle(item, freeze = !frozen) } + .show() + } + + private fun confirmMibFreezeToggle(item: CardItem.Mib) { + if (freezeInFlight) return + val frozen = isMibCardFrozen(item.card.cardStatus) + val titleRes = if (frozen) R.string.card_unfreeze_confirm_title else R.string.card_freeze_confirm_title + val messageRes = if (frozen) R.string.card_unfreeze_confirm_message else R.string.card_freeze_confirm_message + val confirmRes = if (frozen) R.string.card_action_unfreeze else R.string.card_action_freeze + + val ctx = requireContext() + val dp = resources.displayMetrics.density + val inputLayout = TextInputLayout(ctx).apply { + hint = getString(R.string.card_freeze_comments_hint) + val pad = (16 * dp).toInt() + setPadding(pad, pad / 2, pad, 0) + } + val input = TextInputEditText(ctx).apply { + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + maxLines = 3 + } + inputLayout.addView(input) + + MaterialAlertDialogBuilder(ctx) + .setTitle(titleRes) + .setMessage(messageRes) + .setView(inputLayout) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(confirmRes) { _, _ -> + val comments = input.text?.toString()?.trim().orEmpty() + performMibFreezeToggle(item, freeze = !frozen, comments = comments) + } + .show() + } + + private fun performMibFreezeToggle(item: CardItem.Mib, freeze: Boolean, comments: String) { + val app = requireActivity().application as BasedBankApp + val action = if (freeze) "freeze" else "unfreeze" + val successRes = if (freeze) R.string.card_freeze_success else R.string.card_unfreeze_success + val loginId = item.card.loginTag.removePrefix("mib_") + val session = app.mibSessions[loginId] + if (session == null) { + Toast.makeText(requireContext(), R.string.transfer_session_unavailable, Toast.LENGTH_SHORT).show() + return + } + val profiles = app.mibProfilesMap[loginId] ?: emptyList() + val ownerProfile = profiles.firstOrNull { it.profileId == item.card.profileId } + ?: profiles.firstOrNull { it.customerId == item.card.customerId } + freezeInFlight = true + binding.btnFreeze.isEnabled = false + (activity as? HomeActivity)?.setRefreshing(true) + viewLifecycleOwner.lifecycleScope.launch { + val result = withContext(Dispatchers.IO) { + runCatching { + app.mibMutex.withLock { + if (ownerProfile != null) { + app.mibFlowFor(loginId).switchProfile(session, ownerProfile) + } + MibCardsClient().setCardFreezeState(session, item.card.cardId, action, comments) + } + } + } + freezeInFlight = false + if (!isAdded || _binding == null) { + (activity as? HomeActivity)?.setRefreshing(false) + return@launch + } + binding.btnFreeze.isEnabled = true + (activity as? HomeActivity)?.setRefreshing(false) + val response = result.getOrNull() + if (response?.success == true) { + Toast.makeText(requireContext(), successRes, Toast.LENGTH_SHORT).show() + (activity as? HomeActivity)?.triggerRefreshCards() + } else { + val msg = response?.message?.takeIf { it.isNotBlank() } + ?: result.exceptionOrNull()?.message + ?: getString(R.string.card_freeze_failed) + Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() + } + } + } + + private fun performBmlFreezeToggle(item: CardItem.Bml, freeze: Boolean) { + val app = requireActivity().application as BasedBankApp + val action = if (freeze) "freeze" else "unfreeze" + val successRes = if (freeze) R.string.card_freeze_success else R.string.card_unfreeze_success + freezeInFlight = true + binding.btnFreeze.isEnabled = false + (activity as? HomeActivity)?.setRefreshing(true) + viewLifecycleOwner.lifecycleScope.launch { + val session = app.bmlSessionFor(item.account) + if (session == null) { + freezeInFlight = false + if (_binding != null) binding.btnFreeze.isEnabled = true + (activity as? HomeActivity)?.setRefreshing(false) + Toast.makeText(requireContext(), R.string.transfer_session_unavailable, Toast.LENGTH_SHORT).show() + return@launch + } + val result = withContext(Dispatchers.IO) { + runCatching { BmlCardClient().setCardFreezeState(session, item.account.internalId, action) } + } + freezeInFlight = false + if (!isAdded || _binding == null) { + (activity as? HomeActivity)?.setRefreshing(false) + return@launch + } + binding.btnFreeze.isEnabled = true + (activity as? HomeActivity)?.setRefreshing(false) + val response = result.getOrNull() + if (response?.success == true) { + Toast.makeText(requireContext(), successRes, Toast.LENGTH_SHORT).show() + (activity as? HomeActivity)?.refreshBalances(item.account) + } else { + val msg = response?.message?.takeIf { it.isNotBlank() } + ?: result.exceptionOrNull()?.message + ?: getString(R.string.card_freeze_failed) + Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show() + } + } + } + private fun setManageMode(enabled: Boolean) { isManageMode = enabled + if (!enabled) managedCardKey = null requireActivity().title = getString(if (enabled) R.string.card_manage else R.string.nav_pay_with_card) if (enabled) enterManageMode() else exitManageMode() } - private fun enterManageMode() { - val item = cards.getOrNull(currentCardPosition) ?: return + private fun cardItemKey(item: CardItem): String = when (item) { + is CardItem.Bml -> "bml:${item.account.accountNumber}" + is CardItem.Mib -> "mib:${item.card.cardId}" + } - // Bind card data + private fun bindManageCardData(item: CardItem) { val cv = binding.manageCardView when (item) { is CardItem.Mib -> { @@ -270,7 +426,7 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets -> if (assetPath != null) loadCardImage(cv.ivCardImage, assetPath) else cv.ivCardImage.setImageDrawable(null) bindCardStatus(cv.tvCardStatus, mibCardStatusLabel(item.card.cardStatus)) - cv.root.alpha = 1f + cv.root.alpha = if (isMibCardActive(item.card.cardStatus)) 1f else 0.45f } is CardItem.Bml -> { cv.tvCardOwner.text = item.account.accountBriefName @@ -281,6 +437,37 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets -> cv.root.alpha = if (isActive) 1f else 0.45f } } + val isFrozen = when (item) { + is CardItem.Bml -> isBmlFrozen(item.account.statusDesc) + is CardItem.Mib -> isMibCardFrozen(item.card.cardStatus) + } + binding.btnFreeze.setText(if (isFrozen) R.string.card_action_unfreeze else R.string.card_action_freeze) + // MIB doesn't allow change PIN / block while a card is frozen; BML still does. + val mibFrozen = item is CardItem.Mib && isMibCardFrozen(item.card.cardStatus) + binding.btnChangePin.isEnabled = !mibFrozen + binding.btnBlock.isEnabled = !mibFrozen + } + + private fun rebindManagedCardIfNeeded() { + if (!isManageMode) return + val key = managedCardKey ?: return + val newPos = cards.indexOfFirst { cardItemKey(it) == key } + if (newPos < 0) return + if (newPos != currentCardPosition) { + currentCardPosition = newPos + binding.rvCards.scrollToPosition(newPos) + } + bindManageCardData(cards[newPos]) + } + + private fun isBmlFrozen(statusDesc: String): Boolean = + statusDesc.equals("Block Plastic", ignoreCase = true) + + private fun enterManageMode() { + val item = cards.getOrNull(currentCardPosition) ?: return + managedCardKey = cardItemKey(item) + + bindManageCardData(item) // Capture positions BEFORE layout changes (for enter animation + exit animation later) val contentLoc = IntArray(2).also { binding.contentLayout.getLocationOnScreen(it) } @@ -716,7 +903,9 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets -> .map { CardItem.Bml(it) } val bmlActive = bmlItems.filter { it.account.statusDesc.equals("Active", ignoreCase = true) } val bmlInactive = bmlItems.filter { !it.account.statusDesc.equals("Active", ignoreCase = true) } - val all: List = bmlActive + mibItems + bmlInactive + val mibActive = mibItems.filter { isMibCardActive(it.card.cardStatus) } + val mibInactive = mibItems.filter { !isMibCardActive(it.card.cardStatus) } + val all: List = bmlActive + mibActive + bmlInactive + mibInactive // Move default BML card to front cards = if (defaultNum != null) { val def = all.filterIsInstance().firstOrNull { it.account.accountNumber == defaultNum } @@ -903,7 +1092,7 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets -> if (assetPath != null) loadCardImage(ivCardImage, assetPath) else ivCardImage.setImageDrawable(null) bindCardStatus(tvCardStatus, mibCardStatusLabel(item.card.cardStatus)) - itemView.alpha = 1f + itemView.alpha = if (isMibCardActive(item.card.cardStatus)) 1f else 0.45f } is CardItem.Bml -> { tvCardOwner.text = item.account.accountBriefName @@ -1038,9 +1227,13 @@ ViewCompat.setOnApplyWindowInsetsListener(binding.contentLayout) { v, insets -> fun mibCardStatusLabel(cardStatus: String): String? = when (cardStatus) { "CHST0" -> null + "CHST20" -> "Temporary blocked by client" else -> cardStatus } + fun isMibCardActive(cardStatus: String): Boolean = cardStatus == "CHST0" + fun isMibCardFrozen(cardStatus: String): Boolean = cardStatus == "CHST20" + fun bindCardStatus(tv: TextView, statusLabel: String?) { if (statusLabel == null) { tv.visibility = View.GONE; return } tv.visibility = View.VISIBLE diff --git a/app/src/main/java/sh/sar/basedbank/util/CardsCache.kt b/app/src/main/java/sh/sar/basedbank/util/CardsCache.kt index 76b9d38..8151899 100644 --- a/app/src/main/java/sh/sar/basedbank/util/CardsCache.kt +++ b/app/src/main/java/sh/sar/basedbank/util/CardsCache.kt @@ -23,6 +23,7 @@ object CardsCache { put("phoneNumber", c.phoneNumber) put("cardHolderName", c.cardHolderName) put("loginTag", c.loginTag) + put("profileId", c.profileId) }) } context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) @@ -45,7 +46,8 @@ object CardsCache { customerId = o.optString("customerId"), phoneNumber = o.optString("phoneNumber"), cardHolderName = o.optString("cardHolderName"), - loginTag = o.optString("loginTag") + loginTag = o.optString("loginTag"), + profileId = o.optString("profileId") ) } } catch (_: Exception) { emptyList() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1700a98..d03fbfe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -369,7 +369,17 @@ Hide from Dashboard Change PIN Freeze + Unfreeze Block + Freeze card? + This will temporarily stop the card from being used. You can unfreeze it anytime you want to use it again. + Unfreeze card? + This will re-enable the card for transactions. + Card frozen + Card unfrozen + Failed to update card status + Reason (optional) + Temporary blocked by client No cards found