Merge remote-tracking branch 'origin/main'

This commit is contained in:
Juja2025 2025-02-19 17:46:48 +03:00
commit f1ee6061ce
19 changed files with 76 additions and 54 deletions

View File

@ -4,7 +4,7 @@ import com.google.gson.annotations.SerializedName
class LoginDto(
@SerializedName("username")
val username: String?,
val username: String,
@SerializedName("password")
val password: String?
val password: String
)

View File

@ -0,0 +1,5 @@
package ru.myitschool.work.data.dto
enum class Role {
ADMIN, USER
}

View File

@ -3,12 +3,20 @@ package ru.myitschool.work.data.dto
import com.google.gson.annotations.SerializedName
class UserInfoDto(
@SerializedName("id")
val id: Int,
@SerializedName("name")
val fullname: String?,
val fullname: String,
@SerializedName("login")
val login: String,
@SerializedName("role")
val role: Role,
@SerializedName("blocked")
val blocked: Boolean,
@SerializedName("photo")
val imageUrl: String?,
val imageUrl: String,
@SerializedName("position")
val position: String?,
val position: String,
@SerializedName("lastVisit")
val lastEntry: String?,
val lastEntry: String,
)

View File

@ -15,18 +15,18 @@ class AccountRepositoryImpl @Inject constructor(
private val accountNetworkDataSource: AccountNetworkDataSource,
private val userInfoMapper: Lazy<UserInfoMapper>,
): UserInfoRepository {
override suspend fun getInfo(username: String): Result<UserInfoEntity> {
override suspend fun getInfo(basicAuth: String): Result<UserInfoEntity> {
return withContext(Dispatchers.IO) {
accountNetworkDataSource.getInfo(username).fold(
accountNetworkDataSource.getInfo(basicAuth).fold(
onSuccess = { value -> userInfoMapper.get().invoke(value) },
onFailure = { error -> Result.failure(error) }
)
}
}
override suspend fun openByQr(username: String, content: String): Result<Unit> {
override suspend fun openByQr(basicAuth: String, content: String): Result<Unit> {
return withContext(Dispatchers.IO) {
accountNetworkDataSource.openByQr(username, content)
accountNetworkDataSource.openByQr(basicAuth, content)
}
}
}

View File

@ -1,5 +1,6 @@
package ru.myitschool.work.data.repo
import android.util.Base64
import dagger.Reusable
import ru.myitschool.work.data.source.AuthorizationNetworkDataSource
import ru.myitschool.work.data.source.AuthorizationStorageDataSource
@ -19,19 +20,21 @@ class AuthorizationRepositoryImpl @Inject constructor(
override suspend fun login(data: LoginDto): Result<Unit> {
return withContext(Dispatchers.IO) {
authorizationNetworkDataSource.get().checkLogin(data.username!!)
val basicAuthToken = Base64.encodeToString("${data.username}:${data.password}".toByteArray(), Base64.NO_WRAP)
authorizationNetworkDataSource.get().checkAuthCredentials(basicAuthToken)
.onSuccess {
authorizationStorageDataSource.get().updateLogin(data.username)
authorizationStorageDataSource.get().updateBasicAuth(basicAuthToken)
}
}
}
override suspend fun logout() {
authorizationStorageDataSource.get().updateLogin(null)
authorizationStorageDataSource.get().updateBasicAuth(null)
}
override suspend fun getLogin(): Result<String> {
val result = authorizationStorageDataSource.get().login.firstOrNull()
val result = authorizationStorageDataSource.get().basicAuthToken.firstOrNull()
return if (result == null) {
Result.failure(Exception("Not authorize"))
} else {

View File

@ -2,20 +2,21 @@ package ru.myitschool.work.data.source
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.Path
import ru.myitschool.work.data.dto.OpenQrDto
import ru.myitschool.work.data.dto.UserInfoDto
interface AccountApi {
@GET("api/{username}/info")
@GET("api/employee/info")
suspend fun getInfo(
@Path("username") username: String
@Header("Authorization") basicAuth: String
) : UserInfoDto
@PATCH("api/{username}/open")
@PATCH("api/employee/open")
suspend fun openByQr(
@Path("username") username: String,
@Header("Authorization") basicAuth: String,
@Body content: OpenQrDto
)
}

View File

@ -14,14 +14,14 @@ class AccountNetworkDataSource @Inject constructor(
retrofit.create(AccountApi::class.java)
}
suspend fun getInfo(username: String): Result<UserInfoDto> {
return kotlin.runCatching { api.getInfo(username = username) }
suspend fun getInfo(basicAuth: String): Result<UserInfoDto> {
return kotlin.runCatching { api.getInfo(basicAuth = basicAuth) }
}
suspend fun openByQr(username: String, content: String): Result<Unit> {
suspend fun openByQr(basicAuth: String, content: String): Result<Unit> {
return kotlin.runCatching {
api.openByQr(
username = username,
basicAuth = basicAuth,
content = OpenQrDto(value = content)
)
}

View File

@ -1,19 +1,14 @@
package ru.myitschool.work.data.source
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path
interface AuthorizationApi {
@GET("api/{username}/auth")
suspend fun checkLogin(
@Path("username") username: String
) : Response<Unit>
@GET("api/login")
@POST("api/login")
suspend fun login(
@Body content: AccountApi
@Header("Authorization") basicAuthorization: String,
): Response<Unit>
}

View File

@ -13,8 +13,8 @@ class AuthorizationNetworkDataSource @Inject constructor(
retrofit.create(AuthorizationApi::class.java)
}
suspend fun checkLogin(username: String): Result<Unit> {
return kotlin.runCatching { api.checkLogin(username = username) }.fold(
suspend fun checkAuthCredentials(basicAuth: String): Result<Unit> {
return kotlin.runCatching { api.login("Basic $basicAuth") }.fold(
onSuccess = { response ->
when (response.code()) {
200 -> Result.success(Unit)

View File

@ -17,22 +17,22 @@ class AuthorizationStorageDataSource @Inject constructor(
@ApplicationContext private val context: Context,
) {
private val Context.storage: DataStore<Preferences> by preferencesDataStore(name = NAME)
val login: Flow<String?> = context.storage.data.map { preferences ->
preferences[LOGIN_KEY]
val basicAuthToken: Flow<String?> = context.storage.data.map { preferences ->
preferences[TOKEN_KEY]
}
suspend fun updateLogin(username: String?) {
suspend fun updateBasicAuth(token: String?) {
context.storage.edit { settings ->
if (username != null) {
settings[LOGIN_KEY] = username
if (token != null) {
settings[TOKEN_KEY] = "Basic $token"
} else {
settings.remove(LOGIN_KEY)
settings.remove(TOKEN_KEY)
}
}
}
private companion object {
const val NAME = "auth_data"
val LOGIN_KEY = stringPreferencesKey("login")
val TOKEN_KEY = stringPreferencesKey("basicAuthToken")
}
}

View File

@ -11,7 +11,7 @@ class GetUserInfoUseCase @Inject constructor(
) {
suspend operator fun invoke(): Result<UserInfoEntity> {
return getLoginUseCase().fold(
onSuccess = { username -> repo.getInfo(username = username) },
onSuccess = { basicAuth -> repo.getInfo(basicAuth) },
onFailure = { error -> Result.failure(error) }
)
}

View File

@ -10,7 +10,7 @@ class OpenByQrUseCase @Inject constructor(
) {
suspend operator fun invoke(content: String): Result<Unit> {
return getLoginUseCase().fold(
onSuccess = { username -> repo.openByQr(username = username, content = content) },
onSuccess = { basicAuth -> repo.openByQr(basicAuth = basicAuth, content = content) },
onFailure = { error -> Result.failure(error) }
)
}

View File

@ -3,7 +3,7 @@ package ru.myitschool.work.domain.profile.repo
import ru.myitschool.work.domain.profile.entities.UserInfoEntity
interface UserInfoRepository {
suspend fun getInfo(username: String) : Result<UserInfoEntity>
suspend fun getInfo(basicAuth: String) : Result<UserInfoEntity>
suspend fun openByQr(username: String, content: String) : Result<Unit>
suspend fun openByQr(basicAuth: String, content: String) : Result<Unit>
}

View File

@ -64,6 +64,11 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
viewModel.inputLogin(s.toString())
}
})
binding.password.addTextChangedListener(object : TextChangedListener() {
override fun afterTextChanged(s: Editable?) {
viewModel.inputPassword(s.toString())
}
})
}

View File

@ -32,12 +32,12 @@ class LoginViewModel @Inject constructor(
val state = _state.asStateFlow()
private var login: String = ""
private var password: String = "123"//пока пароль по дефалту
private var password: String = ""
fun clickLogin() {
viewModelScope.launch {
_state.update { State.Loading }
val data = LoginDto(login,password)
val data = LoginDto(login, password)
loginUseCase.get().invoke(data).fold(
onSuccess = {
_action.emit(Action.OpenProfile)
@ -56,7 +56,7 @@ class LoginViewModel @Inject constructor(
//Чек пароля на правильность
// Чек пароля на правильность
fun inputLogin(login: String) {
this.login = login
viewModelScope.launch {
@ -67,7 +67,7 @@ class LoginViewModel @Inject constructor(
}
}
}
fun passwordCheck(password: String){
fun inputPassword(password: String){
this.password = password
}

View File

@ -112,6 +112,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
style="@style/Theme.UiTemplate.TextH4"
android:layout_gravity="center"
tools:text="Something wrong. Try again" />
</LinearLayout>

View File

@ -29,7 +29,7 @@
android:id="@+id/showState"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="logout,fullname,photo2,position,lastEntry,scan" />
app:constraint_referenced_ids="fullname,photo2,position,lastEntry,scan,admin,entry_list" />
<TextView
@ -145,22 +145,23 @@
style="@style/Theme.UiTemplate.FAB.AccentColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/profile_scan_button"
android:contentDescription="@string/profile_admin_button"
android:src="@drawable/ic_admin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/scan"
app:layout_constraintStart_toEndOf="@+id/entry_list" />
app:layout_constraintStart_toEndOf="@+id/entry_list"
app:maxImageSize="40dp"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/entry_list"
style="@style/Theme.UiTemplate.FAB.AccentColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/profile_scan_button"
android:src="@drawable/ic_admin"
android:contentDescription="@string/profile_list_button"
android:src="@drawable/icon_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:maxImageSize="40dp" />
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,4 +3,6 @@
<string name="profile_refresh_button">Refresh</string>
<string name="profile_scan_button">Scan QR</string>
<string name="profile_main_textview">Home</string>
<string name="profile_admin_button">Admin panel</string>
<string name="profile_list_button">Entry History</string>
</resources>

View File

@ -3,7 +3,8 @@
<style name="Base.Theme.UiTemplate" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<item name="colorPrimary">@color/AccentBlue</item>
<item name="colorPrimaryDark">@color/AccentBlue</item>
<item name="colorPrimaryDark">@color/white</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">@color/white</item>
<item name="android:colorBackground">@color/white</item>
</style>