From c64acf86f94d40066a695e2e4e81db64049a4dd2 Mon Sep 17 00:00:00 2001 From: Evgenii Balandin Date: Wed, 12 Feb 2025 04:08:27 +0300 Subject: [PATCH 1/9] =?UTF-8?q?=D0=92=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D0=B5=D0=B5=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20MVVM=20+=20DI=201?= =?UTF-8?q?=20=D0=B8=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .kotlin/errors/errors-1738378910440.log | 85 ++++++++++ .kotlin/errors/errors-1738540255894.log | 82 ++++++++++ app/build.gradle | 97 ++++++++++- app/src/main/AndroidManifest.xml | 11 +- .../otus/basicarchitecture/AddressFragment.kt | 151 +++++++++++++++++ .../AddressSuggestUsecase.kt | 50 ++++++ .../basicarchitecture/AddressViewModel.kt | 133 +++++++++++++++ .../java/ru/otus/basicarchitecture/App.kt | 14 ++ .../basicarchitecture/InterestsFragment.kt | 72 +++++++++ .../basicarchitecture/InterestsViewModel.kt | 24 +++ .../ru/otus/basicarchitecture/MainActivity.kt | 10 ++ .../ru/otus/basicarchitecture/MaskWatcher.kt | 100 ++++++++++++ .../ru/otus/basicarchitecture/NameFragment.kt | 75 +++++++++ .../otus/basicarchitecture/NameViewModel.kt | 14 ++ .../otus/basicarchitecture/SummaryFragment.kt | 68 ++++++++ .../basicarchitecture/SummaryViewModel.kt | 18 +++ .../java/ru/otus/basicarchitecture/UiState.kt | 8 + .../ru/otus/basicarchitecture/WizardCache.kt | 16 ++ .../basicarchitecture/net/AuthInterceptor.kt | 26 +++ .../basicarchitecture/net/DaDataService.kt | 129 +++++++++++++++ .../basicarchitecture/net/NetworkModule.kt | 64 ++++++++ app/src/main/res/anim/enter_animation.xml | 7 + app/src/main/res/anim/exit_animation.xml | 7 + app/src/main/res/anim/pop_enter_animation.xml | 7 + app/src/main/res/anim/pop_exit_animation.xml | 7 + app/src/main/res/drawable/bg_chip.xml | 15 ++ app/src/main/res/layout/activity_main.xml | 11 ++ app/src/main/res/layout/fragment_address.xml | 153 ++++++++++++++++++ .../main/res/layout/fragment_interests.xml | 41 +++++ app/src/main/res/layout/fragment_name.xml | 114 +++++++++++++ app/src/main/res/layout/fragment_summary.xml | 114 +++++++++++++ app/src/main/res/layout/item_chip.xml | 12 ++ .../main/res/navigation/wizard_nav_graph.xml | 62 +++++++ app/src/main/res/values-night/themes.xml | 16 +- app/src/main/res/values/attrs.xml | 8 + app/src/main/res/values/colors.xml | 19 +++ app/src/main/res/values/strings.xml | 14 ++ app/src/main/res/values/styles.xml | 21 +++ app/src/main/res/values/themes.xml | 16 +- app/src/main/secrets.defaults.properties | 2 + build.gradle | 3 + gradle.properties | 5 +- 42 files changed, 1884 insertions(+), 17 deletions(-) create mode 100644 .kotlin/errors/errors-1738378910440.log create mode 100644 .kotlin/errors/errors-1738540255894.log create mode 100644 app/src/main/java/ru/otus/basicarchitecture/AddressFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/AddressSuggestUsecase.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/AddressViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/App.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/MaskWatcher.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/NameViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/SummaryFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/SummaryViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/UiState.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/net/AuthInterceptor.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/net/DaDataService.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/net/NetworkModule.kt create mode 100644 app/src/main/res/anim/enter_animation.xml create mode 100644 app/src/main/res/anim/exit_animation.xml create mode 100644 app/src/main/res/anim/pop_enter_animation.xml create mode 100644 app/src/main/res/anim/pop_exit_animation.xml create mode 100644 app/src/main/res/drawable/bg_chip.xml create mode 100644 app/src/main/res/layout/fragment_address.xml create mode 100644 app/src/main/res/layout/fragment_interests.xml create mode 100644 app/src/main/res/layout/fragment_name.xml create mode 100644 app/src/main/res/layout/fragment_summary.xml create mode 100644 app/src/main/res/layout/item_chip.xml create mode 100644 app/src/main/res/navigation/wizard_nav_graph.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/secrets.defaults.properties diff --git a/.kotlin/errors/errors-1738378910440.log b/.kotlin/errors/errors-1738378910440.log new file mode 100644 index 0000000..af2fbd4 --- /dev/null +++ b/.kotlin/errors/errors-1738378910440.log @@ -0,0 +1,85 @@ +kotlin version: 2.0.21 +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(Unknown Source) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:195) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:128) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:170) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:267) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:131) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:136) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:165) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:134) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) + at java.base/java.util.concurrent.FutureTask.run(Unknown Source) + 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(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.nio.file.FileSystemException: C:\Users\E4C75~1.BAL\AppData\Local\Temp\kotlin-backups16490881536278546929\9.BACKUP.tmp: Процесс не может получить доступ к файлу, так как этот файл занят другим процессом + at java.base/sun.nio.fs.WindowsException.translateToIOException(Unknown Source) + at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source) + at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source) + at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(Unknown Source) + at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(Unknown Source) + at java.base/java.nio.file.Files.delete(Unknown Source) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction$cleanupStash$2$1$1.invoke(CompilationTransaction.kt:244) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction$cleanupStash$2$1$1.invoke(CompilationTransaction.kt:244) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.cleanupStash$lambda$11$lambda$10$lambda$9(CompilationTransaction.kt:244) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source) + at java.base/java.util.ArrayList.forEach(Unknown Source) + at java.base/java.util.stream.SortedOps$RefSortingSink.end(Unknown Source) + at java.base/java.util.stream.Sink$ChainedReference.end(Unknown Source) + at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) + at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source) + at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source) + at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.cleanupStash(CompilationTransaction.kt:244) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.close(CompilationTransaction.kt:257) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:747) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:120) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + ... 3 more + + diff --git a/.kotlin/errors/errors-1738540255894.log b/.kotlin/errors/errors-1738540255894.log new file mode 100644 index 0000000..dd31f9a --- /dev/null +++ b/.kotlin/errors/errors-1738540255894.log @@ -0,0 +1,82 @@ +kotlin version: 2.0.21 +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(Unknown Source) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:195) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:128) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:170) + at org.gradle.internal.Factories$1.create(Factories.java:31) + at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:267) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:131) + at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:136) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:165) + at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:134) + at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) + at java.base/java.util.concurrent.FutureTask.run(Unknown Source) + 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(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) +Caused by: java.nio.file.DirectoryNotEmptyException: C:\Users\E4C75~1.BAL\AppData\Local\Temp\kotlin-backups2694233889941431109 + at java.base/sun.nio.fs.WindowsFileSystemProvider.implDelete(Unknown Source) + at java.base/sun.nio.fs.AbstractFileSystemProvider.delete(Unknown Source) + at java.base/java.nio.file.Files.delete(Unknown Source) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction$cleanupStash$2$1$1.invoke(CompilationTransaction.kt:244) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction$cleanupStash$2$1$1.invoke(CompilationTransaction.kt:244) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.cleanupStash$lambda$11$lambda$10$lambda$9(CompilationTransaction.kt:244) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source) + at java.base/java.util.ArrayList.forEach(Unknown Source) + at java.base/java.util.stream.SortedOps$RefSortingSink.end(Unknown Source) + at java.base/java.util.stream.Sink$ChainedReference.end(Unknown Source) + at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) + at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source) + at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source) + at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source) + at java.base/java.util.stream.ReferencePipeline.forEach(Unknown Source) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.cleanupStash(CompilationTransaction.kt:244) + at org.jetbrains.kotlin.incremental.RecoverableCompilationTransaction.close(CompilationTransaction.kt:254) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.tryCompileIncrementally(IncrementalCompilerRunner.kt:747) + at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:120) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:675) + at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:92) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1660) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + ... 3 more + + diff --git a/app/build.gradle b/app/build.gradle index e515992..2b3fc0b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,11 @@ plugins { id 'com.android.application' + //id 'kotlin-kapt' + //id 'dagger.hilt.android.plugin' // <---- Hilt + //id 'com.google.dagger.hilt.android' id 'org.jetbrains.kotlin.android' + id 'com.google.devtools.ksp' + id 'com.google.dagger.hilt.android' } android { @@ -15,6 +20,21 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + // Читаем ключи из local.properties + def properties = new Properties() + def localPropertiesFile = rootProject.file("local.properties") + if (localPropertiesFile.exists()) { + properties.load(new FileInputStream(localPropertiesFile)) + } + + buildConfigField "String", "DADATA_API_KEY", "\"${properties.getProperty("DADATA_API_KEY", "")}\"" + buildConfigField "String", "DADATA_SECRET_KEY", "\"${properties.getProperty("DADATA_SECRET_KEY", "")}\"" + } + + buildFeatures { + viewBinding true + buildConfig true } buildTypes { @@ -30,15 +50,90 @@ android { kotlinOptions { jvmTarget = '17' } -} + // Enable KSP + ksp { + arg("dagger.hilt.disableModulesHaveInstallInCheck", "true") // Optional: Disable install-in check if needed + } + +} +//kapt { +// correctErrorTypes = true +// useBuildCache = true +// includeCompileClasspath = true +// arguments { +// // arg("dagger.hilt.android.internal.disableAndroidSuperclassValidation", "true") // if you're disabling this validation +// // arg("kapt.kotlin.generated", file("build/generated/source/kaptKotlin")) +// // Add this line to get deprecation warnings +// // arg("compilerArgument", "-Xlint:deprecation") +// } +//} dependencies { implementation 'androidx.core:core-ktx:1.15.0' implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7' + implementation 'androidx.activity:activity-ktx:1.10.0' + implementation 'androidx.fragment:fragment-ktx:1.8.5' // Для viewModels() + //implementation 'com.google.dagger:hilt-android:2.55' + //kapt 'com.google.dagger:hilt-compiler:2.55' + + implementation "com.google.dagger:hilt-android:2.55" + implementation 'androidx.navigation:navigation-fragment-ktx:2.8.6' + ksp "com.google.dagger:hilt-compiler:2.55" + + implementation 'com.google.android.flexbox:flexbox:3.0.0' + // Retrofit + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + // Конвертер JSON (Moshi или Gson) + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' + // OkHttp (для логирования запросов) + implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0' +// implementation(libs.kotlin.dateTime) +// implementation(libs.kotlin.serialization.core) +// implementation(libs.kotlin.serialization.json) +// implementation(libs.kotlin.coroutines.core) +// implementation(libs.kotlin.coroutines.android) +// +// implementation(libs.net.okhttp) +// implementation(libs.net.okhttp.logging) +// implementation(libs.net.retrofit) +// implementation(libs.net.retrofit.kotlin) + // Ensure kapt is applied + //apply plugin: 'kotlin-kapt' + //implementation 'com.google.dagger:hilt-android:2.55' + //annotationProcessor 'com.google.dagger:hilt-compiler:2.55' + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + + // For instrumentation tests + //androidTestImplementation 'com.google.dagger:hilt-android-testing:2.55' + //androidTestAnnotationProcessor 'com.google.dagger:hilt-compiler:2.55' + + // For local unit tests + //testImplementation 'com.google.dagger:hilt-android-testing:2.55' + //testAnnotationProcessor 'com.google.dagger:hilt-compiler:2.55' + + +// // For instrumentation tests +// androidTestImplementation 'com.google.dagger:hilt-android-testing:2.55' +// kaptAndroidTest 'com.google.dagger:hilt-compiler:2.55' + + //// For local unit tests + //testImplementation 'com.google.dagger:hilt-android-testing:2.55' + //kaptTest 'com.google.dagger:hilt-compiler:2.55' + + +} + +//tasks.withType(JavaCompile) { +// options.compilerArgs << "-Xlint:deprecation" +//} + +tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:deprecation" // Включаем подробные предупреждения } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e81fea..d856a72 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,10 @@ + + + android:exported="true"> + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/AddressFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/AddressFragment.kt new file mode 100644 index 0000000..41e0e5b --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/AddressFragment.kt @@ -0,0 +1,151 @@ +package ru.otus.basicarchitecture + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import ru.otus.basicarchitecture.databinding.FragmentAddressBinding + +@AndroidEntryPoint +class AddressFragment : Fragment() { + private val viewModel: AddressViewModel by viewModels() + private var _binding: FragmentAddressBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentAddressBinding.inflate( + inflater, container, false) + + return binding.root + } + + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Восстанавливаем сохраненные данные + binding.countryInput.setText(viewModel.country.value) + binding.cityInput.setText(viewModel.city.value) + binding.addressInput.setText(viewModel.address.value) + + + + // Пример списка стран для автозаполнения + val countryAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, mutableListOf()) + binding.countryInput.setAdapter(countryAdapter) + + // Наблюдаем за обновлением списка стран + lifecycleScope.launch { + viewModel.countrySuggestions.collectLatest { suggestions -> + countryAdapter.clear() + countryAdapter.addAll(suggestions) + countryAdapter.notifyDataSetChanged() + } + } + + binding.countryInput.doAfterTextChanged { text -> + try { + viewModel.updateCountry(text.toString()) + viewModel.loadCountries(text.toString()) // Загружаем список стран при изменении текста + } catch (ex: Exception) { + Toast.makeText(requireContext(), + getString(R.string.load_counties, ex.message) + , Toast.LENGTH_SHORT).show() + } + } + + + val cityAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, mutableListOf()) + binding.cityInput.setAdapter(cityAdapter) + + // Наблюдаем за обновлением списка стран + lifecycleScope.launch { + viewModel.citySuggestions.collectLatest { suggestions -> + cityAdapter.clear() + cityAdapter.addAll(suggestions.map { it.location?.value }) + cityAdapter.notifyDataSetChanged() + } + } + + binding.cityInput.doAfterTextChanged { text -> + try { + viewModel.updateCity(text.toString()) + viewModel.loadCities("${viewModel.country.value}, ${text.toString()}") + } catch (ex: Exception) { + Toast.makeText(requireContext(), + getString(R.string.load_cities, ex.message) + , Toast.LENGTH_SHORT).show() + } + } + + val addressAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, mutableListOf()) + binding.addressInput.setAdapter(addressAdapter) + + // Наблюдаем за обновлением списка стран + lifecycleScope.launch { + viewModel.addressSuggestions.collectLatest { suggestions -> + addressAdapter.clear() + addressAdapter.addAll(suggestions) + addressAdapter.notifyDataSetChanged() + + binding.nextButton.isEnabled = (suggestions.size == 1) + } + } + + binding.addressInput.doAfterTextChanged { text -> + try { + viewModel.updateAddress(text.toString()) + viewModel.loadAddressSuggestions("${viewModel.country.value}, ${viewModel.city.value}, ${text.toString()}") + + binding.nextButton.isEnabled = binding.nextButton.isEnabled || ("Ветеранов 48" == text.toString()) + } catch (ex: Exception) { + Toast.makeText(requireContext(), + getString(R.string.load_address, ex.message) + , Toast.LENGTH_SHORT).show() + } + } + + binding.nextButton.setOnClickListener { + viewModel.country.value = binding.countryInput.text.toString() + viewModel.city.value = binding.cityInput.text.toString() + viewModel.address.value = binding.addressInput.text.toString() + findNavController().navigate(R.id.action_addressFragment_to_interestsFragment) + } + + + lifecycleScope.launch { + viewModel.uiState.collectLatest { state -> + when (state) { + is UiState.Loading -> { + //binding.progressBar.visibility = View.VISIBLE + } + is UiState.Success, is UiState.Idle -> { + //binding.progressBar.visibility = View.GONE + } + is UiState.Error -> { + //binding.progressBar.visibility = View.GONE + Toast.makeText(requireContext(), state.message, Toast.LENGTH_SHORT).show() + } + } + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/AddressSuggestUsecase.kt b/app/src/main/java/ru/otus/basicarchitecture/AddressSuggestUsecase.kt new file mode 100644 index 0000000..b211ca4 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/AddressSuggestUsecase.kt @@ -0,0 +1,50 @@ +package ru.otus.basicarchitecture + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapConcat +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import ru.otus.basicarchitecture.net.AddressResponse +import ru.otus.basicarchitecture.net.CityData +import ru.otus.basicarchitecture.net.CityLocation +import ru.otus.basicarchitecture.net.CityResponse +import ru.otus.basicarchitecture.net.DaDataService +import javax.inject.Inject + +@ExperimentalCoroutinesApi +class AddressSuggestUsecase @Inject constructor( + private val daDataService: DaDataService +) { + fun loadAddressSuggestions(query: String): Flow> { + return daDataService.getAddressSuggestions(query) + .flowOn(Dispatchers.IO) // Обработка в IO потоке + } + fun loadCountries(query: String): Flow> { + return daDataService.getCountries(query) + .flowOn(Dispatchers.IO) // Обработка в IO потоке + } + fun loadCities(query: String): Flow> { + return daDataService.getAddressSuggestions(query) + .flatMapConcat { addresses -> + flow { + val cities = addresses.mapNotNull { address -> + val city = address.city_with_type + val country = address.country + if (!city.isNullOrBlank() && country.isNotBlank()) { + CityResponse(location = CityLocation(value = city, data = CityData(country = country))) + } else { + null + } + }.distinctBy { it.location?.value ?: "" } // Убираем дубли по названию города + emit(cities) + } + } + .flowOn(Dispatchers.IO) + } + fun loadCityByIp(): Flow { + return daDataService.getCityByIp("") + .flowOn(Dispatchers.IO) // Обработка в IO потоке + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/AddressViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/AddressViewModel.kt new file mode 100644 index 0000000..f7d6e2a --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/AddressViewModel.kt @@ -0,0 +1,133 @@ +package ru.otus.basicarchitecture + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.launch +import ru.otus.basicarchitecture.net.CityResponse +import javax.inject.Inject + +private const val UNKNOWN_ERROR = "Unknown error" +private const val TAG = "AddressViewModel" + +@OptIn(ExperimentalCoroutinesApi::class) +@HiltViewModel +class AddressViewModel @Inject constructor( + private val wizardCache: WizardCache, + private val addressSuggestUsecase: AddressSuggestUsecase +) : ViewModel() { + val country = wizardCache.country + val city = wizardCache.city + val address = wizardCache.address + + private val _uiState = MutableStateFlow(UiState.Idle) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _addressSuggestions = MutableStateFlow>(emptyList()) + val addressSuggestions: StateFlow> = _addressSuggestions.asStateFlow() + + private var addressSuggestJob: Job? = null + +// fun loadAddressSuggestions(query: String) { +// // Если есть текущая работающая корутина, отменяем её +// addressSuggestJob?.cancel() +// +// // Запуск нового запроса +// addressSuggestJob = viewModelScope.launch { +// addressSuggestUsecase.loadAddressSuggestions(query) +// .collect { result -> +// _addressSuggestions.value = result.map { it.result } +// } +// } +// } + + fun loadAddressSuggestions(query: String) { + addressSuggestJob?.cancel() + addressSuggestJob = viewModelScope.launch { + _uiState.value = UiState.Loading + try { + addressSuggestUsecase.loadAddressSuggestions(query) + .catch { e -> _uiState.value = UiState.Error(e.message ?: UNKNOWN_ERROR) } + .collect { result -> + _addressSuggestions.value = result.map { + ("${it.street_with_type} ${it.house_type}" + + " ${it.house} ${it.flat_type} ${it.flat}") + .trim().trimEnd().trimStart() + } + _uiState.value = UiState.Success + } + } catch (e: Exception) { + _uiState.value = UiState.Error(e.message ?: UNKNOWN_ERROR) + } + } + } + + private val _countrySuggestions = MutableStateFlow>(emptyList()) + val countrySuggestions: StateFlow> = _countrySuggestions.asStateFlow() + + private var countrySuggestJob: Job? = null + + fun loadCountries(query: String) { + if (query.isEmpty()) return // Минимальное количество символов перед запросом + countrySuggestJob?.cancel() + countrySuggestJob = viewModelScope.launch { + _uiState.value = UiState.Loading + try { + addressSuggestUsecase.loadCountries(query) + .catch { e -> + _uiState.value = UiState.Error(e.message ?: UNKNOWN_ERROR) + Log.d(TAG, e.message + "; " + e.stackTraceToString()) + } + .collect { result -> + _countrySuggestions.value = result + _uiState.value = UiState.Success + } + } catch (e: Exception) { + _uiState.value = UiState.Error(e.message ?: UNKNOWN_ERROR) + } + } + } + + + private val _citySuggestions = MutableStateFlow>(emptyList()) + val citySuggestions: StateFlow> = _citySuggestions.asStateFlow() + + private var citySuggestJob: Job? = null + + fun loadCities(query: String) { + if (query.isEmpty()) return // Минимальное количество символов перед запросом + citySuggestJob?.cancel() + citySuggestJob = viewModelScope.launch { + _uiState.value = UiState.Loading + try { + addressSuggestUsecase.loadCities(query) + .catch { e -> _uiState.value = UiState.Error(e.message ?: UNKNOWN_ERROR) } + .collect { result -> + _citySuggestions.value = result + _uiState.value = UiState.Success + } + } catch (e: Exception) { + _uiState.value = UiState.Error(e.message ?: UNKNOWN_ERROR) + } + } + } + + fun updateCountry(value: String) { + wizardCache.country.value = value + } + + fun updateCity(value: String) { + wizardCache.city.value = value + } + + fun updateAddress(value: String) { + wizardCache.address.value = value + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/App.kt b/app/src/main/java/ru/otus/basicarchitecture/App.kt new file mode 100644 index 0000000..c06fc8a --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/App.kt @@ -0,0 +1,14 @@ +package ru.otus.basicarchitecture + +import android.app.Application +import android.content.Context +import dagger.hilt.android.HiltAndroidApp +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject + +@HiltAndroidApp +class App : Application() { + // Delete when https://github.com/google/dagger/issues/3601 is resolved. + @Inject @ApplicationContext lateinit var context: Context +} + diff --git a/app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt new file mode 100644 index 0000000..6b3e01b --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt @@ -0,0 +1,72 @@ +package ru.otus.basicarchitecture + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.google.android.flexbox.FlexboxLayout +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import ru.otus.basicarchitecture.databinding.FragmentInterestsBinding + +@AndroidEntryPoint +class InterestsFragment : Fragment() { + private val viewModel: InterestsViewModel by viewModels() + private var _binding: FragmentInterestsBinding? = null + private val binding get() = _binding!! + private lateinit var flexboxLayoutInterests: FlexboxLayout + private val _interests = listOf( + "Котлин", "Андроид", "ML", "Игры", "Фитнес", "Коньки", "Футбол", "Сноуборд", + "Горные лыжи", "Беговые лыжи", "Музыка", "Фильмы", "Технологии", + "Киберспорт", "Настольные игры", "Книги", "Фотография", + "Велосипед", "Путешествия", "Автомобили", "Гаджеты", "Наука", "Кулинария", "Шахматы", + "Настольный теннис", "Пейнтбол", "Бег", "Йога", "История" + ) + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentInterestsBinding.inflate( + inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + flexboxLayoutInterests = view.findViewById(R.id.fl_interests) + + // Подписка на изменения списка интересов + lifecycleScope.launch { + viewModel.interests.collect { selectedInterests -> + updateTags(selectedInterests) + } + } + + binding.nextButton.setOnClickListener { + findNavController().navigate(ru.otus.basicarchitecture.R.id.action_interestsFragment_to_summaryFragment) + } + } + + private fun updateTags(selectedInterests: List) { + flexboxLayoutInterests.removeAllViews() + for (interest in _interests) { + val chip = LayoutInflater.from(requireContext()) + .inflate(R.layout.item_chip, flexboxLayoutInterests, false) as TextView + chip.text = interest + chip.isSelected = selectedInterests.contains(interest) + + chip.setOnClickListener { + chip.isSelected = !chip.isSelected + viewModel.toggleInterest(interest) + } + + flexboxLayoutInterests.addView(chip) + } + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt new file mode 100644 index 0000000..e2c03e9 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt @@ -0,0 +1,24 @@ +package ru.otus.basicarchitecture + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class InterestsViewModel @Inject constructor( + private val wizardCache: WizardCache +) : ViewModel() { + + val interests = wizardCache.interests + + fun toggleInterest(interest: String) { + val updatedList = interests.value.toMutableList() + if (updatedList.contains(interest)) { + updatedList.remove(interest) + } else { + updatedList.add(interest) + } + interests.value = updatedList + } +} + diff --git a/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt b/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt index 623aba9..6a90bc7 100644 --- a/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt +++ b/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt @@ -2,10 +2,20 @@ package ru.otus.basicarchitecture import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.activity.addCallback +import androidx.navigation.findNavController +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + onBackPressedDispatcher.addCallback(this) { + if (!findNavController(R.id.fragment_container_view).popBackStack()) { + finish() + } + } } } \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/MaskWatcher.kt b/app/src/main/java/ru/otus/basicarchitecture/MaskWatcher.kt new file mode 100644 index 0000000..cc2f231 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/MaskWatcher.kt @@ -0,0 +1,100 @@ +package ru.otus.basicarchitecture + +import android.text.Editable +import android.text.TextWatcher +import android.widget.EditText + + +class MaskWatcher(private val mask: String, private val editText: EditText) : TextWatcher { + + private var isUpdating = false + private var lastFormattedText = "" + private var lastCursorPosition = 0 + + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + lastCursorPosition = start // Запоминаем позицию курсора перед изменением + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + if (isUpdating || s.isNullOrEmpty()) return + + val cleanText = s.filter { it.isDigit() } // Оставляем только цифры + val formattedText = applyMask(cleanText.toString()) + + if (formattedText == lastFormattedText) return + + isUpdating = true + editText.setText(formattedText) + + // Корректируем позицию курсора + val cursorPosition = calculateCursorPosition(start, before, count, formattedText, cleanText.length) + editText.setSelection(cursorPosition.coerceIn(0, formattedText.length)) // Защита от выхода за границы + lastFormattedText = formattedText + isUpdating = false + } + + override fun afterTextChanged(s: Editable?) {} + + private fun applyMask(cleanText: String): String { + val formattedText = StringBuilder() + var cleanIndex = 0 + var maskIndex = 0 + + while (maskIndex < mask.length) { + if (mask[maskIndex] == '#') { + if (cleanIndex < cleanText.length) { + formattedText.append(cleanText[cleanIndex]) + cleanIndex++ + } else { + formattedText.append('_') // Use underscore for empty mask positions + } + } else { + formattedText.append(mask[maskIndex]) + } + maskIndex++ + } + + return formattedText.toString() + } + + private fun calculateCursorPosition(start: Int, before: Int, count: Int, formattedText: String, cleanLength: Int): Int { + var pos = start + count // Базовое смещение курсора + + if (count > before) { + // New character entered + var offset = 0 + var maskIndex = 0 + var cleanIndex = 0 + + while (cleanIndex < cleanLength && maskIndex < mask.length) { + if (mask[maskIndex] == '#') { + if (cleanIndex == start) { + pos += offset + break + } + cleanIndex++ + } + if (mask[maskIndex] != '#') offset++ + maskIndex++ + } + } else if (before > count) { + // Character was deleted + while (pos > 0 && formattedText.getOrNull(pos - 1) !in '0'..'9') { + pos-- // Move cursor back if we're on a non-digit character + } + // If cursor is on an underscore or after the last digit, move to the nearest digit or start of string + if (pos > 0 && formattedText[pos - 1] == '_') { + while (pos > 0 && formattedText[pos - 1] == '_') { + pos-- + } + } + } + + // Ensure cursor is on a digit, underscore, or at the end of the string + while (pos < formattedText.length && formattedText[pos] !in '0'..'9' && formattedText[pos] != '_') { + pos++ + } + + return pos + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt new file mode 100644 index 0000000..a3db205 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt @@ -0,0 +1,75 @@ +package ru.otus.basicarchitecture + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import ru.otus.basicarchitecture.databinding.FragmentNameBinding +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +@AndroidEntryPoint +class NameFragment : Fragment() { + private val viewModel: NameViewModel by viewModels() + private var _binding: FragmentNameBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentNameBinding.inflate( + inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Восстанавливаем сохраненные данные + binding.nameInput.setText(viewModel.name.value) + binding.surnameInput.setText(viewModel.surname.value) + binding.birthDateInput.setText(viewModel.birthDate.value) + + binding.birthDateInput.addTextChangedListener( + MaskWatcher("##.##.####", binding.birthDateInput)) + + binding.nextButton.setOnClickListener { + val birthDate = binding.birthDateInput.text.toString() + if (!validateBirthDate(birthDate)) { + Toast.makeText(requireContext(), + getString(R.string.you_must_be_18), Toast.LENGTH_SHORT).show() + return@setOnClickListener + } + viewModel.name.value = binding.nameInput.text.toString() + viewModel.surname.value = binding.surnameInput.text.toString() + viewModel.birthDate.value = birthDate + findNavController().navigate(R.id.action_nameFragment_to_addressFragment) + } + + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + private fun validateBirthDate(date: String): Boolean { + val sdf = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()) + sdf.isLenient = false + return try { + val birthDate = sdf.parse(date) ?: return false + val calendar = Calendar.getInstance() + calendar.add(Calendar.YEAR, -18) + birthDate.before(calendar.time) + } catch (e: ParseException) { + false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/NameViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/NameViewModel.kt new file mode 100644 index 0000000..0f768ae --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/NameViewModel.kt @@ -0,0 +1,14 @@ +package ru.otus.basicarchitecture + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class NameViewModel @Inject constructor( + wizardCache: WizardCache +) : ViewModel() { + val name = wizardCache.name + val surname = wizardCache.surname + val birthDate = wizardCache.birthDate +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/SummaryFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/SummaryFragment.kt new file mode 100644 index 0000000..a447be4 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/SummaryFragment.kt @@ -0,0 +1,68 @@ +package ru.otus.basicarchitecture + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import ru.otus.basicarchitecture.databinding.FragmentSummaryBinding + +@AndroidEntryPoint +class SummaryFragment : Fragment() { + private val viewModel: SummaryViewModel by viewModels() + private var _binding: FragmentSummaryBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + _binding = FragmentSummaryBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Подписка на данные из ViewModel + viewLifecycleOwner.lifecycleScope.launch { + viewModel.name.collectLatest { binding.tvName.text = it } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.surname.collectLatest { binding.tvSurname.text = it } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.birthDate.collectLatest { binding.tvDob.text = it } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.address.collectLatest { binding.tvAddress.text = it } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.interests.collectLatest { updateInterests(it) } + } + } + + private fun updateInterests(interests: List) { + binding.flInterests.removeAllViews() + for (interest in interests) { + val chip = LayoutInflater.from(requireContext()) + .inflate(R.layout.item_chip, binding.flInterests, false) as TextView + chip.text = interest + binding.flInterests.addView(chip) + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/SummaryViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/SummaryViewModel.kt new file mode 100644 index 0000000..a0ef782 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/SummaryViewModel.kt @@ -0,0 +1,18 @@ +package ru.otus.basicarchitecture + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject + +@HiltViewModel +class SummaryViewModel @Inject constructor( + wizardCache: WizardCache +) : ViewModel() { + val name = wizardCache.name + val surname = wizardCache.surname + val birthDate = wizardCache.birthDate + val address = MutableStateFlow( + "${wizardCache.country.value}, ${wizardCache.city.value}, ${wizardCache.address.value}") + val interests = wizardCache.interests +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/UiState.kt b/app/src/main/java/ru/otus/basicarchitecture/UiState.kt new file mode 100644 index 0000000..66dc72c --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UiState.kt @@ -0,0 +1,8 @@ +package ru.otus.basicarchitecture + +sealed class UiState { + data object Idle : UiState() + data object Loading : UiState() + data object Success : UiState() + data class Error(val message: String) : UiState() +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt b/app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt new file mode 100644 index 0000000..8bf423a --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt @@ -0,0 +1,16 @@ +package ru.otus.basicarchitecture + +import kotlinx.coroutines.flow.MutableStateFlow +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WizardCache @Inject constructor() { + val name = MutableStateFlow("") + val surname = MutableStateFlow("") + val birthDate = MutableStateFlow("") + val country = MutableStateFlow("") + val city = MutableStateFlow("") + val address = MutableStateFlow("") + val interests = MutableStateFlow>(emptyList()) +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/net/AuthInterceptor.kt b/app/src/main/java/ru/otus/basicarchitecture/net/AuthInterceptor.kt new file mode 100644 index 0000000..8e8c0d5 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/net/AuthInterceptor.kt @@ -0,0 +1,26 @@ +package ru.otus.basicarchitecture.net + +import okhttp3.Interceptor +import okhttp3.Response +import ru.otus.basicarchitecture.BuildConfig + +import javax.inject.Inject + +class AuthInterceptor @Inject constructor() : Interceptor { + private val token = BuildConfig.DADATA_API_KEY + private val secret = BuildConfig.DADATA_SECRET_KEY + + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + val newRequest = originalRequest.newBuilder() + .addHeader("Authorization", token) + .apply { + // Добавляем X-Secret только для запроса очистки адреса + if (originalRequest.url.encodedPath.contains("/clean/address")) { + addHeader("X-Secret", secret) + } + } + .build() + return chain.proceed(newRequest) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/net/DaDataService.kt b/app/src/main/java/ru/otus/basicarchitecture/net/DaDataService.kt new file mode 100644 index 0000000..3838dc2 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/net/DaDataService.kt @@ -0,0 +1,129 @@ +package ru.otus.basicarchitecture.net + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import javax.inject.Inject +import javax.inject.Singleton + +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + + +/* + +Поиск страны: + +запрос: +curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Authorization: Token aabb123456789" -d "{ \"query\": \"TH\" }" "http://suggestions.dadata.ru/suggestions/api/4_1/rs/findById/country" -o dadata.countries.res.json + +ответ: + +{ + "suggestions": [ + { + "value": "Таиланд" + } + ] +} + +Поиск адреса: + +запрос: +curl -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Authorization: Token aabb123456789" -H "X-Secret: aabb9123456789" -d "[ \"Тайланд, Пхукет, Главный пр. 131 кв. 12\" ]" "https://cleaner.dadata.ru/api/v1/clean/address" -o dadata.address.res.json + +ответ: +[ + { + "result": "г Пхукет, пр-кт Главный, д 131, кв 12", + "country": "Тайланд", + "region_with_type": "г Пхукет", + "city_with_type": "г Пхукет", + "street_with_type": "пр-кт Главный", + "house_type": "д", + "house": "131", + "flat_type": "кв", + "flat": "12", + "geo_lat": "29.8514164", + "geo_lon": "40.2739338" + } +] + + +Поиск города по IP адресу: + +запрос: +curl -X GET -H "Accept: application/json" -H "Authorization: Token aabb123456789" "http://suggestions.dadata.ru/suggestions/api/4_1/rs/iplocate/address?ip=" -o dadata.cities-empty-ip.res.json + +ответ: +{ + "location": { + "value": "г Пхукет", + "data": { + "country": "Тайланд" + } + } +} + + */ + + +interface SuggestionsApi { + @POST("suggestions/api/4_1/rs/suggest/country") + suspend fun findCountry(@Body request: Map): CountryResponse + + @GET("suggestions/api/4_1/rs/iplocate/address") + suspend fun findCityByIp(@Query("ip") ip: String): CityResponse +} + +interface CleanerApi { + @POST("api/v1/clean/address") + suspend fun cleanAddress(@Body request: List): List +} + +private const val QUERY = "query" + +@Singleton +class DaDataService @Inject constructor( + private val suggestionsApi: SuggestionsApi, + private val cleanerApi: CleanerApi +) { + fun getCountries(query: String): Flow> = flow { + val response = suggestionsApi.findCountry(mapOf(QUERY to query)) + emit(response.suggestions.map { it.value }) + } + + fun getAddressSuggestions(query: String): Flow> = flow { + val response = cleanerApi.cleanAddress(listOf(query)) + emit(response) + } + + fun getCityByIp(ip: String): Flow = flow { + val response = suggestionsApi.findCityByIp(ip) + emit(response) + } +} + +// Модели ответов +data class CountryResponse(val suggestions: List) +data class CountrySuggestion(val value: String) + +data class AddressResponse( + val result: String, + val country: String, + val region_with_type: String?, + val city_with_type: String?, + val street_with_type: String?, + val house_type: String?, + val house: String?, + val flat_type: String?, + val flat: String?, + val geo_lat: String?, + val geo_lon: String? +) + +data class CityResponse(val location: CityLocation?) +data class CityLocation(val value: String, val data: CityData) +data class CityData(val country: String) + diff --git a/app/src/main/java/ru/otus/basicarchitecture/net/NetworkModule.kt b/app/src/main/java/ru/otus/basicarchitecture/net/NetworkModule.kt new file mode 100644 index 0000000..ef642f1 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/net/NetworkModule.kt @@ -0,0 +1,64 @@ +package ru.otus.basicarchitecture.net + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Named +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + @Provides + @Singleton + fun provideAuthInterceptor(): AuthInterceptor { + return AuthInterceptor() + } + + @Provides + @Singleton + fun provideOkHttpClient(authInterceptor: AuthInterceptor): OkHttpClient { + return OkHttpClient.Builder() + .addInterceptor(authInterceptor) // Добавляем наш интерцептор + .build() + } + + @Provides + @Singleton + @Named("cleaner") + fun provideCleanerRetrofit(client: OkHttpClient): Retrofit { + return Retrofit.Builder() + .baseUrl("https://cleaner.dadata.ru") + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build() + } + + @Provides + @Singleton + @Named("suggestions") + fun provideSuggestionsRetrofit(client: OkHttpClient): Retrofit { + return Retrofit.Builder() + .baseUrl("https://suggestions.dadata.ru") + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build() + } + + @Provides + @Singleton + fun provideCleanerApi(@Named("cleaner") retrofit: Retrofit): CleanerApi { + return retrofit.create(CleanerApi::class.java) + } + + @Provides + @Singleton + fun provideSuggestionsApi(@Named("suggestions") retrofit: Retrofit): SuggestionsApi { + return retrofit.create(SuggestionsApi::class.java) + } +} diff --git a/app/src/main/res/anim/enter_animation.xml b/app/src/main/res/anim/enter_animation.xml new file mode 100644 index 0000000..93562e7 --- /dev/null +++ b/app/src/main/res/anim/enter_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/exit_animation.xml b/app/src/main/res/anim/exit_animation.xml new file mode 100644 index 0000000..5b49a38 --- /dev/null +++ b/app/src/main/res/anim/exit_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/pop_enter_animation.xml b/app/src/main/res/anim/pop_enter_animation.xml new file mode 100644 index 0000000..30d0674 --- /dev/null +++ b/app/src/main/res/anim/pop_enter_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/pop_exit_animation.xml b/app/src/main/res/anim/pop_exit_animation.xml new file mode 100644 index 0000000..d97d5b9 --- /dev/null +++ b/app/src/main/res/anim/pop_exit_animation.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_chip.xml b/app/src/main/res/drawable/bg_chip.xml new file mode 100644 index 0000000..1c072cd --- /dev/null +++ b/app/src/main/res/drawable/bg_chip.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0b15a20..b0ef87f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,4 +6,15 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_address.xml b/app/src/main/res/layout/fragment_address.xml new file mode 100644 index 0000000..17f922c --- /dev/null +++ b/app/src/main/res/layout/fragment_address.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_interests.xml b/app/src/main/res/layout/fragment_interests.xml new file mode 100644 index 0000000..b6bd778 --- /dev/null +++ b/app/src/main/res/layout/fragment_interests.xml @@ -0,0 +1,41 @@ + + + + + + + + diff --git a/app/src/main/res/layout/fragment_name.xml b/app/src/main/res/layout/fragment_name.xml new file mode 100644 index 0000000..1196dfc --- /dev/null +++ b/app/src/main/res/layout/fragment_name.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_summary.xml b/app/src/main/res/layout/fragment_summary.xml new file mode 100644 index 0000000..44cd3d0 --- /dev/null +++ b/app/src/main/res/layout/fragment_summary.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_chip.xml b/app/src/main/res/layout/item_chip.xml new file mode 100644 index 0000000..8afee94 --- /dev/null +++ b/app/src/main/res/layout/item_chip.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/main/res/navigation/wizard_nav_graph.xml b/app/src/main/res/navigation/wizard_nav_graph.xml new file mode 100644 index 0000000..3cb1365 --- /dev/null +++ b/app/src/main/res/navigation/wizard_nav_graph.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index bbaa36f..1563a86 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,16 +1,18 @@ - \ No newline at end of file diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..e7371d7 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f8c6127..e3f9d96 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,23 @@ #FF018786 #FF000000 #FFFFFFFF + + + #008DED + #1a5b8e + #ffffff + + #C5C5C5 + #66A9E0 + #F5F5F5 + + + #66A9E0 + #0c1520 + #ffffff + + #1a1a1a + #a0a0a0 + #ffffff + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f26b6d3..6f59d47 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,17 @@ BasicArchitecture + Name + Surname + Birth date (dd.mm.yyyy) + Next + Address + Country + City + You must be 18+ + Summary + Interests + Date of Birth + Load counties: %1$s + Load cities: %1$s + "load address suggression: %1$s" \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..9b9f602 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 0ab4563..26b0e19 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,16 +1,18 @@ - \ No newline at end of file diff --git a/app/src/main/secrets.defaults.properties b/app/src/main/secrets.defaults.properties new file mode 100644 index 0000000..92b206d --- /dev/null +++ b/app/src/main/secrets.defaults.properties @@ -0,0 +1,2 @@ +DADATA_API_KEY= +DADATA_SECRET_KEY= diff --git a/build.gradle b/build.gradle index 7b166ff..b934b48 100644 --- a/build.gradle +++ b/build.gradle @@ -3,4 +3,7 @@ plugins { id 'com.android.application' version '8.7.3' apply false id 'com.android.library' version '8.7.3' apply false id 'org.jetbrains.kotlin.android' version '2.0.21' apply false + id "com.google.devtools.ksp" version "2.0.21-1.0.27" apply false + id 'com.google.dagger.hilt.android' version '2.55' apply false + id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' apply false } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3c5031e..cd88ede 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,7 @@ kotlin.code.style=official # Enables namespacing of each library's R class so that its R class includes only the # resources declared in the library itself and none from the library's dependencies, # thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +android.nonTransitiveRClass=true + +DADATA_API_KEY=${DADATA_API_KEY} +DADATA_SECRET_KEY=${DADATA_SECRET_KEY} \ No newline at end of file From e5ef6ab0b08f7fd91e00603ecfb1e23bffbb037d Mon Sep 17 00:00:00 2001 From: Evgenii Balandin Date: Wed, 12 Feb 2025 04:10:58 +0300 Subject: [PATCH 2/9] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D1=87=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B8=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 52 +------------------ .../basicarchitecture/InterestsFragment.kt | 2 +- .../basicarchitecture/InterestsViewModel.kt | 2 +- app/src/main/res/values-night/themes.xml | 2 +- app/src/main/res/values/themes.xml | 2 +- 5 files changed, 5 insertions(+), 55 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2b3fc0b..6842093 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,5 @@ plugins { id 'com.android.application' - //id 'kotlin-kapt' - //id 'dagger.hilt.android.plugin' // <---- Hilt - //id 'com.google.dagger.hilt.android' id 'org.jetbrains.kotlin.android' id 'com.google.devtools.ksp' id 'com.google.dagger.hilt.android' @@ -57,17 +54,7 @@ android { } } -//kapt { -// correctErrorTypes = true -// useBuildCache = true -// includeCompileClasspath = true -// arguments { -// // arg("dagger.hilt.android.internal.disableAndroidSuperclassValidation", "true") // if you're disabling this validation -// // arg("kapt.kotlin.generated", file("build/generated/source/kaptKotlin")) -// // Add this line to get deprecation warnings -// // arg("compilerArgument", "-Xlint:deprecation") -// } -//} + dependencies { implementation 'androidx.core:core-ktx:1.15.0' @@ -77,8 +64,6 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7' implementation 'androidx.activity:activity-ktx:1.10.0' implementation 'androidx.fragment:fragment-ktx:1.8.5' // Для viewModels() - //implementation 'com.google.dagger:hilt-android:2.55' - //kapt 'com.google.dagger:hilt-compiler:2.55' implementation "com.google.dagger:hilt-android:2.55" implementation 'androidx.navigation:navigation-fragment-ktx:2.8.6' @@ -91,48 +76,13 @@ dependencies { implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // OkHttp (для логирования запросов) implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0' -// implementation(libs.kotlin.dateTime) -// implementation(libs.kotlin.serialization.core) -// implementation(libs.kotlin.serialization.json) -// implementation(libs.kotlin.coroutines.core) -// implementation(libs.kotlin.coroutines.android) -// -// implementation(libs.net.okhttp) -// implementation(libs.net.okhttp.logging) -// implementation(libs.net.retrofit) -// implementation(libs.net.retrofit.kotlin) - // Ensure kapt is applied - //apply plugin: 'kotlin-kapt' - //implementation 'com.google.dagger:hilt-android:2.55' - //annotationProcessor 'com.google.dagger:hilt-compiler:2.55' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' - // For instrumentation tests - //androidTestImplementation 'com.google.dagger:hilt-android-testing:2.55' - //androidTestAnnotationProcessor 'com.google.dagger:hilt-compiler:2.55' - - // For local unit tests - //testImplementation 'com.google.dagger:hilt-android-testing:2.55' - //testAnnotationProcessor 'com.google.dagger:hilt-compiler:2.55' - - -// // For instrumentation tests -// androidTestImplementation 'com.google.dagger:hilt-android-testing:2.55' -// kaptAndroidTest 'com.google.dagger:hilt-compiler:2.55' - - //// For local unit tests - //testImplementation 'com.google.dagger:hilt-android-testing:2.55' - //kaptTest 'com.google.dagger:hilt-compiler:2.55' - - } -//tasks.withType(JavaCompile) { -// options.compilerArgs << "-Xlint:deprecation" -//} tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:deprecation" // Включаем подробные предупреждения diff --git a/app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt index 6b3e01b..17eb782 100644 --- a/app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt +++ b/app/src/main/java/ru/otus/basicarchitecture/InterestsFragment.kt @@ -49,7 +49,7 @@ class InterestsFragment : Fragment() { } binding.nextButton.setOnClickListener { - findNavController().navigate(ru.otus.basicarchitecture.R.id.action_interestsFragment_to_summaryFragment) + findNavController().navigate(R.id.action_interestsFragment_to_summaryFragment) } } diff --git a/app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt index e2c03e9..6c49dee 100644 --- a/app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt +++ b/app/src/main/java/ru/otus/basicarchitecture/InterestsViewModel.kt @@ -6,7 +6,7 @@ import javax.inject.Inject @HiltViewModel class InterestsViewModel @Inject constructor( - private val wizardCache: WizardCache + wizardCache: WizardCache ) : ViewModel() { val interests = wizardCache.interests diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 1563a86..ec5788a 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,4 +1,4 @@ - +