diff --git a/app/src/main/java/ru/myitschool/work/data/di/RepoModule.kt b/app/src/main/java/ru/myitschool/work/data/di/RepoModule.kt index 4b32a3b..17e3ae4 100644 --- a/app/src/main/java/ru/myitschool/work/data/di/RepoModule.kt +++ b/app/src/main/java/ru/myitschool/work/data/di/RepoModule.kt @@ -6,7 +6,9 @@ import dagger.hilt.InstallIn import dagger.hilt.android.components.ViewModelComponent import ru.myitschool.work.data.repo.AccountRepositoryImpl import ru.myitschool.work.data.repo.AuthorizationRepositoryImpl +import ru.myitschool.work.data.repo.EntryRepositoryImpl import ru.myitschool.work.domain.auth.repo.AuthorizationRepository +import ru.myitschool.work.domain.entry.repo.EntryRepository import ru.myitschool.work.domain.profile.repo.UserInfoRepository @Module @@ -22,4 +24,9 @@ abstract class RepoModule { abstract fun bindAccountRepo( impl: AccountRepositoryImpl ): UserInfoRepository + + @Binds + abstract fun bindEntryRepository( + impl: EntryRepositoryImpl + ): EntryRepository } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/dto/EntriesDto.kt b/app/src/main/java/ru/myitschool/work/data/dto/EntriesDto.kt new file mode 100644 index 0000000..c958e69 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/EntriesDto.kt @@ -0,0 +1,17 @@ +package ru.myitschool.work.data.dto + +import com.google.gson.annotations.SerializedName +import java.time.LocalDateTime + +class EntriesDto( + @SerializedName("id") + val id: Long, + @SerializedName("username") + val username: String, + @SerializedName("time") + val time: String, + @SerializedName("type") + val type: VisitType, + @SerializedName("readerId") + val readerId: String, +) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/dto/VisitType.kt b/app/src/main/java/ru/myitschool/work/data/dto/VisitType.kt new file mode 100644 index 0000000..bac828e --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/VisitType.kt @@ -0,0 +1,5 @@ +package ru.myitschool.work.data.dto + +enum class VisitType { + SCANNER, NFC +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/mapper/EntryInfoMapper.kt b/app/src/main/java/ru/myitschool/work/data/mapper/EntryInfoMapper.kt new file mode 100644 index 0000000..c17b91b --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/mapper/EntryInfoMapper.kt @@ -0,0 +1,34 @@ +package ru.myitschool.work.data.mapper + +import android.os.Build +import androidx.annotation.RequiresApi +import ru.myitschool.work.data.dto.EntriesDto +import ru.myitschool.work.data.dto.UserInfoDto +import ru.myitschool.work.domain.profile.entities.UserInfoEntity +import ru.myitschool.work.ui.entrylist.adapter.EntryHistoryEntity +import java.text.SimpleDateFormat +import java.util.Locale +import javax.inject.Inject + +class EntryInfoMapper @Inject constructor() { + + @RequiresApi(Build.VERSION_CODES.O) + operator fun invoke(model: List): Result> { + return kotlin.runCatching { + model.map { + EntryHistoryEntity( + type = it.type.name, + identificator = it.readerId, + time = it.time.toString(), + ) + }.toList() + } + } + + private companion object { + private val simpleDateFormat = SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss", + Locale.US + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/repo/EntryRepositoryImpl.kt b/app/src/main/java/ru/myitschool/work/data/repo/EntryRepositoryImpl.kt new file mode 100644 index 0000000..31ac179 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/repo/EntryRepositoryImpl.kt @@ -0,0 +1,31 @@ +package ru.myitschool.work.data.repo + +import dagger.Lazy +import dagger.Reusable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ru.myitschool.work.data.mapper.EntryInfoMapper +import ru.myitschool.work.data.mapper.UserInfoMapper +import ru.myitschool.work.data.source.AccountNetworkDataSource +import ru.myitschool.work.data.source.EntryNetworkDataSource +import ru.myitschool.work.domain.entry.entities.EntryEntity +import ru.myitschool.work.domain.entry.repo.EntryRepository +import ru.myitschool.work.domain.profile.entities.UserInfoEntity +import ru.myitschool.work.domain.profile.repo.UserInfoRepository +import ru.myitschool.work.ui.entrylist.adapter.EntryHistoryEntity +import javax.inject.Inject + +@Reusable +class EntryRepositoryImpl @Inject constructor( + private val entryNetworkDataSource: EntryNetworkDataSource, + private val entryInfoMapper: Lazy, +): EntryRepository { + override suspend fun getEntries(basicAuth: String): Result> { + return withContext(Dispatchers.IO) { + entryNetworkDataSource.getEntries(basicAuth).fold( + onSuccess = { value -> entryInfoMapper.get().invoke(value) }, + onFailure = { error -> Result.failure(error) } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/source/EntryApi.kt b/app/src/main/java/ru/myitschool/work/data/source/EntryApi.kt new file mode 100644 index 0000000..85bc414 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/source/EntryApi.kt @@ -0,0 +1,12 @@ +package ru.myitschool.work.data.source + +import retrofit2.http.GET +import retrofit2.http.Header +import ru.myitschool.work.data.dto.EntriesDto + +interface EntryApi { + @GET("api/employee/visits") + suspend fun getEntries( + @Header("Authorization") basicAuth: String + ) : List +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/source/EntryNetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/source/EntryNetworkDataSource.kt new file mode 100644 index 0000000..b24eb46 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/source/EntryNetworkDataSource.kt @@ -0,0 +1,21 @@ +package ru.myitschool.work.data.source + +import dagger.Reusable +import retrofit2.Retrofit +import ru.myitschool.work.data.dto.EntriesDto +import ru.myitschool.work.data.dto.OpenQrDto +import ru.myitschool.work.data.dto.UserInfoDto +import javax.inject.Inject + +@Reusable +class EntryNetworkDataSource @Inject constructor( + private val retrofit: Retrofit +) { + private val api by lazy { + retrofit.create(EntryApi::class.java) + } + + suspend fun getEntries(basicAuth: String): Result> { + return kotlin.runCatching { api.getEntries(basicAuth = basicAuth) } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/auth/LoginUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/auth/LoginUseCase.kt index 8a03459..dabd7af 100644 --- a/app/src/main/java/ru/myitschool/work/domain/auth/LoginUseCase.kt +++ b/app/src/main/java/ru/myitschool/work/domain/auth/LoginUseCase.kt @@ -7,9 +7,6 @@ import javax.inject.Inject class LoginUseCase @Inject constructor( private val repo: AuthorizationRepository, ) { -// suspend operator fun invoke(login: String): Result { -// return repo.login(username = login) -// } suspend operator fun invoke(data: LoginDto): Result{ return repo.login(data=data) } diff --git a/app/src/main/java/ru/myitschool/work/domain/entry/EntryListUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/entry/EntryListUseCase.kt new file mode 100644 index 0000000..a3e1767 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entry/EntryListUseCase.kt @@ -0,0 +1,18 @@ +package ru.myitschool.work.domain.entry + +import ru.myitschool.work.domain.auth.GetLoginUseCase +import ru.myitschool.work.domain.entry.repo.EntryRepository +import ru.myitschool.work.ui.entrylist.adapter.EntryHistoryEntity +import javax.inject.Inject + +class EntryListUseCase @Inject constructor( + private val repo: EntryRepository, + private val getLoginUseCase: GetLoginUseCase, +) { + suspend operator fun invoke(): Result> { + return getLoginUseCase().fold( + onSuccess = { basicAuth -> repo.getEntries(basicAuth) }, + onFailure = { error -> Result.failure(error) } + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/entry/entities/EntryEntity.kt b/app/src/main/java/ru/myitschool/work/domain/entry/entities/EntryEntity.kt new file mode 100644 index 0000000..bdda523 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entry/entities/EntryEntity.kt @@ -0,0 +1,10 @@ +package ru.myitschool.work.domain.entry.entities + +import java.time.LocalDateTime + +class EntryEntity( + val username: String, + val time: LocalDateTime, + val type: EntryType, + val readerId: String, +) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/entry/entities/EntryType.kt b/app/src/main/java/ru/myitschool/work/domain/entry/entities/EntryType.kt new file mode 100644 index 0000000..019eb17 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entry/entities/EntryType.kt @@ -0,0 +1,5 @@ +package ru.myitschool.work.domain.entry.entities + +enum class EntryType { + SCANNER, NFC +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/entry/repo/EntryRepository.kt b/app/src/main/java/ru/myitschool/work/domain/entry/repo/EntryRepository.kt new file mode 100644 index 0000000..dbc6eae --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/entry/repo/EntryRepository.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.domain.entry.repo + +import ru.myitschool.work.ui.entrylist.adapter.EntryHistoryEntity + +interface EntryRepository { + suspend fun getEntries(basicAuth: String) : Result> +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListFragment.kt b/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListFragment.kt index 855c64f..2c42be2 100644 --- a/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListFragment.kt +++ b/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListFragment.kt @@ -1,5 +1,6 @@ package ru.myitschool.work.ui.entrylist +import android.content.res.ColorStateList import androidx.fragment.app.viewModels import android.os.Bundle import android.util.Log @@ -8,8 +9,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout +import androidx.core.content.ContextCompat import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout +import com.squareup.picasso.Picasso import dagger.hilt.android.AndroidEntryPoint import ru.myitschool.work.R import ru.myitschool.work.databinding.FragmentEntryListBinding @@ -21,6 +25,8 @@ import ru.myitschool.work.ui.login.LoginViewModel import ru.myitschool.work.ui.profile.ProfileDestination import ru.myitschool.work.ui.profile.ProfileViewModel import ru.myitschool.work.utils.collectWhenStarted +import ru.myitschool.work.utils.visibleOrGone + @AndroidEntryPoint class EntryListFragment : Fragment(R.layout.fragment_entry_list) { @@ -29,22 +35,49 @@ class EntryListFragment : Fragment(R.layout.fragment_entry_list) { private val viewModel: EntryListViewModel by viewModels() + private lateinit var swipeRefreshLayout: SwipeRefreshLayout + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentEntryListBinding.bind(view) initCallback() subscribe() + + swipeRefreshLayout = binding.swipeRefreshLayout2 + + swipeRefreshLayout.setOnRefreshListener { + viewModel.updateEntryList() + } } private fun initCallback() { binding.floatingActionButton2.setOnClickListener { viewModel.closeEntryList() } - binding.recyclerView.adapter = AdapterEntryHistory(viewModel.getEntryList()) +// binding.recyclerView.adapter = AdapterEntryHistory(viewModel.getEntryList()) binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) } private fun subscribe() { + viewModel.state.collectWhenStarted(this) { state -> + binding.recyclerView.visibleOrGone(state is EntryListViewModel.State.Show) + binding.error2.visibleOrGone(state is EntryListViewModel.State.Error) + + when(state) { + is EntryListViewModel.State.Loading -> { + swipeRefreshLayout.isRefreshing = true + } + is EntryListViewModel.State.Error -> { + swipeRefreshLayout.isRefreshing = false + binding.error2.text = state.errorText + } + is EntryListViewModel.State.Show -> { + swipeRefreshLayout.isRefreshing = false + binding.recyclerView.adapter = AdapterEntryHistory(state.entryList) + } + } + } + viewModel.action.collectWhenStarted(this) { action -> when (action) { is EntryListViewModel.Action.OpenProfile -> { diff --git a/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListViewModel.kt index 1572a88..adac896 100644 --- a/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/entrylist/EntryListViewModel.kt @@ -3,31 +3,38 @@ package ru.myitschool.work.ui.entrylist import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.Lazy import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext 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.R +import ru.myitschool.work.domain.entry.EntryListUseCase import ru.myitschool.work.ui.entrylist.adapter.EntryHistoryEntity -import ru.myitschool.work.ui.profile.ProfileViewModel -import ru.myitschool.work.ui.profile.ProfileViewModel.Action -import ru.myitschool.work.ui.profile.ProfileViewModel.State import ru.myitschool.work.utils.MutablePublishFlow -import java.text.SimpleDateFormat -import java.util.Locale import javax.inject.Inject @HiltViewModel class EntryListViewModel @Inject constructor( @ApplicationContext private val context: Context, + private val entryUseCase: Lazy, ) : ViewModel() { private val _action = MutablePublishFlow() val action = _action.asSharedFlow() + private val _state = MutableStateFlow(initialState) + val state = _state.asStateFlow() + + init { + updateEntryList() + } + // Новый список данных - private val entryList = listOf( + private var entryList = listOf( EntryHistoryEntity("type1", "2023-10-01T12:00:00", "id001"), EntryHistoryEntity("type2", "2023-10-02T13:00:00", "id002"), EntryHistoryEntity("type3", "2023-10-03T14:00:00", "id003"), @@ -36,8 +43,28 @@ class EntryListViewModel @Inject constructor( ) // Функция для получения данных - fun getEntryList(): List { - return entryList + fun updateEntryList() { + viewModelScope.launch { + _state.update { State.Loading } + entryUseCase.get().invoke().fold( + onSuccess = { entryListEntity -> + entryList = entryListEntity + _state.update { + State.Show( + entryList = entryListEntity + ) + } + }, + onFailure = { error -> + _state.update { + State.Error( + errorText = error.localizedMessage + ?: context.resources.getString(R.string.login_error) + ) + } + } + ) + } } fun closeEntryList(){ @@ -49,4 +76,20 @@ class EntryListViewModel @Inject constructor( sealed interface Action { data object OpenProfile: Action } + + sealed interface State { + data object Loading : State + + data class Error( + val errorText: String, + ) : State + + data class Show( + val entryList: List + ) : State + } + + private companion object { + val initialState = State.Loading + } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/entrylist/adapter/AdapterEntryHistory.kt b/app/src/main/java/ru/myitschool/work/ui/entrylist/adapter/AdapterEntryHistory.kt index e508c9c..0c21e09 100644 --- a/app/src/main/java/ru/myitschool/work/ui/entrylist/adapter/AdapterEntryHistory.kt +++ b/app/src/main/java/ru/myitschool/work/ui/entrylist/adapter/AdapterEntryHistory.kt @@ -36,8 +36,8 @@ class AdapterEntryHistory(private val datas: List) : Recycle holder.dateText.text = LocalDateTime.parse(datas[position].time).format(DateTimeFormatter.ofPattern("d MMMM yyyy, HH:mm:ss", Locale.getDefault())) - holder.identificatorText.text = datas[position].identificator.toString() - holder.typeText.text = datas[position].type.toString() + holder.identificatorText.text = datas[position].identificator + holder.typeText.text = if (datas[position].type == "SCANNER") "Сканер (Телефон)" else "NFC-карта" } diff --git a/app/src/main/res/layout-land/fragment_entry_list.xml b/app/src/main/res/layout-land/fragment_entry_list.xml index b06a0ff..0a2069a 100644 --- a/app/src/main/res/layout-land/fragment_entry_list.xml +++ b/app/src/main/res/layout-land/fragment_entry_list.xml @@ -1,40 +1,59 @@ - + android:layout_height="match_parent"> - + - + - - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_entry_list.xml b/app/src/main/res/layout/fragment_entry_list.xml index b06a0ff..bce46ef 100644 --- a/app/src/main/res/layout/fragment_entry_list.xml +++ b/app/src/main/res/layout/fragment_entry_list.xml @@ -1,40 +1,60 @@ - + android:layout_height="match_parent"> - + - + - - \ No newline at end of file + + + + + + + + \ No newline at end of file