Merge remote-tracking branch 'origin/master'
This commit is contained in:
		
						commit
						3db6d74782
					
				| @ -1,11 +1,15 @@ | ||||
| package com.displaynone | ||||
| 
 | ||||
| import android.app.Application | ||||
| import com.displaynone.acss.components.acs.models.gate.GateServiceST | ||||
| import com.displaynone.acss.components.acs.models.visit.VisitServiceST | ||||
| import com.displaynone.acss.components.auth.models.user.UserServiceST | ||||
| 
 | ||||
| class ACSSApplication : Application() { | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|         UserServiceST.createInstance(this) | ||||
|         GateServiceST.createInstance() | ||||
|         VisitServiceST.createInstance() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,4 +1,28 @@ | ||||
| package com.displaynone.acss.components.acs.models.gate | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.util.Log | ||||
| import com.displaynone.acss.components.acs.models.gate.repository.GateRepository | ||||
| import com.displaynone.acss.components.auth.internal_utils.AuthTokenManager | ||||
| import com.displaynone.acss.components.auth.internal_utils.UserManager | ||||
| import com.displaynone.acss.components.auth.models.user.UserServiceST | ||||
| 
 | ||||
| class GateServiceST { | ||||
|     companion object { | ||||
|         private var instance: GateServiceST? = null | ||||
| 
 | ||||
|         fun createInstance() { | ||||
|             if (instance == null) { | ||||
|                 instance = GateServiceST() | ||||
|             } | ||||
|         } | ||||
|         fun getInstance(): GateServiceST { | ||||
|             return instance ?: throw RuntimeException("null instance") | ||||
|         } | ||||
|     } | ||||
|     private val gateRepository: GateRepository = GateRepository() | ||||
|     suspend fun openDoor(code: String): Result<Int> { | ||||
|         Log.d("1234", UserServiceST.getInstance().getTokenPair().accessToken) | ||||
|         return gateRepository.openDoor(UserServiceST.getInstance().getTokenPair().accessToken, code = code) | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package com.displaynone.acss.components.acs.models.gate.repository | ||||
| 
 | ||||
| import android.util.Log | ||||
| import com.displaynone.acss.config.Constants.serverUrl | ||||
| import com.displaynone.acss.config.Network | ||||
| import io.ktor.client.call.body | ||||
| import io.ktor.client.request.headers | ||||
| import io.ktor.client.request.patch | ||||
| import io.ktor.client.request.post | ||||
| import io.ktor.client.request.setBody | ||||
| import io.ktor.client.statement.bodyAsText | ||||
| import io.ktor.http.ContentType | ||||
| import io.ktor.http.HttpHeaders | ||||
| import io.ktor.http.HttpStatusCode | ||||
| import io.ktor.http.contentType | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.withContext | ||||
| 
 | ||||
| class GateRepository { | ||||
| 
 | ||||
|     suspend fun openDoor(token: String, code: String): Result<Int> = withContext(Dispatchers.IO){ | ||||
|         runCatching{ | ||||
|             val result = Network.client.post("$serverUrl/api/acs/open") { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, "Bearer $token") | ||||
|                 } | ||||
|                 contentType(ContentType.Application.Json) | ||||
|                 setBody("""{ "code": $code }""") | ||||
|             } | ||||
|             if (result.status != HttpStatusCode.OK) { | ||||
|                 error("Status ${result.status}: ${result.body<String>()}") | ||||
|             } | ||||
|             Log.d("UserRepository", result.bodyAsText()) | ||||
|             result.status.value | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,13 +1,11 @@ | ||||
| package com.displaynone.acss.components.auth.models.user.repository | ||||
| package com.displaynone.acss.components.acs.models.visit | ||||
| 
 | ||||
| import android.graphics.Color | ||||
| import android.view.LayoutInflater | ||||
| import android.view.ViewGroup | ||||
| import androidx.paging.PagingDataAdapter | ||||
| import androidx.recyclerview.widget.DiffUtil | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import com.bumptech.glide.Glide | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.VisitDto | ||||
| import com.displaynone.acss.components.acs.models.visit.repository.dto.VisitDto | ||||
| import com.displaynone.acss.databinding.ItemScannerViewBinding | ||||
| 
 | ||||
| class VisitAdapter: PagingDataAdapter<VisitDto, VisitAdapter.ViewHolder>(VisitDiff) { | ||||
| @ -29,7 +27,8 @@ class VisitAdapter: PagingDataAdapter<VisitDto, VisitAdapter.ViewHolder>(VisitDi | ||||
|             userId = -1, | ||||
|             gateId = -1, | ||||
|             createdAt = "Loading...", | ||||
|         )) | ||||
|         ) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| @ -1,8 +1,8 @@ | ||||
| package com.displaynone.acss.components.auth.models.user.repository | ||||
| package com.displaynone.acss.components.acs.models.visit | ||||
| 
 | ||||
| import androidx.paging.PagingSource | ||||
| import androidx.paging.PagingState | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.VisitDto | ||||
| import com.displaynone.acss.components.acs.models.visit.repository.dto.VisitDto | ||||
| 
 | ||||
| class VisitListPagingSource( | ||||
|     private val request: suspend (pageNum: Int, pageSize: Int) -> Result<List<VisitDto>> | ||||
| @ -1,4 +1,45 @@ | ||||
| package com.displaynone.acss.components.acs.models.visit | ||||
| 
 | ||||
| import android.content.Context | ||||
| import com.displaynone.acss.components.acs.models.visit.repository.VisitRepository | ||||
| import com.displaynone.acss.components.acs.models.visit.repository.dto.VisitDto | ||||
| import com.displaynone.acss.components.auth.internal_utils.AuthTokenManager | ||||
| import com.displaynone.acss.components.auth.internal_utils.UserManager | ||||
| import com.displaynone.acss.components.auth.models.user.UserServiceST | ||||
| 
 | ||||
| class VisitServiceST { | ||||
|     companion object { | ||||
|         private var instance: VisitServiceST? = null | ||||
| 
 | ||||
|         fun createInstance() { | ||||
|             if (instance == null) { | ||||
|                 instance = VisitServiceST() | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         fun getInstance(): VisitServiceST { | ||||
|             return instance ?: throw RuntimeException("null instance") | ||||
|         } | ||||
|     } | ||||
|     private val visitRepository: VisitRepository = VisitRepository() | ||||
|     suspend fun getLastVisitsByLogin(pageNum: Int, | ||||
|                                      pageSize: Int, | ||||
|                                      login: String): Result<List<VisitDto>> { | ||||
|         if (!UserServiceST.getInstance().hasTokens()) { | ||||
|             throw RuntimeException("access token is null") | ||||
|         } | ||||
|         return visitRepository.getLastVisitsByLogin(pageNum, pageSize, UserServiceST.getInstance().getTokenPair().accessToken, login).map { pagingDto -> pagingDto.content } | ||||
|     } | ||||
|     suspend fun getMyLastVisits(pageNum: Int, | ||||
|                                 pageSize: Int): Result<List<VisitDto>> { | ||||
|         if (!UserServiceST.getInstance().hasTokens()) { | ||||
|             throw RuntimeException("access token is null") | ||||
|         } | ||||
| 
 | ||||
|         return visitRepository.getMyLastVisits( | ||||
|             pageNum = pageNum, | ||||
|             pageSize = pageSize, | ||||
|             token = UserServiceST.getInstance().getTokenPair().accessToken | ||||
|         ).map { pagingDto -> pagingDto.content } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,57 @@ | ||||
| package com.displaynone.acss.components.acs.models.visit.repository | ||||
| 
 | ||||
| import android.util.Log | ||||
| import com.displaynone.acss.components.acs.models.visit.repository.dto.LastVisitsDto | ||||
| import com.displaynone.acss.config.Constants.serverUrl | ||||
| import com.displaynone.acss.config.Network | ||||
| import io.ktor.client.call.body | ||||
| import io.ktor.client.request.get | ||||
| import io.ktor.client.request.headers | ||||
| import io.ktor.client.request.setBody | ||||
| import io.ktor.client.statement.bodyAsText | ||||
| import io.ktor.http.HttpHeaders | ||||
| import io.ktor.http.HttpStatusCode | ||||
| import io.ktor.http.encodeURLPath | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.withContext | ||||
| 
 | ||||
| class VisitRepository { | ||||
| 
 | ||||
|     suspend fun getMyLastVisits(pageNum: Int, | ||||
|                                 pageSize: Int, | ||||
|                                 token: String): Result<LastVisitsDto> = withContext(Dispatchers.IO){ | ||||
|         runCatching { | ||||
|             val result = Network.client.get("$serverUrl/api/acs/visits/me?page=$pageNum&size=$pageSize") { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, "Bearer $token") | ||||
|                 } | ||||
|             } | ||||
|             Log.d("VisitRepository", result.bodyAsText()) | ||||
|             if (result.status != HttpStatusCode.OK) { | ||||
|                 error("Status ${result.status}: ${result.body<String>()}") | ||||
|             } | ||||
| 
 | ||||
|             result.body() | ||||
|         } | ||||
|     } | ||||
|     suspend fun getLastVisitsByLogin(pageNum: Int, | ||||
|                                      pageSize: Int, | ||||
|                                      token: String, login: String): Result<LastVisitsDto> = withContext( | ||||
|         Dispatchers.IO){ | ||||
|         runCatching { | ||||
|             Log.d("VisitRepository", login) | ||||
|             val encodedLogin = login.encodeURLPath() | ||||
|             val result = Network.client.get("$serverUrl/api/acs/visits/login/${encodedLogin}?page=$pageNum&size=$pageSize") { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, "Bearer $token") | ||||
|                 } | ||||
|                 setBody("""{ "login": "$encodedLogin" }""") | ||||
|             } | ||||
|             if (result.status != HttpStatusCode.OK) { | ||||
|                 error("Status ${result.status}: ${result.body<String>()}") | ||||
|             } | ||||
|             Log.d("VisitRepository", result.bodyAsText()) | ||||
|             result.body() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.displaynone.acss.components.auth.models.user.repository.dto | ||||
| package com.displaynone.acss.components.acs.models.visit.repository.dto | ||||
| 
 | ||||
| import kotlinx.serialization.SerialName | ||||
| import kotlinx.serialization.Serializable | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.displaynone.acss.components.auth.models.user.repository.dto | ||||
| package com.displaynone.acss.components.acs.models.visit.repository.dto | ||||
| 
 | ||||
| import kotlinx.serialization.SerialName | ||||
| import kotlinx.serialization.Serializable | ||||
| @ -1,14 +1,12 @@ | ||||
| package com.displaynone.acss.components.auth.models.user | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.util.Log | ||||
| import com.displaynone.acss.components.auth.internal_utils.AuthTokenManager | ||||
| import com.displaynone.acss.components.auth.internal_utils.UserManager | ||||
| import com.displaynone.acss.components.auth.models.AuthTokenPair | ||||
| import com.displaynone.acss.components.auth.models.user.repository.UserRepository | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.LastVisitsDto | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.VisitDto | ||||
| import java.util.Optional | ||||
| import com.displaynone.acss.components.acs.models.visit.repository.dto.VisitDto | ||||
| 
 | ||||
| 
 | ||||
| class UserServiceST( | ||||
| @ -43,29 +41,32 @@ class UserServiceST( | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     fun getTokenPair(): AuthTokenPair { | ||||
|         return tokenManager.authTokenPair!! | ||||
|     } | ||||
|     fun hasTokens(): Boolean { | ||||
|         return tokenManager.hasTokens() | ||||
|     } | ||||
|     suspend fun getMyLastVisits(pageNum: Int, | ||||
|                                 pageSize: Int): Result<List<VisitDto>> { | ||||
|         if (!tokenManager.hasTokens()) { | ||||
|             throw RuntimeException("access token is null") | ||||
|         } | ||||
| 
 | ||||
|         return userRepository.getMyLastVisits( | ||||
|             pageNum = pageNum, | ||||
|             pageSize = pageSize, | ||||
|             token = tokenManager.authTokenPair!!.accessToken | ||||
|         ).map { pagingDto -> pagingDto.content } | ||||
|     } | ||||
|     suspend fun getLastVisitsByLogin(pageNum: Int, | ||||
|                                      pageSize: Int, | ||||
|                                      login: String): Result<List<VisitDto>> { | ||||
|         if (!tokenManager.hasTokens()) { | ||||
|             throw RuntimeException("access token is null") | ||||
|         } | ||||
|         return userRepository.getLastVisitsByLogin(pageNum, pageSize, tokenManager.authTokenPair!!.accessToken, login).map { pagingDto -> pagingDto.content } | ||||
|     } | ||||
| //    suspend fun getMyLastVisits(pageNum: Int, | ||||
| //                                pageSize: Int): Result<List<VisitDto>> { | ||||
| //        if (!tokenManager.hasTokens()) { | ||||
| //            throw RuntimeException("access token is null") | ||||
| //        } | ||||
| // | ||||
| //        return userRepository.getMyLastVisits( | ||||
| //            pageNum = pageNum, | ||||
| //            pageSize = pageSize, | ||||
| //            token = tokenManager.authTokenPair!!.accessToken | ||||
| //        ).map { pagingDto -> pagingDto.content } | ||||
| //    } | ||||
| //    suspend fun getLastVisitsByLogin(pageNum: Int, | ||||
| //                                     pageSize: Int, | ||||
| //                                     login: String): Result<List<VisitDto>> { | ||||
| //        if (!tokenManager.hasTokens()) { | ||||
| //            throw RuntimeException("access token is null") | ||||
| //        } | ||||
| //        return userRepository.getLastVisitsByLogin(pageNum, pageSize, tokenManager.authTokenPair!!.accessToken, login).map { pagingDto -> pagingDto.content } | ||||
| //    } | ||||
|     fun logout(){ | ||||
|         tokenManager.clear() | ||||
|     } | ||||
|  | ||||
| @ -2,7 +2,6 @@ package com.displaynone.acss.components.auth.models.user.repository | ||||
| 
 | ||||
| import android.util.Log | ||||
| import com.displaynone.acss.components.auth.models.AuthTokenPair | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.LastVisitsDto | ||||
| import com.displaynone.acss.config.Constants.serverUrl | ||||
| import com.displaynone.acss.config.Network | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.RegisterUserDto | ||||
| @ -11,7 +10,6 @@ import com.displaynone.acss.components.auth.models.user.repository.dto.UserLogin | ||||
| import io.ktor.client.call.body | ||||
| import io.ktor.client.request.get | ||||
| import io.ktor.client.request.headers | ||||
| import io.ktor.client.request.patch | ||||
| import io.ktor.client.request.post | ||||
| import io.ktor.client.request.setBody | ||||
| import io.ktor.client.statement.bodyAsText | ||||
| @ -24,7 +22,6 @@ import io.ktor.http.encodeURLPath | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.withContext | ||||
| import kotlinx.serialization.json.Json | ||||
| import kotlin.math.log | ||||
| 
 | ||||
| class UserRepository( | ||||
| 
 | ||||
| @ -104,72 +101,6 @@ class UserRepository( | ||||
|             result.body() | ||||
|         } | ||||
|     } | ||||
|     suspend fun getMyLastVisits(pageNum: Int, | ||||
|                                 pageSize: Int, | ||||
|                                 token: String): Result<LastVisitsDto> = withContext(Dispatchers.IO){ | ||||
|         runCatching { | ||||
|             val result = Network.client.get("$serverUrl/api/acs/visits/me?page=$pageNum&size=$pageSize") { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, "Bearer $token") | ||||
|                 } | ||||
|             } | ||||
|             Log.d("UserRepository", result.bodyAsText()) | ||||
|             if (result.status != HttpStatusCode.OK) { | ||||
|                 error("Status ${result.status}: ${result.body<String>()}") | ||||
|             } | ||||
| 
 | ||||
|             result.body() | ||||
|         } | ||||
|     } | ||||
|     suspend fun getLastVisitsByLogin(pageNum: Int, | ||||
|                                      pageSize: Int, | ||||
|                                      token: String, login: String): Result<LastVisitsDto> = withContext(Dispatchers.IO){ | ||||
|         runCatching { | ||||
|             Log.d("UserRepository", login) | ||||
|             val encodedLogin = login.encodeURLPath() | ||||
|             val result = Network.client.get("$serverUrl/api/acs/visits/login/${encodedLogin}?page=$pageNum&size=$pageSize") { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, "Bearer $token") | ||||
|                 } | ||||
|                 setBody("""{ "login": "$encodedLogin" }""") | ||||
|             } | ||||
|             if (result.status != HttpStatusCode.OK) { | ||||
|                 error("Status ${result.status}: ${result.body<String>()}") | ||||
|             } | ||||
|             Log.d("UserRepository", result.bodyAsText()) | ||||
|             result.body() | ||||
|         } | ||||
|     } | ||||
|     suspend fun getAllLastVisitsAsAdmin(token: String, login: String): Result<UserDTO> = withContext(Dispatchers.IO){ | ||||
|         runCatching { | ||||
|             val encodedLogin = login.encodeURLPath() | ||||
|             val result = Network.client.get("$serverUrl/api/users/login/$encodedLogin") { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, "Bearer $token") | ||||
|                 } | ||||
|                 setBody("""{ "code": "$encodedLogin" }""") | ||||
|             } | ||||
|             if (result.status != HttpStatusCode.OK) { | ||||
|                 error("Status ${result.status}: ${result.body<String>()}") | ||||
|             } | ||||
|             Log.d("UserRepository", result.bodyAsText()) | ||||
|             result.body() | ||||
|         } | ||||
|     } | ||||
|     suspend fun openDoor(token: String, code: Long): Result<Unit> = withContext(Dispatchers.IO) { | ||||
|         runCatching { | ||||
|             val result = Network.client.patch("$serverUrl/api/open") { | ||||
|                 headers { | ||||
|                     append(HttpHeaders.Authorization, token) | ||||
|                 } | ||||
|                 setBody("""{"value":$code}""") | ||||
|             } | ||||
|             if (result.status != HttpStatusCode.OK) { | ||||
|                 error("Status ${result.status}: ${result.body<String>()}") | ||||
|             } | ||||
|             Unit | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     suspend fun register(login: String, password: String): Result<Unit> = | ||||
|         withContext(Dispatchers.IO) { | ||||
|  | ||||
| @ -12,7 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import com.bumptech.glide.Glide | ||||
| import com.displaynone.acss.R | ||||
| import com.displaynone.acss.components.auth.models.user.UserServiceST | ||||
| import com.displaynone.acss.components.auth.models.user.repository.VisitAdapter | ||||
| import com.displaynone.acss.components.acs.models.visit.VisitAdapter | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO | ||||
| import com.displaynone.acss.databinding.FragmentProfileBinding | ||||
| import com.displaynone.acss.ui.profile.ProfileViewModel.Action | ||||
|  | ||||
| @ -6,9 +6,9 @@ import androidx.lifecycle.viewModelScope | ||||
| import androidx.paging.Pager | ||||
| import androidx.paging.PagingConfig | ||||
| import androidx.paging.cachedIn | ||||
| import androidx.paging.log | ||||
| import com.displaynone.acss.components.auth.models.user.UserServiceST | ||||
| import com.displaynone.acss.components.auth.models.user.repository.VisitListPagingSource | ||||
| import com.displaynone.acss.components.acs.models.visit.VisitListPagingSource | ||||
| import com.displaynone.acss.components.acs.models.visit.VisitServiceST | ||||
| import com.displaynone.acss.components.auth.models.user.repository.dto.UserDTO | ||||
| import kotlinx.coroutines.channels.BufferOverflow | ||||
| import kotlinx.coroutines.channels.Channel | ||||
| @ -36,7 +36,7 @@ class ProfileViewModel(): ViewModel() { | ||||
|             maxSize = 100 | ||||
|         ) | ||||
|     ) { | ||||
|         VisitListPagingSource(UserServiceST.getInstance()::getMyLastVisits) | ||||
|         VisitListPagingSource(VisitServiceST.getInstance()::getMyLastVisits) | ||||
|     }.flow | ||||
|         .cachedIn(viewModelScope) | ||||
| 
 | ||||
| @ -44,7 +44,7 @@ class ProfileViewModel(): ViewModel() { | ||||
|         config = PagingConfig(pageSize = 20, enablePlaceholders = false, maxSize = 100) | ||||
|     ) { | ||||
|         VisitListPagingSource { pageNum, pageSize -> | ||||
|             UserServiceST.getInstance().getLastVisitsByLogin(pageNum,pageSize, login) | ||||
|             VisitServiceST.getInstance().getLastVisitsByLogin(pageNum,pageSize, login) | ||||
|         } | ||||
|     }.flow.cachedIn(viewModelScope) | ||||
| 
 | ||||
|  | ||||
| @ -6,6 +6,7 @@ import android.app.Application | ||||
| import android.net.http.HttpException | ||||
| import android.util.Log | ||||
| import androidx.lifecycle.viewModelScope | ||||
| import com.displaynone.acss.components.acs.models.gate.GateServiceST | ||||
| import com.displaynone.acss.components.auth.models.user.UserServiceST | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.io.IOException | ||||
| @ -18,7 +19,7 @@ class QrResultViewModel(application: Application) : AndroidViewModel(application | ||||
|         viewModelScope.launch { | ||||
|             try { | ||||
|                 val stringCode = code.toString() | ||||
|                 UserServiceST.getInstance().openDoor(stringCode).fold( | ||||
|                 GateServiceST.getInstance().openDoor(stringCode).fold( | ||||
|                     onSuccess = { response -> | ||||
|                         Log.d("QrResultViewModel", "Door opened") | ||||
|                         _state.emit(State.Result( | ||||
|  | ||||
| @ -209,16 +209,6 @@ | ||||
|                 </LinearLayout> | ||||
|             </LinearLayout> | ||||
| 
 | ||||
|             <com.google.android.material.bottomnavigation.BottomNavigationView | ||||
|                 android:id="@+id/bottomNavigation" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="match_parent" | ||||
|                 app:layout_constraintBottom_toBottomOf="parent" | ||||
|                 app:layout_constraintEnd_toEndOf="parent" | ||||
|                 app:layout_constraintHorizontal_bias="0.5" | ||||
|                 app:layout_constraintStart_toStartOf="parent" | ||||
|                 app:menu="@menu/bottom_nav_menu"/> | ||||
| 
 | ||||
|         </androidx.constraintlayout.widget.ConstraintLayout> | ||||
| </androidx.core.widget.NestedScrollView> | ||||
|     </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user