Full write logic of entries fragment

This commit is contained in:
Denis Oleynik 2025-02-20 14:28:45 +03:00
parent 438aca7b75
commit dbcb29ce79
17 changed files with 359 additions and 80 deletions

View File

@ -6,7 +6,9 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent import dagger.hilt.android.components.ViewModelComponent
import ru.myitschool.work.data.repo.AccountRepositoryImpl import ru.myitschool.work.data.repo.AccountRepositoryImpl
import ru.myitschool.work.data.repo.AuthorizationRepositoryImpl 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.auth.repo.AuthorizationRepository
import ru.myitschool.work.domain.entry.repo.EntryRepository
import ru.myitschool.work.domain.profile.repo.UserInfoRepository import ru.myitschool.work.domain.profile.repo.UserInfoRepository
@Module @Module
@ -22,4 +24,9 @@ abstract class RepoModule {
abstract fun bindAccountRepo( abstract fun bindAccountRepo(
impl: AccountRepositoryImpl impl: AccountRepositoryImpl
): UserInfoRepository ): UserInfoRepository
@Binds
abstract fun bindEntryRepository(
impl: EntryRepositoryImpl
): EntryRepository
} }

View File

@ -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,
)

View File

@ -0,0 +1,5 @@
package ru.myitschool.work.data.dto
enum class VisitType {
SCANNER, NFC
}

View File

@ -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<EntriesDto>): Result<List<EntryHistoryEntity>> {
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
)
}
}

View File

@ -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<EntryInfoMapper>,
): EntryRepository {
override suspend fun getEntries(basicAuth: String): Result<List<EntryHistoryEntity>> {
return withContext(Dispatchers.IO) {
entryNetworkDataSource.getEntries(basicAuth).fold(
onSuccess = { value -> entryInfoMapper.get().invoke(value) },
onFailure = { error -> Result.failure(error) }
)
}
}
}

View File

@ -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<EntriesDto>
}

View File

@ -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<List<EntriesDto>> {
return kotlin.runCatching { api.getEntries(basicAuth = basicAuth) }
}
}

View File

@ -7,9 +7,6 @@ import javax.inject.Inject
class LoginUseCase @Inject constructor( class LoginUseCase @Inject constructor(
private val repo: AuthorizationRepository, private val repo: AuthorizationRepository,
) { ) {
// suspend operator fun invoke(login: String): Result<Unit> {
// return repo.login(username = login)
// }
suspend operator fun invoke(data: LoginDto): Result<Unit>{ suspend operator fun invoke(data: LoginDto): Result<Unit>{
return repo.login(data=data) return repo.login(data=data)
} }

View File

@ -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<List<EntryHistoryEntity>> {
return getLoginUseCase().fold(
onSuccess = { basicAuth -> repo.getEntries(basicAuth) },
onFailure = { error -> Result.failure(error) }
)
}
}

View File

@ -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,
)

View File

@ -0,0 +1,5 @@
package ru.myitschool.work.domain.entry.entities
enum class EntryType {
SCANNER, NFC
}

View File

@ -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<List<EntryHistoryEntity>>
}

View File

@ -1,5 +1,6 @@
package ru.myitschool.work.ui.entrylist package ru.myitschool.work.ui.entrylist
import android.content.res.ColorStateList
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@ -8,8 +9,11 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.squareup.picasso.Picasso
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.databinding.FragmentEntryListBinding 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.ProfileDestination
import ru.myitschool.work.ui.profile.ProfileViewModel import ru.myitschool.work.ui.profile.ProfileViewModel
import ru.myitschool.work.utils.collectWhenStarted import ru.myitschool.work.utils.collectWhenStarted
import ru.myitschool.work.utils.visibleOrGone
@AndroidEntryPoint @AndroidEntryPoint
class EntryListFragment : Fragment(R.layout.fragment_entry_list) { 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 val viewModel: EntryListViewModel by viewModels()
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentEntryListBinding.bind(view) _binding = FragmentEntryListBinding.bind(view)
initCallback() initCallback()
subscribe() subscribe()
swipeRefreshLayout = binding.swipeRefreshLayout2
swipeRefreshLayout.setOnRefreshListener {
viewModel.updateEntryList()
}
} }
private fun initCallback() { private fun initCallback() {
binding.floatingActionButton2.setOnClickListener { viewModel.closeEntryList() } binding.floatingActionButton2.setOnClickListener { viewModel.closeEntryList() }
binding.recyclerView.adapter = AdapterEntryHistory(viewModel.getEntryList()) // binding.recyclerView.adapter = AdapterEntryHistory(viewModel.getEntryList())
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())
} }
private fun subscribe() { 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 -> viewModel.action.collectWhenStarted(this) { action ->
when (action) { when (action) {
is EntryListViewModel.Action.OpenProfile -> { is EntryListViewModel.Action.OpenProfile -> {

View File

@ -3,31 +3,38 @@ package ru.myitschool.work.ui.entrylist
import android.content.Context import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.Lazy
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch 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.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 ru.myitschool.work.utils.MutablePublishFlow
import java.text.SimpleDateFormat
import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class EntryListViewModel @Inject constructor( class EntryListViewModel @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val entryUseCase: Lazy<EntryListUseCase>,
) : ViewModel() { ) : ViewModel() {
private val _action = MutablePublishFlow<Action>() private val _action = MutablePublishFlow<Action>()
val action = _action.asSharedFlow() val action = _action.asSharedFlow()
private val _state = MutableStateFlow<EntryListViewModel.State>(initialState)
val state = _state.asStateFlow()
init {
updateEntryList()
}
// Новый список данных // Новый список данных
private val entryList = listOf( private var entryList = listOf(
EntryHistoryEntity("type1", "2023-10-01T12:00:00", "id001"), EntryHistoryEntity("type1", "2023-10-01T12:00:00", "id001"),
EntryHistoryEntity("type2", "2023-10-02T13:00:00", "id002"), EntryHistoryEntity("type2", "2023-10-02T13:00:00", "id002"),
EntryHistoryEntity("type3", "2023-10-03T14:00:00", "id003"), EntryHistoryEntity("type3", "2023-10-03T14:00:00", "id003"),
@ -36,8 +43,28 @@ class EntryListViewModel @Inject constructor(
) )
// Функция для получения данных // Функция для получения данных
fun getEntryList(): List<EntryHistoryEntity> { fun updateEntryList() {
return entryList 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(){ fun closeEntryList(){
@ -49,4 +76,20 @@ class EntryListViewModel @Inject constructor(
sealed interface Action { sealed interface Action {
data object OpenProfile: 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<EntryHistoryEntity>
) : State
}
private companion object {
val initialState = State.Loading
}
} }

View File

@ -36,8 +36,8 @@ class AdapterEntryHistory(private val datas: List<EntryHistoryEntity>) : Recycle
holder.dateText.text = LocalDateTime.parse(datas[position].time).format(DateTimeFormatter.ofPattern("d MMMM yyyy, HH:mm:ss", Locale.getDefault())) 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.identificatorText.text = datas[position].identificator
holder.typeText.text = datas[position].type.toString() holder.typeText.text = if (datas[position].type == "SCANNER") "Сканер (Телефон)" else "NFC-карта"
} }

View File

@ -1,40 +1,59 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout2" android:id="@+id/swipeRefreshLayout2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:padding="16dp"
tools:context=".ui.entrylist.EntryListFragment">
<com.google.android.material.floatingactionbutton.FloatingActionButton <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/floatingActionButton2" android:layout_width="match_parent"
style="@style/Theme.UiTemplate.FAB.Gray" android:layout_height="match_parent"
android:layout_width="wrap_content" android:padding="16dp"
android:layout_height="wrap_content" tools:context=".ui.entrylist.EntryListFragment">
android:clickable="true"
android:src="@drawable/ic_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/textView5" android:id="@+id/error2"
style="@style/Theme.UiTemplate.TextH2" style="@style/Theme.UiTemplate.TextH5"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/entry_history" android:layout_marginHorizontal="16dp"
app:layout_constraintBottom_toBottomOf="@+id/floatingActionButton2" android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent" android:visibility="gone"
app:layout_constraintStart_toEndOf="@+id/floatingActionButton2" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="@+id/floatingActionButton2" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Something wrong. Try later" />
<androidx.recyclerview.widget.RecyclerView <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/recyclerView" android:id="@+id/floatingActionButton2"
android:layout_width="0dp" style="@style/Theme.UiTemplate.FAB.Gray"
android:layout_height="0dp" android:layout_width="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" android:clickable="true"
app:layout_constraintStart_toStartOf="parent" android:src="@drawable/ic_back"
app:layout_constraintTop_toBottomOf="@+id/floatingActionButton2" /> app:layout_constraintStart_toStartOf="parent"
</androidx.constraintlayout.widget.ConstraintLayout> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView5"
style="@style/Theme.UiTemplate.TextH2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/entry_history"
app:layout_constraintBottom_toBottomOf="@+id/floatingActionButton2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/floatingActionButton2"
app:layout_constraintTop_toTopOf="@+id/floatingActionButton2" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
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_toBottomOf="@+id/floatingActionButton2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -1,40 +1,60 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout2" android:id="@+id/swipeRefreshLayout2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:padding="16dp"
tools:context=".ui.entrylist.EntryListFragment">
<com.google.android.material.floatingactionbutton.FloatingActionButton <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/floatingActionButton2" android:layout_width="match_parent"
style="@style/Theme.UiTemplate.FAB.Gray" android:layout_height="match_parent"
android:layout_width="wrap_content" android:padding="16dp"
android:layout_height="wrap_content" tools:context=".ui.entrylist.EntryListFragment">
android:clickable="true"
android:src="@drawable/ic_back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/textView5" android:id="@+id/error2"
style="@style/Theme.UiTemplate.TextH2" style="@style/Theme.UiTemplate.TextH5"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/entry_history" android:layout_marginHorizontal="16dp"
app:layout_constraintBottom_toBottomOf="@+id/floatingActionButton2" android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent" android:visibility="gone"
app:layout_constraintStart_toEndOf="@+id/floatingActionButton2" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="@+id/floatingActionButton2" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Something wrong. Try later" />
<androidx.recyclerview.widget.RecyclerView <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/recyclerView" android:id="@+id/floatingActionButton2"
android:layout_width="0dp" style="@style/Theme.UiTemplate.FAB.Gray"
android:layout_height="0dp" android:layout_width="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent" android:clickable="true"
app:layout_constraintStart_toStartOf="parent" android:src="@drawable/ic_back"
app:layout_constraintTop_toBottomOf="@+id/floatingActionButton2" /> app:layout_constraintStart_toStartOf="parent"
</androidx.constraintlayout.widget.ConstraintLayout> app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView5"
style="@style/Theme.UiTemplate.TextH2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/entry_history"
app:layout_constraintBottom_toBottomOf="@+id/floatingActionButton2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/floatingActionButton2"
app:layout_constraintTop_toTopOf="@+id/floatingActionButton2" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
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_toBottomOf="@+id/floatingActionButton2" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>