android: stabilize DI graph and production api config
Some checks are pending
CI / test (push) Has started running
Some checks are pending
CI / test (push) Has started running
This commit is contained in:
@@ -57,3 +57,10 @@
|
|||||||
- Added unit test for realtime event parsing (`RealtimeEventParserTest`).
|
- Added unit test for realtime event parsing (`RealtimeEventParserTest`).
|
||||||
- Added DAO test (`ChatDaoTest`) using in-memory Room + Robolectric.
|
- Added DAO test (`ChatDaoTest`) using in-memory Room + Robolectric.
|
||||||
- Updated Android checklist status in `docs/android-checklist.md`.
|
- Updated Android checklist status in `docs/android-checklist.md`.
|
||||||
|
|
||||||
|
### Step 10 - Build stabilization fixes
|
||||||
|
- Switched Android API base URL to `https://chat.daemonlord.ru/`.
|
||||||
|
- Added cleartext traffic flag in manifest for local/dev compatibility.
|
||||||
|
- Fixed Hilt dependency cycle by separating refresh `AuthApiService` with a dedicated qualifier.
|
||||||
|
- Added `CoroutineDispatcher` DI provider and qualifier for repositories.
|
||||||
|
- Fixed Material3 experimental API opt-in and removed deprecated `StateFlow.distinctUntilChanged()` usage.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ android {
|
|||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "0.1.0"
|
versionName = "0.1.0"
|
||||||
buildConfigField("String", "API_BASE_URL", "\"http://10.0.2.2:8000/\"")
|
buildConfigField("String", "API_BASE_URL", "\"https://chat.daemonlord.ru/\"")
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
<?xml version=1.0 encoding=utf-8?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android=http://schemas.android.com/apk/res/android>
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<uses-permission android:name=android.permission.INTERNET />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=.MessengerApplication
|
android:name=".MessengerApplication"
|
||||||
android:allowBackup=true
|
android:allowBackup="true"
|
||||||
android:icon=@android:drawable/sym_def_app_icon
|
android:icon="@android:drawable/sym_def_app_icon"
|
||||||
android:label=@string/app_name
|
android:label="@string/app_name"
|
||||||
android:roundIcon=@android:drawable/sym_def_app_icon
|
android:roundIcon="@android:drawable/sym_def_app_icon"
|
||||||
android:supportsRtl=true
|
android:supportsRtl="true"
|
||||||
android:theme=@android:style/Theme.Material.Light.NoActionBar>
|
android:usesCleartextTraffic="true"
|
||||||
|
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
<activity
|
<activity
|
||||||
android:name=.MainActivity
|
android:name=".MainActivity"
|
||||||
android:exported=true>
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name=android.intent.action.MAIN />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name=android.intent.category.LAUNCHER />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ru.daemonlord.messenger.core.token.TokenBundle
|
|||||||
import ru.daemonlord.messenger.core.token.TokenRepository
|
import ru.daemonlord.messenger.core.token.TokenRepository
|
||||||
import ru.daemonlord.messenger.data.auth.api.AuthApiService
|
import ru.daemonlord.messenger.data.auth.api.AuthApiService
|
||||||
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
|
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
|
||||||
|
import ru.daemonlord.messenger.di.RefreshAuthApi
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -16,6 +17,7 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class TokenRefreshAuthenticator @Inject constructor(
|
class TokenRefreshAuthenticator @Inject constructor(
|
||||||
private val tokenRepository: TokenRepository,
|
private val tokenRepository: TokenRepository,
|
||||||
|
@RefreshAuthApi
|
||||||
private val refreshAuthApiService: AuthApiService,
|
private val refreshAuthApiService: AuthApiService,
|
||||||
) : Authenticator {
|
) : Authenticator {
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package ru.daemonlord.messenger.data.auth.repository
|
package ru.daemonlord.messenger.data.auth.repository
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import ru.daemonlord.messenger.core.token.TokenBundle
|
import ru.daemonlord.messenger.core.token.TokenBundle
|
||||||
@@ -10,6 +9,7 @@ import ru.daemonlord.messenger.data.auth.api.AuthApiService
|
|||||||
import ru.daemonlord.messenger.data.auth.dto.AuthUserDto
|
import ru.daemonlord.messenger.data.auth.dto.AuthUserDto
|
||||||
import ru.daemonlord.messenger.data.auth.dto.LoginRequestDto
|
import ru.daemonlord.messenger.data.auth.dto.LoginRequestDto
|
||||||
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
|
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
|
||||||
|
import ru.daemonlord.messenger.di.IoDispatcher
|
||||||
import ru.daemonlord.messenger.domain.auth.model.AuthUser
|
import ru.daemonlord.messenger.domain.auth.model.AuthUser
|
||||||
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
||||||
import ru.daemonlord.messenger.domain.common.AppError
|
import ru.daemonlord.messenger.domain.common.AppError
|
||||||
@@ -22,7 +22,7 @@ import javax.inject.Singleton
|
|||||||
class NetworkAuthRepository @Inject constructor(
|
class NetworkAuthRepository @Inject constructor(
|
||||||
private val authApiService: AuthApiService,
|
private val authApiService: AuthApiService,
|
||||||
private val tokenRepository: TokenRepository,
|
private val tokenRepository: TokenRepository,
|
||||||
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : AuthRepository {
|
) : AuthRepository {
|
||||||
|
|
||||||
override suspend fun login(email: String, password: String): AppResult<AuthUser> = withContext(ioDispatcher) {
|
override suspend fun login(email: String, password: String): AppResult<AuthUser> = withContext(ioDispatcher) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package ru.daemonlord.messenger.data.chat.repository
|
package ru.daemonlord.messenger.data.chat.repository
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.channelFlow
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
@@ -14,6 +13,7 @@ import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
|||||||
import ru.daemonlord.messenger.data.chat.mapper.toChatEntity
|
import ru.daemonlord.messenger.data.chat.mapper.toChatEntity
|
||||||
import ru.daemonlord.messenger.data.chat.mapper.toDomain
|
import ru.daemonlord.messenger.data.chat.mapper.toDomain
|
||||||
import ru.daemonlord.messenger.data.chat.mapper.toUserShortEntityOrNull
|
import ru.daemonlord.messenger.data.chat.mapper.toUserShortEntityOrNull
|
||||||
|
import ru.daemonlord.messenger.di.IoDispatcher
|
||||||
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
import ru.daemonlord.messenger.domain.chat.model.ChatItem
|
||||||
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
import ru.daemonlord.messenger.domain.chat.repository.ChatRepository
|
||||||
import ru.daemonlord.messenger.domain.common.AppError
|
import ru.daemonlord.messenger.domain.common.AppError
|
||||||
@@ -26,7 +26,7 @@ import javax.inject.Singleton
|
|||||||
class NetworkChatRepository @Inject constructor(
|
class NetworkChatRepository @Inject constructor(
|
||||||
private val chatApiService: ChatApiService,
|
private val chatApiService: ChatApiService,
|
||||||
private val chatDao: ChatDao,
|
private val chatDao: ChatDao,
|
||||||
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||||
) : ChatRepository {
|
) : ChatRepository {
|
||||||
|
|
||||||
override fun observeChats(archived: Boolean): Flow<List<ChatItem>> {
|
override fun observeChats(archived: Boolean): Flow<List<ChatItem>> {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import dagger.Module
|
|||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@@ -60,6 +62,7 @@ object NetworkModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
@RefreshAuthApi
|
||||||
fun provideRefreshApiService(
|
fun provideRefreshApiService(
|
||||||
@RefreshClient refreshClient: OkHttpClient,
|
@RefreshClient refreshClient: OkHttpClient,
|
||||||
json: Json,
|
json: Json,
|
||||||
@@ -73,6 +76,11 @@ object NetworkModule {
|
|||||||
.create(AuthApiService::class.java)
|
.create(AuthApiService::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@IoDispatcher
|
||||||
|
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideApiClient(
|
fun provideApiClient(
|
||||||
|
|||||||
@@ -5,3 +5,11 @@ import javax.inject.Qualifier
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(AnnotationRetention.BINARY)
|
@Retention(AnnotationRetention.BINARY)
|
||||||
annotation class RefreshClient
|
annotation class RefreshClient
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class RefreshAuthApi
|
||||||
|
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class IoDispatcher
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.material3.AssistChip
|
import androidx.compose.material3.AssistChip
|
||||||
import androidx.compose.material3.AssistChipDefaults
|
import androidx.compose.material3.AssistChipDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
@@ -45,6 +46,7 @@ fun ChatListRoute(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
fun ChatListScreen(
|
fun ChatListScreen(
|
||||||
state: ChatListUiState,
|
state: ChatListUiState,
|
||||||
onTabSelected: (ChatTab) -> Unit,
|
onTabSelected: (ChatTab) -> Unit,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -59,7 +58,7 @@ class ChatListViewModel @Inject constructor(
|
|||||||
.flatMapLatest { tab ->
|
.flatMapLatest { tab ->
|
||||||
observeChatsUseCase(archived = tab == ChatTab.ARCHIVED)
|
observeChatsUseCase(archived = tab == ChatTab.ARCHIVED)
|
||||||
}
|
}
|
||||||
.combine(searchQuery.distinctUntilChanged()) { chats, query ->
|
.combine(searchQuery) { chats, query ->
|
||||||
chats.filterByQuery(query)
|
chats.filterByQuery(query)
|
||||||
}
|
}
|
||||||
.collectLatest { filtered ->
|
.collectLatest { filtered ->
|
||||||
|
|||||||
Reference in New Issue
Block a user