Add template
This commit is contained in:
parent
a43a36be40
commit
4c051cd158
@ -1,6 +1,9 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlinAndroid
|
kotlinAndroid
|
||||||
androidApplication
|
androidApplication
|
||||||
|
jetbrainsKotlinSerialization version Version.Kotlin.language
|
||||||
|
kotlinAnnotationProcessor
|
||||||
|
id("com.google.dagger.hilt.android").version("2.51.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
val packageName = "ru.myitschool.work"
|
val packageName = "ru.myitschool.work"
|
||||||
@ -34,4 +37,33 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
defaultLibrary()
|
defaultLibrary()
|
||||||
|
|
||||||
|
implementation(Dependencies.AndroidX.activity)
|
||||||
|
implementation(Dependencies.AndroidX.fragment)
|
||||||
|
implementation(Dependencies.AndroidX.constraintLayout)
|
||||||
|
|
||||||
|
implementation(Dependencies.AndroidX.Navigation.fragment)
|
||||||
|
implementation(Dependencies.AndroidX.Navigation.navigationUi)
|
||||||
|
|
||||||
|
implementation(Dependencies.Retrofit.library)
|
||||||
|
implementation(Dependencies.Retrofit.gsonConverter)
|
||||||
|
|
||||||
|
implementation("com.squareup.picasso:picasso:2.8")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
|
||||||
|
implementation("androidx.datastore:datastore-preferences:1.1.1")
|
||||||
|
implementation("com.google.mlkit:barcode-scanning:17.3.0")
|
||||||
|
|
||||||
|
val cameraX = "1.3.4"
|
||||||
|
implementation("androidx.camera:camera-core:$cameraX")
|
||||||
|
implementation("androidx.camera:camera-camera2:$cameraX")
|
||||||
|
implementation("androidx.camera:camera-lifecycle:$cameraX")
|
||||||
|
implementation("androidx.camera:camera-view:$cameraX")
|
||||||
|
implementation("androidx.camera:camera-mlkit-vision:1.4.0-rc04")
|
||||||
|
|
||||||
|
val hilt = "2.51.1"
|
||||||
|
implementation("com.google.dagger:hilt-android:$hilt")
|
||||||
|
kapt("com.google.dagger:hilt-android-compiler:$hilt")
|
||||||
|
}
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
correctErrorTypes = true
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,12 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera.any" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@ -11,6 +16,16 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Default"
|
android:theme="@style/Theme.Default"
|
||||||
tools:targetApi="31" />
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.RootActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
7
app/src/main/java/ru/myitschool/work/App.kt
Normal file
7
app/src/main/java/ru/myitschool/work/App.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package ru.myitschool.work
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class App : Application()
|
5
app/src/main/java/ru/myitschool/work/core/Constants.kt
Normal file
5
app/src/main/java/ru/myitschool/work/core/Constants.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package ru.myitschool.work.core
|
||||||
|
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
|
||||||
|
object Constants {
|
||||||
|
const val SERVER_ADDRESS = "http://localhost:8090"
|
||||||
|
}
|
56
app/src/main/java/ru/myitschool/work/ui/RootActivity.kt
Normal file
56
app/src/main/java/ru/myitschool/work/ui/RootActivity.kt
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package ru.myitschool.work.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.navigation.createGraph
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import androidx.navigation.fragment.fragment
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import ru.myitschool.work.R
|
||||||
|
import ru.myitschool.work.ui.login.LoginDestination
|
||||||
|
import ru.myitschool.work.ui.login.LoginFragment
|
||||||
|
import ru.myitschool.work.ui.qr.scan.QrScanDestination
|
||||||
|
import ru.myitschool.work.ui.qr.scan.QrScanFragment
|
||||||
|
|
||||||
|
// НЕ ИЗМЕНЯЙТЕ НАЗВАНИЕ КЛАССА!
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class RootActivity : AppCompatActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_root)
|
||||||
|
|
||||||
|
val navHostFragment = supportFragmentManager
|
||||||
|
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
|
||||||
|
|
||||||
|
if (navHostFragment != null) {
|
||||||
|
val navController = navHostFragment.navController
|
||||||
|
navController.graph = navController.createGraph(
|
||||||
|
startDestination = LoginDestination
|
||||||
|
) {
|
||||||
|
fragment<LoginFragment, LoginDestination>()
|
||||||
|
fragment<QrScanFragment, QrScanDestination>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBackPressedDispatcher.addCallback(
|
||||||
|
this,
|
||||||
|
object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
onSupportNavigateUp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
|
val navController = findNavController(R.id.nav_host_fragment)
|
||||||
|
val popBackResult = if (navController.previousBackStackEntry != null) {
|
||||||
|
navController.popBackStack()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
return popBackResult || super.onSupportNavigateUp()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package ru.myitschool.work.ui.login
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object LoginDestination
|
@ -0,0 +1,36 @@
|
|||||||
|
package ru.myitschool.work.ui.login
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import ru.myitschool.work.R
|
||||||
|
import ru.myitschool.work.databinding.FragmentLoginBinding
|
||||||
|
import ru.myitschool.work.utils.collectWhenStarted
|
||||||
|
import ru.myitschool.work.utils.visibleOrGone
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class LoginFragment : Fragment(R.layout.fragment_login) {
|
||||||
|
private var _binding: FragmentLoginBinding? = null
|
||||||
|
private val binding: FragmentLoginBinding get() = _binding!!
|
||||||
|
|
||||||
|
private val viewModel: LoginViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
_binding = FragmentLoginBinding.bind(view)
|
||||||
|
subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subscribe() {
|
||||||
|
viewModel.state.collectWhenStarted(this) { state ->
|
||||||
|
binding.loading.visibleOrGone(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
_binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package ru.myitschool.work.ui.login
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class LoginViewModel @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
) : ViewModel() {
|
||||||
|
private val _state = MutableStateFlow(true)
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package ru.myitschool.work.ui.qr.scan
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
|
||||||
|
@Serializable
|
||||||
|
data object QrScanDestination {
|
||||||
|
const val REQUEST_KEY = "qr_result"
|
||||||
|
private const val KEY_QR_DATA = "key_qr"
|
||||||
|
|
||||||
|
fun newInstance(): QrScanFragment {
|
||||||
|
return QrScanFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDataIfExist(bundle: Bundle): String? {
|
||||||
|
return if (bundle.containsKey(KEY_QR_DATA)) {
|
||||||
|
bundle.getString(KEY_QR_DATA)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun packToBundle(data: String): Bundle {
|
||||||
|
return bundleOf(
|
||||||
|
KEY_QR_DATA to data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
package ru.myitschool.work.ui.qr.scan
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.mlkit.vision.MlKitAnalyzer
|
||||||
|
import androidx.camera.view.LifecycleCameraController
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.setFragmentResult
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import com.google.mlkit.vision.barcode.BarcodeScanner
|
||||||
|
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||||
|
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||||
|
import com.google.mlkit.vision.barcode.common.Barcode
|
||||||
|
import ru.myitschool.work.R
|
||||||
|
import ru.myitschool.work.databinding.FragmentQrScanBinding
|
||||||
|
import ru.myitschool.work.utils.collectWhenStarted
|
||||||
|
import ru.myitschool.work.utils.visibleOrGone
|
||||||
|
|
||||||
|
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
|
||||||
|
class QrScanFragment : Fragment(R.layout.fragment_qr_scan) {
|
||||||
|
private var _binding: FragmentQrScanBinding? = null
|
||||||
|
private val binding: FragmentQrScanBinding get() = _binding!!
|
||||||
|
|
||||||
|
private var barcodeScanner: BarcodeScanner? = null
|
||||||
|
private var isCameraInit: Boolean = false
|
||||||
|
private val permissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { isGranted -> viewModel.onPermissionResult(isGranted) }
|
||||||
|
|
||||||
|
private val viewModel: QrScanViewModel by viewModels()
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
_binding = FragmentQrScanBinding.bind(view)
|
||||||
|
sendResult(bundleOf())
|
||||||
|
subscribe()
|
||||||
|
initCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initCallback() {
|
||||||
|
binding.close.setOnClickListener { viewModel.close() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun subscribe() {
|
||||||
|
viewModel.state.collectWhenStarted(this) { state ->
|
||||||
|
binding.loading.visibleOrGone(state is QrScanViewModel.State.Loading)
|
||||||
|
binding.viewFinder.visibleOrGone(state is QrScanViewModel.State.Scan)
|
||||||
|
if (!isCameraInit && state is QrScanViewModel.State.Scan) {
|
||||||
|
startCamera()
|
||||||
|
isCameraInit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.action.collectWhenStarted(this) { action ->
|
||||||
|
when (action) {
|
||||||
|
is QrScanViewModel.Action.RequestPermission -> requestPermission(action.permission)
|
||||||
|
is QrScanViewModel.Action.CloseWithCancel -> {
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
is QrScanViewModel.Action.CloseWithResult -> {
|
||||||
|
sendResult(QrScanDestination.packToBundle(action.result))
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestPermission(permission: String) {
|
||||||
|
permissionLauncher.launch(permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCamera() {
|
||||||
|
val context = requireContext()
|
||||||
|
val cameraController = LifecycleCameraController(context)
|
||||||
|
val previewView: PreviewView = binding.viewFinder
|
||||||
|
val executor = ContextCompat.getMainExecutor(context)
|
||||||
|
|
||||||
|
val options = BarcodeScannerOptions.Builder()
|
||||||
|
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
||||||
|
.build()
|
||||||
|
val barcodeScanner = BarcodeScanning.getClient(options)
|
||||||
|
this.barcodeScanner = barcodeScanner
|
||||||
|
|
||||||
|
cameraController.setImageAnalysisAnalyzer(
|
||||||
|
executor,
|
||||||
|
MlKitAnalyzer(
|
||||||
|
listOf(barcodeScanner),
|
||||||
|
ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED,
|
||||||
|
executor
|
||||||
|
) { result ->
|
||||||
|
result?.getValue(barcodeScanner)?.firstOrNull()?.let { value ->
|
||||||
|
viewModel.findBarcode(value)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
cameraController.bindToLifecycle(this)
|
||||||
|
previewView.controller = cameraController
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
barcodeScanner?.close()
|
||||||
|
barcodeScanner = null
|
||||||
|
_binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun goBack() {
|
||||||
|
findNavControllerOrNull()?.popBackStack()
|
||||||
|
?: requireActivity().onBackPressedDispatcher.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendResult(bundle: Bundle) {
|
||||||
|
setFragmentResult(
|
||||||
|
QrScanDestination.REQUEST_KEY,
|
||||||
|
bundle
|
||||||
|
)
|
||||||
|
findNavControllerOrNull()
|
||||||
|
?.previousBackStackEntry
|
||||||
|
?.savedStateHandle
|
||||||
|
?.set(QrScanDestination.REQUEST_KEY, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findNavControllerOrNull(): NavController? {
|
||||||
|
return try {
|
||||||
|
findNavController()
|
||||||
|
} catch (_: Throwable) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package ru.myitschool.work.ui.qr.scan
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.mlkit.vision.barcode.common.Barcode
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.myitschool.work.utils.MutablePublishFlow
|
||||||
|
|
||||||
|
// НЕ ИЗМЕНЯЙТЕ ЭТОТ ФАЙЛ. В ТЕСТАХ ОН БУДЕМ ВОЗВРАЩЁН В ИСХОДНОЕ СОСТОЯНИЕ
|
||||||
|
class QrScanViewModel(
|
||||||
|
application: Application
|
||||||
|
) : AndroidViewModel(application) {
|
||||||
|
|
||||||
|
private val _action = MutablePublishFlow<Action>()
|
||||||
|
val action = _action.asSharedFlow()
|
||||||
|
|
||||||
|
private val _state = MutableStateFlow<State>(initialState)
|
||||||
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
checkPermission()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onPermissionResult(isGranted: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (isGranted) {
|
||||||
|
_state.update { State.Scan }
|
||||||
|
} else {
|
||||||
|
_action.emit(Action.CloseWithCancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkPermission() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
val isPermissionGranted = ContextCompat.checkSelfPermission(
|
||||||
|
getApplication(),
|
||||||
|
CAMERA_PERMISSION
|
||||||
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
if (isPermissionGranted) {
|
||||||
|
_state.update { State.Scan }
|
||||||
|
} else {
|
||||||
|
delay(1000)
|
||||||
|
_action.emit(Action.RequestPermission(CAMERA_PERMISSION))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findBarcode(barcode: Barcode) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
barcode.rawValue?.let { value ->
|
||||||
|
_action.emit(Action.CloseWithResult(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_action.emit(Action.CloseWithCancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface State {
|
||||||
|
data object Loading : State
|
||||||
|
|
||||||
|
data object Scan : State
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface Action {
|
||||||
|
data class RequestPermission(
|
||||||
|
val permission: String
|
||||||
|
) : Action
|
||||||
|
data object CloseWithCancel : Action
|
||||||
|
data class CloseWithResult(
|
||||||
|
val result: String
|
||||||
|
) : Action
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val initialState = State.Loading
|
||||||
|
|
||||||
|
const val CAMERA_PERMISSION = Manifest.permission.CAMERA
|
||||||
|
}
|
||||||
|
}
|
10
app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt
Normal file
10
app/src/main/java/ru/myitschool/work/utils/FlowExtensions.kt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package ru.myitschool.work.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
|
fun <T> MutablePublishFlow() = MutableSharedFlow<T>(
|
||||||
|
replay = 0,
|
||||||
|
extraBufferCapacity = 1,
|
||||||
|
BufferOverflow.DROP_OLDEST
|
||||||
|
)
|
@ -0,0 +1,18 @@
|
|||||||
|
package ru.myitschool.work.utils
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.flowWithLifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
inline fun <T> Flow<T>.collectWhenStarted(
|
||||||
|
fragment: Fragment,
|
||||||
|
crossinline collector: (T) -> Unit
|
||||||
|
) {
|
||||||
|
fragment.viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
flowWithLifecycle(fragment.viewLifecycleOwner.lifecycle).collect { value ->
|
||||||
|
collector.invoke(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package ru.myitschool.work.utils
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
|
||||||
|
open class TextChangedListener: TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable?) = Unit
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package ru.myitschool.work.utils
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
fun View.visibleOrGone(isVisible: Boolean) {
|
||||||
|
this.visibility = if (isVisible) View.VISIBLE else View.GONE
|
||||||
|
}
|
5
app/src/main/res/drawable/ic_close.xml
Normal file
5
app/src/main/res/drawable/ic_close.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_logout.xml
Normal file
5
app/src/main/res/drawable/ic_logout.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_no_img.xml
Normal file
5
app/src/main/res/drawable/ic_no_img.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M21.9,21.9l-8.49,-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1 0.9,2 2,2h13.17l2.31,2.31L21.9,21.9zM5,18l3.5,-4.5l2.5,3.01L12.17,15l3,3H5zM21,18.17L5.83,3H19c1.1,0 2,0.9 2,2V18.17z"/>
|
||||||
|
|
||||||
|
</vector>
|
25
app/src/main/res/drawable/ic_qr_code.xml
Normal file
25
app/src/main/res/drawable/ic_qr_code.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M3,11h8V3H3V11zM5,5h4v4H5V5z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M3,21h8v-8H3V21zM5,15h4v4H5V15z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M13,3v8h8V3H13zM19,9h-4V5h4V9z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,19h2v2h-2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M13,13h2v2h-2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M15,15h2v2h-2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M13,17h2v2h-2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M15,19h2v2h-2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17,17h2v2h-2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17,13h2v2h-2z"/>
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,15h2v2h-2z"/>
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/ic_refresh.xml
Normal file
5
app/src/main/res/drawable/ic_refresh.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||||
|
|
||||||
|
</vector>
|
13
app/src/main/res/layout/activity_root.xml
Normal file
13
app/src/main/res/layout/activity_root.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/nav_host_fragment"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:defaultNavHost="true" />
|
||||||
|
</FrameLayout>
|
16
app/src/main/res/layout/fragment_login.xml
Normal file
16
app/src/main/res/layout/fragment_login.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
35
app/src/main/res/layout/fragment_qr_scan.xml
Normal file
35
app/src/main/res/layout/fragment_qr_scan.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.camera.view.PreviewView
|
||||||
|
android:id="@+id/viewFinder"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/close"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:contentDescription="@string/close_button"
|
||||||
|
android:src="@drawable/ic_close"
|
||||||
|
app:elevation="0dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
4
app/src/main/res/values/ids.xml
Normal file
4
app/src/main/res/values/ids.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<item name="rootContainer" type="id"/>
|
||||||
|
</resources>
|
@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Work</string>
|
<string name="app_name">NTO Pass</string>
|
||||||
</resources>
|
</resources>
|
4
app/src/main/res/values/strings_qr.xml
Normal file
4
app/src/main/res/values/strings_qr.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="close_button">Close</string>
|
||||||
|
</resources>
|
@ -2,4 +2,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
androidApplication version Version.agp apply false
|
androidApplication version Version.agp apply false
|
||||||
kotlinJvm version Version.Kotlin.language apply false
|
kotlinJvm version Version.Kotlin.language apply false
|
||||||
|
id("com.google.dagger.hilt.android") version "2.51.1" apply false
|
||||||
}
|
}
|
2
buildSrc
2
buildSrc
@ -1 +1 @@
|
|||||||
Subproject commit d9590600045906edeb852eaa3f0b9bf7d1875813
|
Subproject commit ec48d5f6b8c45e8058303282e9ec1c1d0ed02989
|
Loading…
x
Reference in New Issue
Block a user