Compare commits
3 Commits
ai-bullshit
...
0.1.5
| Author | SHA1 | Date | |
|---|---|---|---|
| 0682d6c42c | |||
| 6a69f28256 | |||
| 85cda2065e |
@@ -209,7 +209,7 @@ class MainViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
longPollController.setStateToApply(
|
longPollController.setStateToApply(
|
||||||
if (AppSettings.Debug.longPollInBackground) {
|
if (AppSettings.Experimental.longPollInBackground) {
|
||||||
LongPollState.Background
|
LongPollState.Background
|
||||||
} else {
|
} else {
|
||||||
LongPollState.InApp
|
LongPollState.InApp
|
||||||
@@ -233,7 +233,7 @@ class MainViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun disableBackgroundLongPoll() {
|
private fun disableBackgroundLongPoll() {
|
||||||
AppSettings.Debug.longPollInBackground = false
|
AppSettings.Experimental.longPollInBackground = false
|
||||||
longPollController.setStateToApply(LongPollState.InApp)
|
longPollController.setStateToApply(LongPollState.InApp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@@ -85,6 +84,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
createNotificationChannels()
|
createNotificationChannels()
|
||||||
|
requestNotificationPermissions()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
KoinContext {
|
KoinContext {
|
||||||
@@ -250,12 +250,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val noCategoryName = getString(UiR.string.notification_channel_no_category_name)
|
val noCategoryName = getString(UiR.string.notification_channel_no_category_name)
|
||||||
val noCategoryDescriptionText =
|
val noCategoryDescriptionText =
|
||||||
getString(UiR.string.notification_channel_no_category_description)
|
getString(UiR.string.notification_channel_no_category_description)
|
||||||
val noCategoryImportance = NotificationManagerCompat.IMPORTANCE_HIGH
|
|
||||||
val noCategoryChannel =
|
val noCategoryChannel =
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
AppConstants.NOTIFICATION_CHANNEL_UNCATEGORIZED,
|
AppConstants.NOTIFICATION_CHANNEL_UNCATEGORIZED,
|
||||||
noCategoryName,
|
noCategoryName,
|
||||||
noCategoryImportance
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
).apply {
|
).apply {
|
||||||
description = noCategoryDescriptionText
|
description = noCategoryDescriptionText
|
||||||
}
|
}
|
||||||
@@ -263,12 +262,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val longPollName = getString(UiR.string.notification_channel_long_polling_service_name)
|
val longPollName = getString(UiR.string.notification_channel_long_polling_service_name)
|
||||||
val longPollDescriptionText =
|
val longPollDescriptionText =
|
||||||
getString(UiR.string.notification_channel_long_polling_service_description)
|
getString(UiR.string.notification_channel_long_polling_service_description)
|
||||||
val longPollImportance = NotificationManagerCompat.IMPORTANCE_NONE
|
|
||||||
val longPollChannel =
|
val longPollChannel =
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
AppConstants.NOTIFICATION_CHANNEL_LONG_POLLING,
|
AppConstants.NOTIFICATION_CHANNEL_LONG_POLLING,
|
||||||
longPollName,
|
longPollName,
|
||||||
longPollImportance
|
NotificationManager.IMPORTANCE_NONE
|
||||||
).apply {
|
).apply {
|
||||||
description = longPollDescriptionText
|
description = longPollDescriptionText
|
||||||
}
|
}
|
||||||
@@ -285,9 +283,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun requestNotificationPermissions() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
requestPermissions(
|
||||||
|
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||||
|
REQUEST_NOTIFICATION_PERMISSION_CODE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun toggleLongPollService(
|
private fun toggleLongPollService(
|
||||||
enable: Boolean,
|
enable: Boolean,
|
||||||
inBackground: Boolean = AppSettings.Debug.longPollInBackground
|
inBackground: Boolean = AppSettings.Experimental.longPollInBackground
|
||||||
) {
|
) {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
val longPollIntent = Intent(this, LongPollingService::class.java)
|
val longPollIntent = Intent(this, LongPollingService::class.java)
|
||||||
@@ -313,7 +320,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private fun stopServices() {
|
private fun stopServices() {
|
||||||
toggleOnlineService(enable = false)
|
toggleOnlineService(enable = false)
|
||||||
|
|
||||||
val asForeground = AppSettings.Debug.longPollInBackground
|
val asForeground = AppSettings.Experimental.longPollInBackground
|
||||||
|
|
||||||
if (!asForeground) {
|
if (!asForeground) {
|
||||||
toggleLongPollService(enable = false)
|
toggleLongPollService(enable = false)
|
||||||
@@ -324,4 +331,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
stopServices()
|
stopServices()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REQUEST_NOTIFICATION_PERMISSION_CODE = 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ import dev.meloda.fast.ui.theme.LocalBottomPadding
|
|||||||
import dev.meloda.fast.ui.theme.LocalHazeState
|
import dev.meloda.fast.ui.theme.LocalHazeState
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
@OptIn(ExperimentalHazeMaterialsApi::class)
|
@OptIn(ExperimentalHazeMaterialsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -68,6 +70,14 @@ fun MainScreen(
|
|||||||
mutableIntStateOf(1)
|
mutableIntStateOf(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sharedFlow = remember {
|
||||||
|
MutableSharedFlow<Int>(
|
||||||
|
replay = 0,
|
||||||
|
extraBufferCapacity = 1,
|
||||||
|
onBufferOverflow = BufferOverflow.DROP_OLDEST
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
@@ -98,6 +108,8 @@ fun MainScreen(
|
|||||||
inclusive = true
|
inclusive = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
sharedFlow.tryEmit(index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon = {
|
icon = {
|
||||||
@@ -156,7 +168,11 @@ fun MainScreen(
|
|||||||
enterTransition = { fadeIn(animationSpec = tween(200)) },
|
enterTransition = { fadeIn(animationSpec = tween(200)) },
|
||||||
exitTransition = { fadeOut(animationSpec = tween(200)) }
|
exitTransition = { fadeOut(animationSpec = tween(200)) }
|
||||||
) {
|
) {
|
||||||
navigation<MainGraph>(startDestination = navigationItems[selectedItemIndex].route) {
|
navigation<MainGraph>(
|
||||||
|
startDestination = navigationItems[selectedItemIndex].route,
|
||||||
|
enterTransition = { fadeIn(animationSpec = tween(200)) },
|
||||||
|
exitTransition = { fadeOut(animationSpec = tween(200)) }
|
||||||
|
) {
|
||||||
friendsScreen(
|
friendsScreen(
|
||||||
onError = onError,
|
onError = onError,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
@@ -165,8 +181,9 @@ fun MainScreen(
|
|||||||
conversationsScreen(
|
conversationsScreen(
|
||||||
onError = onError,
|
onError = onError,
|
||||||
onConversationItemClicked = onConversationItemClicked,
|
onConversationItemClicked = onConversationItemClicked,
|
||||||
|
onPhotoClicked = onPhotoClicked,
|
||||||
|
scrollToTopFlow = sharedFlow,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
onPhotoClicked = onPhotoClicked
|
|
||||||
)
|
)
|
||||||
profileScreen(
|
profileScreen(
|
||||||
onError = onError,
|
onError = onError,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class LongPollingService : Service() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (startId > 1) return START_STICKY
|
if (startId > 1) return START_STICKY
|
||||||
|
|
||||||
val inBackground = AppSettings.Debug.longPollInBackground
|
val inBackground = AppSettings.Experimental.longPollInBackground
|
||||||
|
|
||||||
Log.d(
|
Log.d(
|
||||||
STATE_TAG,
|
STATE_TAG,
|
||||||
@@ -258,10 +258,10 @@ class LongPollingService : Service() {
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLowMemory() {
|
override fun onTrimMemory(level: Int) {
|
||||||
Log.d(STATE_TAG, "onLowMemory")
|
Log.d(STATE_TAG, "onTrimMemory")
|
||||||
longPollController.updateCurrentState(LongPollState.Stopped)
|
longPollController.updateCurrentState(LongPollState.Stopped)
|
||||||
super.onLowMemory()
|
super.onTrimMemory(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import org.gradle.kotlin.dsl.configure
|
|||||||
import org.gradle.kotlin.dsl.provideDelegate
|
import org.gradle.kotlin.dsl.provideDelegate
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
|
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
|
|
||||||
|
|
||||||
internal fun Project.configureKotlinAndroid(
|
internal fun Project.configureKotlinAndroid(
|
||||||
commonExtension: CommonExtension<*, *, *, *, *, *>,
|
commonExtension: CommonExtension<*, *, *, *, *, *>,
|
||||||
@@ -40,7 +40,7 @@ internal fun Project.configureKotlinJvm() {
|
|||||||
configureKotlin<KotlinJvmProjectExtension>()
|
configureKotlin<KotlinJvmProjectExtension>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T : KotlinTopLevelExtension> Project.configureKotlin() = configure<T> {
|
private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() = configure<T> {
|
||||||
// Treat all Kotlin warnings as errors (disabled by default)
|
// Treat all Kotlin warnings as errors (disabled by default)
|
||||||
// Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
|
// Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
|
||||||
val warningsAsErrors: String? by project
|
val warningsAsErrors: String? by project
|
||||||
|
|||||||
+5
-1
@@ -1,8 +1,8 @@
|
|||||||
package dev.meloda.fast.data.api.conversations
|
package dev.meloda.fast.data.api.conversations
|
||||||
|
|
||||||
|
import com.slack.eithernet.ApiResult
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConversation
|
||||||
import dev.meloda.fast.network.RestApiErrorDomain
|
import dev.meloda.fast.network.RestApiErrorDomain
|
||||||
import com.slack.eithernet.ApiResult
|
|
||||||
|
|
||||||
interface ConversationsRepository {
|
interface ConversationsRepository {
|
||||||
|
|
||||||
@@ -11,6 +11,10 @@ interface ConversationsRepository {
|
|||||||
offset: Int?
|
offset: Int?
|
||||||
): ApiResult<List<VkConversation>, RestApiErrorDomain>
|
): ApiResult<List<VkConversation>, RestApiErrorDomain>
|
||||||
|
|
||||||
|
suspend fun getConversationsById(
|
||||||
|
peerIds: List<Int>
|
||||||
|
): ApiResult<List<VkConversation>, RestApiErrorDomain>
|
||||||
|
|
||||||
suspend fun storeConversations(conversations: List<VkConversation>)
|
suspend fun storeConversations(conversations: List<VkConversation>)
|
||||||
suspend fun delete(peerId: Int): ApiResult<Int, RestApiErrorDomain>
|
suspend fun delete(peerId: Int): ApiResult<Int, RestApiErrorDomain>
|
||||||
suspend fun pin(peerId: Int): ApiResult<Int, RestApiErrorDomain>
|
suspend fun pin(peerId: Int): ApiResult<Int, RestApiErrorDomain>
|
||||||
|
|||||||
+40
-1
@@ -1,5 +1,6 @@
|
|||||||
package dev.meloda.fast.data.api.conversations
|
package dev.meloda.fast.data.api.conversations
|
||||||
|
|
||||||
|
import com.slack.eithernet.ApiResult
|
||||||
import dev.meloda.fast.common.VkConstants
|
import dev.meloda.fast.common.VkConstants
|
||||||
import dev.meloda.fast.data.VkGroupsMap
|
import dev.meloda.fast.data.VkGroupsMap
|
||||||
import dev.meloda.fast.data.VkMemoryCache
|
import dev.meloda.fast.data.VkMemoryCache
|
||||||
@@ -19,7 +20,6 @@ import dev.meloda.fast.network.RestApiErrorDomain
|
|||||||
import dev.meloda.fast.network.mapApiDefault
|
import dev.meloda.fast.network.mapApiDefault
|
||||||
import dev.meloda.fast.network.mapApiResult
|
import dev.meloda.fast.network.mapApiResult
|
||||||
import dev.meloda.fast.network.service.conversations.ConversationsService
|
import dev.meloda.fast.network.service.conversations.ConversationsService
|
||||||
import com.slack.eithernet.ApiResult
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@@ -79,6 +79,45 @@ class ConversationsRepositoryImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getConversationsById(
|
||||||
|
peerIds: List<Int>
|
||||||
|
): ApiResult<List<VkConversation>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
|
val requestParams = mapOf(
|
||||||
|
"peer_ids" to peerIds.joinToString(separator = ","),
|
||||||
|
"extended" to "1",
|
||||||
|
"fields" to VkConstants.ALL_FIELDS
|
||||||
|
)
|
||||||
|
|
||||||
|
conversationsService.getConversationsById(requestParams).mapApiResult(
|
||||||
|
successMapper = { apiResponse ->
|
||||||
|
val response = apiResponse.requireResponse()
|
||||||
|
|
||||||
|
val profilesList = response.profiles.orEmpty().map(VkUserData::mapToDomain)
|
||||||
|
val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain)
|
||||||
|
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
|
||||||
|
|
||||||
|
val usersMap = VkUsersMap.forUsers(profilesList)
|
||||||
|
val groupsMap = VkGroupsMap.forGroups(groupsList)
|
||||||
|
|
||||||
|
VkMemoryCache.appendUsers(profilesList)
|
||||||
|
VkMemoryCache.appendGroups(groupsList)
|
||||||
|
VkMemoryCache.appendContacts(contactsList)
|
||||||
|
|
||||||
|
response.items.map { item ->
|
||||||
|
item.asDomain().let { conversation ->
|
||||||
|
conversation.copy(
|
||||||
|
user = usersMap.conversationUser(conversation),
|
||||||
|
group = groupsMap.conversationGroup(conversation)
|
||||||
|
).also { VkMemoryCache[conversation.id] = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorMapper = { error ->
|
||||||
|
error?.toDomain()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storeConversations(conversations: List<VkConversation>) {
|
override suspend fun storeConversations(conversations: List<VkConversation>) {
|
||||||
conversationDao.insertAll(conversations.map(VkConversation::asEntity))
|
conversationDao.insertAll(conversations.map(VkConversation::asEntity))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,12 +89,20 @@ object AppSettings {
|
|||||||
)
|
)
|
||||||
set(value) = put(SettingsKeys.KEY_USE_CONTACT_NAMES, value)
|
set(value) = put(SettingsKeys.KEY_USE_CONTACT_NAMES, value)
|
||||||
|
|
||||||
var enablePullToRefresh: Boolean
|
var showEmojiButton: Boolean
|
||||||
get() = get(
|
get() = get(
|
||||||
SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH,
|
SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
|
||||||
SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH
|
SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||||
)
|
)
|
||||||
set(value) = put(SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH, value)
|
set(value) = put(SettingsKeys.KEY_SHOW_EMOJI_BUTTON, value)
|
||||||
|
|
||||||
|
var enableHaptic: Boolean
|
||||||
|
get() = get(
|
||||||
|
SettingsKeys.KEY_ENABLE_HAPTIC,
|
||||||
|
SettingsKeys.DEFAULT_ENABLE_HAPTIC
|
||||||
|
)
|
||||||
|
set(value) = put(SettingsKeys.KEY_ENABLE_HAPTIC, value)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Appearance {
|
object Appearance {
|
||||||
@@ -126,6 +134,13 @@ object AppSettings {
|
|||||||
)
|
)
|
||||||
set(value) = put(SettingsKeys.KEY_USE_DYNAMIC_COLORS, value)
|
set(value) = put(SettingsKeys.KEY_USE_DYNAMIC_COLORS, value)
|
||||||
|
|
||||||
|
var useSystemFont: Boolean
|
||||||
|
get() = get(
|
||||||
|
SettingsKeys.KEY_USE_SYSTEM_FONT,
|
||||||
|
SettingsKeys.DEFAULT_USE_SYSTEM_FONT
|
||||||
|
)
|
||||||
|
set(value) = put(SettingsKeys.KEY_USE_SYSTEM_FONT, value)
|
||||||
|
|
||||||
var appLanguage: String
|
var appLanguage: String
|
||||||
get() = get(
|
get() = get(
|
||||||
SettingsKeys.KEY_APPEARANCE_LANGUAGE,
|
SettingsKeys.KEY_APPEARANCE_LANGUAGE,
|
||||||
@@ -152,6 +167,36 @@ object AppSettings {
|
|||||||
set(value) = put(SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS, value)
|
set(value) = put(SettingsKeys.KEY_ACTIVITY_SEND_ONLINE_STATUS, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object Experimental {
|
||||||
|
var longPollInBackground: Boolean
|
||||||
|
get() = get(
|
||||||
|
SettingsKeys.KEY_LONG_POLL_IN_BACKGROUND,
|
||||||
|
SettingsKeys.DEFAULT_LONG_POLL_IN_BACKGROUND
|
||||||
|
)
|
||||||
|
set(value) = put(SettingsKeys.KEY_LONG_POLL_IN_BACKGROUND, value)
|
||||||
|
|
||||||
|
var showTimeInActionMessages: Boolean
|
||||||
|
get() = get(
|
||||||
|
SettingsKeys.KEY_SHOW_TIME_IN_ACTION_MESSAGES,
|
||||||
|
SettingsKeys.DEFAULT_SHOW_TIME_IN_ACTION_MESSAGES
|
||||||
|
)
|
||||||
|
set(value) = put(SettingsKeys.KEY_SHOW_TIME_IN_ACTION_MESSAGES, value)
|
||||||
|
|
||||||
|
var useBlur: Boolean
|
||||||
|
get() = get(
|
||||||
|
SettingsKeys.KEY_USE_BLUR,
|
||||||
|
SettingsKeys.DEFAULT_USE_BLUR
|
||||||
|
)
|
||||||
|
set(value) = put(SettingsKeys.KEY_USE_BLUR, value)
|
||||||
|
|
||||||
|
var moreAnimations: Boolean
|
||||||
|
get() = get(
|
||||||
|
SettingsKeys.KEY_MORE_ANIMATIONS,
|
||||||
|
SettingsKeys.DEFAULT_MORE_ANIMATIONS
|
||||||
|
)
|
||||||
|
set(value) = put(SettingsKeys.KEY_MORE_ANIMATIONS, value)
|
||||||
|
}
|
||||||
|
|
||||||
object Debug {
|
object Debug {
|
||||||
var showAlertAfterCrash: Boolean
|
var showAlertAfterCrash: Boolean
|
||||||
get() = get(
|
get() = get(
|
||||||
@@ -160,41 +205,6 @@ object AppSettings {
|
|||||||
)
|
)
|
||||||
set(value) = put(SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT, value)
|
set(value) = put(SettingsKeys.KEY_DEBUG_SHOW_CRASH_ALERT, value)
|
||||||
|
|
||||||
var longPollInBackground: Boolean
|
|
||||||
get() = get(
|
|
||||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
|
||||||
SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
|
||||||
)
|
|
||||||
set(value) = put(SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND, value)
|
|
||||||
|
|
||||||
var useBlur: Boolean
|
|
||||||
get() = get(
|
|
||||||
SettingsKeys.KEY_APPEARANCE_USE_BLUR,
|
|
||||||
SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR
|
|
||||||
)
|
|
||||||
set(value) = put(SettingsKeys.KEY_APPEARANCE_USE_BLUR, value)
|
|
||||||
|
|
||||||
var showEmojiButton: Boolean
|
|
||||||
get() = get(
|
|
||||||
SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
|
|
||||||
SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
|
||||||
)
|
|
||||||
set(value) = put(SettingsKeys.KEY_SHOW_EMOJI_BUTTON, value)
|
|
||||||
|
|
||||||
var showTimeInActionMessages: Boolean
|
|
||||||
get() = get(
|
|
||||||
SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
|
|
||||||
SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES
|
|
||||||
)
|
|
||||||
set(value) = put(SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES, value)
|
|
||||||
|
|
||||||
var enableHaptic: Boolean
|
|
||||||
get() = get(
|
|
||||||
SettingsKeys.KEY_DEBUG_ENABLE_HAPTIC,
|
|
||||||
SettingsKeys.DEFAULT_DEBUG_ENABLE_HAPTIC
|
|
||||||
)
|
|
||||||
set(value) = put(SettingsKeys.KEY_DEBUG_ENABLE_HAPTIC, value)
|
|
||||||
|
|
||||||
var networkLogLevel: LogLevel
|
var networkLogLevel: LogLevel
|
||||||
get() = get(
|
get() = get(
|
||||||
SettingsKeys.KEY_DEBUG_NETWORK_LOG_LEVEL,
|
SettingsKeys.KEY_DEBUG_NETWORK_LOG_LEVEL,
|
||||||
@@ -202,13 +212,6 @@ object AppSettings {
|
|||||||
).let(LogLevel::parse)
|
).let(LogLevel::parse)
|
||||||
set(level) = put(SettingsKeys.KEY_DEBUG_NETWORK_LOG_LEVEL, level.value)
|
set(level) = put(SettingsKeys.KEY_DEBUG_NETWORK_LOG_LEVEL, level.value)
|
||||||
|
|
||||||
var useSystemFont: Boolean
|
|
||||||
get() = get(
|
|
||||||
SettingsKeys.KEY_DEBUG_USE_SYSTEM_FONT,
|
|
||||||
SettingsKeys.DEFAULT_DEBUG_USE_SYSTEM_FONT
|
|
||||||
)
|
|
||||||
set(value) = put(SettingsKeys.KEY_DEBUG_USE_SYSTEM_FONT, value)
|
|
||||||
|
|
||||||
var showDebugCategory: Boolean
|
var showDebugCategory: Boolean
|
||||||
get() = get(
|
get() = get(
|
||||||
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
|
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ object SettingsKeys {
|
|||||||
const val KEY_ACCOUNT_LOGOUT = "account_logout"
|
const val KEY_ACCOUNT_LOGOUT = "account_logout"
|
||||||
|
|
||||||
const val KEY_GENERAL = "general"
|
const val KEY_GENERAL = "general"
|
||||||
const val KEY_USE_CONTACT_NAMES = "general_use_contact_names"
|
const val KEY_USE_CONTACT_NAMES = "use_contact_names"
|
||||||
const val DEFAULT_VALUE_USE_CONTACT_NAMES = false
|
const val DEFAULT_VALUE_USE_CONTACT_NAMES = false
|
||||||
const val KEY_ENABLE_PULL_TO_REFRESH = "general_pull_to_refresh"
|
const val KEY_SHOW_EMOJI_BUTTON = "show_emoji_button"
|
||||||
const val DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH = false
|
|
||||||
const val KEY_SHOW_EMOJI_BUTTON = "general_show_emoji_button"
|
|
||||||
const val DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON = false
|
const val DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON = false
|
||||||
|
|
||||||
const val KEY_APPEARANCE = "appearance"
|
const val KEY_APPEARANCE = "appearance"
|
||||||
@@ -23,20 +21,20 @@ object SettingsKeys {
|
|||||||
const val DEFAULT_VALUE_APPEARANCE_AMOLED_THEME = false
|
const val DEFAULT_VALUE_APPEARANCE_AMOLED_THEME = false
|
||||||
const val KEY_USE_DYNAMIC_COLORS = "appearance_use_dynamic_colors"
|
const val KEY_USE_DYNAMIC_COLORS = "appearance_use_dynamic_colors"
|
||||||
const val DEFAULT_VALUE_USE_DYNAMIC_COLORS = false
|
const val DEFAULT_VALUE_USE_DYNAMIC_COLORS = false
|
||||||
const val KEY_APPEARANCE_COLOR_SCHEME = "appearance_color_scheme"
|
const val KEY_COLOR_SCHEME = "appearance_color_scheme"
|
||||||
const val DEFAULT_VALUE_APPEARANCE_COLOR_SCHEME = 0
|
const val DEFAULT_COLOR_SCHEME = 0
|
||||||
const val KEY_APPEARANCE_LANGUAGE = "appearance_language"
|
const val KEY_APPEARANCE_LANGUAGE = "appearance_language"
|
||||||
const val DEFAULT_APPEARANCE_LANGUAGE = ""
|
const val DEFAULT_APPEARANCE_LANGUAGE = ""
|
||||||
const val KEY_APPEARANCE_USE_BLUR = "appearance_use_blur"
|
const val KEY_USE_BLUR = "use_blur"
|
||||||
const val DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR = false
|
const val DEFAULT_USE_BLUR = false
|
||||||
const val KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES =
|
const val KEY_SHOW_TIME_IN_ACTION_MESSAGES =
|
||||||
"appearance_show_time_in_action_messages"
|
"show_time_in_action_messages"
|
||||||
const val DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES = false
|
const val DEFAULT_SHOW_TIME_IN_ACTION_MESSAGES = false
|
||||||
|
|
||||||
const val KEY_FEATURES_FAST_TEXT = "features_fast_text"
|
const val KEY_FEATURES_FAST_TEXT = "features_fast_text"
|
||||||
const val DEFAULT_VALUE_FEATURES_FAST_TEXT = "¯\\_(ツ)_/¯"
|
const val DEFAULT_VALUE_FEATURES_FAST_TEXT = "¯\\_(ツ)_/¯"
|
||||||
const val KEY_FEATURES_LONG_POLL_IN_BACKGROUND = "features_lp_background"
|
const val KEY_LONG_POLL_IN_BACKGROUND = "lp_background"
|
||||||
const val DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND = false
|
const val DEFAULT_LONG_POLL_IN_BACKGROUND = false
|
||||||
|
|
||||||
const val KEY_ACTIVITY_SEND_ONLINE_STATUS = "activity_send_online_status"
|
const val KEY_ACTIVITY_SEND_ONLINE_STATUS = "activity_send_online_status"
|
||||||
const val DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS = false
|
const val DEFAULT_VALUE_KEY_ACTIVITY_SEND_ONLINE_STATUS = false
|
||||||
@@ -44,15 +42,16 @@ object SettingsKeys {
|
|||||||
const val KEY_DEBUG_PERFORM_CRASH = "debug_perform_crash"
|
const val KEY_DEBUG_PERFORM_CRASH = "debug_perform_crash"
|
||||||
const val KEY_DEBUG_SHOW_CRASH_ALERT = "debug_show_crash_alert"
|
const val KEY_DEBUG_SHOW_CRASH_ALERT = "debug_show_crash_alert"
|
||||||
const val KEY_DEBUG_HIDE_DEBUG_LIST = "debug_hide_debug_list"
|
const val KEY_DEBUG_HIDE_DEBUG_LIST = "debug_hide_debug_list"
|
||||||
const val KEY_ENABLE_ANIMATIONS_IN_MESSAGES = "debug_enable_animations_in_messages"
|
const val KEY_ENABLE_HAPTIC = "enable_haptic"
|
||||||
const val KEY_DEBUG_ENABLE_HAPTIC = "debug_enable_haptic"
|
const val DEFAULT_ENABLE_HAPTIC = true
|
||||||
const val DEFAULT_DEBUG_ENABLE_HAPTIC = true
|
|
||||||
const val KEY_DEBUG_NETWORK_LOG_LEVEL = "debug_network_log_level"
|
const val KEY_DEBUG_NETWORK_LOG_LEVEL = "debug_network_log_level"
|
||||||
const val DEFAULT_NETWORK_LOG_LEVEL = 0
|
const val DEFAULT_NETWORK_LOG_LEVEL = 0
|
||||||
const val KEY_DEBUG_USE_SYSTEM_FONT = "debug_use_system_font"
|
const val KEY_USE_SYSTEM_FONT = "use_system_font"
|
||||||
const val DEFAULT_DEBUG_USE_SYSTEM_FONT = false
|
const val DEFAULT_USE_SYSTEM_FONT = false
|
||||||
|
const val KEY_MORE_ANIMATIONS = "more_animations"
|
||||||
|
const val DEFAULT_MORE_ANIMATIONS = false
|
||||||
|
|
||||||
|
|
||||||
const val KEY_SHOW_DEBUG_CATEGORY = "show_debug_category"
|
const val KEY_SHOW_DEBUG_CATEGORY = "show_debug_category"
|
||||||
|
|
||||||
const val ID_DMITRY = 37610580
|
const val ID_DMITRY = 37610580
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
interface UserSettings {
|
interface UserSettings {
|
||||||
|
|
||||||
val useContactNames: StateFlow<Boolean>
|
val useContactNames: StateFlow<Boolean>
|
||||||
val enablePullToRefresh: StateFlow<Boolean>
|
|
||||||
|
|
||||||
val enableMultiline: StateFlow<Boolean>
|
val enableMultiline: StateFlow<Boolean>
|
||||||
val darkMode: StateFlow<DarkMode>
|
val darkMode: StateFlow<DarkMode>
|
||||||
@@ -28,7 +27,6 @@ interface UserSettings {
|
|||||||
val showDebugCategory: StateFlow<Boolean>
|
val showDebugCategory: StateFlow<Boolean>
|
||||||
|
|
||||||
fun onUseContactNamesChanged(use: Boolean)
|
fun onUseContactNamesChanged(use: Boolean)
|
||||||
fun onEnablePullToRefreshChanged(enable: Boolean)
|
|
||||||
|
|
||||||
fun onEnableMultilineChanged(enable: Boolean)
|
fun onEnableMultilineChanged(enable: Boolean)
|
||||||
fun onDarkModeChanged(mode: DarkMode)
|
fun onDarkModeChanged(mode: DarkMode)
|
||||||
@@ -52,7 +50,6 @@ interface UserSettings {
|
|||||||
class UserSettingsImpl : UserSettings {
|
class UserSettingsImpl : UserSettings {
|
||||||
|
|
||||||
override val useContactNames = MutableStateFlow(AppSettings.General.useContactNames)
|
override val useContactNames = MutableStateFlow(AppSettings.General.useContactNames)
|
||||||
override val enablePullToRefresh = MutableStateFlow(AppSettings.General.enablePullToRefresh)
|
|
||||||
|
|
||||||
override val enableMultiline = MutableStateFlow(AppSettings.Appearance.enableMultiline)
|
override val enableMultiline = MutableStateFlow(AppSettings.Appearance.enableMultiline)
|
||||||
override val darkMode = MutableStateFlow(AppSettings.Appearance.darkMode)
|
override val darkMode = MutableStateFlow(AppSettings.Appearance.darkMode)
|
||||||
@@ -65,22 +62,18 @@ class UserSettingsImpl : UserSettings {
|
|||||||
override val sendOnlineStatus = MutableStateFlow(AppSettings.Activity.sendOnlineStatus)
|
override val sendOnlineStatus = MutableStateFlow(AppSettings.Activity.sendOnlineStatus)
|
||||||
|
|
||||||
override val showAlertAfterCrash = MutableStateFlow(AppSettings.Debug.showAlertAfterCrash)
|
override val showAlertAfterCrash = MutableStateFlow(AppSettings.Debug.showAlertAfterCrash)
|
||||||
override val longPollInBackground = MutableStateFlow(AppSettings.Debug.longPollInBackground)
|
override val longPollInBackground = MutableStateFlow(AppSettings.Experimental.longPollInBackground)
|
||||||
override val useBlur = MutableStateFlow(AppSettings.Debug.useBlur)
|
override val useBlur = MutableStateFlow(AppSettings.Experimental.useBlur)
|
||||||
override val showEmojiButton = MutableStateFlow(AppSettings.Debug.showEmojiButton)
|
override val showEmojiButton = MutableStateFlow(AppSettings.General.showEmojiButton)
|
||||||
override val showTimeInActionMessages =
|
override val showTimeInActionMessages =
|
||||||
MutableStateFlow(AppSettings.Debug.showTimeInActionMessages)
|
MutableStateFlow(AppSettings.Experimental.showTimeInActionMessages)
|
||||||
override val useSystemFont = MutableStateFlow(AppSettings.Debug.useSystemFont)
|
override val useSystemFont = MutableStateFlow(AppSettings.Appearance.useSystemFont)
|
||||||
override val showDebugCategory = MutableStateFlow(AppSettings.Debug.showDebugCategory)
|
override val showDebugCategory = MutableStateFlow(AppSettings.Debug.showDebugCategory)
|
||||||
|
|
||||||
override fun onUseContactNamesChanged(use: Boolean) {
|
override fun onUseContactNamesChanged(use: Boolean) {
|
||||||
useContactNames.value = use
|
useContactNames.value = use
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnablePullToRefreshChanged(enable: Boolean) {
|
|
||||||
enablePullToRefresh.value = enable
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onEnableMultilineChanged(enable: Boolean) {
|
override fun onEnableMultilineChanged(enable: Boolean) {
|
||||||
enableMultiline.value = enable
|
enableMultiline.value = enable
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package dev.meloda.fast.domain
|
||||||
|
|
||||||
|
import dev.meloda.fast.data.State
|
||||||
|
import dev.meloda.fast.data.api.conversations.ConversationsRepository
|
||||||
|
import dev.meloda.fast.data.mapToState
|
||||||
|
import dev.meloda.fast.model.api.domain.VkConversation
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
|
class LoadConversationsByIdUseCase(
|
||||||
|
private val conversationsRepository: ConversationsRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
operator fun invoke(peerIds: List<Int>): Flow<State<List<VkConversation>>> = flow {
|
||||||
|
emit(State.Loading)
|
||||||
|
|
||||||
|
val newState = conversationsRepository
|
||||||
|
.getConversationsById(peerIds = peerIds)
|
||||||
|
.mapToState()
|
||||||
|
|
||||||
|
emit(newState)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import dev.meloda.fast.domain.AccountUseCaseImpl
|
|||||||
import dev.meloda.fast.domain.GetCurrentAccountUseCase
|
import dev.meloda.fast.domain.GetCurrentAccountUseCase
|
||||||
import dev.meloda.fast.domain.GetLocalUserByIdUseCase
|
import dev.meloda.fast.domain.GetLocalUserByIdUseCase
|
||||||
import dev.meloda.fast.domain.GetLocalUsersByIdsUseCase
|
import dev.meloda.fast.domain.GetLocalUsersByIdsUseCase
|
||||||
|
import dev.meloda.fast.domain.LoadConversationsByIdUseCase
|
||||||
import dev.meloda.fast.domain.LoadUserByIdUseCase
|
import dev.meloda.fast.domain.LoadUserByIdUseCase
|
||||||
import dev.meloda.fast.domain.LoadUsersByIdsUseCase
|
import dev.meloda.fast.domain.LoadUsersByIdsUseCase
|
||||||
import dev.meloda.fast.domain.StoreUsersUseCase
|
import dev.meloda.fast.domain.StoreUsersUseCase
|
||||||
@@ -24,4 +25,6 @@ val domainModule = module {
|
|||||||
|
|
||||||
singleOf(::AccountUseCaseImpl) bind AccountUseCase::class
|
singleOf(::AccountUseCaseImpl) bind AccountUseCase::class
|
||||||
singleOf(::GetCurrentAccountUseCase)
|
singleOf(::GetCurrentAccountUseCase)
|
||||||
|
|
||||||
|
singleOf(::LoadConversationsByIdUseCase)
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-2
@@ -1,12 +1,12 @@
|
|||||||
package dev.meloda.fast.model.api.responses
|
package dev.meloda.fast.model.api.responses
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
import dev.meloda.fast.model.api.data.VkContactData
|
import dev.meloda.fast.model.api.data.VkContactData
|
||||||
import dev.meloda.fast.model.api.data.VkConversationData
|
import dev.meloda.fast.model.api.data.VkConversationData
|
||||||
import dev.meloda.fast.model.api.data.VkGroupData
|
import dev.meloda.fast.model.api.data.VkGroupData
|
||||||
import dev.meloda.fast.model.api.data.VkMessageData
|
import dev.meloda.fast.model.api.data.VkMessageData
|
||||||
import dev.meloda.fast.model.api.data.VkUserData
|
import dev.meloda.fast.model.api.data.VkUserData
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ConversationsGetResponse(
|
data class ConversationsGetResponse(
|
||||||
@@ -18,6 +18,15 @@ data class ConversationsGetResponse(
|
|||||||
@Json(name = "contacts") val contacts: List<VkContactData>?
|
@Json(name = "contacts") val contacts: List<VkContactData>?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class ConversationsGetByIdResponse(
|
||||||
|
@Json(name = "count") val count: Int,
|
||||||
|
@Json(name = "items") val items: List<VkConversationData>,
|
||||||
|
@Json(name = "profiles") val profiles: List<VkUserData>?,
|
||||||
|
@Json(name = "groups") val groups: List<VkGroupData>?,
|
||||||
|
@Json(name = "contacts") val contacts: List<VkContactData>?
|
||||||
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ConversationsResponseItem(
|
data class ConversationsResponseItem(
|
||||||
@Json(name = "conversation") val conversation: VkConversationData,
|
@Json(name = "conversation") val conversation: VkConversationData,
|
||||||
|
|||||||
+8
-1
@@ -1,10 +1,11 @@
|
|||||||
package dev.meloda.fast.network.service.conversations
|
package dev.meloda.fast.network.service.conversations
|
||||||
|
|
||||||
|
import com.slack.eithernet.ApiResult
|
||||||
import dev.meloda.fast.model.api.responses.ConversationsDeleteResponse
|
import dev.meloda.fast.model.api.responses.ConversationsDeleteResponse
|
||||||
|
import dev.meloda.fast.model.api.responses.ConversationsGetByIdResponse
|
||||||
import dev.meloda.fast.model.api.responses.ConversationsGetResponse
|
import dev.meloda.fast.model.api.responses.ConversationsGetResponse
|
||||||
import dev.meloda.fast.network.ApiResponse
|
import dev.meloda.fast.network.ApiResponse
|
||||||
import dev.meloda.fast.network.RestApiError
|
import dev.meloda.fast.network.RestApiError
|
||||||
import com.slack.eithernet.ApiResult
|
|
||||||
import retrofit2.http.FieldMap
|
import retrofit2.http.FieldMap
|
||||||
import retrofit2.http.FormUrlEncoded
|
import retrofit2.http.FormUrlEncoded
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
@@ -17,6 +18,12 @@ interface ConversationsService {
|
|||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<ConversationsGetResponse>, RestApiError>
|
): ApiResult<ApiResponse<ConversationsGetResponse>, RestApiError>
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(ConversationsUrls.GET_BY_ID)
|
||||||
|
suspend fun getConversationsById(
|
||||||
|
@FieldMap params: Map<String, String>
|
||||||
|
): ApiResult<ApiResponse<ConversationsGetByIdResponse>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.DELETE)
|
@POST(ConversationsUrls.DELETE)
|
||||||
suspend fun delete(
|
suspend fun delete(
|
||||||
|
|||||||
+1
@@ -5,6 +5,7 @@ import dev.meloda.fast.common.AppConstants
|
|||||||
object ConversationsUrls {
|
object ConversationsUrls {
|
||||||
|
|
||||||
const val GET = "${AppConstants.URL_API}/messages.getConversations"
|
const val GET = "${AppConstants.URL_API}/messages.getConversations"
|
||||||
|
const val GET_BY_ID = "${AppConstants.URL_API}/messages.getConversationsById"
|
||||||
const val DELETE = "${AppConstants.URL_API}/messages.deleteConversation"
|
const val DELETE = "${AppConstants.URL_API}/messages.deleteConversation"
|
||||||
const val PIN = "${AppConstants.URL_API}/messages.pinConversation"
|
const val PIN = "${AppConstants.URL_API}/messages.pinConversation"
|
||||||
const val UNPIN = "${AppConstants.URL_API}/messages.unpinConversation"
|
const val UNPIN = "${AppConstants.URL_API}/messages.unpinConversation"
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package dev.meloda.fast.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.Indication
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.IconButtonColors
|
||||||
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalUseFallbackRippleImplementation
|
||||||
|
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||||
|
import androidx.compose.material3.ripple
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun IconButton(
|
||||||
|
onClick: () -> Unit = {},
|
||||||
|
onLongClick: (() -> Unit)? = null,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
colors: IconButtonColors = IconButtonDefaults.iconButtonColors(),
|
||||||
|
interactionSource: MutableInteractionSource? = null,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
|
.clip(IconButtonTokens.StateLayerShape)
|
||||||
|
.background(color = colors.containerColor(enabled))
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
enabled = enabled,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = rippleOrFallbackImplementation(
|
||||||
|
bounded = false,
|
||||||
|
radius = IconButtonTokens.StateLayerSize / 2
|
||||||
|
)
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
val contentColor = colors.contentColor(enabled)
|
||||||
|
CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION_ERROR")
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
internal fun rippleOrFallbackImplementation(
|
||||||
|
bounded: Boolean = true,
|
||||||
|
radius: Dp = Dp.Unspecified,
|
||||||
|
color: Color = Color.Unspecified
|
||||||
|
): Indication {
|
||||||
|
return if (LocalUseFallbackRippleImplementation.current) {
|
||||||
|
rememberRipple(bounded, radius, color)
|
||||||
|
} else {
|
||||||
|
ripple(bounded, radius, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object IconButtonTokens {
|
||||||
|
val StateLayerShape = CircleShape
|
||||||
|
val StateLayerSize = 40.0.dp
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
internal fun IconButtonColors.containerColor(enabled: Boolean): Color =
|
||||||
|
if (enabled) containerColor else disabledContainerColor
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
internal fun IconButtonColors.contentColor(enabled: Boolean): Color =
|
||||||
|
if (enabled) contentColor else disabledContentColor
|
||||||
@@ -182,7 +182,7 @@
|
|||||||
<string name="settings_appearance_multiline_summary">Заголовок чата и текст сообщения смогут занимать несколько строчек</string>
|
<string name="settings_appearance_multiline_summary">Заголовок чата и текст сообщения смогут занимать несколько строчек</string>
|
||||||
<string name="settings_features_title">Фичи</string>
|
<string name="settings_features_title">Фичи</string>
|
||||||
<string name="settings_features_fast_text_title">Fast текст</string>
|
<string name="settings_features_fast_text_title">Fast текст</string>
|
||||||
<string name="settings_features_long_poll_in_background_title">[WIP] LongPoll в фоне</string>
|
<string name="settings_features_long_poll_in_background_title">LongPoll в фоне</string>
|
||||||
<string name="settings_features_long_poll_in_background_summary">Ваши сообщения будут обновляться, даже если приложение находится в фоне</string>
|
<string name="settings_features_long_poll_in_background_summary">Ваши сообщения будут обновляться, даже если приложение находится в фоне</string>
|
||||||
<string name="settings_activity_title">Активность</string>
|
<string name="settings_activity_title">Активность</string>
|
||||||
<string name="settings_activity_send_online_title">Быть «в сети»</string>
|
<string name="settings_activity_send_online_title">Быть «в сети»</string>
|
||||||
|
|||||||
@@ -240,8 +240,8 @@
|
|||||||
<string name="settings_appearance_multiline_summary">The title of the conversation and the text of the message can take up multiple lines</string>
|
<string name="settings_appearance_multiline_summary">The title of the conversation and the text of the message can take up multiple lines</string>
|
||||||
<string name="settings_features_title">Features</string>
|
<string name="settings_features_title">Features</string>
|
||||||
<string name="settings_features_fast_text_title">Fast text</string>
|
<string name="settings_features_fast_text_title">Fast text</string>
|
||||||
<string name="settings_features_long_poll_in_background_title">[WIP] LongPoll in background</string>
|
<string name="settings_features_long_poll_in_background_title">LongPoll in background</string>
|
||||||
<string name="settings_features_long_poll_in_background_summary">Your messages will be updates even when app is not on the screen</string>
|
<string name="settings_features_long_poll_in_background_summary">Your messages will be updating even when app is not on the screen</string>
|
||||||
<string name="settings_activity_title">Activity</string>
|
<string name="settings_activity_title">Activity</string>
|
||||||
<string name="settings_activity_send_online_title">Send online status</string>
|
<string name="settings_activity_send_online_title">Send online status</string>
|
||||||
<string name="settings_activity_send_online_summary">Online status will be sent every five minutes</string>
|
<string name="settings_activity_send_online_summary">Online status will be sent every five minutes</string>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ fun NavGraphBuilder.authNavGraph(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: 17.12.2024, Danil Nikolaev: check clearing backstack from main screen
|
||||||
fun NavController.navigateToAuth(clearBackStack: Boolean = false) {
|
fun NavController.navigateToAuth(clearBackStack: Boolean = false) {
|
||||||
val navController = this
|
val navController = this
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package dev.meloda.fast.auth.captcha.di
|
|||||||
import dev.meloda.fast.auth.captcha.CaptchaViewModel
|
import dev.meloda.fast.auth.captcha.CaptchaViewModel
|
||||||
import dev.meloda.fast.auth.captcha.CaptchaViewModelImpl
|
import dev.meloda.fast.auth.captcha.CaptchaViewModelImpl
|
||||||
import dev.meloda.fast.auth.captcha.validation.CaptchaValidator
|
import dev.meloda.fast.auth.captcha.validation.CaptchaValidator
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ class LoginViewModelImpl(
|
|||||||
|
|
||||||
private fun startLongPoll() {
|
private fun startLongPoll() {
|
||||||
longPollController.setStateToApply(
|
longPollController.setStateToApply(
|
||||||
if (AppSettings.Debug.longPollInBackground) {
|
if (AppSettings.Experimental.longPollInBackground) {
|
||||||
LongPollState.Background
|
LongPollState.Background
|
||||||
} else {
|
} else {
|
||||||
LongPollState.InApp
|
LongPollState.InApp
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import dev.meloda.fast.auth.login.LoginViewModelImpl
|
|||||||
import dev.meloda.fast.domain.OAuthUseCase
|
import dev.meloda.fast.domain.OAuthUseCase
|
||||||
import dev.meloda.fast.domain.OAuthUseCaseImpl
|
import dev.meloda.fast.domain.OAuthUseCaseImpl
|
||||||
import dev.meloda.fast.auth.login.validation.LoginValidator
|
import dev.meloda.fast.auth.login.validation.LoginValidator
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import dev.meloda.fast.domain.AuthUseCaseImpl
|
|||||||
import dev.meloda.fast.auth.validation.ValidationViewModel
|
import dev.meloda.fast.auth.validation.ValidationViewModel
|
||||||
import dev.meloda.fast.auth.validation.ValidationViewModelImpl
|
import dev.meloda.fast.auth.validation.ValidationViewModelImpl
|
||||||
import dev.meloda.fast.auth.validation.validation.ValidationValidator
|
import dev.meloda.fast.auth.validation.validation.ValidationValidator
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|||||||
-8
@@ -70,11 +70,9 @@ import dev.meloda.fast.chatmaterials.ChatMaterialsViewModel
|
|||||||
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModelImpl
|
import dev.meloda.fast.chatmaterials.ChatMaterialsViewModelImpl
|
||||||
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState
|
||||||
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
import dev.meloda.fast.chatmaterials.model.UiChatMaterial
|
||||||
import dev.meloda.fast.datastore.UserSettings
|
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.compose.koinInject
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatMaterialsRoute(
|
fun ChatMaterialsRoute(
|
||||||
@@ -82,15 +80,10 @@ fun ChatMaterialsRoute(
|
|||||||
onPhotoClicked: (url: String) -> Unit,
|
onPhotoClicked: (url: String) -> Unit,
|
||||||
viewModel: ChatMaterialsViewModel = koinViewModel<ChatMaterialsViewModelImpl>()
|
viewModel: ChatMaterialsViewModel = koinViewModel<ChatMaterialsViewModelImpl>()
|
||||||
) {
|
) {
|
||||||
val userSettings: UserSettings = koinInject()
|
|
||||||
|
|
||||||
val enablePullToRefresh by userSettings.enablePullToRefresh.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
ChatMaterialsScreen(
|
ChatMaterialsScreen(
|
||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
enablePullToRefresh = enablePullToRefresh,
|
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onTypeChanged = viewModel::onTypeChanged,
|
onTypeChanged = viewModel::onTypeChanged,
|
||||||
onRefreshDropdownItemClicked = viewModel::onRefresh,
|
onRefreshDropdownItemClicked = viewModel::onRefresh,
|
||||||
@@ -107,7 +100,6 @@ fun ChatMaterialsRoute(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ChatMaterialsScreen(
|
fun ChatMaterialsScreen(
|
||||||
screenState: ChatMaterialsScreenState = ChatMaterialsScreenState.EMPTY,
|
screenState: ChatMaterialsScreenState = ChatMaterialsScreenState.EMPTY,
|
||||||
enablePullToRefresh: Boolean = false,
|
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
onTypeChanged: (String) -> Unit = {},
|
onTypeChanged: (String) -> Unit = {},
|
||||||
onRefreshDropdownItemClicked: () -> Unit = {},
|
onRefreshDropdownItemClicked: () -> Unit = {},
|
||||||
|
|||||||
+23
@@ -26,7 +26,9 @@ import dev.meloda.fast.model.LongPollEvent
|
|||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConversation
|
||||||
import dev.meloda.fast.network.VkErrorCode
|
import dev.meloda.fast.network.VkErrorCode
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -34,6 +36,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
interface ConversationsViewModel {
|
interface ConversationsViewModel {
|
||||||
@@ -43,6 +46,7 @@ interface ConversationsViewModel {
|
|||||||
val imagesToPreload: StateFlow<List<String>>
|
val imagesToPreload: StateFlow<List<String>>
|
||||||
val currentOffset: StateFlow<Int>
|
val currentOffset: StateFlow<Int>
|
||||||
val canPaginate: StateFlow<Boolean>
|
val canPaginate: StateFlow<Boolean>
|
||||||
|
val scrollToTop: StateFlow<Boolean>
|
||||||
|
|
||||||
fun onPaginationConditionsMet()
|
fun onPaginationConditionsMet()
|
||||||
|
|
||||||
@@ -63,6 +67,10 @@ interface ConversationsViewModel {
|
|||||||
|
|
||||||
fun setScrollIndex(index: Int)
|
fun setScrollIndex(index: Int)
|
||||||
fun setScrollOffset(offset: Int)
|
fun setScrollOffset(offset: Int)
|
||||||
|
|
||||||
|
|
||||||
|
fun setScrollToTopFlow(scrollToTopFlow: Flow<Int>)
|
||||||
|
fun onScrolledToTop()
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConversationsViewModelImpl(
|
class ConversationsViewModelImpl(
|
||||||
@@ -78,6 +86,7 @@ class ConversationsViewModelImpl(
|
|||||||
override val imagesToPreload = MutableStateFlow<List<String>>(emptyList())
|
override val imagesToPreload = MutableStateFlow<List<String>>(emptyList())
|
||||||
override val currentOffset = MutableStateFlow(0)
|
override val currentOffset = MutableStateFlow(0)
|
||||||
override val canPaginate = MutableStateFlow(false)
|
override val canPaginate = MutableStateFlow(false)
|
||||||
|
override val scrollToTop = MutableStateFlow(false)
|
||||||
|
|
||||||
override fun onPaginationConditionsMet() {
|
override fun onPaginationConditionsMet() {
|
||||||
currentOffset.update { screenState.value.conversations.size }
|
currentOffset.update { screenState.value.conversations.size }
|
||||||
@@ -217,6 +226,20 @@ class ConversationsViewModelImpl(
|
|||||||
screenState.setValue { old -> old.copy(scrollOffset = offset) }
|
screenState.setValue { old -> old.copy(scrollOffset = offset) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setScrollToTopFlow(scrollToTopFlow: Flow<Int>) {
|
||||||
|
scrollToTopFlow.listenValue(viewModelScope) { index ->
|
||||||
|
if (index == 1) {
|
||||||
|
scrollToTop.emit(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScrolledToTop() {
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
scrollToTop.emit(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun hideOptions(conversationId: Int) {
|
private fun hideOptions(conversationId: Int) {
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
|
|||||||
+3
-3
@@ -1,14 +1,14 @@
|
|||||||
package dev.meloda.fast.conversations.di
|
package dev.meloda.fast.conversations.di
|
||||||
|
|
||||||
import dev.meloda.fast.conversations.ConversationsViewModelImpl
|
import dev.meloda.fast.conversations.ConversationsViewModelImpl
|
||||||
import dev.meloda.fast.domain.ConversationsUseCaseImpl
|
|
||||||
import dev.meloda.fast.domain.ConversationsUseCase
|
import dev.meloda.fast.domain.ConversationsUseCase
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import dev.meloda.fast.domain.ConversationsUseCaseImpl
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val conversationsModule = module {
|
val conversationsModule = module {
|
||||||
singleOf(::ConversationsUseCaseImpl) bind dev.meloda.fast.domain.ConversationsUseCase::class
|
singleOf(::ConversationsUseCaseImpl) bind ConversationsUseCase::class
|
||||||
viewModelOf(::ConversationsViewModelImpl)
|
viewModelOf(::ConversationsViewModelImpl)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -8,6 +8,7 @@ import dev.meloda.fast.conversations.ConversationsViewModelImpl
|
|||||||
import dev.meloda.fast.conversations.presentation.ConversationsRoute
|
import dev.meloda.fast.conversations.presentation.ConversationsRoute
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
import dev.meloda.fast.ui.extensions.sharedViewModel
|
import dev.meloda.fast.ui.extensions.sharedViewModel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -17,11 +18,13 @@ fun NavGraphBuilder.conversationsScreen(
|
|||||||
onError: (BaseError) -> Unit,
|
onError: (BaseError) -> Unit,
|
||||||
onConversationItemClicked: (id: Int) -> Unit,
|
onConversationItemClicked: (id: Int) -> Unit,
|
||||||
onPhotoClicked: (url: String) -> Unit,
|
onPhotoClicked: (url: String) -> Unit,
|
||||||
|
scrollToTopFlow: Flow<Int>,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
) {
|
) {
|
||||||
composable<Conversations> {
|
composable<Conversations> {
|
||||||
val viewModel: ConversationsViewModel =
|
val viewModel: ConversationsViewModel =
|
||||||
it.sharedViewModel<ConversationsViewModelImpl>(navController = navController)
|
it.sharedViewModel<ConversationsViewModelImpl>(navController = navController)
|
||||||
|
viewModel.setScrollToTopFlow(scrollToTopFlow)
|
||||||
|
|
||||||
ConversationsRoute(
|
ConversationsRoute(
|
||||||
onError = onError,
|
onError = onError,
|
||||||
|
|||||||
+16
-3
@@ -101,6 +101,7 @@ fun ConversationsRoute(
|
|||||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
|
val isNeedToScrollToTop by viewModel.scrollToTop.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val imagesToPreload by viewModel.imagesToPreload.collectAsStateWithLifecycle()
|
val imagesToPreload by viewModel.imagesToPreload.collectAsStateWithLifecycle()
|
||||||
LaunchedEffect(imagesToPreload) {
|
LaunchedEffect(imagesToPreload) {
|
||||||
@@ -129,7 +130,9 @@ fun ConversationsRoute(
|
|||||||
onRefresh = viewModel::onRefresh,
|
onRefresh = viewModel::onRefresh,
|
||||||
onConversationPhotoClicked = onConversationPhotoClicked,
|
onConversationPhotoClicked = onConversationPhotoClicked,
|
||||||
setScrollIndex = viewModel::setScrollIndex,
|
setScrollIndex = viewModel::setScrollIndex,
|
||||||
setScrollOffset = viewModel::setScrollOffset
|
setScrollOffset = viewModel::setScrollOffset,
|
||||||
|
isNeedToScrollToTop = isNeedToScrollToTop,
|
||||||
|
onScrolledToTop = viewModel::onScrolledToTop
|
||||||
)
|
)
|
||||||
|
|
||||||
HandleDialogs(
|
HandleDialogs(
|
||||||
@@ -156,7 +159,9 @@ fun ConversationsScreen(
|
|||||||
onRefresh: () -> Unit = {},
|
onRefresh: () -> Unit = {},
|
||||||
onConversationPhotoClicked: (url: String) -> Unit = {},
|
onConversationPhotoClicked: (url: String) -> Unit = {},
|
||||||
setScrollIndex: (Int) -> Unit = {},
|
setScrollIndex: (Int) -> Unit = {},
|
||||||
setScrollOffset: (Int) -> Unit = {}
|
setScrollOffset: (Int) -> Unit = {},
|
||||||
|
isNeedToScrollToTop: Boolean = false,
|
||||||
|
onScrolledToTop: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
@@ -170,6 +175,14 @@ fun ConversationsScreen(
|
|||||||
initialFirstVisibleItemScrollOffset = screenState.scrollOffset
|
initialFirstVisibleItemScrollOffset = screenState.scrollOffset
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(isNeedToScrollToTop) {
|
||||||
|
if (isNeedToScrollToTop) {
|
||||||
|
listState.scrollToItem(0)
|
||||||
|
onScrolledToTop()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(listState) {
|
LaunchedEffect(listState) {
|
||||||
snapshotFlow { listState.firstVisibleItemIndex }
|
snapshotFlow { listState.firstVisibleItemIndex }
|
||||||
.debounce(500L)
|
.debounce(500L)
|
||||||
@@ -310,7 +323,7 @@ fun ConversationsScreen(
|
|||||||
) {
|
) {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (AppSettings.Debug.enableHaptic) {
|
if (AppSettings.General.enableHaptic) {
|
||||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import dev.meloda.fast.data.State
|
|||||||
import dev.meloda.fast.data.processState
|
import dev.meloda.fast.data.processState
|
||||||
import dev.meloda.fast.datastore.UserSettings
|
import dev.meloda.fast.datastore.UserSettings
|
||||||
import dev.meloda.fast.domain.FriendsUseCase
|
import dev.meloda.fast.domain.FriendsUseCase
|
||||||
|
import dev.meloda.fast.domain.LoadUsersByIdsUseCase
|
||||||
import dev.meloda.fast.friends.model.FriendsScreenState
|
import dev.meloda.fast.friends.model.FriendsScreenState
|
||||||
import dev.meloda.fast.friends.util.asPresentation
|
import dev.meloda.fast.friends.util.asPresentation
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
@@ -42,7 +43,8 @@ interface FriendsViewModel {
|
|||||||
|
|
||||||
class FriendsViewModelImpl(
|
class FriendsViewModelImpl(
|
||||||
private val friendsUseCase: FriendsUseCase,
|
private val friendsUseCase: FriendsUseCase,
|
||||||
private val userSettings: UserSettings
|
private val userSettings: UserSettings,
|
||||||
|
private val loadUsersByIdsUseCase: LoadUsersByIdsUseCase
|
||||||
) : ViewModel(), FriendsViewModel {
|
) : ViewModel(), FriendsViewModel {
|
||||||
|
|
||||||
override val screenState = MutableStateFlow(FriendsScreenState.EMPTY)
|
override val screenState = MutableStateFlow(FriendsScreenState.EMPTY)
|
||||||
@@ -94,6 +96,49 @@ class FriendsViewModelImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun loadFriends(offset: Int = currentOffset.value) {
|
private fun loadFriends(offset: Int = currentOffset.value) {
|
||||||
|
friendsUseCase.getOnlineFriends(null, null)
|
||||||
|
.listenValue(viewModelScope) { state ->
|
||||||
|
state.processState(
|
||||||
|
error = { error ->
|
||||||
|
if (error is State.Error.ApiError) {
|
||||||
|
when (error.errorCode) {
|
||||||
|
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
|
||||||
|
baseError.setValue { BaseError.SessionExpired }
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
success = { userIds ->
|
||||||
|
loadUsersByIdsUseCase(userIds = userIds)
|
||||||
|
.listenValue(viewModelScope) { state ->
|
||||||
|
state.processState(
|
||||||
|
error = { error ->
|
||||||
|
if (error is State.Error.ApiError) {
|
||||||
|
when (error.errorCode) {
|
||||||
|
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
|
||||||
|
baseError.setValue { BaseError.SessionExpired }
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
success = { onlineFriends ->
|
||||||
|
screenState.setValue { old ->
|
||||||
|
old.copy(
|
||||||
|
onlineFriends = onlineFriends.map {
|
||||||
|
it.asPresentation(userSettings.useContactNames.value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
|
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
|
||||||
.listenValue(viewModelScope) { state ->
|
.listenValue(viewModelScope) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
@@ -125,10 +170,6 @@ class FriendsViewModelImpl(
|
|||||||
it.asPresentation(userSettings.useContactNames.value)
|
it.asPresentation(userSettings.useContactNames.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
val loadedOnlineFriends = loadedFriends.filter {
|
|
||||||
it.onlineStatus.isOnline()
|
|
||||||
}
|
|
||||||
|
|
||||||
val newState = screenState.value.copy(
|
val newState = screenState.value.copy(
|
||||||
isPaginationExhausted = paginationExhausted
|
isPaginationExhausted = paginationExhausted
|
||||||
)
|
)
|
||||||
@@ -136,18 +177,12 @@ class FriendsViewModelImpl(
|
|||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
friends.emit(response)
|
friends.emit(response)
|
||||||
screenState.setValue {
|
screenState.setValue {
|
||||||
newState.copy(
|
newState.copy(friends = loadedFriends)
|
||||||
friends = loadedFriends,
|
|
||||||
onlineFriends = loadedOnlineFriends
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
friends.emit(friends.value.plus(response))
|
friends.emit(friends.value.plus(response))
|
||||||
screenState.setValue {
|
screenState.setValue {
|
||||||
newState.copy(
|
newState.copy(friends = newState.friends.plus(loadedFriends))
|
||||||
friends = newState.friends.plus(loadedFriends),
|
|
||||||
onlineFriends = newState.onlineFriends.plus(loadedOnlineFriends)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,12 @@ package dev.meloda.fast.friends.di
|
|||||||
import dev.meloda.fast.domain.FriendsUseCase
|
import dev.meloda.fast.domain.FriendsUseCase
|
||||||
import dev.meloda.fast.friends.FriendsViewModelImpl
|
import dev.meloda.fast.friends.FriendsViewModelImpl
|
||||||
import dev.meloda.fast.domain.FriendsUseCaseImpl
|
import dev.meloda.fast.domain.FriendsUseCaseImpl
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val friendsModule = module {
|
val friendsModule = module {
|
||||||
singleOf(::FriendsUseCaseImpl) bind dev.meloda.fast.domain.FriendsUseCase::class
|
singleOf(::FriendsUseCaseImpl) bind FriendsUseCase::class
|
||||||
|
|
||||||
viewModelOf(::FriendsViewModelImpl)
|
viewModelOf(::FriendsViewModelImpl)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
package dev.meloda.fast.languagepicker.di
|
package dev.meloda.fast.languagepicker.di
|
||||||
|
|
||||||
import dev.meloda.fast.languagepicker.LanguagePickerViewModelImpl
|
import dev.meloda.fast.languagepicker.LanguagePickerViewModelImpl
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val languagePickerModule = module {
|
val languagePickerModule = module {
|
||||||
|
|||||||
+14
-17
@@ -1,15 +1,15 @@
|
|||||||
package dev.meloda.fast.messageshistory
|
package dev.meloda.fast.messageshistory
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.core.content.edit
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||||
import com.conena.nanokt.collections.indexOfOrNull
|
import com.conena.nanokt.collections.indexOfOrNull
|
||||||
import com.conena.nanokt.text.isEmptyOrBlank
|
import com.conena.nanokt.text.isEmptyOrBlank
|
||||||
|
import com.conena.nanokt.text.isNotEmptyOrBlank
|
||||||
import dev.meloda.fast.common.extensions.listenValue
|
import dev.meloda.fast.common.extensions.listenValue
|
||||||
import dev.meloda.fast.common.extensions.setValue
|
import dev.meloda.fast.common.extensions.setValue
|
||||||
import dev.meloda.fast.common.provider.ResourceProvider
|
import dev.meloda.fast.common.provider.ResourceProvider
|
||||||
@@ -17,9 +17,9 @@ import dev.meloda.fast.data.UserConfig
|
|||||||
import dev.meloda.fast.data.VkMemoryCache
|
import dev.meloda.fast.data.VkMemoryCache
|
||||||
import dev.meloda.fast.data.processState
|
import dev.meloda.fast.data.processState
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import dev.meloda.fast.datastore.SettingsKeys
|
|
||||||
import dev.meloda.fast.datastore.UserSettings
|
import dev.meloda.fast.datastore.UserSettings
|
||||||
import dev.meloda.fast.domain.ConversationsUseCase
|
import dev.meloda.fast.domain.ConversationsUseCase
|
||||||
|
import dev.meloda.fast.domain.LoadConversationsByIdUseCase
|
||||||
import dev.meloda.fast.domain.LongPollUpdatesParser
|
import dev.meloda.fast.domain.LongPollUpdatesParser
|
||||||
import dev.meloda.fast.domain.MessagesUseCase
|
import dev.meloda.fast.domain.MessagesUseCase
|
||||||
import dev.meloda.fast.messageshistory.model.ActionMode
|
import dev.meloda.fast.messageshistory.model.ActionMode
|
||||||
@@ -54,19 +54,18 @@ interface MessagesHistoryViewModel {
|
|||||||
fun onRefresh()
|
fun onRefresh()
|
||||||
fun onAttachmentButtonClicked()
|
fun onAttachmentButtonClicked()
|
||||||
fun onMessageInputChanged(newText: TextFieldValue)
|
fun onMessageInputChanged(newText: TextFieldValue)
|
||||||
fun onEmojiButtonClicked()
|
fun onEmojiButtonLongClicked()
|
||||||
fun onActionButtonClicked()
|
fun onActionButtonClicked()
|
||||||
|
|
||||||
fun onPaginationConditionsMet()
|
fun onPaginationConditionsMet()
|
||||||
fun onToggleAnimationsDropdownItemClicked(enableAnimations: Boolean)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessagesHistoryViewModelImpl(
|
class MessagesHistoryViewModelImpl(
|
||||||
private val messagesUseCase: MessagesUseCase,
|
private val messagesUseCase: MessagesUseCase,
|
||||||
private val conversationsUseCase: ConversationsUseCase,
|
private val conversationsUseCase: ConversationsUseCase,
|
||||||
private val preferences: SharedPreferences,
|
|
||||||
private val resourceProvider: ResourceProvider,
|
private val resourceProvider: ResourceProvider,
|
||||||
private val userSettings: UserSettings,
|
private val userSettings: UserSettings,
|
||||||
|
private val loadConversationsByIdUseCase: LoadConversationsByIdUseCase,
|
||||||
updatesParser: LongPollUpdatesParser,
|
updatesParser: LongPollUpdatesParser,
|
||||||
savedStateHandle: SavedStateHandle
|
savedStateHandle: SavedStateHandle
|
||||||
) : MessagesHistoryViewModel, ViewModel() {
|
) : MessagesHistoryViewModel, ViewModel() {
|
||||||
@@ -123,8 +122,15 @@ class MessagesHistoryViewModelImpl(
|
|||||||
screenState.setValue { old -> old.copy(message = newText) }
|
screenState.setValue { old -> old.copy(message = newText) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEmojiButtonClicked() {
|
override fun onEmojiButtonLongClicked() {
|
||||||
|
AppSettings.Features.fastText.takeIf { it.isNotEmptyOrBlank() }?.let { text ->
|
||||||
|
screenState.setValue { old ->
|
||||||
|
val newText = "${old.message.text}$text"
|
||||||
|
old.copy(
|
||||||
|
message = TextFieldValue(text = newText, selection = TextRange(newText.length))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionButtonClicked() {
|
override fun onActionButtonClicked() {
|
||||||
@@ -150,15 +156,6 @@ class MessagesHistoryViewModelImpl(
|
|||||||
loadMessagesHistory()
|
loadMessagesHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onToggleAnimationsDropdownItemClicked(enableAnimations: Boolean) {
|
|
||||||
preferences.edit {
|
|
||||||
putBoolean(
|
|
||||||
SettingsKeys.KEY_ENABLE_ANIMATIONS_IN_MESSAGES,
|
|
||||||
enableAnimations
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleNewMessage(event: LongPollEvent.VkMessageNewEvent) {
|
private fun handleNewMessage(event: LongPollEvent.VkMessageNewEvent) {
|
||||||
val message = event.message
|
val message = event.message
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,17 +1,17 @@
|
|||||||
package dev.meloda.fast.messageshistory.di
|
package dev.meloda.fast.messageshistory.di
|
||||||
|
|
||||||
import dev.meloda.fast.domain.MessagesUseCase
|
import dev.meloda.fast.domain.MessagesUseCase
|
||||||
|
import dev.meloda.fast.domain.MessagesUseCaseImpl
|
||||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModel
|
import dev.meloda.fast.messageshistory.MessagesHistoryViewModel
|
||||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModelImpl
|
import dev.meloda.fast.messageshistory.MessagesHistoryViewModelImpl
|
||||||
import dev.meloda.fast.domain.MessagesUseCaseImpl
|
|
||||||
import dev.meloda.fast.messageshistory.validation.MessagesHistoryValidator
|
import dev.meloda.fast.messageshistory.validation.MessagesHistoryValidator
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val messagesHistoryModule = module {
|
val messagesHistoryModule = module {
|
||||||
singleOf(::MessagesUseCaseImpl) bind dev.meloda.fast.domain.MessagesUseCase::class
|
singleOf(::MessagesUseCaseImpl) bind MessagesUseCase::class
|
||||||
singleOf(::MessagesHistoryValidator)
|
singleOf(::MessagesHistoryValidator)
|
||||||
viewModelOf(::MessagesHistoryViewModelImpl) bind MessagesHistoryViewModel::class
|
viewModelOf(::MessagesHistoryViewModelImpl) bind MessagesHistoryViewModel::class
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -18,6 +18,7 @@ data class MessagesHistoryScreenState(
|
|||||||
val isPaginating: Boolean,
|
val isPaginating: Boolean,
|
||||||
val isPaginationExhausted: Boolean,
|
val isPaginationExhausted: Boolean,
|
||||||
val actionMode: ActionMode,
|
val actionMode: ActionMode,
|
||||||
|
val chatImageUrl: String?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -33,6 +34,7 @@ data class MessagesHistoryScreenState(
|
|||||||
isPaginating = false,
|
isPaginating = false,
|
||||||
isPaginationExhausted = false,
|
isPaginationExhausted = false,
|
||||||
actionMode = ActionMode.Record,
|
actionMode = ActionMode.Record,
|
||||||
|
chatImageUrl = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-39
@@ -1,10 +1,12 @@
|
|||||||
package dev.meloda.fast.messageshistory.presentation
|
package dev.meloda.fast.messageshistory.presentation
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.Animatable
|
import androidx.compose.animation.core.Animatable
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -23,9 +25,11 @@ import androidx.compose.foundation.layout.imeNestedScroll
|
|||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||||
@@ -37,7 +41,6 @@ import androidx.compose.material3.DropdownMenuItem
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@@ -60,6 +63,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
@@ -72,12 +76,12 @@ import androidx.compose.ui.unit.LayoutDirection
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import coil.compose.AsyncImage
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.hazeChild
|
import dev.chrisbanes.haze.hazeChild
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import dev.meloda.fast.datastore.SettingsKeys
|
|
||||||
import dev.meloda.fast.datastore.UserSettings
|
import dev.meloda.fast.datastore.UserSettings
|
||||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModel
|
import dev.meloda.fast.messageshistory.MessagesHistoryViewModel
|
||||||
import dev.meloda.fast.messageshistory.MessagesHistoryViewModelImpl
|
import dev.meloda.fast.messageshistory.MessagesHistoryViewModelImpl
|
||||||
@@ -86,8 +90,10 @@ import dev.meloda.fast.messageshistory.model.MessagesHistoryScreenState
|
|||||||
import dev.meloda.fast.messageshistory.util.firstMessage
|
import dev.meloda.fast.messageshistory.util.firstMessage
|
||||||
import dev.meloda.fast.messageshistory.util.indexOfMessageByCmId
|
import dev.meloda.fast.messageshistory.util.indexOfMessageByCmId
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
|
import dev.meloda.fast.ui.components.IconButton
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
import dev.meloda.fast.ui.util.getImage
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
@@ -115,11 +121,11 @@ fun MessagesHistoryRoute(
|
|||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
|
onChatMaterialsDropdownItemClicked = onChatMaterialsDropdownItemClicked,
|
||||||
onRefreshDropdownItemClicked = viewModel::onRefresh,
|
onRefreshDropdownItemClicked = viewModel::onRefresh,
|
||||||
onToggleAnimationsDropdownItemClicked = viewModel::onToggleAnimationsDropdownItemClicked,
|
|
||||||
onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
|
onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
|
||||||
onMessageInputChanged = viewModel::onMessageInputChanged,
|
onMessageInputChanged = viewModel::onMessageInputChanged,
|
||||||
onAttachmentButtonClicked = viewModel::onAttachmentButtonClicked,
|
onAttachmentButtonClicked = viewModel::onAttachmentButtonClicked,
|
||||||
onActionButtonClicked = viewModel::onActionButtonClicked
|
onActionButtonClicked = viewModel::onActionButtonClicked,
|
||||||
|
onEmojiButtonLongClicked = viewModel::onEmojiButtonLongClicked
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +147,8 @@ fun MessagesHistoryScreen(
|
|||||||
onPaginationConditionsMet: () -> Unit = {},
|
onPaginationConditionsMet: () -> Unit = {},
|
||||||
onMessageInputChanged: (TextFieldValue) -> Unit = {},
|
onMessageInputChanged: (TextFieldValue) -> Unit = {},
|
||||||
onAttachmentButtonClicked: () -> Unit = {},
|
onAttachmentButtonClicked: () -> Unit = {},
|
||||||
onActionButtonClicked: () -> Unit = {}
|
onActionButtonClicked: () -> Unit = {},
|
||||||
|
onEmojiButtonLongClicked: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
|
|
||||||
@@ -172,15 +179,6 @@ fun MessagesHistoryScreen(
|
|||||||
|
|
||||||
val hazeState = remember { HazeState() }
|
val hazeState = remember { HazeState() }
|
||||||
|
|
||||||
var animationsEnabled by remember {
|
|
||||||
mutableStateOf(
|
|
||||||
preferences.getBoolean(
|
|
||||||
SettingsKeys.KEY_ENABLE_ANIMATIONS_IN_MESSAGES,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val toolbarColorAlpha by animateFloatAsState(
|
val toolbarColorAlpha by animateFloatAsState(
|
||||||
targetValue = if (!listState.canScrollForward) 1f else 0f,
|
targetValue = if (!listState.canScrollForward) 1f else 0f,
|
||||||
label = "toolbarColorAlpha",
|
label = "toolbarColorAlpha",
|
||||||
@@ -210,6 +208,33 @@ fun MessagesHistoryScreen(
|
|||||||
)
|
)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
title = {
|
title = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
val avatar = screenState.avatar.getImage()
|
||||||
|
if (avatar is Painter) {
|
||||||
|
Image(
|
||||||
|
painter = avatar,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(36.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AsyncImage(
|
||||||
|
model = screenState.avatar.getImage(),
|
||||||
|
contentDescription = "Profile Image",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(36.dp)
|
||||||
|
.clip(CircleShape),
|
||||||
|
placeholder = painterResource(id = UiR.drawable.ic_account_circle_cut),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text =
|
text =
|
||||||
if (screenState.isLoading) stringResource(id = UiR.string.title_loading)
|
if (screenState.isLoading) stringResource(id = UiR.string.title_loading)
|
||||||
@@ -218,6 +243,7 @@ fun MessagesHistoryScreen(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.headlineSmall
|
style = MaterialTheme.typography.headlineSmall
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
@@ -280,31 +306,19 @@ fun MessagesHistoryScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (preferences.getBoolean(
|
val showHorizontalProgressBar by remember(screenState) {
|
||||||
SettingsKeys.KEY_SHOW_DEBUG_CATEGORY,
|
derivedStateOf { screenState.isLoading && screenState.messages.isNotEmpty() }
|
||||||
false
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
HorizontalDivider()
|
|
||||||
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = {
|
|
||||||
Text(text = if (animationsEnabled) "Disable animations" else "Enable animations")
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
dropDownMenuExpanded = false
|
|
||||||
animationsEnabled = !animationsEnabled
|
|
||||||
onToggleAnimationsDropdownItemClicked(animationsEnabled)
|
|
||||||
}
|
}
|
||||||
)
|
if (showHorizontalProgressBar) {
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if (screenState.isLoading && screenState.messages.isNotEmpty()) {
|
|
||||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||||
}
|
}
|
||||||
|
AnimatedVisibility(!showHorizontalProgressBar) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { padding ->
|
) { padding ->
|
||||||
@@ -320,7 +334,6 @@ fun MessagesHistoryScreen(
|
|||||||
listState = listState,
|
listState = listState,
|
||||||
immutableMessages = ImmutableList.copyOf(screenState.messages),
|
immutableMessages = ImmutableList.copyOf(screenState.messages),
|
||||||
isPaginating = screenState.isPaginating,
|
isPaginating = screenState.isPaginating,
|
||||||
enableAnimations = animationsEnabled,
|
|
||||||
messageBarHeight = messageBarHeight,
|
messageBarHeight = messageBarHeight,
|
||||||
onRequestScrollToCmId = { cmId ->
|
onRequestScrollToCmId = { cmId ->
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
@@ -371,7 +384,7 @@ fun MessagesHistoryScreen(
|
|||||||
Column(verticalArrangement = Arrangement.Bottom) {
|
Column(verticalArrangement = Arrangement.Bottom) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (AppSettings.Debug.enableHaptic) {
|
if (AppSettings.General.enableHaptic) {
|
||||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -389,6 +402,12 @@ fun MessagesHistoryScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongClick = {
|
||||||
|
if (AppSettings.General.enableHaptic) {
|
||||||
|
view.performHapticFeedback(HapticFeedbackConstantsCompat.LONG_PRESS)
|
||||||
|
}
|
||||||
|
onEmojiButtonLongClicked()
|
||||||
|
},
|
||||||
modifier = Modifier.rotate(rotation.value)
|
modifier = Modifier.rotate(rotation.value)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -427,7 +446,7 @@ fun MessagesHistoryScreen(
|
|||||||
Column(verticalArrangement = Arrangement.Bottom) {
|
Column(verticalArrangement = Arrangement.Bottom) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (AppSettings.Debug.enableHaptic) {
|
if (AppSettings.General.enableHaptic) {
|
||||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -463,7 +482,7 @@ fun MessagesHistoryScreen(
|
|||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (screenState.actionMode == ActionMode.Record) {
|
if (screenState.actionMode == ActionMode.Record) {
|
||||||
if (AppSettings.Debug.enableHaptic) {
|
if (AppSettings.General.enableHaptic) {
|
||||||
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
view.performHapticFeedback(HapticFeedbackConstantsCompat.REJECT)
|
||||||
}
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
|||||||
+8
-2
@@ -13,12 +13,14 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.haze
|
import dev.chrisbanes.haze.haze
|
||||||
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import dev.meloda.fast.messageshistory.model.UiItem
|
import dev.meloda.fast.messageshistory.model.UiItem
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
@@ -30,11 +32,15 @@ fun MessagesList(
|
|||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
immutableMessages: ImmutableList<UiItem>,
|
immutableMessages: ImmutableList<UiItem>,
|
||||||
isPaginating: Boolean,
|
isPaginating: Boolean,
|
||||||
enableAnimations: Boolean,
|
|
||||||
messageBarHeight: Dp,
|
messageBarHeight: Dp,
|
||||||
onRequestScrollToCmId: (cmId: Int) -> Unit = {}
|
onRequestScrollToCmId: (cmId: Int) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val messages = immutableMessages.toList()
|
val enableAnimations = remember {
|
||||||
|
AppSettings.Experimental.moreAnimations
|
||||||
|
}
|
||||||
|
val messages = remember(immutableMessages) {
|
||||||
|
immutableMessages.toList()
|
||||||
|
}
|
||||||
val currentTheme = LocalThemeConfig.current
|
val currentTheme = LocalThemeConfig.current
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package dev.meloda.fast.photoviewer.di
|
|||||||
|
|
||||||
import dev.meloda.fast.photoviewer.PhotoViewViewModel
|
import dev.meloda.fast.photoviewer.PhotoViewViewModel
|
||||||
import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl
|
import dev.meloda.fast.photoviewer.PhotoViewViewModelImpl
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -123,7 +123,7 @@ fun TopBar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
// IconButton(
|
// IconButton.kt(
|
||||||
// onClick = { dropdownMenuShown = true }
|
// onClick = { dropdownMenuShown = true }
|
||||||
// ) {
|
// ) {
|
||||||
// Icon(
|
// Icon(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dev.meloda.fast.profile.di
|
package dev.meloda.fast.profile.di
|
||||||
|
|
||||||
import dev.meloda.fast.profile.ProfileViewModelImpl
|
import dev.meloda.fast.profile.ProfileViewModelImpl
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val profileModule = module {
|
val profileModule = module {
|
||||||
|
|||||||
@@ -154,13 +154,6 @@ class SettingsViewModelImpl(
|
|||||||
userSettings.onUseContactNamesChanged(isUsing)
|
userSettings.onUseContactNamesChanged(isUsing)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH -> {
|
|
||||||
val enable =
|
|
||||||
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH
|
|
||||||
userSettings.onEnablePullToRefreshChanged(enable)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SettingsKeys.KEY_APPEARANCE_MULTILINE -> {
|
SettingsKeys.KEY_APPEARANCE_MULTILINE -> {
|
||||||
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_MULTILINE
|
val isUsing = newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_MULTILINE
|
||||||
userSettings.onEnableMultilineChanged(isUsing)
|
userSettings.onEnableMultilineChanged(isUsing)
|
||||||
@@ -206,9 +199,9 @@ class SettingsViewModelImpl(
|
|||||||
userSettings.onShowAlertAfterCrashChanged(show)
|
userSettings.onShowAlertAfterCrashChanged(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND -> {
|
SettingsKeys.KEY_LONG_POLL_IN_BACKGROUND -> {
|
||||||
val inBackground = newValue as? Boolean
|
val inBackground = newValue as? Boolean
|
||||||
?: SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND
|
?: SettingsKeys.DEFAULT_LONG_POLL_IN_BACKGROUND
|
||||||
userSettings.onLongPollInBackgroundChanged(inBackground)
|
userSettings.onLongPollInBackgroundChanged(inBackground)
|
||||||
|
|
||||||
longPollController.setStateToApply(
|
longPollController.setStateToApply(
|
||||||
@@ -221,9 +214,9 @@ class SettingsViewModelImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_APPEARANCE_USE_BLUR -> {
|
SettingsKeys.KEY_USE_BLUR -> {
|
||||||
val isUsing =
|
val isUsing =
|
||||||
newValue as? Boolean ?: SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR
|
newValue as? Boolean ?: SettingsKeys.DEFAULT_USE_BLUR
|
||||||
userSettings.onUseBlurChanged(isUsing)
|
userSettings.onUseBlurChanged(isUsing)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,14 +225,14 @@ class SettingsViewModelImpl(
|
|||||||
userSettings.onShowEmojiButtonChanged(show)
|
userSettings.onShowEmojiButtonChanged(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES -> {
|
SettingsKeys.KEY_SHOW_TIME_IN_ACTION_MESSAGES -> {
|
||||||
val show = newValue as? Boolean
|
val show = newValue as? Boolean
|
||||||
?: SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES
|
?: SettingsKeys.DEFAULT_SHOW_TIME_IN_ACTION_MESSAGES
|
||||||
userSettings.onShowTimeInActionMessagesChanged(show)
|
userSettings.onShowTimeInActionMessagesChanged(show)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsKeys.KEY_DEBUG_USE_SYSTEM_FONT -> {
|
SettingsKeys.KEY_USE_SYSTEM_FONT -> {
|
||||||
val use = newValue as? Boolean ?: SettingsKeys.DEFAULT_DEBUG_USE_SYSTEM_FONT
|
val use = newValue as? Boolean ?: SettingsKeys.DEFAULT_USE_SYSTEM_FONT
|
||||||
userSettings.onUseSystemFontChanged(use)
|
userSettings.onUseSystemFontChanged(use)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,10 +276,16 @@ class SettingsViewModelImpl(
|
|||||||
text = UiText.Resource(UiR.string.settings_general_contact_names_summary),
|
text = UiText.Resource(UiR.string.settings_general_contact_names_summary),
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_CONTACT_NAMES
|
||||||
)
|
)
|
||||||
val generalEnablePullToRefresh = SettingsItem.Switch(
|
val generalShowEmojiButton = SettingsItem.Switch(
|
||||||
key = SettingsKeys.KEY_ENABLE_PULL_TO_REFRESH,
|
key = SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_ENABLE_PULL_TO_REFRESH,
|
title = UiText.Simple("Show emoji button"),
|
||||||
title = UiText.Resource(UiR.string.settings_general_enable_pull_to_refresh_title)
|
text = UiText.Simple("Show emoji button in chat panel"),
|
||||||
|
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
||||||
|
)
|
||||||
|
val generalEnableHaptic = SettingsItem.Switch(
|
||||||
|
key = SettingsKeys.KEY_ENABLE_HAPTIC,
|
||||||
|
defaultValue = SettingsKeys.DEFAULT_ENABLE_HAPTIC,
|
||||||
|
title = UiText.Simple("Enable haptic")
|
||||||
)
|
)
|
||||||
|
|
||||||
val appearanceTitle = SettingsItem.Title(
|
val appearanceTitle = SettingsItem.Title(
|
||||||
@@ -340,7 +339,11 @@ class SettingsViewModelImpl(
|
|||||||
text = UiText.Resource(UiR.string.settings_dynamic_colors_description),
|
text = UiText.Resource(UiR.string.settings_dynamic_colors_description),
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
|
defaultValue = SettingsKeys.DEFAULT_VALUE_USE_DYNAMIC_COLORS
|
||||||
)
|
)
|
||||||
|
val appearanceUseSystemFont = SettingsItem.Switch(
|
||||||
|
key = SettingsKeys.KEY_USE_SYSTEM_FONT,
|
||||||
|
defaultValue = SettingsKeys.DEFAULT_USE_SYSTEM_FONT,
|
||||||
|
title = UiText.Simple("Use system font")
|
||||||
|
)
|
||||||
val appearanceLanguage = SettingsItem.TitleText(
|
val appearanceLanguage = SettingsItem.TitleText(
|
||||||
key = SettingsKeys.KEY_APPEARANCE_LANGUAGE,
|
key = SettingsKeys.KEY_APPEARANCE_LANGUAGE,
|
||||||
title = UiText.Resource(UiR.string.settings_application_language),
|
title = UiText.Resource(UiR.string.settings_application_language),
|
||||||
@@ -374,6 +377,34 @@ class SettingsViewModelImpl(
|
|||||||
text = UiText.Resource(UiR.string.settings_activity_send_online_summary)
|
text = UiText.Resource(UiR.string.settings_activity_send_online_summary)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val experimentalTitle = SettingsItem.Title(
|
||||||
|
key = "experimental",
|
||||||
|
title = UiText.Simple("Experimental - VERY unstable")
|
||||||
|
)
|
||||||
|
val experimentalLongPollBackground = SettingsItem.Switch(
|
||||||
|
key = SettingsKeys.KEY_LONG_POLL_IN_BACKGROUND,
|
||||||
|
defaultValue = SettingsKeys.DEFAULT_LONG_POLL_IN_BACKGROUND,
|
||||||
|
title = UiText.Resource(UiR.string.settings_features_long_poll_in_background_title),
|
||||||
|
text = UiText.Resource(UiR.string.settings_features_long_poll_in_background_summary)
|
||||||
|
)
|
||||||
|
val experimentalShowTimeInActionMessages = SettingsItem.Switch(
|
||||||
|
key = SettingsKeys.KEY_SHOW_TIME_IN_ACTION_MESSAGES,
|
||||||
|
defaultValue = SettingsKeys.DEFAULT_SHOW_TIME_IN_ACTION_MESSAGES,
|
||||||
|
title = UiText.Simple("Show time in action messages")
|
||||||
|
)
|
||||||
|
val experimentalUseBlur = SettingsItem.Switch(
|
||||||
|
key = SettingsKeys.KEY_USE_BLUR,
|
||||||
|
defaultValue = SettingsKeys.DEFAULT_USE_BLUR,
|
||||||
|
title = UiText.Simple("Use blur"),
|
||||||
|
text = UiText.Simple("Adds blur wherever possible\nWorks on android 12 and newer"),
|
||||||
|
)
|
||||||
|
val enableAnimations = SettingsItem.Switch(
|
||||||
|
key = SettingsKeys.KEY_MORE_ANIMATIONS,
|
||||||
|
defaultValue = SettingsKeys.DEFAULT_MORE_ANIMATIONS,
|
||||||
|
title = UiText.Simple("More animations"),
|
||||||
|
text = UiText.Simple("Use animations wherever possible")
|
||||||
|
)
|
||||||
|
|
||||||
val debugTitle = SettingsItem.Title(
|
val debugTitle = SettingsItem.Title(
|
||||||
key = "debug",
|
key = "debug",
|
||||||
title = UiText.Resource(UiR.string.settings_debug_title)
|
title = UiText.Resource(UiR.string.settings_debug_title)
|
||||||
@@ -389,34 +420,6 @@ class SettingsViewModelImpl(
|
|||||||
title = UiText.Simple("Show alert after crash"),
|
title = UiText.Simple("Show alert after crash"),
|
||||||
text = UiText.Simple("Shows alert dialog with stacktrace after app crashed\n(it will be not shown if you perform crash manually)")
|
text = UiText.Simple("Shows alert dialog with stacktrace after app crashed\n(it will be not shown if you perform crash manually)")
|
||||||
)
|
)
|
||||||
val debugLongPollBackground = SettingsItem.Switch(
|
|
||||||
key = SettingsKeys.KEY_FEATURES_LONG_POLL_IN_BACKGROUND,
|
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_FEATURES_LONG_POLL_IN_BACKGROUND,
|
|
||||||
title = UiText.Resource(UiR.string.settings_features_long_poll_in_background_title),
|
|
||||||
text = UiText.Resource(UiR.string.settings_features_long_poll_in_background_summary)
|
|
||||||
)
|
|
||||||
val debugUseBlur = SettingsItem.Switch(
|
|
||||||
key = SettingsKeys.KEY_APPEARANCE_USE_BLUR,
|
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_APPEARANCE_USE_BLUR,
|
|
||||||
title = UiText.Simple("[WIP] Use blur"),
|
|
||||||
text = UiText.Simple("Adds blur wherever possible\nWorks on android 12 and newer"),
|
|
||||||
)
|
|
||||||
val debugShowEmojiButton = SettingsItem.Switch(
|
|
||||||
key = SettingsKeys.KEY_SHOW_EMOJI_BUTTON,
|
|
||||||
title = UiText.Simple("Show emoji button"),
|
|
||||||
text = UiText.Simple("Show emoji button in chat panel"),
|
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_KEY_SHOW_EMOJI_BUTTON
|
|
||||||
)
|
|
||||||
val debugShowTimeInActionMessages = SettingsItem.Switch(
|
|
||||||
key = SettingsKeys.KEY_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
|
|
||||||
defaultValue = SettingsKeys.DEFAULT_VALUE_APPEARANCE_SHOW_TIME_IN_ACTION_MESSAGES,
|
|
||||||
title = UiText.Simple("Show time in action messages")
|
|
||||||
)
|
|
||||||
val debugEnableHaptic = SettingsItem.Switch(
|
|
||||||
key = SettingsKeys.KEY_DEBUG_ENABLE_HAPTIC,
|
|
||||||
defaultValue = SettingsKeys.DEFAULT_DEBUG_ENABLE_HAPTIC,
|
|
||||||
title = UiText.Simple("Enable haptic")
|
|
||||||
)
|
|
||||||
|
|
||||||
val logLevelValues = listOf(
|
val logLevelValues = listOf(
|
||||||
LogLevel.NONE to UiText.Simple("None"),
|
LogLevel.NONE to UiText.Simple("None"),
|
||||||
@@ -440,12 +443,6 @@ class SettingsViewModelImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val debugUseSystemFont = SettingsItem.Switch(
|
|
||||||
key = SettingsKeys.KEY_DEBUG_USE_SYSTEM_FONT,
|
|
||||||
defaultValue = SettingsKeys.DEFAULT_DEBUG_USE_SYSTEM_FONT,
|
|
||||||
title = UiText.Simple("Use system font")
|
|
||||||
)
|
|
||||||
|
|
||||||
val debugHideDebugList = SettingsItem.TitleText(
|
val debugHideDebugList = SettingsItem.TitleText(
|
||||||
key = SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST,
|
key = SettingsKeys.KEY_DEBUG_HIDE_DEBUG_LIST,
|
||||||
title = UiText.Simple("Hide debug list")
|
title = UiText.Simple("Hide debug list")
|
||||||
@@ -458,7 +455,8 @@ class SettingsViewModelImpl(
|
|||||||
val generalList = listOf(
|
val generalList = listOf(
|
||||||
generalTitle,
|
generalTitle,
|
||||||
generalUseContactNames,
|
generalUseContactNames,
|
||||||
generalEnablePullToRefresh
|
generalShowEmojiButton,
|
||||||
|
generalEnableHaptic
|
||||||
)
|
)
|
||||||
val appearanceList = listOf(
|
val appearanceList = listOf(
|
||||||
appearanceTitle,
|
appearanceTitle,
|
||||||
@@ -466,6 +464,7 @@ class SettingsViewModelImpl(
|
|||||||
appearanceDarkTheme,
|
appearanceDarkTheme,
|
||||||
appearanceUseAmoledDarkTheme,
|
appearanceUseAmoledDarkTheme,
|
||||||
appearanceUseDynamicColors,
|
appearanceUseDynamicColors,
|
||||||
|
appearanceUseSystemFont,
|
||||||
appearanceLanguage
|
appearanceLanguage
|
||||||
)
|
)
|
||||||
val featuresList = listOf(
|
val featuresList = listOf(
|
||||||
@@ -476,18 +475,19 @@ class SettingsViewModelImpl(
|
|||||||
activityTitle,
|
activityTitle,
|
||||||
visibilitySendOnlineStatus,
|
visibilitySendOnlineStatus,
|
||||||
)
|
)
|
||||||
|
val experimentalList = listOf(
|
||||||
|
experimentalTitle,
|
||||||
|
experimentalLongPollBackground,
|
||||||
|
experimentalShowTimeInActionMessages,
|
||||||
|
experimentalUseBlur,
|
||||||
|
enableAnimations
|
||||||
|
)
|
||||||
val debugList = mutableListOf<SettingsItem<*>>()
|
val debugList = mutableListOf<SettingsItem<*>>()
|
||||||
listOf(
|
listOf(
|
||||||
debugTitle,
|
debugTitle,
|
||||||
debugPerformCrash,
|
debugPerformCrash,
|
||||||
debugShowCrashAlert,
|
debugShowCrashAlert,
|
||||||
debugLongPollBackground,
|
|
||||||
debugUseBlur,
|
|
||||||
debugShowEmojiButton,
|
|
||||||
debugShowTimeInActionMessages,
|
|
||||||
debugEnableHaptic,
|
|
||||||
debugNetworkLogLevel,
|
debugNetworkLogLevel,
|
||||||
debugUseSystemFont
|
|
||||||
).forEach(debugList::add)
|
).forEach(debugList::add)
|
||||||
|
|
||||||
debugList += debugHideDebugList
|
debugList += debugHideDebugList
|
||||||
@@ -499,6 +499,7 @@ class SettingsViewModelImpl(
|
|||||||
appearanceList,
|
appearanceList,
|
||||||
featuresList,
|
featuresList,
|
||||||
visibilityList,
|
visibilityList,
|
||||||
|
experimentalList,
|
||||||
debugList,
|
debugList,
|
||||||
).forEach(settingsList::addAll)
|
).forEach(settingsList::addAll)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package dev.meloda.fast.settings.di
|
|||||||
|
|
||||||
import dev.meloda.fast.settings.SettingsViewModel
|
import dev.meloda.fast.settings.SettingsViewModel
|
||||||
import dev.meloda.fast.settings.SettingsViewModelImpl
|
import dev.meloda.fast.settings.SettingsViewModelImpl
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -113,7 +113,7 @@ fun SettingsScreen(
|
|||||||
|
|
||||||
LaunchedEffect(hapticType) {
|
LaunchedEffect(hapticType) {
|
||||||
if (hapticType != null) {
|
if (hapticType != null) {
|
||||||
if (AppSettings.Debug.enableHaptic) {
|
if (AppSettings.General.enableHaptic) {
|
||||||
view.performHapticFeedback(hapticType.getHaptic())
|
view.performHapticFeedback(hapticType.getHaptic())
|
||||||
}
|
}
|
||||||
onHapticPerformed()
|
onHapticPerformed()
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
minSdk = "23"
|
minSdk = "23"
|
||||||
targetSdk = "35"
|
targetSdk = "35"
|
||||||
compileSdk = "35"
|
compileSdk = "35"
|
||||||
versionCode = "7"
|
versionCode = "8"
|
||||||
versionName = "0.1.4"
|
versionName = "0.1.5"
|
||||||
|
|
||||||
agp = "8.7.3"
|
agp = "8.7.3"
|
||||||
converterMoshi = "2.11.0"
|
converterMoshi = "2.11.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user