Compare commits
21 Commits
7b2c102470
...
0.2.3
| Author | SHA1 | Date | |
|---|---|---|---|
| 155a3666ad | |||
| ce375c902c | |||
| 96b4fc8539 | |||
| e3e9157dd5 | |||
| 5aa28066d7 | |||
| 1638d70ef2 | |||
| 8c13d9e7e1 | |||
| 5dd829b6f6 | |||
| 2a238fa1bf | |||
| 3f54961ac6 | |||
| 045f2e8268 | |||
| 3eb33b2612 | |||
| f2d565fd3e | |||
| 7ab333280c | |||
| 45ee0acea5 | |||
| 7b6571f208 | |||
| 389d3f9e52 | |||
| 69a50f8fcd | |||
| 8839015249 | |||
| 478639e427 | |||
| dcbfd43896 |
@@ -40,7 +40,7 @@ jobs:
|
|||||||
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload APK with original name
|
- name: Upload APK with original name
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ env.APK_NAME }}
|
name: ${{ env.APK_NAME }}
|
||||||
path: ${{ env.APK_PATH }}
|
path: ${{ env.APK_PATH }}
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
echo "APK_NAME=$(basename $APK_PATH)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload APK with original name
|
- name: Upload APK with original name
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: ${{ env.APK_NAME }}
|
name: ${{ env.APK_NAME }}
|
||||||
path: ${{ env.APK_PATH }}
|
path: ${{ env.APK_PATH }}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
run: ./gradlew assembleRelease
|
run: ./gradlew assembleRelease
|
||||||
|
|
||||||
- name: Upload release APK
|
- name: Upload release APK
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: app-release.apk
|
name: app-release.apk
|
||||||
path: app/build/outputs/apk/release/app-release.apk
|
path: app/build/outputs/apk/release/app-release.apk
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
run: ./gradlew bundleRelease
|
run: ./gradlew bundleRelease
|
||||||
|
|
||||||
- name: Upload release Bundle
|
- name: Upload release Bundle
|
||||||
uses: actions/upload-artifact@v5
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: app-release.aab
|
name: app-release.aab
|
||||||
path: app/build/outputs/bundle/release/app-release.aab
|
path: app/build/outputs/bundle/release/app-release.aab
|
||||||
|
|||||||
+14
-14
@@ -13,8 +13,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "dev.meloda.fastvk"
|
applicationId = "dev.meloda.fastvk"
|
||||||
|
|
||||||
versionCode = 10
|
versionCode = 11
|
||||||
versionName = "0.2.2"
|
versionName = "0.2.3"
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
@@ -59,17 +59,17 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
// applicationVariants.all {
|
||||||
outputs.all {
|
// outputs.all {
|
||||||
val date = System.currentTimeMillis() / 1000
|
// val date = System.currentTimeMillis() / 1000
|
||||||
val buildType = buildType.name
|
// val buildType = buildType.name
|
||||||
val appVersion = versionName
|
// val appVersion = versionName
|
||||||
val appVersionCode = versionCode
|
// val appVersionCode = versionCode
|
||||||
|
//
|
||||||
val newApkName = "app-$buildType-v$appVersion($appVersionCode)-$date.apk"
|
// val newApkName = "app-$buildType-v$appVersion($appVersionCode)-$date.apk"
|
||||||
(this as? BaseVariantOutputImpl)?.outputFileName = newApkName
|
// (this as? BaseVariantOutputImpl)?.outputFileName = newApkName
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
@@ -82,7 +82,7 @@ dependencies {
|
|||||||
implementation(projects.feature.auth)
|
implementation(projects.feature.auth)
|
||||||
|
|
||||||
implementation(projects.feature.chatmaterials)
|
implementation(projects.feature.chatmaterials)
|
||||||
implementation(projects.feature.conversations)
|
implementation(projects.feature.convos)
|
||||||
implementation(projects.feature.languagepicker)
|
implementation(projects.feature.languagepicker)
|
||||||
implementation(projects.feature.messageshistory)
|
implementation(projects.feature.messageshistory)
|
||||||
implementation(projects.feature.photoviewer)
|
implementation(projects.feature.photoviewer)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.app.Application
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.ImageLoaderFactory
|
import coil.ImageLoaderFactory
|
||||||
|
import com.skydoves.compose.stability.runtime.ComposeStabilityAnalyzer
|
||||||
|
import dev.meloda.fast.auth.BuildConfig
|
||||||
import dev.meloda.fast.common.di.applicationModule
|
import dev.meloda.fast.common.di.applicationModule
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import org.koin.android.ext.android.get
|
import org.koin.android.ext.android.get
|
||||||
@@ -20,6 +22,8 @@ class AppGlobal : Application(), ImageLoaderFactory {
|
|||||||
AppSettings.init(preferences)
|
AppSettings.init(preferences)
|
||||||
|
|
||||||
initKoin()
|
initKoin()
|
||||||
|
|
||||||
|
ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initKoin() {
|
private fun initKoin() {
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import dev.meloda.fast.common.LongPollControllerImpl
|
|||||||
import dev.meloda.fast.common.provider.Provider
|
import dev.meloda.fast.common.provider.Provider
|
||||||
import dev.meloda.fast.common.provider.ResourceProvider
|
import dev.meloda.fast.common.provider.ResourceProvider
|
||||||
import dev.meloda.fast.common.provider.ResourceProviderImpl
|
import dev.meloda.fast.common.provider.ResourceProviderImpl
|
||||||
import dev.meloda.fast.conversations.di.conversationsModule
|
import dev.meloda.fast.convos.di.convosModule
|
||||||
import dev.meloda.fast.conversations.di.createChatModule
|
import dev.meloda.fast.convos.di.createChatModule
|
||||||
import dev.meloda.fast.domain.di.domainModule
|
import dev.meloda.fast.domain.di.domainModule
|
||||||
import dev.meloda.fast.friends.di.friendsModule
|
import dev.meloda.fast.friends.di.friendsModule
|
||||||
import dev.meloda.fast.languagepicker.di.languagePickerModule
|
import dev.meloda.fast.languagepicker.di.languagePickerModule
|
||||||
@@ -41,7 +41,7 @@ val applicationModule = module {
|
|||||||
loginModule,
|
loginModule,
|
||||||
validationModule,
|
validationModule,
|
||||||
captchaModule,
|
captchaModule,
|
||||||
conversationsModule,
|
convosModule,
|
||||||
settingsModule,
|
settingsModule,
|
||||||
messagesHistoryModule,
|
messagesHistoryModule,
|
||||||
photoViewModule,
|
photoViewModule,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package dev.meloda.fast.navigation
|
|||||||
|
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import dev.meloda.fast.conversations.navigation.ConversationsGraph
|
import dev.meloda.fast.convos.navigation.ConvoGraph
|
||||||
import dev.meloda.fast.friends.navigation.Friends
|
import dev.meloda.fast.friends.navigation.Friends
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
import dev.meloda.fast.model.BottomNavigationItem
|
import dev.meloda.fast.model.BottomNavigationItem
|
||||||
@@ -21,7 +21,7 @@ object Main
|
|||||||
fun NavGraphBuilder.mainScreen(
|
fun NavGraphBuilder.mainScreen(
|
||||||
onError: (BaseError) -> Unit,
|
onError: (BaseError) -> Unit,
|
||||||
onSettingsButtonClicked: () -> Unit,
|
onSettingsButtonClicked: () -> Unit,
|
||||||
onNavigateToMessagesHistory: (conversationId: Long) -> Unit,
|
onNavigateToMessagesHistory: (convoId: Long) -> Unit,
|
||||||
onPhotoClicked: (url: String) -> Unit,
|
onPhotoClicked: (url: String) -> Unit,
|
||||||
onMessageClicked: (userid: Long) -> Unit,
|
onMessageClicked: (userid: Long) -> Unit,
|
||||||
onNavigateToCreateChat: () -> Unit
|
onNavigateToCreateChat: () -> Unit
|
||||||
@@ -29,20 +29,20 @@ fun NavGraphBuilder.mainScreen(
|
|||||||
val navigationItems = ImmutableList.of(
|
val navigationItems = ImmutableList.of(
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
titleResId = R.string.title_friends,
|
titleResId = R.string.title_friends,
|
||||||
selectedIconResId = R.drawable.baseline_people_alt_24,
|
selectedIconResId = R.drawable.ic_group_fill_round_24,
|
||||||
unselectedIconResId = R.drawable.outline_people_alt_24,
|
unselectedIconResId = R.drawable.ic_group_round_24,
|
||||||
route = Friends,
|
route = Friends,
|
||||||
),
|
),
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
titleResId = R.string.title_conversations,
|
titleResId = R.string.title_convos,
|
||||||
selectedIconResId = R.drawable.baseline_chat_24,
|
selectedIconResId = R.drawable.ic_mail_fill_round_24,
|
||||||
unselectedIconResId = R.drawable.outline_chat_24,
|
unselectedIconResId = R.drawable.ic_mail_round_24,
|
||||||
route = ConversationsGraph
|
route = ConvoGraph
|
||||||
),
|
),
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
titleResId = R.string.title_profile,
|
titleResId = R.string.title_profile,
|
||||||
selectedIconResId = R.drawable.baseline_account_circle_24,
|
selectedIconResId = R.drawable.ic_account_circle_fill_round_24,
|
||||||
unselectedIconResId = R.drawable.outline_account_circle_24,
|
unselectedIconResId = R.drawable.ic_account_circle_round_24,
|
||||||
route = Profile
|
route = Profile
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.Manifest
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -15,43 +14,20 @@ import androidx.activity.SystemBarStyle
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
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.LocalResources
|
|
||||||
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 com.conena.nanokt.android.content.pxToDp
|
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
|
||||||
import dev.meloda.fast.MainViewModel
|
import dev.meloda.fast.MainViewModel
|
||||||
import dev.meloda.fast.MainViewModelImpl
|
import dev.meloda.fast.MainViewModelImpl
|
||||||
import dev.meloda.fast.common.AppConstants
|
import dev.meloda.fast.common.AppConstants
|
||||||
import dev.meloda.fast.common.LongPollController
|
|
||||||
import dev.meloda.fast.common.model.LongPollState
|
|
||||||
import dev.meloda.fast.datastore.AppSettings
|
import dev.meloda.fast.datastore.AppSettings
|
||||||
import dev.meloda.fast.datastore.UserSettings
|
|
||||||
import dev.meloda.fast.model.api.domain.VkUser
|
|
||||||
import dev.meloda.fast.service.OnlineService
|
import dev.meloda.fast.service.OnlineService
|
||||||
import dev.meloda.fast.service.longpolling.LongPollingService
|
import dev.meloda.fast.service.longpolling.LongPollingService
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
import dev.meloda.fast.ui.common.LocalSizeConfig
|
|
||||||
import dev.meloda.fast.ui.model.DeviceSize
|
|
||||||
import dev.meloda.fast.ui.model.SizeConfig
|
|
||||||
import dev.meloda.fast.ui.model.ThemeConfig
|
|
||||||
import dev.meloda.fast.ui.theme.AppTheme
|
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
|
||||||
import dev.meloda.fast.ui.theme.LocalUser
|
|
||||||
import dev.meloda.fast.ui.util.isNeedToEnableDarkMode
|
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.compose.koinInject
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@@ -88,168 +64,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
requestNotificationPermissions()
|
requestNotificationPermissions()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val resources = LocalResources.current
|
|
||||||
|
|
||||||
val userSettings: UserSettings = koinInject()
|
|
||||||
val longPollController: LongPollController = koinInject()
|
|
||||||
|
|
||||||
val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle()
|
|
||||||
val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle()
|
Log.d("VM_CREATE", "onCreate: viewModel: $viewModel")
|
||||||
|
}
|
||||||
|
|
||||||
LifecycleResumeEffect(true) {
|
LifecycleResumeEffect(true) {
|
||||||
viewModel.onAppResumed(intent)
|
viewModel.onAppResumed(intent)
|
||||||
onPauseOrDispose {}
|
onPauseOrDispose {}
|
||||||
}
|
}
|
||||||
|
|
||||||
val permissionState =
|
RootScreen(
|
||||||
rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS)
|
toggleLongPollService = { enable, inBackground ->
|
||||||
|
|
||||||
val isNeedToCheckPermission by viewModel.isNeedToCheckNotificationsPermission.collectAsStateWithLifecycle()
|
|
||||||
val isNeedToRequestPermission by viewModel.isNeedToRequestNotifications.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
LaunchedEffect(isNeedToCheckPermission) {
|
|
||||||
if (isNeedToCheckPermission) {
|
|
||||||
viewModel.onPermissionCheckStatus(permissionState.status)
|
|
||||||
|
|
||||||
if (permissionState.status.isGranted) {
|
|
||||||
if (longPollCurrentState == LongPollState.InApp) {
|
|
||||||
toggleLongPollService(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleLongPollService(
|
toggleLongPollService(
|
||||||
enable = true,
|
enable = enable,
|
||||||
inBackground = true
|
inBackground = inBackground ?: AppSettings.Experimental.longPollInBackground
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}
|
toggleOnlineService = ::toggleOnlineService
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(isNeedToRequestPermission) {
|
|
||||||
if (isNeedToRequestPermission) {
|
|
||||||
viewModel.onPermissionsRequested()
|
|
||||||
permissionState.launchPermissionRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LifecycleResumeEffect(longPollStateToApply) {
|
|
||||||
Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply")
|
|
||||||
if (longPollStateToApply != LongPollState.Background) {
|
|
||||||
if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched()
|
|
||||||
&& longPollCurrentState != longPollStateToApply
|
|
||||||
) {
|
|
||||||
toggleLongPollService(false)
|
|
||||||
Log.d("LongPoll", "recreate()")
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleLongPollService(
|
|
||||||
enable = longPollStateToApply.isLaunched(),
|
|
||||||
inBackground = longPollStateToApply == LongPollState.Background
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onPauseOrDispose {}
|
|
||||||
}
|
|
||||||
|
|
||||||
val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle()
|
|
||||||
LifecycleResumeEffect(sendOnline) {
|
|
||||||
toggleOnlineService(sendOnline)
|
|
||||||
|
|
||||||
onPauseOrDispose {
|
|
||||||
toggleOnlineService(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceWidthDp = remember(true) {
|
|
||||||
resources.displayMetrics.widthPixels.pxToDp()
|
|
||||||
}
|
|
||||||
val deviceHeightDp = remember(true) {
|
|
||||||
resources.displayMetrics.heightPixels.pxToDp()
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceWidthSize by remember(deviceWidthDp) {
|
|
||||||
derivedStateOf {
|
|
||||||
when {
|
|
||||||
deviceWidthDp <= 360 -> DeviceSize.Small
|
|
||||||
deviceWidthDp <= 600 -> DeviceSize.Compact
|
|
||||||
deviceWidthDp <= 840 -> DeviceSize.Medium
|
|
||||||
else -> DeviceSize.Expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val deviceHeightSize by remember(deviceHeightDp) {
|
|
||||||
derivedStateOf {
|
|
||||||
when {
|
|
||||||
deviceHeightDp <= 480 -> DeviceSize.Small
|
|
||||||
deviceHeightDp <= 700 -> DeviceSize.Compact
|
|
||||||
deviceHeightDp <= 900 -> DeviceSize.Medium
|
|
||||||
else -> DeviceSize.Expanded
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val sizeConfig by remember(deviceWidthSize, deviceHeightSize) {
|
|
||||||
mutableStateOf(
|
|
||||||
SizeConfig(
|
|
||||||
widthSize = deviceWidthSize,
|
|
||||||
heightSize = deviceHeightSize
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val darkMode by userSettings.darkMode.collectAsStateWithLifecycle()
|
|
||||||
val dynamicColors by userSettings.enableDynamicColors.collectAsStateWithLifecycle()
|
|
||||||
val amoledDark by userSettings.enableAmoledDark.collectAsStateWithLifecycle()
|
|
||||||
val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle()
|
|
||||||
val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle()
|
|
||||||
val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle()
|
|
||||||
val enableAnimations by userSettings.enableAnimations.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode)
|
|
||||||
|
|
||||||
val themeConfig by remember(
|
|
||||||
darkMode,
|
|
||||||
dynamicColors,
|
|
||||||
amoledDark,
|
|
||||||
enableBlur,
|
|
||||||
enableMultiline,
|
|
||||||
setDarkMode,
|
|
||||||
useSystemFont
|
|
||||||
) {
|
|
||||||
derivedStateOf {
|
|
||||||
ThemeConfig(
|
|
||||||
darkMode = setDarkMode,
|
|
||||||
dynamicColors = dynamicColors,
|
|
||||||
selectedColorScheme = 0,
|
|
||||||
amoledDark = amoledDark,
|
|
||||||
enableBlur = enableBlur,
|
|
||||||
enableMultiline = enableMultiline,
|
|
||||||
useSystemFont = useSystemFont,
|
|
||||||
enableAnimations = enableAnimations
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalThemeConfig provides themeConfig,
|
|
||||||
LocalSizeConfig provides sizeConfig,
|
|
||||||
LocalUser provides currentUser
|
|
||||||
) {
|
|
||||||
AppTheme(
|
|
||||||
useDarkTheme = themeConfig.darkMode,
|
|
||||||
useDynamicColors = themeConfig.dynamicColors,
|
|
||||||
selectedColorScheme = themeConfig.selectedColorScheme,
|
|
||||||
useAmoledBackground = themeConfig.amoledDark,
|
|
||||||
useSystemFont = themeConfig.useSystemFont
|
|
||||||
) {
|
|
||||||
RootScreen(viewModel = viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotificationChannels() {
|
private fun createNotificationChannels() {
|
||||||
@@ -279,7 +113,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val notificationManager: NotificationManager =
|
val notificationManager: NotificationManager =
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
notificationManager.createNotificationChannels(
|
notificationManager.createNotificationChannels(
|
||||||
listOf(
|
listOf(
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ import dev.chrisbanes.haze.HazeState
|
|||||||
import dev.chrisbanes.haze.hazeEffect
|
import dev.chrisbanes.haze.hazeEffect
|
||||||
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.conversations.navigation.ConversationsGraph
|
import dev.meloda.fast.convos.navigation.ConvoGraph
|
||||||
import dev.meloda.fast.conversations.navigation.conversationsGraph
|
import dev.meloda.fast.convos.navigation.convosGraph
|
||||||
import dev.meloda.fast.friends.navigation.Friends
|
import dev.meloda.fast.friends.navigation.Friends
|
||||||
import dev.meloda.fast.friends.navigation.friendsScreen
|
import dev.meloda.fast.friends.navigation.friendsScreen
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
@@ -60,7 +60,7 @@ fun MainScreen(
|
|||||||
navigationItems: ImmutableList<BottomNavigationItem>,
|
navigationItems: ImmutableList<BottomNavigationItem>,
|
||||||
onError: (BaseError) -> Unit = {},
|
onError: (BaseError) -> Unit = {},
|
||||||
onSettingsButtonClicked: () -> Unit = {},
|
onSettingsButtonClicked: () -> Unit = {},
|
||||||
onNavigateToMessagesHistory: (conversationId: Long) -> Unit = {},
|
onNavigateToMessagesHistory: (convoId: Long) -> Unit = {},
|
||||||
onPhotoClicked: (url: String) -> Unit = {},
|
onPhotoClicked: (url: String) -> Unit = {},
|
||||||
onMessageClicked: (userid: Long) -> Unit = {},
|
onMessageClicked: (userid: Long) -> Unit = {},
|
||||||
onNavigateToCreateChat: () -> Unit = {}
|
onNavigateToCreateChat: () -> Unit = {}
|
||||||
@@ -197,14 +197,14 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
conversationsGraph(
|
convosGraph(
|
||||||
activity = activity,
|
activity = activity,
|
||||||
onError = onError,
|
onError = onError,
|
||||||
onNavigateToMessagesHistory = onNavigateToMessagesHistory,
|
onNavigateToMessagesHistory = onNavigateToMessagesHistory,
|
||||||
onNavigateToCreateChat = onNavigateToCreateChat,
|
onNavigateToCreateChat = onNavigateToCreateChat,
|
||||||
onScrolledToTop = {
|
onScrolledToTop = {
|
||||||
tabReselected = tabReselected.toMutableMap().also {
|
tabReselected = tabReselected.toMutableMap().also {
|
||||||
it[ConversationsGraph] = false
|
it[ConvoGraph] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package dev.meloda.fast.presentation
|
package dev.meloda.fast.presentation
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.compose.LocalActivity
|
import androidx.activity.compose.LocalActivity
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
@@ -15,44 +17,223 @@ import androidx.compose.material3.TextButton
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalResources
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.conena.nanokt.android.content.pxToDp
|
||||||
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
import com.google.accompanist.permissions.isGranted
|
||||||
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import dev.meloda.fast.MainViewModel
|
import dev.meloda.fast.MainViewModel
|
||||||
|
import dev.meloda.fast.MainViewModelImpl
|
||||||
import dev.meloda.fast.auth.authNavGraph
|
import dev.meloda.fast.auth.authNavGraph
|
||||||
import dev.meloda.fast.auth.navigateToAuth
|
import dev.meloda.fast.auth.navigateToAuth
|
||||||
import dev.meloda.fast.chatmaterials.navigation.chatMaterialsScreen
|
import dev.meloda.fast.chatmaterials.navigation.chatMaterialsScreen
|
||||||
import dev.meloda.fast.chatmaterials.navigation.navigateToChatMaterials
|
import dev.meloda.fast.chatmaterials.navigation.navigateToChatMaterials
|
||||||
import dev.meloda.fast.conversations.navigation.createChatScreen
|
import dev.meloda.fast.common.LongPollController
|
||||||
import dev.meloda.fast.conversations.navigation.navigateToCreateChat
|
import dev.meloda.fast.common.model.LongPollState
|
||||||
|
import dev.meloda.fast.convos.navigation.createChatScreen
|
||||||
|
import dev.meloda.fast.convos.navigation.navigateToCreateChat
|
||||||
|
import dev.meloda.fast.datastore.UserSettings
|
||||||
import dev.meloda.fast.languagepicker.navigation.languagePickerScreen
|
import dev.meloda.fast.languagepicker.navigation.languagePickerScreen
|
||||||
import dev.meloda.fast.languagepicker.navigation.navigateToLanguagePicker
|
import dev.meloda.fast.languagepicker.navigation.navigateToLanguagePicker
|
||||||
import dev.meloda.fast.messageshistory.navigation.messagesHistoryScreen
|
import dev.meloda.fast.messageshistory.navigation.messagesHistoryScreen
|
||||||
import dev.meloda.fast.messageshistory.navigation.navigateToMessagesHistory
|
import dev.meloda.fast.messageshistory.navigation.navigateToMessagesHistory
|
||||||
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
import dev.meloda.fast.navigation.Main
|
import dev.meloda.fast.navigation.Main
|
||||||
import dev.meloda.fast.navigation.mainScreen
|
import dev.meloda.fast.navigation.mainScreen
|
||||||
import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog
|
import dev.meloda.fast.photoviewer.presentation.PhotoViewDialog
|
||||||
import dev.meloda.fast.settings.navigation.navigateToSettings
|
import dev.meloda.fast.settings.navigation.navigateToSettings
|
||||||
import dev.meloda.fast.settings.navigation.settingsScreen
|
import dev.meloda.fast.settings.navigation.settingsScreen
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.common.LocalSizeConfig
|
||||||
|
import dev.meloda.fast.ui.model.DeviceSize
|
||||||
|
import dev.meloda.fast.ui.model.SizeConfig
|
||||||
|
import dev.meloda.fast.ui.model.ThemeConfig
|
||||||
|
import dev.meloda.fast.ui.theme.AppTheme
|
||||||
import dev.meloda.fast.ui.theme.LocalNavController
|
import dev.meloda.fast.ui.theme.LocalNavController
|
||||||
import dev.meloda.fast.ui.theme.LocalNavRootController
|
import dev.meloda.fast.ui.theme.LocalNavRootController
|
||||||
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
|
import dev.meloda.fast.ui.theme.LocalUser
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||||
|
import dev.meloda.fast.ui.util.isNeedToEnableDarkMode
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
import org.koin.compose.koinInject
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RootScreen(
|
fun RootScreen(
|
||||||
navController: NavHostController = rememberNavController(),
|
toggleLongPollService: (enable: Boolean, inBackground: Boolean?) -> Unit,
|
||||||
viewModel: MainViewModel
|
toggleOnlineService: (enable: Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val resources = LocalResources.current
|
||||||
|
|
||||||
|
val userSettings: UserSettings = koinInject()
|
||||||
|
val longPollController: LongPollController = koinInject()
|
||||||
|
|
||||||
|
val longPollCurrentState by longPollController.currentState.collectAsStateWithLifecycle()
|
||||||
|
val longPollStateToApply by longPollController.stateToApply.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val viewModel: MainViewModel = koinViewModel<MainViewModelImpl>()
|
||||||
|
LaunchedEffect(viewModel) {
|
||||||
|
Log.d("VM_CREATE", "RootScreen(): viewModel: $viewModel")
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentUser: VkUser? by viewModel.currentUser.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val permissionState =
|
||||||
|
rememberPermissionState(permission = Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
|
||||||
|
val isNeedToCheckPermission by viewModel.isNeedToCheckNotificationsPermission.collectAsStateWithLifecycle()
|
||||||
|
val isNeedToRequestPermission by viewModel.isNeedToRequestNotifications.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(isNeedToCheckPermission) {
|
||||||
|
if (isNeedToCheckPermission) {
|
||||||
|
viewModel.onPermissionCheckStatus(permissionState.status)
|
||||||
|
|
||||||
|
if (permissionState.status.isGranted) {
|
||||||
|
if (longPollCurrentState == LongPollState.InApp) {
|
||||||
|
toggleLongPollService(false, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLongPollService(true, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(isNeedToRequestPermission) {
|
||||||
|
if (isNeedToRequestPermission) {
|
||||||
|
viewModel.onPermissionsRequested()
|
||||||
|
permissionState.launchPermissionRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LifecycleResumeEffect(longPollStateToApply) {
|
||||||
|
Log.d("LongPollMainActivity", "longPollStateToApply: $longPollStateToApply")
|
||||||
|
if (longPollStateToApply != LongPollState.Background) {
|
||||||
|
if (longPollStateToApply.isLaunched() && longPollCurrentState.isLaunched()
|
||||||
|
&& longPollCurrentState != longPollStateToApply
|
||||||
|
) {
|
||||||
|
toggleLongPollService(false, null)
|
||||||
|
Log.d("LongPoll", "recreate()")
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLongPollService(
|
||||||
|
longPollStateToApply.isLaunched(),
|
||||||
|
longPollStateToApply == LongPollState.Background
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPauseOrDispose {}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sendOnline by userSettings.sendOnlineStatus.collectAsStateWithLifecycle()
|
||||||
|
LifecycleResumeEffect(sendOnline) {
|
||||||
|
toggleOnlineService(sendOnline)
|
||||||
|
|
||||||
|
onPauseOrDispose {
|
||||||
|
toggleOnlineService(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceWidthDp = remember(true) {
|
||||||
|
resources.displayMetrics.widthPixels.pxToDp()
|
||||||
|
}
|
||||||
|
val deviceHeightDp = remember(true) {
|
||||||
|
resources.displayMetrics.heightPixels.pxToDp()
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceWidthSize by remember(deviceWidthDp) {
|
||||||
|
derivedStateOf {
|
||||||
|
when {
|
||||||
|
deviceWidthDp <= 360 -> DeviceSize.Small
|
||||||
|
deviceWidthDp <= 600 -> DeviceSize.Compact
|
||||||
|
deviceWidthDp <= 840 -> DeviceSize.Medium
|
||||||
|
else -> DeviceSize.Expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val deviceHeightSize by remember(deviceHeightDp) {
|
||||||
|
derivedStateOf {
|
||||||
|
when {
|
||||||
|
deviceHeightDp <= 480 -> DeviceSize.Small
|
||||||
|
deviceHeightDp <= 700 -> DeviceSize.Compact
|
||||||
|
deviceHeightDp <= 900 -> DeviceSize.Medium
|
||||||
|
else -> DeviceSize.Expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sizeConfig by remember(deviceWidthSize, deviceHeightSize) {
|
||||||
|
mutableStateOf(
|
||||||
|
SizeConfig(
|
||||||
|
widthSize = deviceWidthSize,
|
||||||
|
heightSize = deviceHeightSize
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val darkMode by userSettings.darkMode.collectAsStateWithLifecycle()
|
||||||
|
val dynamicColors by userSettings.enableDynamicColors.collectAsStateWithLifecycle()
|
||||||
|
val amoledDark by userSettings.enableAmoledDark.collectAsStateWithLifecycle()
|
||||||
|
val enableBlur by userSettings.useBlur.collectAsStateWithLifecycle()
|
||||||
|
val enableMultiline by userSettings.enableMultiline.collectAsStateWithLifecycle()
|
||||||
|
val useSystemFont by userSettings.useSystemFont.collectAsStateWithLifecycle()
|
||||||
|
val enableAnimations by userSettings.enableAnimations.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val setDarkMode = isNeedToEnableDarkMode(darkMode = darkMode)
|
||||||
|
|
||||||
|
val themeConfig by remember(
|
||||||
|
darkMode,
|
||||||
|
dynamicColors,
|
||||||
|
amoledDark,
|
||||||
|
enableBlur,
|
||||||
|
enableMultiline,
|
||||||
|
setDarkMode,
|
||||||
|
useSystemFont
|
||||||
|
) {
|
||||||
|
derivedStateOf {
|
||||||
|
ThemeConfig(
|
||||||
|
darkMode = setDarkMode,
|
||||||
|
dynamicColors = dynamicColors,
|
||||||
|
selectedColorScheme = 0,
|
||||||
|
amoledDark = amoledDark,
|
||||||
|
enableBlur = enableBlur,
|
||||||
|
enableMultiline = enableMultiline,
|
||||||
|
useSystemFont = useSystemFont,
|
||||||
|
enableAnimations = enableAnimations
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalThemeConfig provides themeConfig,
|
||||||
|
LocalSizeConfig provides sizeConfig,
|
||||||
|
LocalUser provides currentUser
|
||||||
|
) {
|
||||||
|
AppTheme(
|
||||||
|
useDarkTheme = themeConfig.darkMode,
|
||||||
|
useDynamicColors = themeConfig.dynamicColors,
|
||||||
|
selectedColorScheme = themeConfig.selectedColorScheme,
|
||||||
|
useAmoledBackground = themeConfig.amoledDark,
|
||||||
|
useSystemFont = themeConfig.useSystemFont
|
||||||
|
) {
|
||||||
|
val navController: NavHostController = rememberNavController()
|
||||||
val activity = LocalActivity.current
|
val activity = LocalActivity.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val startDestination by viewModel.startDestination.collectAsStateWithLifecycle()
|
val startDestination by viewModel.startDestination.collectAsStateWithLifecycle()
|
||||||
@@ -149,7 +330,9 @@ fun RootScreen(
|
|||||||
onError = viewModel::onError,
|
onError = viewModel::onError,
|
||||||
onSettingsButtonClicked = navController::navigateToSettings,
|
onSettingsButtonClicked = navController::navigateToSettings,
|
||||||
onNavigateToMessagesHistory = navController::navigateToMessagesHistory,
|
onNavigateToMessagesHistory = navController::navigateToMessagesHistory,
|
||||||
onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null },
|
onPhotoClicked = { url ->
|
||||||
|
photoViewerInfo = listOf(url) to null
|
||||||
|
},
|
||||||
onMessageClicked = navController::navigateToMessagesHistory,
|
onMessageClicked = navController::navigateToMessagesHistory,
|
||||||
onNavigateToCreateChat = navController::navigateToCreateChat
|
onNavigateToCreateChat = navController::navigateToCreateChat
|
||||||
)
|
)
|
||||||
@@ -164,12 +347,14 @@ fun RootScreen(
|
|||||||
)
|
)
|
||||||
chatMaterialsScreen(
|
chatMaterialsScreen(
|
||||||
onBack = navController::navigateUp,
|
onBack = navController::navigateUp,
|
||||||
onPhotoClicked = { url -> photoViewerInfo = listOf(url) to null }
|
onPhotoClicked = { url ->
|
||||||
|
photoViewerInfo = listOf(url) to null
|
||||||
|
}
|
||||||
)
|
)
|
||||||
createChatScreen(
|
createChatScreen(
|
||||||
onChatCreated = { conversationId ->
|
onChatCreated = { convoId ->
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
navController.navigateToMessagesHistory(conversationId)
|
navController.navigateToMessagesHistory(convoId)
|
||||||
},
|
},
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
@@ -191,12 +376,16 @@ fun RootScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
PhotoViewDialog(
|
PhotoViewDialog(
|
||||||
photoViewerInfo = photoViewerInfo,
|
photoViewerInfo = photoViewerInfo?.let { info ->
|
||||||
|
info.first.toImmutableList() to info.second
|
||||||
|
},
|
||||||
onDismiss = { photoViewerInfo = null }
|
onDismiss = { photoViewerInfo = null }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun NavController.navigateToMain() {
|
fun NavController.navigateToMain() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class AndroidApplicationComposeConventionPlugin : Plugin<Project> {
|
|||||||
with(target) {
|
with(target) {
|
||||||
apply(plugin = "com.android.application")
|
apply(plugin = "com.android.application")
|
||||||
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
|
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
|
||||||
|
apply(plugin = "com.github.skydoves.compose.stability.analyzer")
|
||||||
|
|
||||||
val extension = extensions.getByType<ApplicationExtension>()
|
val extension = extensions.getByType<ApplicationExtension>()
|
||||||
configureAndroidCompose(extension)
|
configureAndroidCompose(extension)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import com.android.build.api.dsl.ApplicationExtension
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
import dev.meloda.fast.configureKotlinAndroid
|
import dev.meloda.fast.configureKotlinAndroid
|
||||||
import dev.meloda.fast.libs
|
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.kotlin.dsl.configure
|
import org.gradle.kotlin.dsl.configure
|
||||||
@@ -10,12 +9,15 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
|
|||||||
with(target) {
|
with(target) {
|
||||||
with(pluginManager) {
|
with(pluginManager) {
|
||||||
apply("com.android.application")
|
apply("com.android.application")
|
||||||
apply("org.jetbrains.kotlin.android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions.configure<ApplicationExtension> {
|
extensions.configure<ApplicationExtension> {
|
||||||
configureKotlinAndroid(this)
|
configureKotlinAndroid(this)
|
||||||
defaultConfig.targetSdk = 36
|
defaultConfig {
|
||||||
|
targetSdk = 36
|
||||||
|
compileSdk = 36
|
||||||
|
minSdk = 23
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import com.android.build.gradle.LibraryExtension
|
import com.android.build.api.dsl.LibraryExtension
|
||||||
import dev.meloda.fast.configureAndroidCompose
|
import dev.meloda.fast.configureAndroidCompose
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
@@ -10,6 +10,7 @@ class AndroidLibraryComposeConventionPlugin : Plugin<Project> {
|
|||||||
with(target) {
|
with(target) {
|
||||||
apply(plugin = "com.android.library")
|
apply(plugin = "com.android.library")
|
||||||
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
|
apply(plugin = "org.jetbrains.kotlin.plugin.compose")
|
||||||
|
apply(plugin = "com.github.skydoves.compose.stability.analyzer")
|
||||||
|
|
||||||
val extension = extensions.getByType<LibraryExtension>()
|
val extension = extensions.getByType<LibraryExtension>()
|
||||||
extension.androidResources.enable = false
|
extension.androidResources.enable = false
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import com.android.build.api.dsl.LibraryExtension
|
||||||
import com.android.build.api.variant.LibraryAndroidComponentsExtension
|
import com.android.build.api.variant.LibraryAndroidComponentsExtension
|
||||||
import com.android.build.gradle.LibraryExtension
|
|
||||||
import dev.meloda.fast.configureKotlinAndroid
|
import dev.meloda.fast.configureKotlinAndroid
|
||||||
import dev.meloda.fast.disableUnnecessaryAndroidTests
|
import dev.meloda.fast.disableUnnecessaryAndroidTests
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
@@ -13,7 +13,6 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
|
|||||||
with(target) {
|
with(target) {
|
||||||
with(pluginManager) {
|
with(pluginManager) {
|
||||||
apply("com.android.library")
|
apply("com.android.library")
|
||||||
apply("org.jetbrains.kotlin.android")
|
|
||||||
apply("org.jetbrains.kotlin.plugin.parcelize")
|
apply("org.jetbrains.kotlin.plugin.parcelize")
|
||||||
apply("org.jetbrains.kotlin.plugin.serialization")
|
apply("org.jetbrains.kotlin.plugin.serialization")
|
||||||
}
|
}
|
||||||
@@ -21,7 +20,6 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
|
|||||||
extensions.configure<LibraryExtension> {
|
extensions.configure<LibraryExtension> {
|
||||||
configureKotlinAndroid(this)
|
configureKotlinAndroid(this)
|
||||||
androidResources.enable = false
|
androidResources.enable = false
|
||||||
defaultConfig.targetSdk = 36
|
|
||||||
defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
defaultConfig.testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
extensions.configure<LibraryAndroidComponentsExtension> {
|
extensions.configure<LibraryAndroidComponentsExtension> {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import com.android.build.gradle.TestExtension
|
import com.android.build.api.dsl.TestExtension
|
||||||
import dev.meloda.fast.configureKotlinAndroid
|
import dev.meloda.fast.configureKotlinAndroid
|
||||||
import dev.meloda.fast.libs
|
|
||||||
import org.gradle.api.Plugin
|
import org.gradle.api.Plugin
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.kotlin.dsl.configure
|
import org.gradle.kotlin.dsl.configure
|
||||||
@@ -10,7 +9,6 @@ class AndroidTestConventionPlugin : Plugin<Project> {
|
|||||||
with(target) {
|
with(target) {
|
||||||
with(pluginManager) {
|
with(pluginManager) {
|
||||||
apply("com.android.test")
|
apply("com.android.test")
|
||||||
apply("org.jetbrains.kotlin.android")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extensions.configure<TestExtension> {
|
extensions.configure<TestExtension> {
|
||||||
|
|||||||
@@ -5,12 +5,10 @@ import org.gradle.api.Project
|
|||||||
import org.gradle.kotlin.dsl.dependencies
|
import org.gradle.kotlin.dsl.dependencies
|
||||||
|
|
||||||
internal fun Project.configureAndroidCompose(
|
internal fun Project.configureAndroidCompose(
|
||||||
commonExtension: CommonExtension<*, *, *, *, *, *>,
|
commonExtension: CommonExtension,
|
||||||
) {
|
) {
|
||||||
commonExtension.apply {
|
commonExtension.apply {
|
||||||
buildFeatures {
|
buildFeatures.compose = true
|
||||||
compose = true
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
val bom = libs.findLibrary("compose-bom").get()
|
val bom = libs.findLibrary("compose-bom").get()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package dev.meloda.fast
|
package dev.meloda.fast
|
||||||
|
|
||||||
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
import com.android.build.api.dsl.CommonExtension
|
import com.android.build.api.dsl.CommonExtension
|
||||||
|
import com.android.build.api.dsl.CompileOptions
|
||||||
|
import com.android.build.api.dsl.LibraryExtension
|
||||||
import org.gradle.api.JavaVersion
|
import org.gradle.api.JavaVersion
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.plugins.JavaPluginExtension
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
@@ -13,24 +16,25 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
||||||
|
|
||||||
internal fun Project.configureKotlinAndroid(
|
internal fun Project.configureKotlinAndroid(
|
||||||
commonExtension: CommonExtension<*, *, *, *, *, *>,
|
commonExtension: CommonExtension,
|
||||||
) {
|
) {
|
||||||
|
when (commonExtension) {
|
||||||
|
is ApplicationExtension -> commonExtension.compileOptions(buildCompileOptions())
|
||||||
|
is LibraryExtension -> commonExtension.compileOptions(buildCompileOptions())
|
||||||
|
}
|
||||||
|
|
||||||
commonExtension.apply {
|
commonExtension.apply {
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 23
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configureKotlin<KotlinAndroidProjectExtension>()
|
configureKotlin<KotlinAndroidProjectExtension>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildCompileOptions(): CompileOptions.() -> Unit = {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
|
}
|
||||||
|
|
||||||
internal fun Project.configureKotlinJvm() {
|
internal fun Project.configureKotlinJvm() {
|
||||||
extensions.configure<JavaPluginExtension> {
|
extensions.configure<JavaPluginExtension> {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
sourceCompatibility = JavaVersion.VERSION_21
|
||||||
@@ -47,7 +51,7 @@ private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() =
|
|||||||
when (this) {
|
when (this) {
|
||||||
is KotlinAndroidProjectExtension -> compilerOptions
|
is KotlinAndroidProjectExtension -> compilerOptions
|
||||||
is KotlinJvmProjectExtension -> compilerOptions
|
is KotlinJvmProjectExtension -> compilerOptions
|
||||||
else -> TODO("Unsupported project extension $this ${T::class}")
|
else -> throw IllegalArgumentException("Unsupported project extension $this ${T::class}")
|
||||||
}.apply {
|
}.apply {
|
||||||
jvmTarget = JvmTarget.JVM_21
|
jvmTarget = JvmTarget.JVM_21
|
||||||
allWarningsAsErrors = warningsAsErrors.toBoolean()
|
allWarningsAsErrors = warningsAsErrors.toBoolean()
|
||||||
@@ -55,7 +59,8 @@ private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() =
|
|||||||
"-opt-in=kotlin.RequiresOptIn",
|
"-opt-in=kotlin.RequiresOptIn",
|
||||||
// Enable experimental coroutines APIs, including Flow
|
// Enable experimental coroutines APIs, including Flow
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview"
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
|
"-Xannotation-default-target=param-property",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.android.library) apply false
|
alias(libs.plugins.android.library) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
|
||||||
alias(libs.plugins.kotlin.parcelize) apply false
|
alias(libs.plugins.kotlin.parcelize) apply false
|
||||||
alias(libs.plugins.kotlin.serialization) apply false
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
alias(libs.plugins.kotlin.jvm) apply false
|
alias(libs.plugins.kotlin.jvm) apply false
|
||||||
@@ -9,4 +8,6 @@ plugins {
|
|||||||
alias(libs.plugins.room) apply false
|
alias(libs.plugins.room) apply false
|
||||||
alias(libs.plugins.ksp) apply false
|
alias(libs.plugins.ksp) apply false
|
||||||
alias(libs.plugins.module.graph) apply true
|
alias(libs.plugins.module.graph) apply true
|
||||||
|
alias(libs.plugins.versions) apply true
|
||||||
|
alias(libs.plugins.stability.analyzer) apply false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,3 +138,15 @@ fun <T : Any> Bundle.getParcelableCompat(key: String, clazz: KClass<T>): T? {
|
|||||||
getParcelable(key)
|
getParcelable(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infix fun ClosedRange<Int>.collidesWith(other: ClosedRange<Int>): Boolean {
|
||||||
|
return this.start < other.endInclusive && other.start < this.endInclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun ClosedRange<Int>.minus(other: ClosedRange<Int>): ClosedRange<Int> {
|
||||||
|
return (this.start - other.start)..(this.endInclusive - other.endInclusive)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun ClosedRange<Int>.minus(other: Int): ClosedRange<Int> {
|
||||||
|
return (this.start - other)..(this.endInclusive - other)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package dev.meloda.fast.common.util
|
package dev.meloda.fast.common.util
|
||||||
|
|
||||||
import com.conena.nanokt.jvm.util.dayOfMonth
|
import com.conena.nanokt.jvm.util.dayOfMonth
|
||||||
import com.conena.nanokt.jvm.util.hour
|
|
||||||
import com.conena.nanokt.jvm.util.hourOfDay
|
import com.conena.nanokt.jvm.util.hourOfDay
|
||||||
import com.conena.nanokt.jvm.util.millisecond
|
import com.conena.nanokt.jvm.util.millisecond
|
||||||
import com.conena.nanokt.jvm.util.minute
|
import com.conena.nanokt.jvm.util.minute
|
||||||
@@ -12,6 +11,12 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
import kotlin.time.Duration.Companion.hours
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
object TimeUtils {
|
object TimeUtils {
|
||||||
|
|
||||||
@@ -56,37 +61,23 @@ object TimeUtils {
|
|||||||
monthShort: () -> String,
|
monthShort: () -> String,
|
||||||
weekShort: () -> String,
|
weekShort: () -> String,
|
||||||
dayShort: () -> String,
|
dayShort: () -> String,
|
||||||
|
minuteShort: () -> String,
|
||||||
|
secondShort: () -> String,
|
||||||
now: () -> String
|
now: () -> String
|
||||||
): String {
|
): String {
|
||||||
val now = Calendar.getInstance()
|
val now = Clock.System.now()
|
||||||
val then = Calendar.getInstance().also { it.timeInMillis = date }
|
val then = Instant.fromEpochMilliseconds(date)
|
||||||
|
val diff = now - then
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
now.year != then.year -> {
|
diff > 365.days -> "${diff.inWholeDays / 365}${yearShort().lowercase()}"
|
||||||
"${now.year - then.year}${yearShort().lowercase()}"
|
diff > 30.days -> "${diff.inWholeDays / 30}${monthShort().lowercase()}"
|
||||||
}
|
diff > 7.days -> "${diff.inWholeDays / 7}${weekShort().lowercase()}"
|
||||||
|
diff > 1.days -> "${diff.inWholeDays}${dayShort().lowercase()}"
|
||||||
now.month != then.month -> {
|
diff > 1.hours -> "${diff.inWholeHours}h"
|
||||||
"${now.month - then.month}${monthShort().lowercase()}"
|
diff > 1.minutes -> "${diff.inWholeMinutes}${minuteShort().lowercase()}"
|
||||||
}
|
diff > 1.seconds -> "${diff.inWholeSeconds}${secondShort().lowercase()}"
|
||||||
|
else -> now().lowercase()
|
||||||
now.dayOfMonth != then.dayOfMonth -> {
|
|
||||||
val change = now.dayOfMonth - then.dayOfMonth
|
|
||||||
|
|
||||||
if (change % 7 == 0) {
|
|
||||||
"${change / 7}${weekShort().lowercase()}"
|
|
||||||
} else {
|
|
||||||
"$change${dayShort().lowercase()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
now.hour == then.hour && now.minute == then.minute -> {
|
|
||||||
now().lowercase()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
SimpleDateFormat("HH:mm", Locale.getDefault()).format(date)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
package dev.meloda.fast.common.util
|
package dev.meloda.fast.common.util
|
||||||
|
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
fun String.urlEncode(encoding: String = "utf-8"): String {
|
fun String.urlEncode(encoding: String = "utf-8"): String {
|
||||||
return URLEncoder.encode(this, encoding)
|
return URLEncoder.encode(this, encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun String.sha256() = this.hashString("SHA-256")
|
||||||
|
|
||||||
|
fun String.hashString(algorithm: String): String {
|
||||||
|
return MessageDigest
|
||||||
|
.getInstance(algorithm)
|
||||||
|
.digest(this.toByteArray())
|
||||||
|
.fold("") { str, it -> str + "%02x".format(it) }
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ dependencies {
|
|||||||
api(projects.core.network)
|
api(projects.core.network)
|
||||||
api(projects.core.database)
|
api(projects.core.database)
|
||||||
|
|
||||||
// TODO: 11/08/2024, Danil Nikolaev: remove?
|
|
||||||
implementation(libs.retrofit)
|
implementation(libs.retrofit)
|
||||||
implementation(libs.eithernet)
|
implementation(libs.eithernet)
|
||||||
implementation(libs.koin.android)
|
implementation(libs.koin.android)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dev.meloda.fast.data
|
package dev.meloda.fast.data
|
||||||
|
|
||||||
import dev.meloda.fast.model.api.data.VkMessageData
|
import dev.meloda.fast.model.api.data.VkMessageData
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -16,9 +16,9 @@ class VkGroupsMap(
|
|||||||
|
|
||||||
fun groups(): List<VkGroupDomain> = map.values.toList()
|
fun groups(): List<VkGroupDomain> = map.values.toList()
|
||||||
|
|
||||||
fun conversationGroup(conversation: VkConversation): VkGroupDomain? =
|
fun convoGroup(convo: VkConvo): VkGroupDomain? =
|
||||||
if (!conversation.peerType.isGroup()) null
|
if (!convo.peerType.isGroup()) null
|
||||||
else map[abs(conversation.id)]
|
else map[abs(convo.id)]
|
||||||
|
|
||||||
fun messageActionGroup(message: VkMessage): VkGroupDomain? =
|
fun messageActionGroup(message: VkMessage): VkGroupDomain? =
|
||||||
if (message.actionMemberId == null || message.actionMemberId!! >= 0) null
|
if (message.actionMemberId == null || message.actionMemberId!! >= 0) null
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package dev.meloda.fast.data
|
|||||||
|
|
||||||
import dev.meloda.fast.data.UserConfig.userId
|
import dev.meloda.fast.data.UserConfig.userId
|
||||||
import dev.meloda.fast.model.api.domain.VkContactDomain
|
import dev.meloda.fast.model.api.domain.VkContactDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkUser
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
@@ -13,7 +13,7 @@ object VkMemoryCache {
|
|||||||
private val users: HashMap<Long, VkUser> = hashMapOf()
|
private val users: HashMap<Long, VkUser> = hashMapOf()
|
||||||
private val groups: HashMap<Long, VkGroupDomain> = hashMapOf()
|
private val groups: HashMap<Long, VkGroupDomain> = hashMapOf()
|
||||||
private val messages: HashMap<Long, VkMessage> = hashMapOf()
|
private val messages: HashMap<Long, VkMessage> = hashMapOf()
|
||||||
private val conversations: HashMap<Long, VkConversation> = hashMapOf()
|
private val convos: HashMap<Long, VkConvo> = hashMapOf()
|
||||||
private val contacts: HashMap<Long, VkContactDomain> = hashMapOf()
|
private val contacts: HashMap<Long, VkContactDomain> = hashMapOf()
|
||||||
|
|
||||||
fun appendUsers(users: List<VkUser>) {
|
fun appendUsers(users: List<VkUser>) {
|
||||||
@@ -28,9 +28,9 @@ object VkMemoryCache {
|
|||||||
messages.forEach { message -> VkMemoryCache.messages[message.id] = message }
|
messages.forEach { message -> VkMemoryCache.messages[message.id] = message }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun appendConversations(conversations: List<VkConversation>) {
|
fun appendConvos(convos: List<VkConvo>) {
|
||||||
conversations.forEach { conversation ->
|
convos.forEach { convo ->
|
||||||
VkMemoryCache.conversations[conversation.id] = conversation
|
VkMemoryCache.convos[convo.id] = convo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +50,8 @@ object VkMemoryCache {
|
|||||||
messages[messageId] = message
|
messages[messageId] = message
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun set(conversationId: Long, conversation: VkConversation) {
|
operator fun set(convoId: Long, convo: VkConvo) {
|
||||||
conversations[conversationId] = conversation
|
convos[convoId] = convo
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun set(contactId: Long, contact: VkContactDomain) {
|
operator fun set(contactId: Long, contact: VkContactDomain) {
|
||||||
@@ -94,16 +94,16 @@ object VkMemoryCache {
|
|||||||
return ids.mapNotNull { id -> messages[id] }
|
return ids.mapNotNull { id -> messages[id] }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getConversation(id: Long): VkConversation? {
|
fun getConvo(id: Long): VkConvo? {
|
||||||
return getConversations(id).firstOrNull()
|
return getConvos(id).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getConversations(vararg ids: Long): List<VkConversation> {
|
fun getConvos(vararg ids: Long): List<VkConvo> {
|
||||||
return getConversations(ids.toList())
|
return getConvos(ids.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getConversations(ids: List<Long>): List<VkConversation> {
|
fun getConvos(ids: List<Long>): List<VkConvo> {
|
||||||
return ids.mapNotNull { id -> conversations[id] }
|
return ids.mapNotNull { id -> convos[id] }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getContact(id: Long): VkContactDomain? {
|
fun getContact(id: Long): VkContactDomain? {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package dev.meloda.fast.data
|
package dev.meloda.fast.data
|
||||||
|
|
||||||
import dev.meloda.fast.data.UserConfig.userId
|
|
||||||
import dev.meloda.fast.model.api.data.VkMessageData
|
import dev.meloda.fast.model.api.data.VkMessageData
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkUser
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
|
|
||||||
@@ -16,9 +15,9 @@ class VkUsersMap(
|
|||||||
|
|
||||||
fun users(): List<VkUser> = map.values.toList()
|
fun users(): List<VkUser> = map.values.toList()
|
||||||
|
|
||||||
fun conversationUser(conversation: VkConversation): VkUser? =
|
fun convoUser(convo: VkConvo): VkUser? =
|
||||||
if (!conversation.peerType.isUser()) null
|
if (!convo.peerType.isUser()) null
|
||||||
else map[conversation.id]
|
else map[convo.id]
|
||||||
|
|
||||||
fun messageActionUser(message: VkMessage): VkUser? =
|
fun messageActionUser(message: VkMessage): VkUser? =
|
||||||
if (message.actionMemberId == null || message.actionMemberId!! <= 0) null
|
if (message.actionMemberId == null || message.actionMemberId!! <= 0) null
|
||||||
|
|||||||
+10
-10
@@ -1,25 +1,25 @@
|
|||||||
package dev.meloda.fast.data.api.conversations
|
package dev.meloda.fast.data.api.convos
|
||||||
|
|
||||||
import com.slack.eithernet.ApiResult
|
import com.slack.eithernet.ApiResult
|
||||||
import dev.meloda.fast.model.ConversationsFilter
|
import dev.meloda.fast.model.ConvosFilter
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.network.RestApiErrorDomain
|
import dev.meloda.fast.network.RestApiErrorDomain
|
||||||
|
|
||||||
interface ConversationsRepository {
|
interface ConvosRepository {
|
||||||
|
|
||||||
suspend fun storeConversations(conversations: List<VkConversation>)
|
suspend fun storeConvos(convos: List<VkConvo>)
|
||||||
|
|
||||||
suspend fun getConversations(
|
suspend fun getConvos(
|
||||||
count: Int?,
|
count: Int?,
|
||||||
offset: Int?,
|
offset: Int?,
|
||||||
filter: ConversationsFilter
|
filter: ConvosFilter
|
||||||
): ApiResult<List<VkConversation>, RestApiErrorDomain>
|
): ApiResult<List<VkConvo>, RestApiErrorDomain>
|
||||||
|
|
||||||
suspend fun getConversationsById(
|
suspend fun getConvosById(
|
||||||
peerIds: List<Long>,
|
peerIds: List<Long>,
|
||||||
extended: Boolean? = null,
|
extended: Boolean? = null,
|
||||||
fields: String? = null
|
fields: String? = null
|
||||||
): ApiResult<List<VkConversation>, RestApiErrorDomain>
|
): ApiResult<List<VkConvo>, RestApiErrorDomain>
|
||||||
|
|
||||||
suspend fun delete(peerId: Long): ApiResult<Long, RestApiErrorDomain>
|
suspend fun delete(peerId: Long): ApiResult<Long, RestApiErrorDomain>
|
||||||
suspend fun pin(peerId: Long): ApiResult<Int, RestApiErrorDomain>
|
suspend fun pin(peerId: Long): ApiResult<Int, RestApiErrorDomain>
|
||||||
+43
-43
@@ -1,51 +1,51 @@
|
|||||||
package dev.meloda.fast.data.api.conversations
|
package dev.meloda.fast.data.api.convos
|
||||||
|
|
||||||
import com.slack.eithernet.ApiResult
|
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
|
||||||
import dev.meloda.fast.data.VkUsersMap
|
import dev.meloda.fast.data.VkUsersMap
|
||||||
import dev.meloda.fast.database.dao.ConversationDao
|
import dev.meloda.fast.database.dao.ConvoDao
|
||||||
import dev.meloda.fast.database.dao.GroupDao
|
import dev.meloda.fast.database.dao.GroupDao
|
||||||
import dev.meloda.fast.database.dao.MessageDao
|
import dev.meloda.fast.database.dao.MessageDao
|
||||||
import dev.meloda.fast.database.dao.UserDao
|
import dev.meloda.fast.database.dao.UserDao
|
||||||
import dev.meloda.fast.model.ConversationsFilter
|
import dev.meloda.fast.model.ConvosFilter
|
||||||
import dev.meloda.fast.model.api.data.VkContactData
|
import dev.meloda.fast.model.api.data.VkContactData
|
||||||
import dev.meloda.fast.model.api.data.VkGroupData
|
import dev.meloda.fast.model.api.data.VkGroupData
|
||||||
import dev.meloda.fast.model.api.data.VkUserData
|
import dev.meloda.fast.model.api.data.VkUserData
|
||||||
import dev.meloda.fast.model.api.data.asDomain
|
import dev.meloda.fast.model.api.data.asDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkUser
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
import dev.meloda.fast.model.api.domain.asEntity
|
import dev.meloda.fast.model.api.domain.asEntity
|
||||||
import dev.meloda.fast.model.api.requests.ConversationsGetRequest
|
import dev.meloda.fast.model.api.requests.ConvosGetRequest
|
||||||
import dev.meloda.fast.network.RestApiErrorDomain
|
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.convos.ConvosService
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ConversationsRepositoryImpl(
|
class ConvosRepositoryImpl(
|
||||||
private val conversationsService: ConversationsService,
|
private val convosService: ConvosService,
|
||||||
private val messageDao: MessageDao,
|
private val messageDao: MessageDao,
|
||||||
private val userDao: UserDao,
|
private val userDao: UserDao,
|
||||||
private val groupDao: GroupDao,
|
private val groupDao: GroupDao,
|
||||||
private val conversationDao: ConversationDao
|
private val convoDao: ConvoDao
|
||||||
) : ConversationsRepository {
|
) : ConvosRepository {
|
||||||
|
|
||||||
override suspend fun storeConversations(conversations: List<VkConversation>) {
|
override suspend fun storeConvos(convos: List<VkConvo>) {
|
||||||
conversationDao.insertAll(conversations.map(VkConversation::asEntity))
|
convoDao.insertAll(convos.map(VkConvo::asEntity))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getConversations(
|
override suspend fun getConvos(
|
||||||
count: Int?,
|
count: Int?,
|
||||||
offset: Int?,
|
offset: Int?,
|
||||||
filter: ConversationsFilter
|
filter: ConvosFilter
|
||||||
): ApiResult<List<VkConversation>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<List<VkConvo>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
val requestModel = ConversationsGetRequest(
|
val requestModel = ConvosGetRequest(
|
||||||
count = count,
|
count = count,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
fields = VkConstants.ALL_FIELDS,
|
fields = VkConstants.ALL_FIELDS,
|
||||||
@@ -54,7 +54,7 @@ class ConversationsRepositoryImpl(
|
|||||||
startMessageId = null
|
startMessageId = null
|
||||||
)
|
)
|
||||||
|
|
||||||
conversationsService.getConversations(requestModel.map).mapApiResult(
|
convosService.getConvos(requestModel.map).mapApiResult(
|
||||||
successMapper = { apiResponse ->
|
successMapper = { apiResponse ->
|
||||||
val response = apiResponse.requireResponse()
|
val response = apiResponse.requireResponse()
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ class ConversationsRepositoryImpl(
|
|||||||
VkMemoryCache.appendGroups(groupsList)
|
VkMemoryCache.appendGroups(groupsList)
|
||||||
VkMemoryCache.appendContacts(contactsList)
|
VkMemoryCache.appendContacts(contactsList)
|
||||||
|
|
||||||
val conversations = response.items.map { item ->
|
val convos = response.items.map { item ->
|
||||||
val lastMessage = item.lastMessage?.asDomain()?.let { message ->
|
val lastMessage = item.lastMessage?.asDomain()?.let { message ->
|
||||||
message.copy(
|
message.copy(
|
||||||
user = usersMap.messageUser(message),
|
user = usersMap.messageUser(message),
|
||||||
@@ -84,24 +84,24 @@ class ConversationsRepositoryImpl(
|
|||||||
)
|
)
|
||||||
).also { VkMemoryCache[message.id] = it }
|
).also { VkMemoryCache[message.id] = it }
|
||||||
}
|
}
|
||||||
item.conversation.asDomain(lastMessage).let { conversation ->
|
item.convo.asDomain(lastMessage).let { convo ->
|
||||||
conversation.copy(
|
convo.copy(
|
||||||
user = usersMap.conversationUser(conversation),
|
user = usersMap.convoUser(convo),
|
||||||
group = groupsMap.conversationGroup(conversation)
|
group = groupsMap.convoGroup(convo)
|
||||||
).also { VkMemoryCache[conversation.id] = it }
|
).also { VkMemoryCache[convo.id] = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val messages = conversations.mapNotNull(VkConversation::lastMessage)
|
val messages = convos.mapNotNull(VkConvo::lastMessage)
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
conversationDao.insertAll(conversations.map(VkConversation::asEntity))
|
convoDao.insertAll(convos.map(VkConvo::asEntity))
|
||||||
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
||||||
userDao.insertAll(profilesList.map(VkUser::asEntity))
|
userDao.insertAll(profilesList.map(VkUser::asEntity))
|
||||||
groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity))
|
groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity))
|
||||||
}
|
}
|
||||||
|
|
||||||
conversations
|
convos
|
||||||
},
|
},
|
||||||
errorMapper = { error ->
|
errorMapper = { error ->
|
||||||
error?.toDomain()
|
error?.toDomain()
|
||||||
@@ -109,11 +109,11 @@ class ConversationsRepositoryImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getConversationsById(
|
override suspend fun getConvosById(
|
||||||
peerIds: List<Long>,
|
peerIds: List<Long>,
|
||||||
extended: Boolean?,
|
extended: Boolean?,
|
||||||
fields: String?
|
fields: String?
|
||||||
): ApiResult<List<VkConversation>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<List<VkConvo>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
val requestParams = mutableMapOf(
|
val requestParams = mutableMapOf(
|
||||||
"peer_ids" to peerIds.joinToString(separator = ",")
|
"peer_ids" to peerIds.joinToString(separator = ",")
|
||||||
).apply {
|
).apply {
|
||||||
@@ -121,7 +121,7 @@ class ConversationsRepositoryImpl(
|
|||||||
fields?.let { this["fields"] = it }
|
fields?.let { this["fields"] = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
conversationsService.getConversationsById(requestParams).mapApiResult(
|
convosService.getConvosById(requestParams).mapApiResult(
|
||||||
successMapper = { apiResponse ->
|
successMapper = { apiResponse ->
|
||||||
val response = apiResponse.requireResponse()
|
val response = apiResponse.requireResponse()
|
||||||
|
|
||||||
@@ -132,17 +132,17 @@ class ConversationsRepositoryImpl(
|
|||||||
val usersMap = VkUsersMap.forUsers(profilesList)
|
val usersMap = VkUsersMap.forUsers(profilesList)
|
||||||
val groupsMap = VkGroupsMap.forGroups(groupsList)
|
val groupsMap = VkGroupsMap.forGroups(groupsList)
|
||||||
|
|
||||||
val conversations = response.items.map { item ->
|
val convos = response.items.map { item ->
|
||||||
item.asDomain().let { conversation ->
|
item.asDomain().let { convo ->
|
||||||
conversation.copy(
|
convo.copy(
|
||||||
user = usersMap.conversationUser(conversation),
|
user = usersMap.convoUser(convo),
|
||||||
group = groupsMap.conversationGroup(conversation)
|
group = groupsMap.convoGroup(convo)
|
||||||
).also { VkMemoryCache[conversation.id] = it }
|
).also { VkMemoryCache[convo.id] = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
conversationDao.insertAll(conversations.map(VkConversation::asEntity))
|
convoDao.insertAll(convos.map(VkConvo::asEntity))
|
||||||
userDao.insertAll(profilesList.map(VkUser::asEntity))
|
userDao.insertAll(profilesList.map(VkUser::asEntity))
|
||||||
groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity))
|
groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity))
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ class ConversationsRepositoryImpl(
|
|||||||
VkMemoryCache.appendGroups(groupsList)
|
VkMemoryCache.appendGroups(groupsList)
|
||||||
VkMemoryCache.appendContacts(contactsList)
|
VkMemoryCache.appendContacts(contactsList)
|
||||||
|
|
||||||
conversations
|
convos
|
||||||
},
|
},
|
||||||
errorMapper = { error ->
|
errorMapper = { error ->
|
||||||
error?.toDomain()
|
error?.toDomain()
|
||||||
@@ -161,7 +161,7 @@ class ConversationsRepositoryImpl(
|
|||||||
|
|
||||||
override suspend fun delete(peerId: Long): ApiResult<Long, RestApiErrorDomain> =
|
override suspend fun delete(peerId: Long): ApiResult<Long, RestApiErrorDomain> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
conversationsService.delete(mapOf("peer_id" to peerId.toString())).mapApiResult(
|
convosService.delete(mapOf("peer_id" to peerId.toString())).mapApiResult(
|
||||||
successMapper = { response -> response.requireResponse().lastDeletedId },
|
successMapper = { response -> response.requireResponse().lastDeletedId },
|
||||||
errorMapper = { error -> error?.toDomain() }
|
errorMapper = { error -> error?.toDomain() }
|
||||||
)
|
)
|
||||||
@@ -170,19 +170,19 @@ class ConversationsRepositoryImpl(
|
|||||||
override suspend fun pin(
|
override suspend fun pin(
|
||||||
peerId: Long
|
peerId: Long
|
||||||
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
conversationsService.pin(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
convosService.pin(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unpin(
|
override suspend fun unpin(
|
||||||
peerId: Long
|
peerId: Long
|
||||||
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
conversationsService.unpin(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
convosService.unpin(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun reorderPinned(
|
override suspend fun reorderPinned(
|
||||||
peerIds: List<Long>
|
peerIds: List<Long>
|
||||||
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
conversationsService
|
convosService
|
||||||
.reorderPinned(mapOf("peer_ids" to peerIds.joinToString(",")))
|
.reorderPinned(mapOf("peer_ids" to peerIds.joinToString(",")))
|
||||||
.mapApiDefault()
|
.mapApiDefault()
|
||||||
}
|
}
|
||||||
@@ -190,12 +190,12 @@ class ConversationsRepositoryImpl(
|
|||||||
override suspend fun archive(
|
override suspend fun archive(
|
||||||
peerId: Long
|
peerId: Long
|
||||||
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
conversationsService.archive(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
convosService.archive(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun unarchive(
|
override suspend fun unarchive(
|
||||||
peerId: Long
|
peerId: Long
|
||||||
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<Int, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
conversationsService.unarchive(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
convosService.unarchive(mapOf("peer_id" to peerId.toString())).mapApiDefault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package dev.meloda.fast.data.api.messages
|
package dev.meloda.fast.data.api.messages
|
||||||
|
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
|
|
||||||
data class MessagesHistoryInfo(
|
data class MessagesHistoryInfo(
|
||||||
val messages: List<VkMessage>,
|
val messages: List<VkMessage>,
|
||||||
val conversations: List<VkConversation>
|
val convos: List<VkConvo>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import dev.meloda.fast.model.api.data.VkChatData
|
|||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetConvoMembersResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesSendResponse
|
import dev.meloda.fast.model.api.responses.MessagesSendResponse
|
||||||
import dev.meloda.fast.network.RestApiErrorDomain
|
import dev.meloda.fast.network.RestApiErrorDomain
|
||||||
@@ -15,7 +15,7 @@ interface MessagesRepository {
|
|||||||
suspend fun storeMessages(messages: List<VkMessage>)
|
suspend fun storeMessages(messages: List<VkMessage>)
|
||||||
|
|
||||||
suspend fun getHistory(
|
suspend fun getHistory(
|
||||||
conversationId: Long,
|
convoId: Long,
|
||||||
offset: Int?,
|
offset: Int?,
|
||||||
count: Int?
|
count: Int?
|
||||||
): ApiResult<MessagesHistoryInfo, RestApiErrorDomain>
|
): ApiResult<MessagesHistoryInfo, RestApiErrorDomain>
|
||||||
@@ -99,13 +99,13 @@ interface MessagesRepository {
|
|||||||
fields: String? = null
|
fields: String? = null
|
||||||
): ApiResult<VkChatData, RestApiErrorDomain>
|
): ApiResult<VkChatData, RestApiErrorDomain>
|
||||||
|
|
||||||
suspend fun getConversationMembers(
|
suspend fun getConvoMembers(
|
||||||
peerId: Long,
|
peerId: Long,
|
||||||
offset: Int? = null,
|
offset: Int? = null,
|
||||||
count: Int? = null,
|
count: Int? = null,
|
||||||
extended: Boolean? = null,
|
extended: Boolean? = null,
|
||||||
fields: String? = null
|
fields: String? = null
|
||||||
): ApiResult<MessagesGetConversationMembersResponse, RestApiErrorDomain>
|
): ApiResult<MessagesGetConvoMembersResponse, RestApiErrorDomain>
|
||||||
|
|
||||||
suspend fun removeChatUser(
|
suspend fun removeChatUser(
|
||||||
chatId: Long,
|
chatId: Long,
|
||||||
|
|||||||
+36
-32
@@ -5,7 +5,7 @@ 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
|
||||||
import dev.meloda.fast.data.VkUsersMap
|
import dev.meloda.fast.data.VkUsersMap
|
||||||
import dev.meloda.fast.database.dao.ConversationDao
|
import dev.meloda.fast.database.dao.ConvoDao
|
||||||
import dev.meloda.fast.database.dao.GroupDao
|
import dev.meloda.fast.database.dao.GroupDao
|
||||||
import dev.meloda.fast.database.dao.MessageDao
|
import dev.meloda.fast.database.dao.MessageDao
|
||||||
import dev.meloda.fast.database.dao.UserDao
|
import dev.meloda.fast.database.dao.UserDao
|
||||||
@@ -17,7 +17,7 @@ import dev.meloda.fast.model.api.data.VkUserData
|
|||||||
import dev.meloda.fast.model.api.data.asDomain
|
import dev.meloda.fast.model.api.data.asDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
import dev.meloda.fast.model.api.domain.VkGroupDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkUser
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
@@ -27,7 +27,7 @@ import dev.meloda.fast.model.api.requests.MessagesDeleteRequest
|
|||||||
import dev.meloda.fast.model.api.requests.MessagesEditRequest
|
import dev.meloda.fast.model.api.requests.MessagesEditRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesGetByIdRequest
|
import dev.meloda.fast.model.api.requests.MessagesGetByIdRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesGetChatRequest
|
import dev.meloda.fast.model.api.requests.MessagesGetChatRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesGetConversationMembersRequest
|
import dev.meloda.fast.model.api.requests.MessagesGetConvoMembersRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest
|
import dev.meloda.fast.model.api.requests.MessagesGetHistoryAttachmentsRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesGetHistoryRequest
|
import dev.meloda.fast.model.api.requests.MessagesGetHistoryRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesMarkAsImportantRequest
|
import dev.meloda.fast.model.api.requests.MessagesMarkAsImportantRequest
|
||||||
@@ -36,7 +36,7 @@ import dev.meloda.fast.model.api.requests.MessagesPinMessageRequest
|
|||||||
import dev.meloda.fast.model.api.requests.MessagesRemoveChatUserRequest
|
import dev.meloda.fast.model.api.requests.MessagesRemoveChatUserRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesSendRequest
|
import dev.meloda.fast.model.api.requests.MessagesSendRequest
|
||||||
import dev.meloda.fast.model.api.requests.MessagesUnpinMessageRequest
|
import dev.meloda.fast.model.api.requests.MessagesUnpinMessageRequest
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetConvoMembersResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesSendResponse
|
import dev.meloda.fast.model.api.responses.MessagesSendResponse
|
||||||
import dev.meloda.fast.network.RestApiErrorDomain
|
import dev.meloda.fast.network.RestApiErrorDomain
|
||||||
@@ -52,18 +52,18 @@ class MessagesRepositoryImpl(
|
|||||||
private val messageDao: MessageDao,
|
private val messageDao: MessageDao,
|
||||||
private val userDao: UserDao,
|
private val userDao: UserDao,
|
||||||
private val groupDao: GroupDao,
|
private val groupDao: GroupDao,
|
||||||
private val conversationDao: ConversationDao
|
private val convoDao: ConvoDao
|
||||||
) : MessagesRepository {
|
) : MessagesRepository {
|
||||||
|
|
||||||
override suspend fun getHistory(
|
override suspend fun getHistory(
|
||||||
conversationId: Long,
|
convoId: Long,
|
||||||
offset: Int?,
|
offset: Int?,
|
||||||
count: Int?
|
count: Int?
|
||||||
): ApiResult<MessagesHistoryInfo, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
): ApiResult<MessagesHistoryInfo, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
val requestModel = MessagesGetHistoryRequest(
|
val requestModel = MessagesGetHistoryRequest(
|
||||||
count = count,
|
count = count,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
peerId = conversationId,
|
peerId = convoId,
|
||||||
extended = true,
|
extended = true,
|
||||||
startMessageId = null,
|
startMessageId = null,
|
||||||
rev = null,
|
rev = null,
|
||||||
@@ -92,29 +92,31 @@ class MessagesRepositoryImpl(
|
|||||||
group = groupsMap.messageGroup(message),
|
group = groupsMap.messageGroup(message),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
actionUser = usersMap.messageActionUser(message),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionGroup = groupsMap.messageActionGroup(message),
|
||||||
replyMessage = message.replyMessage?.copy(
|
replyMessage = message.replyMessage.let { replyMessage ->
|
||||||
user = usersMap.messageUser(message),
|
replyMessage?.copy(
|
||||||
group = groupsMap.messageGroup(message),
|
user = usersMap.messageUser(replyMessage),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
group = groupsMap.messageGroup(replyMessage),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionUser = usersMap.messageActionUser(replyMessage),
|
||||||
|
actionGroup = groupsMap.messageActionGroup(replyMessage),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
).also { VkMemoryCache[message.id] = it }
|
).also { VkMemoryCache[message.id] = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val conversations = response.conversations.orEmpty().map { item ->
|
val convos = response.convos.orEmpty().map { item ->
|
||||||
val message = messages.firstOrNull { it.id == item.lastMessageId }
|
val message = messages.firstOrNull { it.id == item.lastMessageId }
|
||||||
item.asDomain(message)
|
item.asDomain(message)
|
||||||
.let { conversation ->
|
.let { convo ->
|
||||||
conversation.copy(
|
convo.copy(
|
||||||
user = usersMap.conversationUser(conversation),
|
user = usersMap.convoUser(convo),
|
||||||
group = groupsMap.conversationGroup(conversation)
|
group = groupsMap.convoGroup(convo)
|
||||||
).also { VkMemoryCache[conversation.id] = it }
|
).also { VkMemoryCache[convo.id] = it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
conversationDao.insertAll(conversations.map(VkConversation::asEntity))
|
convoDao.insertAll(convos.map(VkConvo::asEntity))
|
||||||
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
messageDao.insertAll(messages.map(VkMessage::asEntity))
|
||||||
userDao.insertAll(profilesList.map(VkUser::asEntity))
|
userDao.insertAll(profilesList.map(VkUser::asEntity))
|
||||||
groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity))
|
groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity))
|
||||||
@@ -122,7 +124,7 @@ class MessagesRepositoryImpl(
|
|||||||
|
|
||||||
MessagesHistoryInfo(
|
MessagesHistoryInfo(
|
||||||
messages = messages,
|
messages = messages,
|
||||||
conversations = conversations
|
convos = convos
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
errorMapper = { error ->
|
errorMapper = { error ->
|
||||||
@@ -167,12 +169,14 @@ class MessagesRepositoryImpl(
|
|||||||
group = groupsMap.messageGroup(message),
|
group = groupsMap.messageGroup(message),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
actionUser = usersMap.messageActionUser(message),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionGroup = groupsMap.messageActionGroup(message),
|
||||||
replyMessage = message.replyMessage?.asDomain()?.copy(
|
replyMessage = message.replyMessage?.asDomain().let { replyMessage ->
|
||||||
user = usersMap.messageUser(message),
|
replyMessage?.copy(
|
||||||
group = groupsMap.messageGroup(message),
|
user = usersMap.messageUser(replyMessage),
|
||||||
actionUser = usersMap.messageActionUser(message),
|
group = groupsMap.messageGroup(replyMessage),
|
||||||
actionGroup = groupsMap.messageActionGroup(message),
|
actionUser = usersMap.messageActionUser(replyMessage),
|
||||||
|
actionGroup = groupsMap.messageActionGroup(replyMessage),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +243,7 @@ class MessagesRepositoryImpl(
|
|||||||
offset = offset,
|
offset = offset,
|
||||||
preserveOrder = true,
|
preserveOrder = true,
|
||||||
attachmentTypes = attachmentTypes,
|
attachmentTypes = attachmentTypes,
|
||||||
conversationMessageId = cmId,
|
cmId = cmId,
|
||||||
fields = VkConstants.ALL_FIELDS
|
fields = VkConstants.ALL_FIELDS
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -293,7 +297,7 @@ class MessagesRepositoryImpl(
|
|||||||
val requestModel = MessagesPinMessageRequest(
|
val requestModel = MessagesPinMessageRequest(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
messageId = messageId,
|
messageId = messageId,
|
||||||
conversationMessageId = cmId
|
cmId = cmId
|
||||||
)
|
)
|
||||||
|
|
||||||
messagesService.pin(requestModel.map).mapApiResult(
|
messagesService.pin(requestModel.map).mapApiResult(
|
||||||
@@ -339,7 +343,7 @@ class MessagesRepositoryImpl(
|
|||||||
val requestModel = MessagesDeleteRequest(
|
val requestModel = MessagesDeleteRequest(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
messagesIds = messageIds,
|
messagesIds = messageIds,
|
||||||
conversationsMessagesIds = cmIds,
|
cmIds = cmIds,
|
||||||
isSpam = spam,
|
isSpam = spam,
|
||||||
deleteForAll = deleteForAll
|
deleteForAll = deleteForAll
|
||||||
)
|
)
|
||||||
@@ -390,15 +394,15 @@ class MessagesRepositoryImpl(
|
|||||||
messagesService.getChat(requestModel.map).mapApiDefault()
|
messagesService.getChat(requestModel.map).mapApiDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getConversationMembers(
|
override suspend fun getConvoMembers(
|
||||||
peerId: Long,
|
peerId: Long,
|
||||||
offset: Int?,
|
offset: Int?,
|
||||||
count: Int?,
|
count: Int?,
|
||||||
extended: Boolean?,
|
extended: Boolean?,
|
||||||
fields: String?
|
fields: String?
|
||||||
): ApiResult<MessagesGetConversationMembersResponse, RestApiErrorDomain> =
|
): ApiResult<MessagesGetConvoMembersResponse, RestApiErrorDomain> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val requestModel = MessagesGetConversationMembersRequest(
|
val requestModel = MessagesGetConvoMembersRequest(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
count = count,
|
count = count,
|
||||||
@@ -406,7 +410,7 @@ class MessagesRepositoryImpl(
|
|||||||
fields = fields
|
fields = fields
|
||||||
)
|
)
|
||||||
|
|
||||||
messagesService.getConversationMembers(requestModel.map).mapApiDefault()
|
messagesService.getConvoMembers(requestModel.map).mapApiDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun removeChatUser(
|
override suspend fun removeChatUser(
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import dev.meloda.fast.data.api.account.AccountRepositoryImpl
|
|||||||
import dev.meloda.fast.data.api.audios.AudiosRepository
|
import dev.meloda.fast.data.api.audios.AudiosRepository
|
||||||
import dev.meloda.fast.data.api.auth.AuthRepository
|
import dev.meloda.fast.data.api.auth.AuthRepository
|
||||||
import dev.meloda.fast.data.api.auth.AuthRepositoryImpl
|
import dev.meloda.fast.data.api.auth.AuthRepositoryImpl
|
||||||
import dev.meloda.fast.data.api.conversations.ConversationsRepository
|
import dev.meloda.fast.data.api.convos.ConvosRepository
|
||||||
import dev.meloda.fast.data.api.conversations.ConversationsRepositoryImpl
|
import dev.meloda.fast.data.api.convos.ConvosRepositoryImpl
|
||||||
import dev.meloda.fast.data.api.files.FilesRepository
|
import dev.meloda.fast.data.api.files.FilesRepository
|
||||||
import dev.meloda.fast.data.api.friends.FriendsRepository
|
import dev.meloda.fast.data.api.friends.FriendsRepository
|
||||||
import dev.meloda.fast.data.api.friends.FriendsRepositoryImpl
|
import dev.meloda.fast.data.api.friends.FriendsRepositoryImpl
|
||||||
@@ -45,7 +45,7 @@ val dataModule = module {
|
|||||||
|
|
||||||
singleOf(::AuthRepositoryImpl) bind AuthRepository::class
|
singleOf(::AuthRepositoryImpl) bind AuthRepository::class
|
||||||
|
|
||||||
singleOf(::ConversationsRepositoryImpl) bind ConversationsRepository::class
|
singleOf(::ConvosRepositoryImpl) bind ConvosRepository::class
|
||||||
|
|
||||||
singleOf(::FilesRepository)
|
singleOf(::FilesRepository)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 10,
|
"version": 10,
|
||||||
"identityHash": "fa307a5eb2e1f7d601bd1374174635cd",
|
"identityHash": "6c315b7f800694f635318d86032746ec",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "users",
|
"tableName": "users",
|
||||||
@@ -41,50 +41,42 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "onlineAppId",
|
"fieldPath": "onlineAppId",
|
||||||
"columnName": "onlineAppId",
|
"columnName": "onlineAppId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "lastSeen",
|
"fieldPath": "lastSeen",
|
||||||
"columnName": "lastSeen",
|
"columnName": "lastSeen",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "lastSeenStatus",
|
"fieldPath": "lastSeenStatus",
|
||||||
"columnName": "lastSeenStatus",
|
"columnName": "lastSeenStatus",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "birthday",
|
"fieldPath": "birthday",
|
||||||
"columnName": "birthday",
|
"columnName": "birthday",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo50",
|
"fieldPath": "photo50",
|
||||||
"columnName": "photo50",
|
"columnName": "photo50",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo100",
|
"fieldPath": "photo100",
|
||||||
"columnName": "photo100",
|
"columnName": "photo100",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo200",
|
"fieldPath": "photo200",
|
||||||
"columnName": "photo200",
|
"columnName": "photo200",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo400Orig",
|
"fieldPath": "photo400Orig",
|
||||||
"columnName": "photo400Orig",
|
"columnName": "photo400Orig",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
@@ -92,9 +84,7 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "groups",
|
"tableName": "groups",
|
||||||
@@ -121,26 +111,22 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "photo50",
|
"fieldPath": "photo50",
|
||||||
"columnName": "photo50",
|
"columnName": "photo50",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo100",
|
"fieldPath": "photo100",
|
||||||
"columnName": "photo100",
|
"columnName": "photo100",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo200",
|
"fieldPath": "photo200",
|
||||||
"columnName": "photo200",
|
"columnName": "photo200",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "membersCount",
|
"fieldPath": "membersCount",
|
||||||
"columnName": "membersCount",
|
"columnName": "membersCount",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
@@ -148,13 +134,11 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "messages",
|
"tableName": "messages",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `conversationMessageId` INTEGER NOT NULL, `text` TEXT, `isOut` INTEGER NOT NULL, `peerId` INTEGER NOT NULL, `fromId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `randomId` INTEGER NOT NULL, `action` TEXT, `actionMemberId` INTEGER, `actionText` TEXT, `actionConversationMessageId` INTEGER, `actionMessage` TEXT, `updateTime` INTEGER, `important` INTEGER NOT NULL, `forwardIds` TEXT, `attachments` TEXT, `replyMessageId` INTEGER, `geoType` TEXT, `pinnedAt` INTEGER, `isPinned` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `cmId` INTEGER NOT NULL, `text` TEXT, `isOut` INTEGER NOT NULL, `peerId` INTEGER NOT NULL, `fromId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `randomId` INTEGER NOT NULL, `action` TEXT, `actionMemberId` INTEGER, `actionText` TEXT, `actionCmId` INTEGER, `actionMessage` TEXT, `updateTime` INTEGER, `important` INTEGER NOT NULL, `forwardIds` TEXT, `attachments` TEXT, `replyMessageId` INTEGER, `geoType` TEXT, `pinnedAt` INTEGER, `isPinned` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
@@ -163,16 +147,15 @@
|
|||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "conversationMessageId",
|
"fieldPath": "cmId",
|
||||||
"columnName": "conversationMessageId",
|
"columnName": "cmId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "text",
|
"fieldPath": "text",
|
||||||
"columnName": "text",
|
"columnName": "text",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "isOut",
|
"fieldPath": "isOut",
|
||||||
@@ -207,38 +190,32 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "action",
|
"fieldPath": "action",
|
||||||
"columnName": "action",
|
"columnName": "action",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "actionMemberId",
|
"fieldPath": "actionMemberId",
|
||||||
"columnName": "actionMemberId",
|
"columnName": "actionMemberId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "actionText",
|
"fieldPath": "actionText",
|
||||||
"columnName": "actionText",
|
"columnName": "actionText",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "actionConversationMessageId",
|
"fieldPath": "actionCmId",
|
||||||
"columnName": "actionConversationMessageId",
|
"columnName": "actionCmId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "actionMessage",
|
"fieldPath": "actionMessage",
|
||||||
"columnName": "actionMessage",
|
"columnName": "actionMessage",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "updateTime",
|
"fieldPath": "updateTime",
|
||||||
"columnName": "updateTime",
|
"columnName": "updateTime",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "important",
|
"fieldPath": "important",
|
||||||
@@ -249,32 +226,27 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "forwardIds",
|
"fieldPath": "forwardIds",
|
||||||
"columnName": "forwardIds",
|
"columnName": "forwardIds",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "attachments",
|
"fieldPath": "attachments",
|
||||||
"columnName": "attachments",
|
"columnName": "attachments",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "replyMessageId",
|
"fieldPath": "replyMessageId",
|
||||||
"columnName": "replyMessageId",
|
"columnName": "replyMessageId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "geoType",
|
"fieldPath": "geoType",
|
||||||
"columnName": "geoType",
|
"columnName": "geoType",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "pinnedAt",
|
"fieldPath": "pinnedAt",
|
||||||
"columnName": "pinnedAt",
|
"columnName": "pinnedAt",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "isPinned",
|
"fieldPath": "isPinned",
|
||||||
@@ -288,13 +260,11 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "conversations",
|
"tableName": "conversations",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localId` INTEGER NOT NULL, `ownerId` INTEGER, `title` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `isPhantom` INTEGER NOT NULL, `lastConversationMessageId` INTEGER NOT NULL, `inReadCmId` INTEGER NOT NULL, `outReadCmId` INTEGER NOT NULL, `inRead` INTEGER NOT NULL, `outRead` INTEGER NOT NULL, `lastMessageId` INTEGER, `unreadCount` INTEGER NOT NULL, `membersCount` INTEGER, `canChangePin` INTEGER NOT NULL, `canChangeInfo` INTEGER NOT NULL, `majorId` INTEGER NOT NULL, `minorId` INTEGER NOT NULL, `pinnedMessageId` INTEGER, `peerType` TEXT NOT NULL, `isArchived` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localId` INTEGER NOT NULL, `ownerId` INTEGER, `title` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `isPhantom` INTEGER NOT NULL, `lastCmId` INTEGER NOT NULL, `inReadCmId` INTEGER NOT NULL, `outReadCmId` INTEGER NOT NULL, `inRead` INTEGER NOT NULL, `outRead` INTEGER NOT NULL, `lastMessageId` INTEGER, `unreadCount` INTEGER NOT NULL, `membersCount` INTEGER, `canChangePin` INTEGER NOT NULL, `canChangeInfo` INTEGER NOT NULL, `majorId` INTEGER NOT NULL, `minorId` INTEGER NOT NULL, `pinnedMessageId` INTEGER, `peerType` TEXT NOT NULL, `isArchived` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
@@ -311,32 +281,27 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "ownerId",
|
"fieldPath": "ownerId",
|
||||||
"columnName": "ownerId",
|
"columnName": "ownerId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "title",
|
"fieldPath": "title",
|
||||||
"columnName": "title",
|
"columnName": "title",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo50",
|
"fieldPath": "photo50",
|
||||||
"columnName": "photo50",
|
"columnName": "photo50",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo100",
|
"fieldPath": "photo100",
|
||||||
"columnName": "photo100",
|
"columnName": "photo100",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "photo200",
|
"fieldPath": "photo200",
|
||||||
"columnName": "photo200",
|
"columnName": "photo200",
|
||||||
"affinity": "TEXT",
|
"affinity": "TEXT"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "isPhantom",
|
"fieldPath": "isPhantom",
|
||||||
@@ -345,8 +310,8 @@
|
|||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "lastConversationMessageId",
|
"fieldPath": "lastCmId",
|
||||||
"columnName": "lastConversationMessageId",
|
"columnName": "lastCmId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
@@ -377,8 +342,7 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "lastMessageId",
|
"fieldPath": "lastMessageId",
|
||||||
"columnName": "lastMessageId",
|
"columnName": "lastMessageId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "unreadCount",
|
"fieldPath": "unreadCount",
|
||||||
@@ -389,8 +353,7 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "membersCount",
|
"fieldPath": "membersCount",
|
||||||
"columnName": "membersCount",
|
"columnName": "membersCount",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "canChangePin",
|
"fieldPath": "canChangePin",
|
||||||
@@ -419,8 +382,7 @@
|
|||||||
{
|
{
|
||||||
"fieldPath": "pinnedMessageId",
|
"fieldPath": "pinnedMessageId",
|
||||||
"columnName": "pinnedMessageId",
|
"columnName": "pinnedMessageId",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER"
|
||||||
"notNull": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "peerType",
|
"fieldPath": "peerType",
|
||||||
@@ -440,15 +402,12 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"indices": [],
|
|
||||||
"foreignKeys": []
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [],
|
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fa307a5eb2e1f7d601bd1374174635cd')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6c315b7f800694f635318d86032746ec')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,413 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 11,
|
||||||
|
"identityHash": "a746865995959331f8a1b512c049dacb",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "users",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `firstName` TEXT NOT NULL, `lastName` TEXT NOT NULL, `isOnline` INTEGER NOT NULL, `isOnlineMobile` INTEGER NOT NULL, `onlineAppId` INTEGER, `lastSeen` INTEGER, `lastSeenStatus` TEXT, `birthday` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `photo400Orig` TEXT, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "firstName",
|
||||||
|
"columnName": "firstName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastName",
|
||||||
|
"columnName": "lastName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isOnline",
|
||||||
|
"columnName": "isOnline",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isOnlineMobile",
|
||||||
|
"columnName": "isOnlineMobile",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "onlineAppId",
|
||||||
|
"columnName": "onlineAppId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSeen",
|
||||||
|
"columnName": "lastSeen",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSeenStatus",
|
||||||
|
"columnName": "lastSeenStatus",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "birthday",
|
||||||
|
"columnName": "birthday",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo50",
|
||||||
|
"columnName": "photo50",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo100",
|
||||||
|
"columnName": "photo100",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo200",
|
||||||
|
"columnName": "photo200",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo400Orig",
|
||||||
|
"columnName": "photo400Orig",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "groups",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `screenName` TEXT NOT NULL, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `membersCount` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "screenName",
|
||||||
|
"columnName": "screenName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo50",
|
||||||
|
"columnName": "photo50",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo100",
|
||||||
|
"columnName": "photo100",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo200",
|
||||||
|
"columnName": "photo200",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "membersCount",
|
||||||
|
"columnName": "membersCount",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "messages",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `cmId` INTEGER NOT NULL, `text` TEXT, `isOut` INTEGER NOT NULL, `peerId` INTEGER NOT NULL, `fromId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `randomId` INTEGER NOT NULL, `action` TEXT, `actionMemberId` INTEGER, `actionText` TEXT, `actionCmId` INTEGER, `actionMessage` TEXT, `updateTime` INTEGER, `important` INTEGER NOT NULL, `forwardIds` TEXT, `attachments` TEXT, `replyMessageId` INTEGER, `geoType` TEXT, `pinnedAt` INTEGER, `isPinned` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "cmId",
|
||||||
|
"columnName": "cmId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "text",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isOut",
|
||||||
|
"columnName": "isOut",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "peerId",
|
||||||
|
"columnName": "peerId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "fromId",
|
||||||
|
"columnName": "fromId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "date",
|
||||||
|
"columnName": "date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "randomId",
|
||||||
|
"columnName": "randomId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "action",
|
||||||
|
"columnName": "action",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actionMemberId",
|
||||||
|
"columnName": "actionMemberId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actionText",
|
||||||
|
"columnName": "actionText",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actionCmId",
|
||||||
|
"columnName": "actionCmId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "actionMessage",
|
||||||
|
"columnName": "actionMessage",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "updateTime",
|
||||||
|
"columnName": "updateTime",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "important",
|
||||||
|
"columnName": "important",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "forwardIds",
|
||||||
|
"columnName": "forwardIds",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "attachments",
|
||||||
|
"columnName": "attachments",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "replyMessageId",
|
||||||
|
"columnName": "replyMessageId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "geoType",
|
||||||
|
"columnName": "geoType",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pinnedAt",
|
||||||
|
"columnName": "pinnedAt",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPinned",
|
||||||
|
"columnName": "isPinned",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "convos",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localId` INTEGER NOT NULL, `ownerId` INTEGER, `title` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `isPhantom` INTEGER NOT NULL, `lastCmId` INTEGER NOT NULL, `inReadCmId` INTEGER NOT NULL, `outReadCmId` INTEGER NOT NULL, `inRead` INTEGER NOT NULL, `outRead` INTEGER NOT NULL, `lastMessageId` INTEGER, `unreadCount` INTEGER NOT NULL, `membersCount` INTEGER, `canChangePin` INTEGER NOT NULL, `canChangeInfo` INTEGER NOT NULL, `majorId` INTEGER NOT NULL, `minorId` INTEGER NOT NULL, `pinnedMessageId` INTEGER, `peerType` TEXT NOT NULL, `isArchived` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "localId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "ownerId",
|
||||||
|
"columnName": "ownerId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo50",
|
||||||
|
"columnName": "photo50",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo100",
|
||||||
|
"columnName": "photo100",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "photo200",
|
||||||
|
"columnName": "photo200",
|
||||||
|
"affinity": "TEXT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isPhantom",
|
||||||
|
"columnName": "isPhantom",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastCmId",
|
||||||
|
"columnName": "lastCmId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "inReadCmId",
|
||||||
|
"columnName": "inReadCmId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "outReadCmId",
|
||||||
|
"columnName": "outReadCmId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "inRead",
|
||||||
|
"columnName": "inRead",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "outRead",
|
||||||
|
"columnName": "outRead",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastMessageId",
|
||||||
|
"columnName": "lastMessageId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "unreadCount",
|
||||||
|
"columnName": "unreadCount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "membersCount",
|
||||||
|
"columnName": "membersCount",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canChangePin",
|
||||||
|
"columnName": "canChangePin",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canChangeInfo",
|
||||||
|
"columnName": "canChangeInfo",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "majorId",
|
||||||
|
"columnName": "majorId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minorId",
|
||||||
|
"columnName": "minorId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "pinnedMessageId",
|
||||||
|
"columnName": "pinnedMessageId",
|
||||||
|
"affinity": "INTEGER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "peerType",
|
||||||
|
"columnName": "peerType",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isArchived",
|
||||||
|
"columnName": "isArchived",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a746865995959331f8a1b512c049dacb')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,12 +3,12 @@ package dev.meloda.fast.database
|
|||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import dev.meloda.fast.database.dao.ConversationDao
|
import dev.meloda.fast.database.dao.ConvoDao
|
||||||
import dev.meloda.fast.database.dao.GroupDao
|
import dev.meloda.fast.database.dao.GroupDao
|
||||||
import dev.meloda.fast.database.dao.MessageDao
|
import dev.meloda.fast.database.dao.MessageDao
|
||||||
import dev.meloda.fast.database.dao.UserDao
|
import dev.meloda.fast.database.dao.UserDao
|
||||||
import dev.meloda.fast.database.typeconverters.Converters
|
import dev.meloda.fast.database.typeconverters.Converters
|
||||||
import dev.meloda.fast.model.database.VkConversationEntity
|
import dev.meloda.fast.model.database.VkConvoEntity
|
||||||
import dev.meloda.fast.model.database.VkGroupEntity
|
import dev.meloda.fast.model.database.VkGroupEntity
|
||||||
import dev.meloda.fast.model.database.VkMessageEntity
|
import dev.meloda.fast.model.database.VkMessageEntity
|
||||||
import dev.meloda.fast.model.database.VkUserEntity
|
import dev.meloda.fast.model.database.VkUserEntity
|
||||||
@@ -18,15 +18,15 @@ import dev.meloda.fast.model.database.VkUserEntity
|
|||||||
VkUserEntity::class,
|
VkUserEntity::class,
|
||||||
VkGroupEntity::class,
|
VkGroupEntity::class,
|
||||||
VkMessageEntity::class,
|
VkMessageEntity::class,
|
||||||
VkConversationEntity::class
|
VkConvoEntity::class
|
||||||
],
|
],
|
||||||
|
|
||||||
version = 10
|
version = 11
|
||||||
)
|
)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class CacheDatabase : RoomDatabase() {
|
abstract class CacheDatabase : RoomDatabase() {
|
||||||
abstract fun userDao(): UserDao
|
abstract fun userDao(): UserDao
|
||||||
abstract fun groupDao(): GroupDao
|
abstract fun groupDao(): GroupDao
|
||||||
abstract fun messageDao(): MessageDao
|
abstract fun messageDao(): MessageDao
|
||||||
abstract fun conversationDao(): ConversationDao
|
abstract fun convoDao(): ConvoDao
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package dev.meloda.fast.database.dao
|
|
||||||
|
|
||||||
import androidx.room.Dao
|
|
||||||
import androidx.room.Query
|
|
||||||
import androidx.room.Transaction
|
|
||||||
import dev.meloda.fast.model.database.ConversationWithMessage
|
|
||||||
import dev.meloda.fast.model.database.VkConversationEntity
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
abstract class ConversationDao : EntityDao<VkConversationEntity> {
|
|
||||||
|
|
||||||
@Query("SELECT * FROM conversations")
|
|
||||||
abstract suspend fun getAll(): List<VkConversationEntity>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM conversations WHERE id IN (:ids)")
|
|
||||||
abstract suspend fun getAllByIds(ids: List<Int>): List<VkConversationEntity>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM conversations WHERE id IS (:id)")
|
|
||||||
abstract suspend fun getById(id: Long): VkConversationEntity?
|
|
||||||
|
|
||||||
@Transaction
|
|
||||||
@Query("SELECT * FROM conversations WHERE id IS (:id)")
|
|
||||||
abstract suspend fun getByIdWithMessage(id: Long): ConversationWithMessage?
|
|
||||||
|
|
||||||
@Query("DELETE FROM conversations WHERE rowid IN (:ids)")
|
|
||||||
abstract suspend fun deleteByIds(ids: List<Int>): Int
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.meloda.fast.database.dao
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Transaction
|
||||||
|
import dev.meloda.fast.model.database.ConvoWithMessage
|
||||||
|
import dev.meloda.fast.model.database.VkConvoEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
abstract class ConvoDao : EntityDao<VkConvoEntity> {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM convos")
|
||||||
|
abstract suspend fun getAll(): List<VkConvoEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM convos WHERE id IN (:ids)")
|
||||||
|
abstract suspend fun getAllByIds(ids: List<Int>): List<VkConvoEntity>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM convos WHERE id IS (:id)")
|
||||||
|
abstract suspend fun getById(id: Long): VkConvoEntity?
|
||||||
|
|
||||||
|
@Transaction
|
||||||
|
@Query("SELECT * FROM convos WHERE id IS (:id)")
|
||||||
|
abstract suspend fun getByIdWithMessage(id: Long): ConvoWithMessage?
|
||||||
|
|
||||||
|
@Query("DELETE FROM convos WHERE rowid IN (:ids)")
|
||||||
|
abstract suspend fun deleteByIds(ids: List<Int>): Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -10,8 +10,8 @@ abstract class MessageDao : EntityDao<VkMessageEntity> {
|
|||||||
@Query("SELECT * FROM messages")
|
@Query("SELECT * FROM messages")
|
||||||
abstract suspend fun getAll(): List<VkMessageEntity>
|
abstract suspend fun getAll(): List<VkMessageEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM messages WHERE peerId IS (:conversationId)")
|
@Query("SELECT * FROM messages WHERE peerId IS (:convoId)")
|
||||||
abstract suspend fun getAll(conversationId: Long): List<VkMessageEntity>
|
abstract suspend fun getAll(convoId: Long): List<VkMessageEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM messages WHERE id IN (:ids)")
|
@Query("SELECT * FROM messages WHERE id IN (:ids)")
|
||||||
abstract suspend fun getAllByIds(ids: List<Int>): List<VkMessageEntity>
|
abstract suspend fun getAllByIds(ids: List<Int>): List<VkMessageEntity>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ val databaseModule = module {
|
|||||||
single { cacheDB().userDao() }
|
single { cacheDB().userDao() }
|
||||||
single { cacheDB().groupDao() }
|
single { cacheDB().groupDao() }
|
||||||
single { cacheDB().messageDao() }
|
single { cacheDB().messageDao() }
|
||||||
single { cacheDB().conversationDao() }
|
single { cacheDB().convoDao() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Scope.cacheDB(): CacheDatabase = get()
|
private fun Scope.cacheDB(): CacheDatabase = get()
|
||||||
|
|||||||
@@ -8,11 +8,16 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api(projects.core.common)
|
||||||
api(projects.core.data)
|
api(projects.core.data)
|
||||||
api(projects.core.model)
|
api(projects.core.model)
|
||||||
|
|
||||||
// TODO: 11/08/2024, Danil Nikolaev: remove?
|
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.koin.core)
|
implementation(libs.koin.core)
|
||||||
implementation(libs.eithernet)
|
implementation(libs.eithernet)
|
||||||
|
|
||||||
|
implementation(libs.bundles.nanokt)
|
||||||
|
|
||||||
|
implementation(platform(libs.compose.bom))
|
||||||
|
implementation(libs.compose.ui)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -1,25 +1,25 @@
|
|||||||
package dev.meloda.fast.domain
|
package dev.meloda.fast.domain
|
||||||
|
|
||||||
import dev.meloda.fast.data.State
|
import dev.meloda.fast.data.State
|
||||||
import dev.meloda.fast.model.ConversationsFilter
|
import dev.meloda.fast.model.ConvosFilter
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ConversationsUseCase : BaseUseCase {
|
interface ConvoUseCase : BaseUseCase {
|
||||||
|
|
||||||
suspend fun storeConversations(conversations: List<VkConversation>)
|
suspend fun storeConvos(convos: List<VkConvo>)
|
||||||
|
|
||||||
fun getConversations(
|
fun getConvos(
|
||||||
count: Int? = null,
|
count: Int? = null,
|
||||||
offset: Int? = null,
|
offset: Int? = null,
|
||||||
filter: ConversationsFilter
|
filter: ConvosFilter
|
||||||
): Flow<State<List<VkConversation>>>
|
): Flow<State<List<VkConvo>>>
|
||||||
|
|
||||||
fun getById(
|
fun getById(
|
||||||
peerIds: List<Long>,
|
peerIds: List<Long>,
|
||||||
extended: Boolean? = null,
|
extended: Boolean? = null,
|
||||||
fields: String? = null
|
fields: String? = null
|
||||||
): Flow<State<List<VkConversation>>>
|
): Flow<State<List<VkConvo>>>
|
||||||
|
|
||||||
fun delete(peerId: Long): Flow<State<Long>>
|
fun delete(peerId: Long): Flow<State<Long>>
|
||||||
|
|
||||||
+15
-15
@@ -1,30 +1,30 @@
|
|||||||
package dev.meloda.fast.domain
|
package dev.meloda.fast.domain
|
||||||
|
|
||||||
import dev.meloda.fast.data.State
|
import dev.meloda.fast.data.State
|
||||||
import dev.meloda.fast.data.api.conversations.ConversationsRepository
|
import dev.meloda.fast.data.api.convos.ConvosRepository
|
||||||
import dev.meloda.fast.data.mapToState
|
import dev.meloda.fast.data.mapToState
|
||||||
import dev.meloda.fast.model.ConversationsFilter
|
import dev.meloda.fast.model.ConvosFilter
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class ConversationsUseCaseImpl(
|
class ConvoUseCaseImpl(
|
||||||
private val repository: ConversationsRepository,
|
private val repository: ConvosRepository,
|
||||||
) : ConversationsUseCase {
|
) : ConvoUseCase {
|
||||||
|
|
||||||
override suspend fun storeConversations(
|
override suspend fun storeConvos(
|
||||||
conversations: List<VkConversation>
|
convos: List<VkConvo>
|
||||||
) = withContext(Dispatchers.IO) {
|
) = withContext(Dispatchers.IO) {
|
||||||
repository.storeConversations(conversations)
|
repository.storeConvos(convos)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getConversations(
|
override fun getConvos(
|
||||||
count: Int?,
|
count: Int?,
|
||||||
offset: Int?,
|
offset: Int?,
|
||||||
filter: ConversationsFilter
|
filter: ConvosFilter
|
||||||
): Flow<State<List<VkConversation>>> = flowNewState {
|
): Flow<State<List<VkConvo>>> = flowNewState {
|
||||||
repository.getConversations(
|
repository.getConvos(
|
||||||
count = count,
|
count = count,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
filter = filter
|
filter = filter
|
||||||
@@ -35,8 +35,8 @@ class ConversationsUseCaseImpl(
|
|||||||
peerIds: List<Long>,
|
peerIds: List<Long>,
|
||||||
extended: Boolean?,
|
extended: Boolean?,
|
||||||
fields: String?
|
fields: String?
|
||||||
): Flow<State<List<VkConversation>>> = flowNewState {
|
): Flow<State<List<VkConvo>>> = flowNewState {
|
||||||
repository.getConversationsById(
|
repository.getConvosById(
|
||||||
peerIds = peerIds,
|
peerIds = peerIds,
|
||||||
extended = extended,
|
extended = extended,
|
||||||
fields = fields
|
fields = fields
|
||||||
@@ -6,10 +6,7 @@ import dev.meloda.fast.model.database.AccountEntity
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class GetCurrentAccountUseCase(
|
class GetCurrentAccountUseCase(private val accountsRepository: AccountsRepository) {
|
||||||
private val accountsRepository: AccountsRepository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend operator fun invoke(): AccountEntity? = withContext(Dispatchers.IO) {
|
suspend operator fun invoke(): AccountEntity? = withContext(Dispatchers.IO) {
|
||||||
accountsRepository.getAccountById(UserConfig.currentUserId)
|
accountsRepository.getAccountById(UserConfig.currentUserId)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -1,22 +1,22 @@
|
|||||||
package dev.meloda.fast.domain
|
package dev.meloda.fast.domain
|
||||||
|
|
||||||
import dev.meloda.fast.data.State
|
import dev.meloda.fast.data.State
|
||||||
import dev.meloda.fast.data.api.conversations.ConversationsRepository
|
import dev.meloda.fast.data.api.convos.ConvosRepository
|
||||||
import dev.meloda.fast.data.mapToState
|
import dev.meloda.fast.data.mapToState
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
class LoadConversationsByIdUseCase(
|
class LoadConvosByIdUseCase(
|
||||||
private val conversationsRepository: ConversationsRepository
|
private val convosRepository: ConvosRepository
|
||||||
) : BaseUseCase {
|
) : BaseUseCase {
|
||||||
|
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
peerIds: List<Long>,
|
peerIds: List<Long>,
|
||||||
extended: Boolean? = null,
|
extended: Boolean? = null,
|
||||||
fields: String? = null
|
fields: String? = null
|
||||||
): Flow<State<List<VkConversation>>> = flowNewState {
|
): Flow<State<List<VkConvo>>> = flowNewState {
|
||||||
conversationsRepository
|
convosRepository
|
||||||
.getConversationsById(
|
.getConvosById(
|
||||||
peerIds = peerIds,
|
peerIds = peerIds,
|
||||||
extended = extended,
|
extended = extended,
|
||||||
fields = fields,
|
fields = fields,
|
||||||
@@ -9,12 +9,12 @@ import dev.meloda.fast.common.extensions.toList
|
|||||||
import dev.meloda.fast.data.UserConfig
|
import dev.meloda.fast.data.UserConfig
|
||||||
import dev.meloda.fast.data.processState
|
import dev.meloda.fast.data.processState
|
||||||
import dev.meloda.fast.model.ApiEvent
|
import dev.meloda.fast.model.ApiEvent
|
||||||
import dev.meloda.fast.model.ConversationFlags
|
import dev.meloda.fast.model.ConvoFlags
|
||||||
import dev.meloda.fast.model.InteractionType
|
import dev.meloda.fast.model.InteractionType
|
||||||
import dev.meloda.fast.model.LongPollEvent
|
import dev.meloda.fast.model.LongPollEvent
|
||||||
import dev.meloda.fast.model.LongPollParsedEvent
|
import dev.meloda.fast.model.LongPollParsedEvent
|
||||||
import dev.meloda.fast.model.MessageFlags
|
import dev.meloda.fast.model.MessageFlags
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -28,7 +28,7 @@ import kotlin.coroutines.resume
|
|||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
class LongPollUpdatesParser(
|
class LongPollUpdatesParser(
|
||||||
private val conversationsUseCase: ConversationsUseCase,
|
private val convoUseCase: ConvoUseCase,
|
||||||
private val messagesUseCase: MessagesUseCase
|
private val messagesUseCase: MessagesUseCase
|
||||||
) {
|
) {
|
||||||
private val job = SupervisorJob()
|
private val job = SupervisorJob()
|
||||||
@@ -271,9 +271,9 @@ class LongPollUpdatesParser(
|
|||||||
val message =
|
val message =
|
||||||
async { loadMessage(peerId = peerId, cmId = cmId) }.await()
|
async { loadMessage(peerId = peerId, cmId = cmId) }.await()
|
||||||
|
|
||||||
val conversation =
|
val convo =
|
||||||
async {
|
async {
|
||||||
loadConversation(
|
loadConvo(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
extended = true,
|
extended = true,
|
||||||
fields = VkConstants.ALL_FIELDS
|
fields = VkConstants.ALL_FIELDS
|
||||||
@@ -287,7 +287,7 @@ class LongPollUpdatesParser(
|
|||||||
.onEvent(
|
.onEvent(
|
||||||
LongPollParsedEvent.NewMessage(
|
LongPollParsedEvent.NewMessage(
|
||||||
message = message,
|
message = message,
|
||||||
inArchive = conversation?.isArchived == true
|
inArchive = convo?.isArchived == true
|
||||||
// TODO: 03-Apr-25, Danil Nikolaev:
|
// TODO: 03-Apr-25, Danil Nikolaev:
|
||||||
// load user settings about restoring chats with
|
// load user settings about restoring chats with
|
||||||
// enabled notifications from archive
|
// enabled notifications from archive
|
||||||
@@ -368,13 +368,13 @@ class LongPollUpdatesParser(
|
|||||||
|
|
||||||
val eventsToSend = mutableListOf<LongPollParsedEvent>()
|
val eventsToSend = mutableListOf<LongPollParsedEvent>()
|
||||||
|
|
||||||
val parsedFlags = ConversationFlags.parse(flags)
|
val parsedFlags = ConvoFlags.parse(flags)
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
parsedFlags.forEach { flag ->
|
parsedFlags.forEach { flag ->
|
||||||
when (flag) {
|
when (flag) {
|
||||||
ConversationFlags.ARCHIVED -> {
|
ConvoFlags.ARCHIVED -> {
|
||||||
val conversation = loadConversation(
|
val convo = loadConvo(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
extended = true,
|
extended = true,
|
||||||
fields = VkConstants.ALL_FIELDS
|
fields = VkConstants.ALL_FIELDS
|
||||||
@@ -382,11 +382,11 @@ class LongPollUpdatesParser(
|
|||||||
|
|
||||||
val message = loadMessage(
|
val message = loadMessage(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
cmId = conversation.lastCmId
|
cmId = convo.lastCmId
|
||||||
)
|
)
|
||||||
|
|
||||||
val eventToSend = LongPollParsedEvent.ChatArchived(
|
val eventToSend = LongPollParsedEvent.ChatArchived(
|
||||||
conversation = conversation.copy(lastMessage = message),
|
convo = convo.copy(lastMessage = message),
|
||||||
archived = false
|
archived = false
|
||||||
)
|
)
|
||||||
eventsToSend += eventToSend
|
eventsToSend += eventToSend
|
||||||
@@ -423,13 +423,13 @@ class LongPollUpdatesParser(
|
|||||||
|
|
||||||
val eventsToSend = mutableListOf<LongPollParsedEvent>()
|
val eventsToSend = mutableListOf<LongPollParsedEvent>()
|
||||||
|
|
||||||
val parsedFlags = ConversationFlags.parse(flags)
|
val parsedFlags = ConvoFlags.parse(flags)
|
||||||
|
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
parsedFlags.forEach { flag ->
|
parsedFlags.forEach { flag ->
|
||||||
when (flag) {
|
when (flag) {
|
||||||
ConversationFlags.ARCHIVED -> {
|
ConvoFlags.ARCHIVED -> {
|
||||||
val conversation = loadConversation(
|
val convo = loadConvo(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
extended = true,
|
extended = true,
|
||||||
fields = VkConstants.ALL_FIELDS
|
fields = VkConstants.ALL_FIELDS
|
||||||
@@ -437,11 +437,11 @@ class LongPollUpdatesParser(
|
|||||||
|
|
||||||
val message = loadMessage(
|
val message = loadMessage(
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
cmId = conversation.lastCmId
|
cmId = convo.lastCmId
|
||||||
)
|
)
|
||||||
|
|
||||||
val eventToSend = LongPollParsedEvent.ChatArchived(
|
val eventToSend = LongPollParsedEvent.ChatArchived(
|
||||||
conversation = conversation.copy(lastMessage = message),
|
convo = convo.copy(lastMessage = message),
|
||||||
archived = true
|
archived = true
|
||||||
)
|
)
|
||||||
eventsToSend += eventToSend
|
eventsToSend += eventToSend
|
||||||
@@ -673,29 +673,29 @@ class LongPollUpdatesParser(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadConversation(
|
private suspend fun loadConvo(
|
||||||
peerId: Long,
|
peerId: Long,
|
||||||
extended: Boolean = false,
|
extended: Boolean = false,
|
||||||
fields: String? = null
|
fields: String? = null
|
||||||
): VkConversation? = suspendCoroutine { continuation ->
|
): VkConvo? = suspendCoroutine { continuation ->
|
||||||
coroutineScope.launch(Dispatchers.IO) {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
conversationsUseCase.getById(
|
convoUseCase.getById(
|
||||||
peerIds = listOf(peerId),
|
peerIds = listOf(peerId),
|
||||||
extended = extended,
|
extended = extended,
|
||||||
fields = fields
|
fields = fields
|
||||||
).listenValue(coroutineScope) { state ->
|
).listenValue(coroutineScope) { state ->
|
||||||
state.processState(
|
state.processState(
|
||||||
error = { error ->
|
error = { error ->
|
||||||
Log.e("LongPollUpdatesParser", "loadConversation: error: $error")
|
Log.e("LongPollUpdatesParser", "loadConvo: error: $error")
|
||||||
continuation.resume(null)
|
continuation.resume(null)
|
||||||
},
|
},
|
||||||
success = { response ->
|
success = { response ->
|
||||||
val conversation = response.singleOrNull() ?: run {
|
val convo = response.singleOrNull() ?: run {
|
||||||
continuation.resume(null)
|
continuation.resume(null)
|
||||||
return@listenValue
|
return@listenValue
|
||||||
}
|
}
|
||||||
|
|
||||||
continuation.resume(conversation)
|
continuation.resume(convo)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ interface MessagesUseCase : BaseUseCase {
|
|||||||
suspend fun storeMessages(messages: List<VkMessage>)
|
suspend fun storeMessages(messages: List<VkMessage>)
|
||||||
|
|
||||||
fun getMessagesHistory(
|
fun getMessagesHistory(
|
||||||
conversationId: Long,
|
convoId: Long,
|
||||||
count: Int?,
|
count: Int?,
|
||||||
offset: Int?
|
offset: Int?
|
||||||
): Flow<State<MessagesHistoryInfo>>
|
): Flow<State<MessagesHistoryInfo>>
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ class MessagesUseCaseImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getMessagesHistory(
|
override fun getMessagesHistory(
|
||||||
conversationId: Long,
|
convoId: Long,
|
||||||
count: Int?,
|
count: Int?,
|
||||||
offset: Int?
|
offset: Int?
|
||||||
): Flow<State<MessagesHistoryInfo>> = flowNewState {
|
): Flow<State<MessagesHistoryInfo>> = flowNewState {
|
||||||
repository.getHistory(
|
repository.getHistory(
|
||||||
conversationId = conversationId,
|
convoId = convoId,
|
||||||
offset = offset,
|
offset = offset,
|
||||||
count = count
|
count = count
|
||||||
).mapToState()
|
).mapToState()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ 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.GetMessageReadPeersUseCase
|
import dev.meloda.fast.domain.GetMessageReadPeersUseCase
|
||||||
import dev.meloda.fast.domain.LoadConversationsByIdUseCase
|
import dev.meloda.fast.domain.LoadConvosByIdUseCase
|
||||||
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
|
||||||
@@ -27,7 +27,7 @@ val domainModule = module {
|
|||||||
singleOf(::AccountUseCaseImpl) bind AccountUseCase::class
|
singleOf(::AccountUseCaseImpl) bind AccountUseCase::class
|
||||||
singleOf(::GetCurrentAccountUseCase)
|
singleOf(::GetCurrentAccountUseCase)
|
||||||
|
|
||||||
singleOf(::LoadConversationsByIdUseCase)
|
singleOf(::LoadConvosByIdUseCase)
|
||||||
|
|
||||||
singleOf(::GetMessageReadPeersUseCase)
|
singleOf(::GetMessageReadPeersUseCase)
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-185
@@ -1,7 +1,6 @@
|
|||||||
package dev.meloda.fast.conversations.util
|
package dev.meloda.fast.domain.util
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
@@ -13,64 +12,22 @@ import dev.meloda.fast.common.extensions.orDots
|
|||||||
import dev.meloda.fast.common.model.UiImage
|
import dev.meloda.fast.common.model.UiImage
|
||||||
import dev.meloda.fast.common.model.UiText
|
import dev.meloda.fast.common.model.UiText
|
||||||
import dev.meloda.fast.common.model.parseString
|
import dev.meloda.fast.common.model.parseString
|
||||||
import dev.meloda.fast.common.util.TimeUtils
|
|
||||||
import dev.meloda.fast.data.UserConfig
|
import dev.meloda.fast.data.UserConfig
|
||||||
import dev.meloda.fast.data.VkMemoryCache
|
import dev.meloda.fast.data.VkMemoryCache
|
||||||
import dev.meloda.fast.model.InteractionType
|
import dev.meloda.fast.model.InteractionType
|
||||||
import dev.meloda.fast.model.api.PeerType
|
import dev.meloda.fast.model.api.PeerType
|
||||||
import dev.meloda.fast.model.api.data.AttachmentType
|
import dev.meloda.fast.model.api.data.AttachmentType
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
import dev.meloda.fast.ui.model.api.ActionState
|
|
||||||
import dev.meloda.fast.ui.model.api.ConversationOption
|
|
||||||
import dev.meloda.fast.ui.model.api.UiConversation
|
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
|
||||||
import dev.meloda.fast.ui.util.emptyImmutableList
|
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.ln
|
import kotlin.math.ln
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
fun VkConversation.asPresentation(
|
fun VkConvo.extractAvatar(): UiImage = when (peerType) {
|
||||||
resources: Resources,
|
|
||||||
useContactName: Boolean,
|
|
||||||
isExpanded: Boolean = false,
|
|
||||||
options: ImmutableList<ConversationOption> = emptyImmutableList()
|
|
||||||
): UiConversation = UiConversation(
|
|
||||||
id = id,
|
|
||||||
lastMessageId = lastMessageId,
|
|
||||||
avatar = extractAvatar(),
|
|
||||||
title = extractTitle(this, useContactName, resources),
|
|
||||||
unreadCount = extractUnreadCount(lastMessage, this),
|
|
||||||
date = TimeUtils.getLocalizedTime(
|
|
||||||
date = (lastMessage?.date ?: -1) * 1000L,
|
|
||||||
yearShort = { resources.getString(R.string.year_short) },
|
|
||||||
monthShort = { resources.getString(R.string.month_short) },
|
|
||||||
weekShort = { resources.getString(R.string.week_short) },
|
|
||||||
dayShort = { resources.getString(R.string.day_short) },
|
|
||||||
now = { resources.getString(R.string.time_now) },
|
|
||||||
),
|
|
||||||
message = extractMessage(resources, lastMessage, id, peerType),
|
|
||||||
attachmentImage = if (lastMessage?.text == null) null
|
|
||||||
else getAttachmentConversationIcon(lastMessage),
|
|
||||||
isPinned = majorId > 0,
|
|
||||||
actionImageId = ActionState.parse(isPhantom, isCallInProgress).getResourceId(),
|
|
||||||
isBirthday = extractBirthday(this),
|
|
||||||
isUnread = !isRead(),
|
|
||||||
isAccount = isAccount(id),
|
|
||||||
isOnline = !isAccount(id) && user?.onlineStatus?.isOnline() == true,
|
|
||||||
lastMessage = lastMessage,
|
|
||||||
peerType = peerType,
|
|
||||||
interactionText = extractInteractionText(resources, this),
|
|
||||||
isExpanded = isExpanded,
|
|
||||||
isArchived = isArchived,
|
|
||||||
options = options
|
|
||||||
)
|
|
||||||
|
|
||||||
fun VkConversation.extractAvatar() = when (peerType) {
|
|
||||||
PeerType.USER -> {
|
PeerType.USER -> {
|
||||||
if (isAccount(id)) null
|
if (isAccount(id)) null
|
||||||
else user?.photo200
|
else user?.photo200
|
||||||
@@ -83,20 +40,19 @@ fun VkConversation.extractAvatar() = when (peerType) {
|
|||||||
PeerType.CHAT -> {
|
PeerType.CHAT -> {
|
||||||
photo200
|
photo200
|
||||||
}
|
}
|
||||||
}?.let(UiImage::Url) ?: UiImage.Resource(R.drawable.ic_account_circle_cut)
|
}?.let(UiImage::Url) ?: UiImage.Resource(R.drawable.ic_account_circle_fill_round_24)
|
||||||
|
|
||||||
private fun extractTitle(
|
fun VkConvo.extractTitle(
|
||||||
conversation: VkConversation,
|
|
||||||
useContactName: Boolean,
|
useContactName: Boolean,
|
||||||
resources: Resources
|
resources: Resources
|
||||||
) = when (conversation.peerType) {
|
) = when (peerType) {
|
||||||
PeerType.USER -> {
|
PeerType.USER -> {
|
||||||
if (isAccount(conversation.id)) {
|
if (isAccount(id)) {
|
||||||
UiText.Resource(R.string.favorites)
|
UiText.Resource(R.string.favorites)
|
||||||
} else {
|
} else {
|
||||||
val userName = conversation.user?.let { user ->
|
val userName = user?.let { user ->
|
||||||
if (useContactName) {
|
if (useContactName) {
|
||||||
VkMemoryCache.getContact(user.id)?.name ?: user.fullName
|
VkMemoryCache.getContact(user.id)?.name
|
||||||
} else {
|
} else {
|
||||||
user.fullName
|
user.fullName
|
||||||
}
|
}
|
||||||
@@ -106,22 +62,22 @@ private fun extractTitle(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PeerType.GROUP -> UiText.Simple(conversation.group?.name.orDots())
|
PeerType.GROUP -> UiText.Simple(group?.name.orDots())
|
||||||
PeerType.CHAT -> UiText.Simple(conversation.title.orDots())
|
PeerType.CHAT -> UiText.Simple(title.orDots())
|
||||||
}.parseString(resources).orDots()
|
}.parseString(resources).orDots()
|
||||||
|
|
||||||
private fun extractUnreadCount(
|
fun extractUnreadCount(
|
||||||
lastMessage: VkMessage?,
|
lastMessage: VkMessage?,
|
||||||
conversation: VkConversation
|
convo: VkConvo
|
||||||
): String? = when {
|
): String? = when {
|
||||||
lastMessage?.isOut == false && conversation.isInRead() -> null
|
lastMessage?.isOut == false && convo.isInRead() -> null
|
||||||
conversation.unreadCount == 0 -> null
|
convo.unreadCount == 0 -> null
|
||||||
conversation.unreadCount < 1000 -> conversation.unreadCount.toString()
|
convo.unreadCount < 1000 -> convo.unreadCount.toString()
|
||||||
else -> {
|
else -> {
|
||||||
val exp = (ln(conversation.unreadCount.toDouble()) / ln(1000.0)).toInt()
|
val exp = (ln(convo.unreadCount.toDouble()) / ln(1000.0)).toInt()
|
||||||
val suffix = "KMBT"[exp - 1]
|
val suffix = "KMBT"[exp - 1]
|
||||||
|
|
||||||
val result = conversation.unreadCount / 1000.0.pow(exp.toDouble())
|
val result = convo.unreadCount / 1000.0.pow(exp.toDouble())
|
||||||
|
|
||||||
if (result.toLong().toDouble() == result) {
|
if (result.toLong().toDouble() == result) {
|
||||||
String.format(Locale.getDefault(), "%.0f%s", result, suffix)
|
String.format(Locale.getDefault(), "%.0f%s", result, suffix)
|
||||||
@@ -131,11 +87,12 @@ private fun extractUnreadCount(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractMessage(
|
fun extractMessage(
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
lastMessage: VkMessage?,
|
lastMessage: VkMessage?,
|
||||||
peerId: Long,
|
peerId: Long,
|
||||||
peerType: PeerType
|
peerType: PeerType,
|
||||||
|
showPeer: Boolean = true
|
||||||
): AnnotatedString {
|
): AnnotatedString {
|
||||||
val youPrefix = UiText.Resource(R.string.you_message_prefix)
|
val youPrefix = UiText.Resource(R.string.you_message_prefix)
|
||||||
.parseString(resources)
|
.parseString(resources)
|
||||||
@@ -160,6 +117,8 @@ private fun extractMessage(
|
|||||||
val messageText = lastMessage?.text.orEmpty()
|
val messageText = lastMessage?.text.orEmpty()
|
||||||
|
|
||||||
val prefixText: AnnotatedString? = when {
|
val prefixText: AnnotatedString? = when {
|
||||||
|
!showPeer -> null
|
||||||
|
|
||||||
actionMessage != null -> null
|
actionMessage != null -> null
|
||||||
|
|
||||||
lastMessage == null -> null
|
lastMessage == null -> null
|
||||||
@@ -226,16 +185,17 @@ private fun extractMessage(
|
|||||||
.let { text ->
|
.let { text ->
|
||||||
extractTextWithVisualizedMentions(
|
extractTextWithVisualizedMentions(
|
||||||
isOut = lastMessage?.isOut == true,
|
isOut = lastMessage?.isOut == true,
|
||||||
originalText = text
|
originalText = text,
|
||||||
|
formatData = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.let { text -> prefix + text }
|
.let { text -> prefix + text.orEmpty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalText
|
return finalText
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractActionText(
|
fun extractActionText(
|
||||||
lastMessage: VkMessage?,
|
lastMessage: VkMessage?,
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
youPrefix: String
|
youPrefix: String
|
||||||
@@ -510,16 +470,25 @@ private fun extractActionText(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractAttachmentIcon(
|
fun extractAttachmentIcon(
|
||||||
lastMessage: VkMessage?
|
lastMessage: VkMessage?
|
||||||
): UiImage? = when {
|
): UiImage? = when {
|
||||||
lastMessage == null -> null
|
lastMessage == null -> null
|
||||||
lastMessage.text == null -> null
|
lastMessage.text == null -> null
|
||||||
|
lastMessage.geoType != null -> {
|
||||||
|
val geoType = lastMessage.geoType
|
||||||
|
if (geoType == "point") {
|
||||||
|
UiImage.Resource(R.drawable.ic_pin_drop_fill_round_24)
|
||||||
|
} else {
|
||||||
|
UiImage.Resource(R.drawable.ic_map_fill_round_24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
!lastMessage.forwards.isNullOrEmpty() -> {
|
!lastMessage.forwards.isNullOrEmpty() -> {
|
||||||
if (lastMessage.forwards.orEmpty().size == 1) {
|
if (lastMessage.forwards.orEmpty().size == 1) {
|
||||||
UiImage.Resource(R.drawable.ic_attachment_forwarded_message)
|
UiImage.Resource(R.drawable.ic_reply_round_24)
|
||||||
} else {
|
} else {
|
||||||
UiImage.Resource(R.drawable.ic_attachment_forwarded_messages)
|
UiImage.Resource(R.drawable.ic_reply_all_round_24)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,19 +496,15 @@ private fun extractAttachmentIcon(
|
|||||||
lastMessage.attachments?.let { attachments ->
|
lastMessage.attachments?.let { attachments ->
|
||||||
if (attachments.isEmpty()) return null
|
if (attachments.isEmpty()) return null
|
||||||
if (attachments.size == 1 || isAttachmentsHaveOneType(attachments)) {
|
if (attachments.size == 1 || isAttachmentsHaveOneType(attachments)) {
|
||||||
lastMessage.geoType?.let {
|
|
||||||
return UiImage.Resource(R.drawable.ic_map_marker)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAttachmentIconByType(attachments.first().type)
|
getAttachmentIconByType(attachments.first().type)
|
||||||
} else {
|
} else {
|
||||||
UiImage.Resource(R.drawable.ic_baseline_attach_file_24)
|
UiImage.Resource(R.drawable.ic_attach_file_round_24)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractAttachmentText(
|
fun extractAttachmentText(
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
lastMessage: VkMessage?
|
lastMessage: VkMessage?
|
||||||
): AnnotatedString? = when {
|
): AnnotatedString? = when {
|
||||||
@@ -605,22 +570,22 @@ private fun extractAttachmentText(
|
|||||||
|
|
||||||
private fun getAttachmentIconByType(attachmentType: AttachmentType): UiImage? {
|
private fun getAttachmentIconByType(attachmentType: AttachmentType): UiImage? {
|
||||||
return when (attachmentType) {
|
return when (attachmentType) {
|
||||||
AttachmentType.PHOTO -> R.drawable.ic_attachment_photo
|
AttachmentType.PHOTO -> R.drawable.ic_image_fill_round_24
|
||||||
AttachmentType.VIDEO -> R.drawable.ic_attachment_video
|
AttachmentType.VIDEO -> R.drawable.ic_video_fill_round_24
|
||||||
AttachmentType.AUDIO -> R.drawable.ic_attachment_audio
|
AttachmentType.AUDIO -> R.drawable.ic_music_note_round_24
|
||||||
AttachmentType.FILE -> R.drawable.ic_attachment_file
|
AttachmentType.FILE -> R.drawable.ic_draft_fill_round_24
|
||||||
AttachmentType.LINK -> R.drawable.ic_attachment_link
|
AttachmentType.LINK -> R.drawable.ic_language_round_24
|
||||||
AttachmentType.AUDIO_MESSAGE -> R.drawable.ic_attachment_voice
|
AttachmentType.AUDIO_MESSAGE -> R.drawable.ic_mic_fill_round_24
|
||||||
AttachmentType.MINI_APP -> R.drawable.ic_attachment_mini_app
|
AttachmentType.MINI_APP -> R.drawable.ic_widgets_fill_round_24
|
||||||
AttachmentType.STICKER -> R.drawable.ic_attachment_sticker
|
AttachmentType.STICKER -> R.drawable.ic_sticker_fill_round_24
|
||||||
AttachmentType.GIFT -> R.drawable.ic_attachment_gift
|
AttachmentType.GIFT -> R.drawable.ic_attachment_gift_old
|
||||||
AttachmentType.WALL -> R.drawable.ic_attachment_wall
|
AttachmentType.WALL -> R.drawable.ic_brick_fill_round_24
|
||||||
AttachmentType.GRAFFITI -> R.drawable.ic_attachment_graffiti
|
AttachmentType.GRAFFITI -> R.drawable.ic_fragrance_fill_round_24
|
||||||
AttachmentType.POLL -> R.drawable.ic_attachment_poll
|
AttachmentType.POLL -> R.drawable.ic_insert_chart_fill_round_24
|
||||||
AttachmentType.WALL_REPLY -> R.drawable.ic_attachment_wall_reply
|
AttachmentType.WALL_REPLY -> R.drawable.ic_comment_fill_round_24
|
||||||
AttachmentType.CALL -> R.drawable.ic_attachment_call
|
AttachmentType.CALL -> R.drawable.ic_call_round_24
|
||||||
AttachmentType.GROUP_CALL_IN_PROGRESS -> R.drawable.ic_attachment_group_call
|
AttachmentType.GROUP_CALL_IN_PROGRESS -> R.drawable.ic_perm_phone_msg_fill_round_24
|
||||||
AttachmentType.STORY -> R.drawable.ic_attachment_story
|
AttachmentType.STORY -> R.drawable.ic_history_toggle_off_round_24
|
||||||
AttachmentType.UNKNOWN -> null
|
AttachmentType.UNKNOWN -> null
|
||||||
AttachmentType.CURATOR -> null
|
AttachmentType.CURATOR -> null
|
||||||
AttachmentType.EVENT -> null
|
AttachmentType.EVENT -> null
|
||||||
@@ -631,7 +596,7 @@ private fun getAttachmentIconByType(attachmentType: AttachmentType): UiImage? {
|
|||||||
AttachmentType.NARRATIVE -> null
|
AttachmentType.NARRATIVE -> null
|
||||||
AttachmentType.ARTICLE -> null
|
AttachmentType.ARTICLE -> null
|
||||||
AttachmentType.VIDEO_MESSAGE -> null
|
AttachmentType.VIDEO_MESSAGE -> null
|
||||||
AttachmentType.GROUP_CHAT_STICKER -> R.drawable.ic_attachment_sticker
|
AttachmentType.GROUP_CHAT_STICKER -> R.drawable.ic_sticker_fill_round_24
|
||||||
AttachmentType.STICKER_PACK_PREVIEW -> null
|
AttachmentType.STICKER_PACK_PREVIEW -> null
|
||||||
}?.let(UiImage::Resource)
|
}?.let(UiImage::Resource)
|
||||||
}
|
}
|
||||||
@@ -649,7 +614,7 @@ private fun isAttachmentsHaveOneType(attachments: List<VkAttachment>): Boolean {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractForwardsText(
|
fun extractForwardsText(
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
lastMessage: VkMessage?
|
lastMessage: VkMessage?
|
||||||
): AnnotatedString? = when {
|
): AnnotatedString? = when {
|
||||||
@@ -670,69 +635,7 @@ private fun extractForwardsText(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun extractTextWithVisualizedMentions(
|
fun getAttachmentUiText(
|
||||||
isOut: Boolean,
|
|
||||||
originalText: String
|
|
||||||
): AnnotatedString = buildAnnotatedString {
|
|
||||||
val regex = """\[(id|club)(\d+)\|([^]]+)]""".toRegex()
|
|
||||||
|
|
||||||
val mentions = mutableListOf<MentionIndex>()
|
|
||||||
|
|
||||||
var currentIndex = 0
|
|
||||||
val replacements = mutableListOf<Pair<IntRange, String>>()
|
|
||||||
|
|
||||||
val result = regex.replace(originalText) { matchResult ->
|
|
||||||
val idPrefix = matchResult.groups[1]?.value.orEmpty()
|
|
||||||
val startIndex = matchResult.range.first
|
|
||||||
val endIndex = matchResult.range.last
|
|
||||||
|
|
||||||
val id = matchResult.groups[2]?.value ?: ""
|
|
||||||
|
|
||||||
val replaced = matchResult.groups[3]?.value.orEmpty()
|
|
||||||
|
|
||||||
val indexRange =
|
|
||||||
(startIndex + currentIndex)..startIndex + currentIndex + replaced.length
|
|
||||||
|
|
||||||
replacements.add(indexRange to replaced)
|
|
||||||
|
|
||||||
mentions += MentionIndex(
|
|
||||||
id = id.toLongOrNull() ?: -1,
|
|
||||||
idPrefix = idPrefix,
|
|
||||||
indexRange = indexRange
|
|
||||||
)
|
|
||||||
|
|
||||||
currentIndex += replaced.length - (endIndex - startIndex + 1)
|
|
||||||
|
|
||||||
replaced
|
|
||||||
}
|
|
||||||
|
|
||||||
append(result)
|
|
||||||
|
|
||||||
mentions.forEach { mention ->
|
|
||||||
val startIndex = mention.indexRange.first
|
|
||||||
val endIndex = mention.indexRange.last
|
|
||||||
|
|
||||||
addStyle(
|
|
||||||
style = SpanStyle(color = Color.Red),
|
|
||||||
start = startIndex,
|
|
||||||
end = endIndex
|
|
||||||
)
|
|
||||||
addStringAnnotation(
|
|
||||||
tag = mention.idPrefix,
|
|
||||||
annotation = mention.id.toString(),
|
|
||||||
start = startIndex,
|
|
||||||
end = endIndex
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class MentionIndex(
|
|
||||||
val id: Long,
|
|
||||||
val idPrefix: String,
|
|
||||||
val indexRange: IntRange
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getAttachmentUiText(
|
|
||||||
attachment: VkAttachment,
|
attachment: VkAttachment,
|
||||||
size: Int = 1,
|
size: Int = 1,
|
||||||
): UiText {
|
): UiText {
|
||||||
@@ -787,22 +690,8 @@ private fun getAttachmentUiText(
|
|||||||
}.let(UiText::Resource)
|
}.let(UiText::Resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAttachmentConversationIcon(message: VkMessage?): UiImage? {
|
fun extractBirthday(convo: VkConvo): Boolean {
|
||||||
return message?.attachments?.let { attachments ->
|
val birthday = convo.user?.birthday ?: return false
|
||||||
if (attachments.isEmpty()) return null
|
|
||||||
if (attachments.size == 1 || isAttachmentsHaveOneType(attachments)) {
|
|
||||||
message.geoType?.let {
|
|
||||||
return UiImage.Resource(R.drawable.ic_map_marker)
|
|
||||||
}
|
|
||||||
getAttachmentIconByType(attachments.first().type)
|
|
||||||
} else {
|
|
||||||
UiImage.Resource(R.drawable.ic_baseline_attach_file_24)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractBirthday(conversation: VkConversation): Boolean {
|
|
||||||
val birthday = conversation.user?.birthday ?: return false
|
|
||||||
val splitBirthday = birthday.split(".").mapNotNull(String::toIntOrNull)
|
val splitBirthday = birthday.split(".").mapNotNull(String::toIntOrNull)
|
||||||
|
|
||||||
if (splitBirthday.isEmpty()) return false
|
if (splitBirthday.isEmpty()) return false
|
||||||
@@ -822,25 +711,23 @@ private fun extractBirthday(conversation: VkConversation): Boolean {
|
|||||||
} else false
|
} else false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractReadCondition(
|
fun extractReadCondition(
|
||||||
conversation: VkConversation,
|
convo: VkConvo,
|
||||||
lastMessage: VkMessage?
|
lastMessage: VkMessage?
|
||||||
): Boolean = !conversation.isRead(lastMessage)
|
): Boolean = !convo.isRead(lastMessage)
|
||||||
|
|
||||||
private fun isAccount(peerId: Long) = peerId == UserConfig.userId
|
fun extractInteractionText(
|
||||||
|
|
||||||
private fun extractInteractionText(
|
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
conversation: VkConversation
|
convo: VkConvo
|
||||||
): String? {
|
): String? {
|
||||||
val interactionType = InteractionType.parse(conversation.interactionType)
|
val interactionType = InteractionType.parse(convo.interactionType)
|
||||||
val interactiveUsers = extractInteractionUsers(conversation)
|
val interactiveUsers = extractInteractionUsers(convo)
|
||||||
|
|
||||||
val typingText =
|
val typingText =
|
||||||
if (interactionType == null) {
|
if (interactionType == null) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
if (!conversation.peerType.isChat() && interactiveUsers.size == 1) {
|
if (!convo.peerType.isChat() && interactiveUsers.size == 1) {
|
||||||
when (interactionType) {
|
when (interactionType) {
|
||||||
InteractionType.File -> R.string.chat_interaction_uploading_file
|
InteractionType.File -> R.string.chat_interaction_uploading_file
|
||||||
InteractionType.Photo -> R.string.chat_interaction_uploading_photo
|
InteractionType.Photo -> R.string.chat_interaction_uploading_photo
|
||||||
@@ -865,8 +752,8 @@ private fun extractInteractionText(
|
|||||||
return typingText
|
return typingText
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractInteractionUsers(conversation: VkConversation): List<String> {
|
fun extractInteractionUsers(convo: VkConvo): List<String> {
|
||||||
return conversation.interactionIds.mapNotNull { id ->
|
return convo.interactionIds.mapNotNull { id ->
|
||||||
when {
|
when {
|
||||||
id > 0 -> VkMemoryCache.getUser(id)?.fullName
|
id > 0 -> VkMemoryCache.getUser(id)?.fullName
|
||||||
id < 0 -> VkMemoryCache.getGroup(id)?.name
|
id < 0 -> VkMemoryCache.getGroup(id)?.name
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package dev.meloda.fast.domain.util
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import dev.meloda.fast.common.util.TimeUtils
|
||||||
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.model.vk.ActionState
|
||||||
|
import dev.meloda.fast.ui.model.vk.ConvoOption
|
||||||
|
import dev.meloda.fast.ui.model.vk.UiConvo
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
import dev.meloda.fast.ui.util.emptyImmutableList
|
||||||
|
|
||||||
|
fun VkConvo.asPresentation(
|
||||||
|
resources: Resources,
|
||||||
|
useContactName: Boolean,
|
||||||
|
isExpanded: Boolean = false,
|
||||||
|
options: ImmutableList<ConvoOption> = emptyImmutableList()
|
||||||
|
): UiConvo = UiConvo(
|
||||||
|
id = id,
|
||||||
|
lastMessageId = lastMessageId,
|
||||||
|
avatar = extractAvatar(),
|
||||||
|
title = extractTitle(useContactName, resources),
|
||||||
|
unreadCount = extractUnreadCount(lastMessage, this),
|
||||||
|
date = TimeUtils.getLocalizedTime(
|
||||||
|
date = (lastMessage?.date ?: -1) * 1000L,
|
||||||
|
yearShort = { resources.getString(R.string.year_short) },
|
||||||
|
monthShort = { resources.getString(R.string.month_short) },
|
||||||
|
weekShort = { resources.getString(R.string.week_short) },
|
||||||
|
dayShort = { resources.getString(R.string.day_short) },
|
||||||
|
minuteShort = { resources.getString(R.string.minute_short) },
|
||||||
|
secondShort = { resources.getString(R.string.second_short) },
|
||||||
|
now = { resources.getString(R.string.time_now) },
|
||||||
|
),
|
||||||
|
message = extractMessage(resources, lastMessage, id, peerType),
|
||||||
|
attachmentImage = if (lastMessage?.text == null) null
|
||||||
|
else extractAttachmentIcon(lastMessage),
|
||||||
|
isPinned = majorId > 0,
|
||||||
|
actionImageId = ActionState.parse(isPhantom, isCallInProgress).getResourceId(),
|
||||||
|
isBirthday = extractBirthday(this),
|
||||||
|
isUnread = !isRead(),
|
||||||
|
isAccount = isAccount(id),
|
||||||
|
isOnline = !isAccount(id) && user?.onlineStatus?.isOnline() == true,
|
||||||
|
lastMessage = lastMessage,
|
||||||
|
peerType = peerType,
|
||||||
|
interactionText = extractInteractionText(resources, this),
|
||||||
|
isExpanded = isExpanded,
|
||||||
|
isArchived = isArchived,
|
||||||
|
options = options
|
||||||
|
)
|
||||||
+1
-1
@@ -3,7 +3,7 @@ package dev.meloda.fast.domain.util
|
|||||||
import dev.meloda.fast.common.model.UiImage
|
import dev.meloda.fast.common.model.UiImage
|
||||||
import dev.meloda.fast.data.VkMemoryCache
|
import dev.meloda.fast.data.VkMemoryCache
|
||||||
import dev.meloda.fast.model.api.domain.VkUser
|
import dev.meloda.fast.model.api.domain.VkUser
|
||||||
import dev.meloda.fast.ui.model.api.UiFriend
|
import dev.meloda.fast.ui.model.vk.UiFriend
|
||||||
|
|
||||||
fun VkUser.asPresentation(
|
fun VkUser.asPresentation(
|
||||||
useContactNames: Boolean = false
|
useContactNames: Boolean = false
|
||||||
+10
-269
@@ -1,34 +1,21 @@
|
|||||||
package dev.meloda.fast.messageshistory.util
|
package dev.meloda.fast.domain.util
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.AnnotatedString.Annotation
|
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.StringAnnotation
|
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import dev.meloda.fast.common.extensions.orDots
|
import dev.meloda.fast.common.extensions.orDots
|
||||||
import dev.meloda.fast.common.model.UiImage
|
import dev.meloda.fast.common.model.UiImage
|
||||||
import dev.meloda.fast.common.model.UiText
|
import dev.meloda.fast.common.model.UiText
|
||||||
import dev.meloda.fast.common.model.parseString
|
import dev.meloda.fast.common.model.parseString
|
||||||
import dev.meloda.fast.common.provider.ResourceProvider
|
|
||||||
import dev.meloda.fast.data.UserConfig
|
import dev.meloda.fast.data.UserConfig
|
||||||
import dev.meloda.fast.data.VkMemoryCache
|
import dev.meloda.fast.model.api.PeerType.Companion.getPeerType
|
||||||
import dev.meloda.fast.messageshistory.model.SendingStatus
|
|
||||||
import dev.meloda.fast.messageshistory.model.UiItem
|
|
||||||
import dev.meloda.fast.model.api.PeerType
|
|
||||||
import dev.meloda.fast.model.api.domain.FormatDataType
|
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
private fun isAccount(fromId: Long) = fromId == UserConfig.userId
|
|
||||||
|
|
||||||
fun VkMessage.extractAvatar() = when {
|
fun VkMessage.extractAvatar() = when {
|
||||||
isUser() -> {
|
isUser() -> {
|
||||||
if (isAccount(id)) null
|
if (isAccount(id)) null
|
||||||
@@ -40,7 +27,7 @@ fun VkMessage.extractAvatar() = when {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}?.let(UiImage::Url) ?: UiImage.Resource(R.drawable.ic_account_circle_cut)
|
}?.let(UiImage::Url) ?: UiImage.Resource(R.drawable.ic_account_circle_fill_round_24)
|
||||||
|
|
||||||
fun VkMessage.extractDate(): String =
|
fun VkMessage.extractDate(): String =
|
||||||
SimpleDateFormat("HH:mm", Locale.getDefault()).format(date * 1000L)
|
SimpleDateFormat("HH:mm", Locale.getDefault()).format(date * 1000L)
|
||||||
@@ -58,111 +45,15 @@ fun VkMessage.extractTitle(): String = when {
|
|||||||
|
|
||||||
fun VkMessage.extractReplyTitle(): String? = replyMessage?.extractTitle()
|
fun VkMessage.extractReplyTitle(): String? = replyMessage?.extractTitle()
|
||||||
|
|
||||||
// TODO: 24-Jun-25, Danil Nikolaev: improve
|
fun VkMessage.extractReplySummary(resources: Resources): AnnotatedString? =
|
||||||
fun VkMessage.extractReplySummary(): String? = when (val message = replyMessage) {
|
extractMessage(
|
||||||
null -> null
|
resources = resources,
|
||||||
else -> {
|
lastMessage = this,
|
||||||
when {
|
peerId = peerId,
|
||||||
message.text != null -> message.text
|
peerType = getPeerType(),
|
||||||
else -> null
|
showPeer = false
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun VkConversation.extractAvatar(): UiImage = when (peerType) {
|
|
||||||
PeerType.USER -> {
|
|
||||||
if (isAccount(id)) null
|
|
||||||
else user?.photo200
|
|
||||||
}
|
|
||||||
|
|
||||||
PeerType.GROUP -> {
|
|
||||||
group?.photo200
|
|
||||||
}
|
|
||||||
|
|
||||||
PeerType.CHAT -> {
|
|
||||||
photo200
|
|
||||||
}
|
|
||||||
}?.let(UiImage::Url) ?: UiImage.Resource(R.drawable.ic_account_circle_cut)
|
|
||||||
|
|
||||||
fun VkConversation.extractTitle(
|
|
||||||
useContactName: Boolean,
|
|
||||||
resources: Resources
|
|
||||||
) = when (peerType) {
|
|
||||||
PeerType.USER -> {
|
|
||||||
if (isAccount(id)) {
|
|
||||||
UiText.Resource(R.string.favorites)
|
|
||||||
} else {
|
|
||||||
val userName = user?.let { user ->
|
|
||||||
if (useContactName) {
|
|
||||||
VkMemoryCache.getContact(user.id)?.name
|
|
||||||
} else {
|
|
||||||
user.fullName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UiText.Simple(userName.orDots())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PeerType.GROUP -> UiText.Simple(group?.name.orDots())
|
|
||||||
PeerType.CHAT -> UiText.Simple(title.orDots())
|
|
||||||
}.parseString(resources).orDots()
|
|
||||||
|
|
||||||
fun VkMessage.asPresentation(
|
|
||||||
conversation: VkConversation,
|
|
||||||
resourceProvider: ResourceProvider,
|
|
||||||
showName: Boolean,
|
|
||||||
prevMessage: VkMessage?,
|
|
||||||
nextMessage: VkMessage?,
|
|
||||||
showTimeInActionMessages: Boolean,
|
|
||||||
isSelected: Boolean
|
|
||||||
): UiItem = when {
|
|
||||||
action != null -> UiItem.ActionMessage(
|
|
||||||
id = id,
|
|
||||||
cmId = cmId,
|
|
||||||
text = extractActionText(
|
|
||||||
resources = resourceProvider.resources,
|
|
||||||
youPrefix = resourceProvider.getString(R.string.you_message_prefix),
|
|
||||||
showTime = showTimeInActionMessages
|
|
||||||
) ?: buildAnnotatedString { },
|
|
||||||
actionCmId = actionConversationMessageId
|
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> UiItem.Message(
|
|
||||||
id = id,
|
|
||||||
cmId = cmId,
|
|
||||||
text = extractTextWithVisualizedMentions(
|
|
||||||
isOut = isOut,
|
|
||||||
originalText = text,
|
|
||||||
formatData = formatData
|
|
||||||
),
|
|
||||||
isOut = isOut,
|
|
||||||
fromId = fromId,
|
|
||||||
date = extractDate(),
|
|
||||||
randomId = randomId,
|
|
||||||
isInChat = isPeerChat(),
|
|
||||||
name = extractTitle(),
|
|
||||||
showDate = true,
|
|
||||||
showAvatar = extractShowAvatar(nextMessage),
|
|
||||||
showName = showName && extractShowName(prevMessage),
|
|
||||||
avatar = extractAvatar(),
|
|
||||||
isEdited = updateTime != null,
|
|
||||||
isRead = isRead(conversation),
|
|
||||||
sendingStatus = when {
|
|
||||||
isFailed() -> SendingStatus.FAILED
|
|
||||||
id <= 0 -> SendingStatus.SENDING
|
|
||||||
else -> SendingStatus.SENT
|
|
||||||
},
|
|
||||||
isSelected = isSelected,
|
|
||||||
isPinned = isPinned,
|
|
||||||
isImportant = isImportant,
|
|
||||||
attachments = attachments?.ifEmpty { null },
|
|
||||||
replyCmId = replyMessage?.cmId,
|
|
||||||
replyTitle = extractReplyTitle(),
|
|
||||||
replySummary = extractReplySummary()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean {
|
fun VkMessage.extractShowAvatar(nextMessage: VkMessage?): Boolean {
|
||||||
if (isOut) return false
|
if (isOut) return false
|
||||||
return nextMessage == null || nextMessage.fromId != fromId
|
return nextMessage == null || nextMessage.fromId != fromId
|
||||||
@@ -568,153 +459,3 @@ fun VkMessage.extractActionText(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 04-Apr-25, Danil Nikolaev: get rid of method duplication
|
|
||||||
fun extractTextWithVisualizedMentions(
|
|
||||||
isOut: Boolean,
|
|
||||||
originalText: String?,
|
|
||||||
formatData: VkMessage.FormatData?
|
|
||||||
): AnnotatedString? {
|
|
||||||
if (originalText == null) return null
|
|
||||||
|
|
||||||
val annotations =
|
|
||||||
mutableListOf<AnnotatedString.Range<out Annotation>>()
|
|
||||||
|
|
||||||
val regex = """\[(id|club)(\d+)\|([^]]+)]""".toRegex()
|
|
||||||
|
|
||||||
val mentions = mutableListOf<MentionIndex>()
|
|
||||||
|
|
||||||
var currentIndex = 0
|
|
||||||
val replacements = mutableListOf<Pair<IntRange, String>>()
|
|
||||||
|
|
||||||
val newText = regex.replace(originalText) { matchResult ->
|
|
||||||
val idPrefix = matchResult.groups[1]?.value.orEmpty()
|
|
||||||
val startIndex = matchResult.range.first
|
|
||||||
val endIndex = matchResult.range.last
|
|
||||||
|
|
||||||
val id = matchResult.groups[2]?.value ?: ""
|
|
||||||
|
|
||||||
val replaced = matchResult.groups[3]?.value.orEmpty()
|
|
||||||
|
|
||||||
val indexRange =
|
|
||||||
(startIndex + currentIndex)..startIndex + currentIndex + replaced.length
|
|
||||||
|
|
||||||
replacements.add(indexRange to replaced)
|
|
||||||
|
|
||||||
mentions += MentionIndex(
|
|
||||||
id = id.toLongOrNull() ?: -1,
|
|
||||||
idPrefix = idPrefix,
|
|
||||||
indexRange = indexRange
|
|
||||||
)
|
|
||||||
|
|
||||||
currentIndex += replaced.length - (endIndex - startIndex + 1)
|
|
||||||
|
|
||||||
replaced
|
|
||||||
}
|
|
||||||
|
|
||||||
mentions.forEach { mention ->
|
|
||||||
val startIndex = mention.indexRange.first
|
|
||||||
val endIndex = mention.indexRange.last
|
|
||||||
|
|
||||||
annotations += if (isOut) {
|
|
||||||
AnnotatedString.Range(
|
|
||||||
item = SpanStyle(textDecoration = TextDecoration.Underline),
|
|
||||||
start = startIndex,
|
|
||||||
end = endIndex
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
AnnotatedString.Range(
|
|
||||||
item = SpanStyle(color = Color.Red),
|
|
||||||
start = startIndex,
|
|
||||||
end = endIndex
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
annotations += AnnotatedString.Range(
|
|
||||||
item = StringAnnotation(mention.id.toString()),
|
|
||||||
tag = mention.idPrefix,
|
|
||||||
start = startIndex,
|
|
||||||
end = endIndex
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (formatData == null) return AnnotatedString(text = newText, annotations = annotations)
|
|
||||||
|
|
||||||
var current = 0
|
|
||||||
|
|
||||||
val newOffsets = formatData.items.map { (offset, length) ->
|
|
||||||
val r = replacements.filter { (range, _) ->
|
|
||||||
(range - current) collidesWith (offset..<offset + length) || offset > range.first
|
|
||||||
}
|
|
||||||
|
|
||||||
current = r.sumOf { (range, _) -> range.last - range.first - 1 }
|
|
||||||
|
|
||||||
offset + current
|
|
||||||
}
|
|
||||||
|
|
||||||
formatData.items.forEachIndexed { index, item ->
|
|
||||||
val offset = newOffsets[index]
|
|
||||||
|
|
||||||
val spanStyle = when (item.type) {
|
|
||||||
FormatDataType.BOLD -> {
|
|
||||||
SpanStyle(fontWeight = FontWeight.SemiBold)
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatDataType.ITALIC -> {
|
|
||||||
SpanStyle(fontStyle = FontStyle.Italic)
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatDataType.UNDERLINE -> {
|
|
||||||
SpanStyle(textDecoration = TextDecoration.Underline)
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatDataType.URL -> {
|
|
||||||
annotations += AnnotatedString.Range(
|
|
||||||
item = StringAnnotation(item.url.orEmpty()),
|
|
||||||
start = offset,
|
|
||||||
end = offset + item.length,
|
|
||||||
tag = newText.substring(offset, offset + item.length)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isOut) {
|
|
||||||
SpanStyle(
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
textDecoration = TextDecoration.Underline
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
SpanStyle(
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = Color.Red
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
annotations += AnnotatedString.Range(
|
|
||||||
item = spanStyle,
|
|
||||||
start = offset,
|
|
||||||
end = offset + item.length
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return AnnotatedString(text = newText, annotations = annotations)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class MentionIndex(
|
|
||||||
val id: Long,
|
|
||||||
val idPrefix: String,
|
|
||||||
val indexRange: IntRange
|
|
||||||
)
|
|
||||||
|
|
||||||
infix fun ClosedRange<Int>.collidesWith(other: ClosedRange<Int>): Boolean {
|
|
||||||
return this.start < other.endInclusive && other.start < this.endInclusive
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun ClosedRange<Int>.minus(other: ClosedRange<Int>): ClosedRange<Int> {
|
|
||||||
return (this.start - other.start)..(this.endInclusive - other.endInclusive)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun ClosedRange<Int>.minus(other: Int): ClosedRange<Int> {
|
|
||||||
return (this.start - other)..(this.endInclusive - other)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package dev.meloda.fast.domain.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import dev.meloda.fast.common.provider.ResourceProvider
|
||||||
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.model.vk.MessageUiItem
|
||||||
|
import dev.meloda.fast.ui.model.vk.SendingStatus
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||||
|
|
||||||
|
fun VkMessage.asPresentation(
|
||||||
|
convo: VkConvo,
|
||||||
|
resourceProvider: ResourceProvider,
|
||||||
|
showName: Boolean,
|
||||||
|
prevMessage: VkMessage?,
|
||||||
|
nextMessage: VkMessage?,
|
||||||
|
showTimeInActionMessages: Boolean,
|
||||||
|
isSelected: Boolean
|
||||||
|
): MessageUiItem = when {
|
||||||
|
action != null -> MessageUiItem.ActionMessage(
|
||||||
|
id = id,
|
||||||
|
cmId = cmId,
|
||||||
|
text = extractActionText(
|
||||||
|
resources = resourceProvider.resources,
|
||||||
|
youPrefix = resourceProvider.getString(R.string.you_message_prefix),
|
||||||
|
showTime = showTimeInActionMessages
|
||||||
|
) ?: buildAnnotatedString { },
|
||||||
|
actionCmId = actionCmId
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> MessageUiItem.Message(
|
||||||
|
id = id,
|
||||||
|
cmId = cmId,
|
||||||
|
text = extractTextWithVisualizedMentions(
|
||||||
|
isOut = isOut,
|
||||||
|
originalText = text,
|
||||||
|
formatData = formatData
|
||||||
|
),
|
||||||
|
isOut = isOut,
|
||||||
|
fromId = fromId,
|
||||||
|
date = extractDate(),
|
||||||
|
randomId = randomId,
|
||||||
|
isInChat = isPeerChat(),
|
||||||
|
name = extractTitle(),
|
||||||
|
showDate = true,
|
||||||
|
showAvatar = extractShowAvatar(nextMessage),
|
||||||
|
showName = showName && extractShowName(prevMessage),
|
||||||
|
avatar = extractAvatar(),
|
||||||
|
isEdited = updateTime != null,
|
||||||
|
isRead = isRead(convo),
|
||||||
|
sendingStatus = when {
|
||||||
|
isFailed() -> SendingStatus.FAILED
|
||||||
|
id <= 0 -> SendingStatus.SENDING
|
||||||
|
else -> SendingStatus.SENT
|
||||||
|
},
|
||||||
|
isSelected = isSelected,
|
||||||
|
isPinned = isPinned,
|
||||||
|
isImportant = isImportant,
|
||||||
|
attachments = attachments?.ifEmpty { null }?.toImmutableList(),
|
||||||
|
replyCmId = replyMessage?.cmId,
|
||||||
|
replyTitle = extractReplyTitle(),
|
||||||
|
replySummary = replyMessage?.extractReplySummary(resourceProvider.resources)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package dev.meloda.fast.domain.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.StringAnnotation
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||||
|
import dev.meloda.fast.common.extensions.collidesWith
|
||||||
|
import dev.meloda.fast.common.extensions.minus
|
||||||
|
import dev.meloda.fast.data.UserConfig
|
||||||
|
import dev.meloda.fast.model.api.domain.FormatDataType
|
||||||
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
|
import dev.meloda.fast.ui.model.vk.MentionIndex
|
||||||
|
import dev.meloda.fast.ui.model.vk.MessageUiItem
|
||||||
|
|
||||||
|
fun emptyAnnotatedString(): AnnotatedString = AnnotatedString(text = "")
|
||||||
|
|
||||||
|
fun AnnotatedString?.orEmpty(): AnnotatedString = this ?: emptyAnnotatedString()
|
||||||
|
|
||||||
|
fun String.annotated(): AnnotatedString = AnnotatedString(text = this)
|
||||||
|
|
||||||
|
fun isAccount(id: Long) = id == UserConfig.userId
|
||||||
|
|
||||||
|
fun extractTextWithVisualizedMentions(
|
||||||
|
isOut: Boolean,
|
||||||
|
originalText: String?,
|
||||||
|
formatData: VkMessage.FormatData?
|
||||||
|
): AnnotatedString? {
|
||||||
|
if (originalText == null) return null
|
||||||
|
|
||||||
|
val annotations =
|
||||||
|
mutableListOf<AnnotatedString.Range<out androidx.compose.ui.text.AnnotatedString.Annotation>>()
|
||||||
|
|
||||||
|
val regex = """\[(id|club)(\d+)\|([^]]+)]""".toRegex()
|
||||||
|
|
||||||
|
val mentions = mutableListOf<MentionIndex>()
|
||||||
|
|
||||||
|
var currentIndex = 0
|
||||||
|
val replacements = mutableListOf<Pair<IntRange, String>>()
|
||||||
|
|
||||||
|
val newText = regex.replace(originalText) { matchResult ->
|
||||||
|
val idPrefix = matchResult.groups[1]?.value.orEmpty()
|
||||||
|
val startIndex = matchResult.range.first
|
||||||
|
val endIndex = matchResult.range.last
|
||||||
|
|
||||||
|
val id = matchResult.groups[2]?.value ?: ""
|
||||||
|
|
||||||
|
val replaced = matchResult.groups[3]?.value.orEmpty()
|
||||||
|
|
||||||
|
val indexRange =
|
||||||
|
(startIndex + currentIndex)..startIndex + currentIndex + replaced.length
|
||||||
|
|
||||||
|
replacements.add(indexRange to replaced)
|
||||||
|
|
||||||
|
mentions += MentionIndex(
|
||||||
|
id = id.toLongOrNull() ?: -1,
|
||||||
|
idPrefix = idPrefix,
|
||||||
|
indexRange = indexRange
|
||||||
|
)
|
||||||
|
|
||||||
|
currentIndex += replaced.length - (endIndex - startIndex + 1)
|
||||||
|
|
||||||
|
replaced
|
||||||
|
}
|
||||||
|
|
||||||
|
mentions.forEach { mention ->
|
||||||
|
val startIndex = mention.indexRange.first
|
||||||
|
val endIndex = mention.indexRange.last
|
||||||
|
|
||||||
|
annotations += if (isOut) {
|
||||||
|
AnnotatedString.Range(
|
||||||
|
item = SpanStyle(textDecoration = TextDecoration.Underline),
|
||||||
|
start = startIndex,
|
||||||
|
end = endIndex
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
AnnotatedString.Range(
|
||||||
|
item = SpanStyle(color = Color.Red),
|
||||||
|
start = startIndex,
|
||||||
|
end = endIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations += AnnotatedString.Range(
|
||||||
|
item = StringAnnotation(mention.id.toString()),
|
||||||
|
tag = mention.idPrefix,
|
||||||
|
start = startIndex,
|
||||||
|
end = endIndex
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formatData == null) {
|
||||||
|
return AnnotatedString(text = newText, annotations = annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
var current = 0
|
||||||
|
|
||||||
|
val newOffsets = formatData.items.map { (offset, length) ->
|
||||||
|
val r = replacements.filter { (range, _) ->
|
||||||
|
(range - current) collidesWith (offset..<offset + length) || offset > range.first
|
||||||
|
}
|
||||||
|
|
||||||
|
current = r.sumOf { (range, _) -> range.last - range.first - 1 }
|
||||||
|
|
||||||
|
offset + current
|
||||||
|
}
|
||||||
|
|
||||||
|
formatData.items.forEachIndexed { index, item ->
|
||||||
|
val offset = newOffsets[index]
|
||||||
|
|
||||||
|
val spanStyle = when (item.type) {
|
||||||
|
FormatDataType.BOLD -> {
|
||||||
|
SpanStyle(fontWeight = FontWeight.SemiBold)
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatDataType.ITALIC -> {
|
||||||
|
SpanStyle(fontStyle = FontStyle.Italic)
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatDataType.UNDERLINE -> {
|
||||||
|
SpanStyle(textDecoration = TextDecoration.Underline)
|
||||||
|
}
|
||||||
|
|
||||||
|
FormatDataType.URL -> {
|
||||||
|
annotations += AnnotatedString.Range(
|
||||||
|
item = StringAnnotation(item.url.orEmpty()),
|
||||||
|
start = offset,
|
||||||
|
end = offset + item.length,
|
||||||
|
tag = newText.substring(offset, offset + item.length)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isOut) {
|
||||||
|
SpanStyle(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
textDecoration = TextDecoration.Underline
|
||||||
|
)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
SpanStyle(
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
color = Color.Red
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations += AnnotatedString.Range(
|
||||||
|
item = spanStyle,
|
||||||
|
start = offset,
|
||||||
|
end = offset + item.length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnnotatedString(text = newText, annotations = annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun List<MessageUiItem>.firstMessage(): MessageUiItem.Message =
|
||||||
|
filterIsInstance<MessageUiItem.Message>().first()
|
||||||
|
|
||||||
|
fun List<MessageUiItem>.firstMessageOrNull(): MessageUiItem.Message? =
|
||||||
|
filterIsInstance<MessageUiItem.Message>().firstOrNull()
|
||||||
|
|
||||||
|
fun List<MessageUiItem>.indexOfMessageById(messageId: Long): Int =
|
||||||
|
indexOfFirst { it.id == messageId }
|
||||||
|
|
||||||
|
fun List<MessageUiItem>.findMessageById(messageId: Long): MessageUiItem.Message? =
|
||||||
|
firstOrNull { it.id == messageId } as MessageUiItem.Message?
|
||||||
|
|
||||||
|
fun List<MessageUiItem>.indexOfMessageByCmId(cmId: Long): Int? =
|
||||||
|
indexOfFirstOrNull { it.cmId == cmId }
|
||||||
|
|
||||||
|
fun List<MessageUiItem>.findMessageByCmId(cmId: Long): MessageUiItem.Message =
|
||||||
|
first { it.cmId == cmId } as MessageUiItem.Message
|
||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "dev.meloda.fast.datastore"
|
namespace = "dev.meloda.fast.model"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -12,7 +12,7 @@ dependencies {
|
|||||||
ksp(libs.moshi.kotlin.codegen)
|
ksp(libs.moshi.kotlin.codegen)
|
||||||
|
|
||||||
implementation(platform(libs.compose.bom))
|
implementation(platform(libs.compose.bom))
|
||||||
implementation(libs.bundles.compose)
|
implementation(libs.compose.ui)
|
||||||
|
|
||||||
implementation(libs.room.ktx)
|
implementation(libs.room.ktx)
|
||||||
implementation(libs.room.runtime)
|
implementation(libs.room.runtime)
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
package dev.meloda.fast.model
|
package dev.meloda.fast.model
|
||||||
|
|
||||||
enum class ConversationFlags(val value: Int) {
|
enum class ConvoFlags(val value: Int) {
|
||||||
DISABLE_PUSH(16),
|
DISABLE_PUSH(16),
|
||||||
DISABLE_SOUND(32),
|
DISABLE_SOUND(32),
|
||||||
INCOMING_CHAT_REQUEST(256),
|
INCOMING_CHAT_REQUEST(256),
|
||||||
@@ -17,10 +17,10 @@ enum class ConversationFlags(val value: Int) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun parse(mask: Int): List<ConversationFlags> {
|
fun parse(mask: Int): List<ConvoFlags> {
|
||||||
val flags = mutableListOf<ConversationFlags>()
|
val flags = mutableListOf<ConvoFlags>()
|
||||||
|
|
||||||
ConversationFlags.entries.forEach { flag ->
|
ConvoFlags.entries.forEach { flag ->
|
||||||
if (mask and flag.value > 0) {
|
if (mask and flag.value > 0) {
|
||||||
flags.add(flag)
|
flags.add(flag)
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
package dev.meloda.fast.model
|
package dev.meloda.fast.model
|
||||||
|
|
||||||
enum class ConversationsFilter {
|
enum class ConvosFilter {
|
||||||
ALL, UNREAD, ARCHIVE, BUSINESS_NOTIFY
|
ALL, UNREAD, ARCHIVE, BUSINESS_NOTIFY
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.meloda.fast.model
|
package dev.meloda.fast.model
|
||||||
|
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
|
|
||||||
sealed interface LongPollParsedEvent {
|
sealed interface LongPollParsedEvent {
|
||||||
@@ -92,7 +92,7 @@ sealed interface LongPollParsedEvent {
|
|||||||
) : LongPollParsedEvent
|
) : LongPollParsedEvent
|
||||||
|
|
||||||
data class ChatArchived(
|
data class ChatArchived(
|
||||||
val conversation: VkConversation,
|
val convo: VkConvo,
|
||||||
val archived: Boolean
|
val archived: Boolean
|
||||||
) : LongPollParsedEvent
|
) : LongPollParsedEvent
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package dev.meloda.fast.model.api
|
package dev.meloda.fast.model.api
|
||||||
|
|
||||||
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
|
|
||||||
enum class PeerType(val value: String) {
|
enum class PeerType(val value: String) {
|
||||||
USER("user"),
|
USER("user"),
|
||||||
GROUP("group"),
|
GROUP("group"),
|
||||||
@@ -13,5 +15,14 @@ enum class PeerType(val value: String) {
|
|||||||
fun parse(type: String): PeerType {
|
fun parse(type: String): PeerType {
|
||||||
return entries.first { it.value == type }
|
return entries.first { it.value == type }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun VkMessage.getPeerType(): PeerType {
|
||||||
|
return when {
|
||||||
|
peerId > 2_000_000_000 -> CHAT
|
||||||
|
peerId > 0 -> USER
|
||||||
|
peerId < 0 -> GROUP
|
||||||
|
else -> throw IllegalArgumentException("Unknown peer type for peerId: 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ import dev.meloda.fast.model.api.domain.VkAttachmentHistoryMessage
|
|||||||
data class VkAttachmentHistoryMessageData(
|
data class VkAttachmentHistoryMessageData(
|
||||||
@Json(name = "message_id") val messageId: Long,
|
@Json(name = "message_id") val messageId: Long,
|
||||||
@Json(name = "date") val date: Int,
|
@Json(name = "date") val date: Int,
|
||||||
@Json(name = "cmid") val conversationMessageId: Long,
|
@Json(name = "cmid") val cmId: Long,
|
||||||
@Json(name = "from_id") val fromId: Long,
|
@Json(name = "from_id") val fromId: Long,
|
||||||
@Json(name = "position") val position: Int,
|
@Json(name = "position") val position: Int,
|
||||||
@Json(name = "attachment") val attachment: VkAttachmentItemData
|
@Json(name = "attachment") val attachment: VkAttachmentItemData
|
||||||
@@ -16,7 +16,7 @@ data class VkAttachmentHistoryMessageData(
|
|||||||
|
|
||||||
fun toDomain(): VkAttachmentHistoryMessage = VkAttachmentHistoryMessage(
|
fun toDomain(): VkAttachmentHistoryMessage = VkAttachmentHistoryMessage(
|
||||||
messageId = messageId,
|
messageId = messageId,
|
||||||
conversationMessageId = conversationMessageId,
|
cmId = cmId,
|
||||||
date = date,
|
date = date,
|
||||||
fromId = fromId,
|
fromId = fromId,
|
||||||
position = position,
|
position = position,
|
||||||
|
|||||||
+9
-9
@@ -3,19 +3,19 @@ package dev.meloda.fast.model.api.data
|
|||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import dev.meloda.fast.model.api.PeerType
|
import dev.meloda.fast.model.api.PeerType
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
import dev.meloda.fast.model.api.domain.VkMessage
|
import dev.meloda.fast.model.api.domain.VkMessage
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class VkConversationData(
|
data class VkConvoData(
|
||||||
@Json(name = "peer") val peer: Peer,
|
@Json(name = "peer") val peer: Peer,
|
||||||
@Json(name = "last_message_id") val lastMessageId: Long?,
|
@Json(name = "last_message_id") val lastMessageId: Long?,
|
||||||
@Json(name = "in_read") val inRead: Long,
|
@Json(name = "in_read") val inRead: Long,
|
||||||
@Json(name = "out_read") val outRead: Long,
|
@Json(name = "out_read") val outRead: Long,
|
||||||
@Json(name = "in_read_cmid") val inReadConversationMessageId: Long,
|
@Json(name = "in_read_cmid") val inReadCmId: Long,
|
||||||
@Json(name = "out_read_cmid") val outReadConversationMessageId: Long,
|
@Json(name = "out_read_cmid") val outReadCmId: Long,
|
||||||
@Json(name = "sort_id") val sortId: SortId,
|
@Json(name = "sort_id") val sortId: SortId,
|
||||||
@Json(name = "last_conversation_message_id") val lastConversationMessageId: Long,
|
@Json(name = "last_conversation_message_id") val lastCmId: Long,
|
||||||
@Json(name = "is_marked_unread") val isMarkedUnread: Boolean,
|
@Json(name = "is_marked_unread") val isMarkedUnread: Boolean,
|
||||||
@Json(name = "important") val important: Boolean,
|
@Json(name = "important") val important: Boolean,
|
||||||
@Json(name = "push_settings") val pushSettings: PushSettings?,
|
@Json(name = "push_settings") val pushSettings: PushSettings?,
|
||||||
@@ -111,7 +111,7 @@ data class VkConversationData(
|
|||||||
|
|
||||||
fun asDomain(
|
fun asDomain(
|
||||||
lastMessage: VkMessage? = null,
|
lastMessage: VkMessage? = null,
|
||||||
): VkConversation = VkConversation(
|
): VkConvo = VkConvo(
|
||||||
id = peer.id,
|
id = peer.id,
|
||||||
localId = peer.localId,
|
localId = peer.localId,
|
||||||
title = chatSettings?.title,
|
title = chatSettings?.title,
|
||||||
@@ -120,7 +120,7 @@ data class VkConversationData(
|
|||||||
photo200 = chatSettings?.photo?.photo200,
|
photo200 = chatSettings?.photo?.photo200,
|
||||||
isCallInProgress = callInProgress != null,
|
isCallInProgress = callInProgress != null,
|
||||||
isPhantom = chatSettings?.isDisappearing == true,
|
isPhantom = chatSettings?.isDisappearing == true,
|
||||||
lastCmId = lastConversationMessageId,
|
lastCmId = lastCmId,
|
||||||
inRead = inRead,
|
inRead = inRead,
|
||||||
outRead = outRead,
|
outRead = outRead,
|
||||||
lastMessageId = lastMessageId,
|
lastMessageId = lastMessageId,
|
||||||
@@ -132,8 +132,8 @@ data class VkConversationData(
|
|||||||
canChangePin = chatSettings?.acl?.canChangePin == true,
|
canChangePin = chatSettings?.acl?.canChangePin == true,
|
||||||
canChangeInfo = chatSettings?.acl?.canChangeInfo == true,
|
canChangeInfo = chatSettings?.acl?.canChangeInfo == true,
|
||||||
pinnedMessageId = chatSettings?.pinnedMessage?.id,
|
pinnedMessageId = chatSettings?.pinnedMessage?.id,
|
||||||
inReadCmId = inReadConversationMessageId,
|
inReadCmId = inReadCmId,
|
||||||
outReadCmId = outReadConversationMessageId,
|
outReadCmId = outReadCmId,
|
||||||
interactionType = -1,
|
interactionType = -1,
|
||||||
interactionIds = emptyList(),
|
interactionIds = emptyList(),
|
||||||
peerType = PeerType.parse(peer.type),
|
peerType = PeerType.parse(peer.type),
|
||||||
@@ -56,7 +56,7 @@ data class VkMessageData(
|
|||||||
@Json(name = "type") val type: String,
|
@Json(name = "type") val type: String,
|
||||||
@Json(name = "member_id") val memberId: Long?,
|
@Json(name = "member_id") val memberId: Long?,
|
||||||
@Json(name = "text") val text: String?,
|
@Json(name = "text") val text: String?,
|
||||||
@Json(name = "conversation_message_id") val conversationMessageId: Long?,
|
@Json(name = "conversation_message_id") val cmId: Long?,
|
||||||
@Json(name = "message") val message: String?
|
@Json(name = "message") val message: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ fun VkMessageData.asDomain(): VkMessage = VkMessage(
|
|||||||
action = VkMessage.Action.parse(action?.type),
|
action = VkMessage.Action.parse(action?.type),
|
||||||
actionMemberId = action?.memberId,
|
actionMemberId = action?.memberId,
|
||||||
actionText = action?.text,
|
actionText = action?.text,
|
||||||
actionConversationMessageId = action?.conversationMessageId,
|
actionCmId = action?.cmId,
|
||||||
actionMessage = action?.message,
|
actionMessage = action?.message,
|
||||||
geoType = geo?.type,
|
geoType = geo?.type,
|
||||||
isImportant = important == true,
|
isImportant = important == true,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ data class VkPinnedMessageData(
|
|||||||
@Json(name = "from_id") val fromId: Long,
|
@Json(name = "from_id") val fromId: Long,
|
||||||
@Json(name = "out") val out: Boolean?,
|
@Json(name = "out") val out: Boolean?,
|
||||||
@Json(name = "text") val text: String,
|
@Json(name = "text") val text: String,
|
||||||
@Json(name = "conversation_message_id") val conversationMessageId: Long,
|
@Json(name = "conversation_message_id") val cmId: Long,
|
||||||
@Json(name = "fwd_messages") val forwards: List<VkMessageData>?,
|
@Json(name = "fwd_messages") val forwards: List<VkMessageData>?,
|
||||||
@Json(name = "important") val important: Boolean = false,
|
@Json(name = "important") val important: Boolean = false,
|
||||||
@Json(name = "random_id") val randomId: Long = 0,
|
@Json(name = "random_id") val randomId: Long = 0,
|
||||||
@@ -28,7 +28,7 @@ data class VkPinnedMessageData(
|
|||||||
|
|
||||||
fun mapToDomain(): VkMessage = VkMessage(
|
fun mapToDomain(): VkMessage = VkMessage(
|
||||||
id = id ?: -1,
|
id = id ?: -1,
|
||||||
cmId = conversationMessageId,
|
cmId = cmId,
|
||||||
text = text.ifBlank { null },
|
text = text.ifBlank { null },
|
||||||
isOut = out == true,
|
isOut = out == true,
|
||||||
peerId = peerId ?: -1,
|
peerId = peerId ?: -1,
|
||||||
@@ -38,7 +38,7 @@ data class VkPinnedMessageData(
|
|||||||
action = VkMessage.Action.parse(action?.type),
|
action = VkMessage.Action.parse(action?.type),
|
||||||
actionMemberId = action?.memberId,
|
actionMemberId = action?.memberId,
|
||||||
actionText = action?.text,
|
actionText = action?.text,
|
||||||
actionConversationMessageId = action?.conversationMessageId,
|
actionCmId = action?.cmId,
|
||||||
actionMessage = action?.message,
|
actionMessage = action?.message,
|
||||||
geoType = geo?.type,
|
geoType = geo?.type,
|
||||||
isImportant = important,
|
isImportant = important,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package dev.meloda.fast.model.api.domain
|
package dev.meloda.fast.model.api.domain
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import dev.meloda.fast.model.api.data.AttachmentType
|
import dev.meloda.fast.model.api.data.AttachmentType
|
||||||
|
|
||||||
|
@Immutable
|
||||||
interface VkAttachment {
|
interface VkAttachment {
|
||||||
val type: AttachmentType
|
val type: AttachmentType
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ package dev.meloda.fast.model.api.domain
|
|||||||
|
|
||||||
data class VkAttachmentHistoryMessage(
|
data class VkAttachmentHistoryMessage(
|
||||||
val messageId: Long,
|
val messageId: Long,
|
||||||
val conversationMessageId: Long,
|
val cmId: Long,
|
||||||
val date: Int,
|
val date: Int,
|
||||||
val fromId: Long,
|
val fromId: Long,
|
||||||
val position: Int,
|
val position: Int,
|
||||||
|
|||||||
+5
-5
@@ -1,9 +1,9 @@
|
|||||||
package dev.meloda.fast.model.api.domain
|
package dev.meloda.fast.model.api.domain
|
||||||
|
|
||||||
import dev.meloda.fast.model.api.PeerType
|
import dev.meloda.fast.model.api.PeerType
|
||||||
import dev.meloda.fast.model.database.VkConversationEntity
|
import dev.meloda.fast.model.database.VkConvoEntity
|
||||||
|
|
||||||
data class VkConversation(
|
data class VkConvo(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val localId: Long,
|
val localId: Long,
|
||||||
val ownerId: Long?,
|
val ownerId: Long?,
|
||||||
@@ -54,7 +54,7 @@ data class VkConversation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY: VkConversation = VkConversation(
|
val EMPTY: VkConvo = VkConvo(
|
||||||
id = -1,
|
id = -1,
|
||||||
localId = -1,
|
localId = -1,
|
||||||
ownerId = null,
|
ownerId = null,
|
||||||
@@ -90,7 +90,7 @@ data class VkConversation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VkConversation.asEntity(): VkConversationEntity = VkConversationEntity(
|
fun VkConvo.asEntity(): VkConvoEntity = VkConvoEntity(
|
||||||
id = id,
|
id = id,
|
||||||
localId = localId,
|
localId = localId,
|
||||||
ownerId = ownerId,
|
ownerId = ownerId,
|
||||||
@@ -99,7 +99,7 @@ fun VkConversation.asEntity(): VkConversationEntity = VkConversationEntity(
|
|||||||
photo100 = photo100,
|
photo100 = photo100,
|
||||||
photo200 = photo200,
|
photo200 = photo200,
|
||||||
isPhantom = isPhantom,
|
isPhantom = isPhantom,
|
||||||
lastConversationMessageId = lastCmId,
|
lastCmId = lastCmId,
|
||||||
inReadCmId = inReadCmId,
|
inReadCmId = inReadCmId,
|
||||||
outReadCmId = outReadCmId,
|
outReadCmId = outReadCmId,
|
||||||
inRead = inRead,
|
inRead = inRead,
|
||||||
@@ -16,7 +16,7 @@ data class VkMessage(
|
|||||||
val action: Action?,
|
val action: Action?,
|
||||||
val actionMemberId: Long?,
|
val actionMemberId: Long?,
|
||||||
val actionText: String?,
|
val actionText: String?,
|
||||||
val actionConversationMessageId: Long?,
|
val actionCmId: Long?,
|
||||||
val actionMessage: String?,
|
val actionMessage: String?,
|
||||||
|
|
||||||
val updateTime: Int?,
|
val updateTime: Int?,
|
||||||
@@ -44,9 +44,9 @@ data class VkMessage(
|
|||||||
|
|
||||||
fun isGroup() = fromId < 0
|
fun isGroup() = fromId < 0
|
||||||
|
|
||||||
fun isRead(conversation: VkConversation): Boolean = when {
|
fun isRead(convo: VkConvo): Boolean = when {
|
||||||
id <= 0 -> false
|
id <= 0 -> false
|
||||||
else -> conversation.isRead(this)
|
else -> convo.isRead(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasAttachments(): Boolean = attachments.orEmpty().isNotEmpty()
|
fun hasAttachments(): Boolean = attachments.orEmpty().isNotEmpty()
|
||||||
@@ -98,7 +98,7 @@ data class VkMessage(
|
|||||||
|
|
||||||
fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity(
|
fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity(
|
||||||
id = id,
|
id = id,
|
||||||
conversationMessageId = cmId,
|
cmId = cmId,
|
||||||
text = text,
|
text = text,
|
||||||
isOut = isOut,
|
isOut = isOut,
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
@@ -108,7 +108,7 @@ fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity(
|
|||||||
action = action?.value,
|
action = action?.value,
|
||||||
actionMemberId = actionMemberId,
|
actionMemberId = actionMemberId,
|
||||||
actionText = actionText,
|
actionText = actionText,
|
||||||
actionConversationMessageId = actionConversationMessageId,
|
actionCmId = actionCmId,
|
||||||
actionMessage = actionMessage,
|
actionMessage = actionMessage,
|
||||||
updateTime = updateTime,
|
updateTime = updateTime,
|
||||||
important = isImportant,
|
important = isImportant,
|
||||||
|
|||||||
+3
-3
@@ -1,12 +1,12 @@
|
|||||||
package dev.meloda.fast.model.api.requests
|
package dev.meloda.fast.model.api.requests
|
||||||
|
|
||||||
import dev.meloda.fast.model.ConversationsFilter
|
import dev.meloda.fast.model.ConvosFilter
|
||||||
|
|
||||||
data class ConversationsGetRequest(
|
data class ConvosGetRequest(
|
||||||
val count: Int? = null,
|
val count: Int? = null,
|
||||||
val offset: Int? = null,
|
val offset: Int? = null,
|
||||||
val fields: String = "",
|
val fields: String = "",
|
||||||
val filter: ConversationsFilter = ConversationsFilter.ALL,
|
val filter: ConvosFilter = ConvosFilter.ALL,
|
||||||
val extended: Boolean? = true,
|
val extended: Boolean? = true,
|
||||||
val startMessageId: Long? = null
|
val startMessageId: Long? = null
|
||||||
) {
|
) {
|
||||||
@@ -115,7 +115,7 @@ data class MessagesGetLongPollServerRequest(
|
|||||||
data class MessagesPinMessageRequest(
|
data class MessagesPinMessageRequest(
|
||||||
val peerId: Long,
|
val peerId: Long,
|
||||||
val messageId: Long? = null,
|
val messageId: Long? = null,
|
||||||
val conversationMessageId: Long? = null
|
val cmId: Long? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val map: Map<String, String>
|
val map: Map<String, String>
|
||||||
@@ -123,7 +123,7 @@ data class MessagesPinMessageRequest(
|
|||||||
"peer_id" to peerId.toString()
|
"peer_id" to peerId.toString()
|
||||||
).apply {
|
).apply {
|
||||||
messageId?.let { this["message_id"] = it.toString() }
|
messageId?.let { this["message_id"] = it.toString() }
|
||||||
conversationMessageId?.let { this["conversation_message_id"] = it.toString() }
|
cmId?.let { this["conversation_message_id"] = it.toString() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ data class MessagesUnpinMessageRequest(val peerId: Long) {
|
|||||||
data class MessagesDeleteRequest(
|
data class MessagesDeleteRequest(
|
||||||
val peerId: Long,
|
val peerId: Long,
|
||||||
val messagesIds: List<Long>? = null,
|
val messagesIds: List<Long>? = null,
|
||||||
val conversationsMessagesIds: List<Long>? = null,
|
val cmIds: List<Long>? = null,
|
||||||
val isSpam: Boolean? = null,
|
val isSpam: Boolean? = null,
|
||||||
val deleteForAll: Boolean? = null
|
val deleteForAll: Boolean? = null
|
||||||
) {
|
) {
|
||||||
@@ -149,7 +149,7 @@ data class MessagesDeleteRequest(
|
|||||||
deleteForAll?.let { this["delete_for_all"] = it.asInt().toString() }
|
deleteForAll?.let { this["delete_for_all"] = it.asInt().toString() }
|
||||||
messagesIds?.let { this["message_ids"] = it.joinToString() }
|
messagesIds?.let { this["message_ids"] = it.joinToString() }
|
||||||
|
|
||||||
conversationsMessagesIds?.let {
|
cmIds?.let {
|
||||||
this["conversation_message_ids"] = it.joinToString()
|
this["conversation_message_ids"] = it.joinToString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +228,7 @@ data class MessagesGetChatRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class MessagesGetConversationMembersRequest(
|
data class MessagesGetConvoMembersRequest(
|
||||||
val peerId: Long,
|
val peerId: Long,
|
||||||
val offset: Int? = null,
|
val offset: Int? = null,
|
||||||
val count: Int? = null,
|
val count: Int? = null,
|
||||||
@@ -267,14 +267,14 @@ data class MessagesGetHistoryAttachmentsRequest(
|
|||||||
val offset: Int?,
|
val offset: Int?,
|
||||||
val preserveOrder: Boolean?,
|
val preserveOrder: Boolean?,
|
||||||
val attachmentTypes: List<String>,
|
val attachmentTypes: List<String>,
|
||||||
val conversationMessageId: Long,
|
val cmId: Long,
|
||||||
val fields: String?
|
val fields: String?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val map = mutableMapOf(
|
val map = mutableMapOf(
|
||||||
"peer_id" to peerId.toString(),
|
"peer_id" to peerId.toString(),
|
||||||
"attachment_types" to attachmentTypes.joinToString(","),
|
"attachment_types" to attachmentTypes.joinToString(","),
|
||||||
"cmid" to conversationMessageId.toString()
|
"cmid" to cmId.toString()
|
||||||
).apply {
|
).apply {
|
||||||
extended?.let { this["extended"] = it.toString() }
|
extended?.let { this["extended"] = it.toString() }
|
||||||
count?.let { this["count"] = it.toString() }
|
count?.let { this["count"] = it.toString() }
|
||||||
|
|||||||
+8
-8
@@ -3,15 +3,15 @@ package dev.meloda.fast.model.api.responses
|
|||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
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.VkConvoData
|
||||||
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
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ConversationsGetResponse(
|
data class ConvosGetResponse(
|
||||||
@Json(name = "count") val count: Int,
|
@Json(name = "count") val count: Int,
|
||||||
@Json(name = "items") val items: List<ConversationsResponseItem>,
|
@Json(name = "items") val items: List<ConvosResponseItem>,
|
||||||
@Json(name = "unread_count") val unreadCount: Int?,
|
@Json(name = "unread_count") val unreadCount: Int?,
|
||||||
@Json(name = "profiles") val profiles: List<VkUserData>?,
|
@Json(name = "profiles") val profiles: List<VkUserData>?,
|
||||||
@Json(name = "groups") val groups: List<VkGroupData>?,
|
@Json(name = "groups") val groups: List<VkGroupData>?,
|
||||||
@@ -19,21 +19,21 @@ data class ConversationsGetResponse(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ConversationsGetByIdResponse(
|
data class ConvosGetByIdResponse(
|
||||||
@Json(name = "count") val count: Int,
|
@Json(name = "count") val count: Int,
|
||||||
@Json(name = "items") val items: List<VkConversationData>,
|
@Json(name = "items") val items: List<VkConvoData>,
|
||||||
@Json(name = "profiles") val profiles: List<VkUserData>?,
|
@Json(name = "profiles") val profiles: List<VkUserData>?,
|
||||||
@Json(name = "groups") val groups: List<VkGroupData>?,
|
@Json(name = "groups") val groups: List<VkGroupData>?,
|
||||||
@Json(name = "contacts") val contacts: List<VkContactData>?
|
@Json(name = "contacts") val contacts: List<VkContactData>?
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ConversationsResponseItem(
|
data class ConvosResponseItem(
|
||||||
@Json(name = "conversation") val conversation: VkConversationData,
|
@Json(name = "conversation") val convo: VkConvoData,
|
||||||
@Json(name = "last_message") val lastMessage: VkMessageData?
|
@Json(name = "last_message") val lastMessage: VkMessageData?
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ConversationsDeleteResponse(
|
data class ConvosDeleteResponse(
|
||||||
@Json(name = "last_deleted_id") val lastDeletedId: Long
|
@Json(name = "last_deleted_id") val lastDeletedId: Long
|
||||||
)
|
)
|
||||||
@@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass
|
|||||||
import dev.meloda.fast.model.api.data.VkAttachmentHistoryMessageData
|
import dev.meloda.fast.model.api.data.VkAttachmentHistoryMessageData
|
||||||
import dev.meloda.fast.model.api.data.VkChatMemberData
|
import dev.meloda.fast.model.api.data.VkChatMemberData
|
||||||
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.VkConvoData
|
||||||
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
|
||||||
@@ -14,7 +14,7 @@ import dev.meloda.fast.model.api.data.VkUserData
|
|||||||
data class MessagesGetHistoryResponse(
|
data class MessagesGetHistoryResponse(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
val items: List<VkMessageData>,
|
val items: List<VkMessageData>,
|
||||||
val conversations: List<VkConversationData>?,
|
val convos: List<VkConvoData>?,
|
||||||
val profiles: List<VkUserData>?,
|
val profiles: List<VkUserData>?,
|
||||||
val groups: List<VkGroupData>?,
|
val groups: List<VkGroupData>?,
|
||||||
val contacts: List<VkContactData>?
|
val contacts: List<VkContactData>?
|
||||||
@@ -30,7 +30,7 @@ data class MessagesGetByIdResponse(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessagesGetConversationMembersResponse(
|
data class MessagesGetConvoMembersResponse(
|
||||||
val count: Int,
|
val count: Int,
|
||||||
val items: List<VkChatMemberData>?,
|
val items: List<VkChatMemberData>?,
|
||||||
val profiles: List<VkUserData>?,
|
val profiles: List<VkUserData>?,
|
||||||
|
|||||||
+2
-2
@@ -3,8 +3,8 @@ package dev.meloda.fast.model.database
|
|||||||
import androidx.room.Embedded
|
import androidx.room.Embedded
|
||||||
import androidx.room.Relation
|
import androidx.room.Relation
|
||||||
|
|
||||||
data class ConversationWithMessage(
|
data class ConvoWithMessage(
|
||||||
@Embedded val conversation: VkConversationEntity,
|
@Embedded val convo: VkConvoEntity,
|
||||||
@Relation(
|
@Relation(
|
||||||
parentColumn = "lastMessageId",
|
parentColumn = "lastMessageId",
|
||||||
entityColumn = "id"
|
entityColumn = "id"
|
||||||
+6
-6
@@ -3,10 +3,10 @@ package dev.meloda.fast.model.database
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import dev.meloda.fast.model.api.PeerType
|
import dev.meloda.fast.model.api.PeerType
|
||||||
import dev.meloda.fast.model.api.domain.VkConversation
|
import dev.meloda.fast.model.api.domain.VkConvo
|
||||||
|
|
||||||
@Entity(tableName = "conversations")
|
@Entity(tableName = "convos")
|
||||||
data class VkConversationEntity(
|
data class VkConvoEntity(
|
||||||
@PrimaryKey val id: Long,
|
@PrimaryKey val id: Long,
|
||||||
val localId: Long,
|
val localId: Long,
|
||||||
val ownerId: Long?,
|
val ownerId: Long?,
|
||||||
@@ -15,7 +15,7 @@ data class VkConversationEntity(
|
|||||||
val photo100: String?,
|
val photo100: String?,
|
||||||
val photo200: String?,
|
val photo200: String?,
|
||||||
val isPhantom: Boolean,
|
val isPhantom: Boolean,
|
||||||
val lastConversationMessageId: Long,
|
val lastCmId: Long,
|
||||||
val inReadCmId: Long,
|
val inReadCmId: Long,
|
||||||
val outReadCmId: Long,
|
val outReadCmId: Long,
|
||||||
val inRead: Long,
|
val inRead: Long,
|
||||||
@@ -32,7 +32,7 @@ data class VkConversationEntity(
|
|||||||
val isArchived: Boolean
|
val isArchived: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
fun VkConversationEntity.asExternalModel(): VkConversation = VkConversation(
|
fun VkConvoEntity.asExternalModel(): VkConvo = VkConvo(
|
||||||
id = id,
|
id = id,
|
||||||
localId = localId,
|
localId = localId,
|
||||||
ownerId = ownerId,
|
ownerId = ownerId,
|
||||||
@@ -42,7 +42,7 @@ fun VkConversationEntity.asExternalModel(): VkConversation = VkConversation(
|
|||||||
photo200 = photo200,
|
photo200 = photo200,
|
||||||
isCallInProgress = false,
|
isCallInProgress = false,
|
||||||
isPhantom = isPhantom,
|
isPhantom = isPhantom,
|
||||||
lastCmId = lastConversationMessageId,
|
lastCmId = lastCmId,
|
||||||
inReadCmId = inReadCmId,
|
inReadCmId = inReadCmId,
|
||||||
outReadCmId = outReadCmId,
|
outReadCmId = outReadCmId,
|
||||||
inRead = inRead,
|
inRead = inRead,
|
||||||
@@ -8,7 +8,7 @@ import dev.meloda.fast.model.api.domain.VkUnknownAttachment
|
|||||||
@Entity(tableName = "messages")
|
@Entity(tableName = "messages")
|
||||||
data class VkMessageEntity(
|
data class VkMessageEntity(
|
||||||
@PrimaryKey val id: Long,
|
@PrimaryKey val id: Long,
|
||||||
val conversationMessageId: Long,
|
val cmId: Long,
|
||||||
val text: String?,
|
val text: String?,
|
||||||
val isOut: Boolean,
|
val isOut: Boolean,
|
||||||
val peerId: Long,
|
val peerId: Long,
|
||||||
@@ -18,7 +18,7 @@ data class VkMessageEntity(
|
|||||||
val action: String?,
|
val action: String?,
|
||||||
val actionMemberId: Long?,
|
val actionMemberId: Long?,
|
||||||
val actionText: String?,
|
val actionText: String?,
|
||||||
val actionConversationMessageId: Long?,
|
val actionCmId: Long?,
|
||||||
val actionMessage: String?,
|
val actionMessage: String?,
|
||||||
val updateTime: Int?,
|
val updateTime: Int?,
|
||||||
val important: Boolean,
|
val important: Boolean,
|
||||||
@@ -32,7 +32,7 @@ data class VkMessageEntity(
|
|||||||
|
|
||||||
fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
|
fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
|
||||||
id = id,
|
id = id,
|
||||||
cmId = conversationMessageId,
|
cmId = cmId,
|
||||||
text = text,
|
text = text,
|
||||||
isOut = isOut,
|
isOut = isOut,
|
||||||
peerId = peerId,
|
peerId = peerId,
|
||||||
@@ -42,7 +42,7 @@ fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
|
|||||||
action = VkMessage.Action.parse(action),
|
action = VkMessage.Action.parse(action),
|
||||||
actionMemberId = actionMemberId,
|
actionMemberId = actionMemberId,
|
||||||
actionText = actionText,
|
actionText = actionText,
|
||||||
actionConversationMessageId = actionConversationMessageId,
|
actionCmId = actionCmId,
|
||||||
actionMessage = actionMessage,
|
actionMessage = actionMessage,
|
||||||
updateTime = updateTime,
|
updateTime = updateTime,
|
||||||
isImportant = important,
|
isImportant = important,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import dev.meloda.fast.network.interceptor.VersionInterceptor
|
|||||||
import dev.meloda.fast.network.service.account.AccountService
|
import dev.meloda.fast.network.service.account.AccountService
|
||||||
import dev.meloda.fast.network.service.audios.AudiosService
|
import dev.meloda.fast.network.service.audios.AudiosService
|
||||||
import dev.meloda.fast.network.service.auth.AuthService
|
import dev.meloda.fast.network.service.auth.AuthService
|
||||||
import dev.meloda.fast.network.service.conversations.ConversationsService
|
import dev.meloda.fast.network.service.convos.ConvosService
|
||||||
import dev.meloda.fast.network.service.files.FilesService
|
import dev.meloda.fast.network.service.files.FilesService
|
||||||
import dev.meloda.fast.network.service.friends.FriendsService
|
import dev.meloda.fast.network.service.friends.FriendsService
|
||||||
import dev.meloda.fast.network.service.longpoll.LongPollService
|
import dev.meloda.fast.network.service.longpoll.LongPollService
|
||||||
@@ -80,7 +80,7 @@ val networkModule = module {
|
|||||||
|
|
||||||
single { service(AccountService::class.java) }
|
single { service(AccountService::class.java) }
|
||||||
single { service(AudiosService::class.java) }
|
single { service(AudiosService::class.java) }
|
||||||
single { service(ConversationsService::class.java) }
|
single { service(ConvosService::class.java) }
|
||||||
single { service(FilesService::class.java) }
|
single { service(FilesService::class.java) }
|
||||||
single { service(LongPollService::class.java) }
|
single { service(LongPollService::class.java) }
|
||||||
single { service(MessagesService::class.java) }
|
single { service(MessagesService::class.java) }
|
||||||
|
|||||||
+18
-18
@@ -1,61 +1,61 @@
|
|||||||
package dev.meloda.fast.network.service.conversations
|
package dev.meloda.fast.network.service.convos
|
||||||
|
|
||||||
import com.slack.eithernet.ApiResult
|
import com.slack.eithernet.ApiResult
|
||||||
import dev.meloda.fast.model.api.responses.ConversationsDeleteResponse
|
import dev.meloda.fast.model.api.responses.ConvosDeleteResponse
|
||||||
import dev.meloda.fast.model.api.responses.ConversationsGetByIdResponse
|
import dev.meloda.fast.model.api.responses.ConvosGetByIdResponse
|
||||||
import dev.meloda.fast.model.api.responses.ConversationsGetResponse
|
import dev.meloda.fast.model.api.responses.ConvosGetResponse
|
||||||
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 retrofit2.http.FieldMap
|
import retrofit2.http.FieldMap
|
||||||
import retrofit2.http.FormUrlEncoded
|
import retrofit2.http.FormUrlEncoded
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
|
||||||
interface ConversationsService {
|
interface ConvosService {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.GET)
|
@POST(ConvosUrls.GET)
|
||||||
suspend fun getConversations(
|
suspend fun getConvos(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<ConversationsGetResponse>, RestApiError>
|
): ApiResult<ApiResponse<ConvosGetResponse>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.GET_BY_ID)
|
@POST(ConvosUrls.GET_BY_ID)
|
||||||
suspend fun getConversationsById(
|
suspend fun getConvosById(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<ConversationsGetByIdResponse>, RestApiError>
|
): ApiResult<ApiResponse<ConvosGetByIdResponse>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.DELETE)
|
@POST(ConvosUrls.DELETE)
|
||||||
suspend fun delete(
|
suspend fun delete(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<ConversationsDeleteResponse>, RestApiError>
|
): ApiResult<ApiResponse<ConvosDeleteResponse>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.PIN)
|
@POST(ConvosUrls.PIN)
|
||||||
suspend fun pin(
|
suspend fun pin(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<Int>, RestApiError>
|
): ApiResult<ApiResponse<Int>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.UNPIN)
|
@POST(ConvosUrls.UNPIN)
|
||||||
suspend fun unpin(
|
suspend fun unpin(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<Int>, RestApiError>
|
): ApiResult<ApiResponse<Int>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.REORDER_PINNED)
|
@POST(ConvosUrls.REORDER_PINNED)
|
||||||
suspend fun reorderPinned(
|
suspend fun reorderPinned(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<Int>, RestApiError>
|
): ApiResult<ApiResponse<Int>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.ARCHIVE)
|
@POST(ConvosUrls.ARCHIVE)
|
||||||
suspend fun archive(
|
suspend fun archive(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<Int>, RestApiError>
|
): ApiResult<ApiResponse<Int>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(ConversationsUrls.UNARCHIVE)
|
@POST(ConvosUrls.UNARCHIVE)
|
||||||
suspend fun unarchive(
|
suspend fun unarchive(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<Int>, RestApiError>
|
): ApiResult<ApiResponse<Int>, RestApiError>
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
package dev.meloda.fast.network.service.conversations
|
package dev.meloda.fast.network.service.convos
|
||||||
|
|
||||||
import dev.meloda.fast.common.AppConstants
|
import dev.meloda.fast.common.AppConstants
|
||||||
|
|
||||||
object ConversationsUrls {
|
object ConvosUrls {
|
||||||
|
|
||||||
private const val URL = AppConstants.URL_API
|
private const val URL = AppConstants.URL_API
|
||||||
|
|
||||||
+4
-4
@@ -6,7 +6,7 @@ import dev.meloda.fast.model.api.data.VkLongPollData
|
|||||||
import dev.meloda.fast.model.api.data.VkMessageData
|
import dev.meloda.fast.model.api.data.VkMessageData
|
||||||
import dev.meloda.fast.model.api.responses.MessagesCreateChatResponse
|
import dev.meloda.fast.model.api.responses.MessagesCreateChatResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetByIdResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetByIdResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetConversationMembersResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetConvoMembersResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetHistoryAttachmentsResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetHistoryResponse
|
||||||
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
|
import dev.meloda.fast.model.api.responses.MessagesGetReadPeersResponse
|
||||||
@@ -99,10 +99,10 @@ interface MessagesService {
|
|||||||
): ApiResult<ApiResponse<VkChatData>, RestApiError>
|
): ApiResult<ApiResponse<VkChatData>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(MessagesUrls.GET_CONVERSATIONS_MEMBERS)
|
@POST(MessagesUrls.GET_CONVOS_MEMBERS)
|
||||||
suspend fun getConversationMembers(
|
suspend fun getConvoMembers(
|
||||||
@FieldMap params: Map<String, String>
|
@FieldMap params: Map<String, String>
|
||||||
): ApiResult<ApiResponse<MessagesGetConversationMembersResponse>, RestApiError>
|
): ApiResult<ApiResponse<MessagesGetConvoMembersResponse>, RestApiError>
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(MessagesUrls.REMOVE_CHAT_USER)
|
@POST(MessagesUrls.REMOVE_CHAT_USER)
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ object MessagesUrls {
|
|||||||
const val GET_BY_ID = "$URL/messages.getById"
|
const val GET_BY_ID = "$URL/messages.getById"
|
||||||
const val MARK_AS_READ = "$URL/messages.markAsRead"
|
const val MARK_AS_READ = "$URL/messages.markAsRead"
|
||||||
const val GET_CHAT = "$URL/messages.getChat"
|
const val GET_CHAT = "$URL/messages.getChat"
|
||||||
const val GET_CONVERSATIONS_MEMBERS = "$URL/messages.getConversationMembers"
|
const val GET_CONVOS_MEMBERS = "$URL/messages.getConversationMembers"
|
||||||
const val REMOVE_CHAT_USER = "$URL/messages.removeChatUser"
|
const val REMOVE_CHAT_USER = "$URL/messages.removeChatUser"
|
||||||
const val GET_HISTORY_ATTACHMENTS = "$URL/messages.getHistoryAttachments"
|
const val GET_HISTORY_ATTACHMENTS = "$URL/messages.getHistoryAttachments"
|
||||||
const val CREATE_CHAT = "$URL/messages.createChat"
|
const val CREATE_CHAT = "$URL/messages.createChat"
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package dev.meloda.fast.ui.common
|
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.AndroidUiModes.UI_MODE_NIGHT_YES
|
||||||
|
import androidx.compose.ui.tooling.preview.AndroidUiModes.UI_MODE_TYPE_NORMAL
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.Wallpapers.BLUE_DOMINATED_EXAMPLE
|
||||||
|
import androidx.compose.ui.tooling.preview.Wallpapers.GREEN_DOMINATED_EXAMPLE
|
||||||
|
import androidx.compose.ui.tooling.preview.Wallpapers.RED_DOMINATED_EXAMPLE
|
||||||
|
import androidx.compose.ui.tooling.preview.Wallpapers.YELLOW_DOMINATED_EXAMPLE
|
||||||
|
|
||||||
|
@Preview(name = "70%", fontScale = 0.70f)
|
||||||
|
@Preview(name = "85%", fontScale = 0.85f)
|
||||||
|
@Preview(name = "100%", fontScale = 1.0f)
|
||||||
|
@Preview(name = "115%", fontScale = 1.15f)
|
||||||
|
@Preview(name = "130%", fontScale = 1.3f)
|
||||||
|
@Preview(name = "150%", fontScale = 1.5f)
|
||||||
|
@Preview(name = "180%", fontScale = 1.8f)
|
||||||
|
@Preview(name = "200%", fontScale = 2f)
|
||||||
|
|
||||||
|
@Preview(name = "Light")
|
||||||
|
@Preview(name = "Red", wallpaper = RED_DOMINATED_EXAMPLE)
|
||||||
|
@Preview(name = "Blue", wallpaper = BLUE_DOMINATED_EXAMPLE)
|
||||||
|
@Preview(name = "Green", wallpaper = GREEN_DOMINATED_EXAMPLE)
|
||||||
|
@Preview(name = "Yellow", wallpaper = YELLOW_DOMINATED_EXAMPLE)
|
||||||
|
|
||||||
|
@Preview(name = "Dark", uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL)
|
||||||
|
@Preview(
|
||||||
|
name = "Dark Red",
|
||||||
|
uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
|
||||||
|
wallpaper = RED_DOMINATED_EXAMPLE
|
||||||
|
)
|
||||||
|
@Preview(
|
||||||
|
name = "Dark Blue",
|
||||||
|
uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
|
||||||
|
wallpaper = BLUE_DOMINATED_EXAMPLE
|
||||||
|
)
|
||||||
|
@Preview(
|
||||||
|
name = "Dark Green",
|
||||||
|
uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
|
||||||
|
wallpaper = GREEN_DOMINATED_EXAMPLE
|
||||||
|
)
|
||||||
|
@Preview(
|
||||||
|
name = "Dark Yellow",
|
||||||
|
uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL,
|
||||||
|
wallpaper = YELLOW_DOMINATED_EXAMPLE
|
||||||
|
)
|
||||||
|
|
||||||
|
annotation class FastPreview
|
||||||
@@ -25,7 +25,7 @@ import dev.meloda.fast.ui.R
|
|||||||
@Composable
|
@Composable
|
||||||
fun ErrorView(
|
fun ErrorView(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
iconResId: Int? = R.drawable.round_error_24,
|
iconResId: Int? = R.drawable.ic_error_fill_round_24,
|
||||||
text: String,
|
text: String,
|
||||||
buttonText: String? = null,
|
buttonText: String? = null,
|
||||||
onButtonClick: (() -> Unit)? = null,
|
onButtonClick: (() -> Unit)? = null,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package dev.meloda.fast.ui.components
|
|||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -9,19 +11,27 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
import androidx.compose.material3.BasicAlertDialog
|
import androidx.compose.material3.BasicAlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
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.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.material3.contentColorFor
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -30,9 +40,22 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.layout.onPlaced
|
import androidx.compose.ui.layout.onPlaced
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewDynamicColors
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewFontScale
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.common.FastPreview
|
||||||
|
import dev.meloda.fast.ui.theme.AppTheme
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||||
|
|
||||||
@@ -41,23 +64,31 @@ import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
|||||||
fun MaterialDialog(
|
fun MaterialDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
confirmText: String? = null,
|
icon: ImageVector? = null,
|
||||||
confirmAction: (() -> Unit)? = null,
|
iconTint: Color = MaterialTheme.colorScheme.primary,
|
||||||
cancelText: String? = null,
|
|
||||||
cancelAction: (() -> Unit)? = null,
|
|
||||||
neutralText: String? = null,
|
|
||||||
neutralAction: (() -> Unit)? = null,
|
|
||||||
title: String? = null,
|
title: String? = null,
|
||||||
text: String? = null,
|
text: String? = null,
|
||||||
selectionType: SelectionType = SelectionType.None,
|
selectionType: SelectionType = SelectionType.None,
|
||||||
items: ImmutableList<String> = ImmutableList.empty(),
|
items: ImmutableList<String> = ImmutableList.empty(),
|
||||||
preSelectedItems: ImmutableList<Int> = ImmutableList.empty(),
|
preSelectedItems: ImmutableList<Int> = ImmutableList.empty(),
|
||||||
onItemClick: ((index: Int) -> Unit)? = null,
|
onItemClick: ((index: Int) -> Unit)? = null,
|
||||||
|
confirmText: String? = null,
|
||||||
|
confirmAction: (() -> Unit)? = null,
|
||||||
|
confirmContainerColor: Color = MaterialTheme.colorScheme.primary,
|
||||||
|
confirmContentColor: Color = MaterialTheme.colorScheme.contentColorFor(confirmContainerColor),
|
||||||
|
cancelText: String? = null,
|
||||||
|
cancelAction: (() -> Unit)? = null,
|
||||||
|
cancelContainerColor: Color = Color.Transparent,
|
||||||
|
cancelContentColor: Color = MaterialTheme.colorScheme.contentColorFor(cancelContainerColor),
|
||||||
|
neutralText: String? = null,
|
||||||
|
neutralAction: (() -> Unit)? = null,
|
||||||
|
neutralContainerColor: Color = Color.Transparent,
|
||||||
|
neutralContentColor: Color = MaterialTheme.colorScheme.contentColorFor(neutralContainerColor),
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
actionInvokeDismiss: ActionInvokeDismiss = ActionInvokeDismiss.IfNoAction,
|
actionInvokeDismiss: ActionInvokeDismiss = ActionInvokeDismiss.IfNoAction,
|
||||||
customContent: (@Composable ColumnScope.() -> Unit)? = null
|
customContent: (@Composable ColumnScope.() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
var alertItems by remember {
|
var alertItems by remember(items, preSelectedItems) {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
items.mapIndexed { index, title ->
|
items.mapIndexed { index, title ->
|
||||||
DialogItem(
|
DialogItem(
|
||||||
@@ -77,6 +108,13 @@ fun MaterialDialog(
|
|||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val canScrollBackward by remember { derivedStateOf { scrollState.value > 0 } }
|
val canScrollBackward by remember { derivedStateOf { scrollState.value > 0 } }
|
||||||
val canScrollForward by remember { derivedStateOf { scrollState.value < scrollState.maxValue } }
|
val canScrollForward by remember { derivedStateOf { scrollState.value < scrollState.maxValue } }
|
||||||
|
val shouldAddVerticalPadding = remember(
|
||||||
|
icon, title, text, items,
|
||||||
|
confirmText, cancelText, neutralText
|
||||||
|
) {
|
||||||
|
icon != null || title != null || text != null || items.isNotEmpty() ||
|
||||||
|
confirmText != null || cancelText != null || neutralText != null
|
||||||
|
}
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -84,19 +122,33 @@ fun MaterialDialog(
|
|||||||
shape = AlertDialogDefaults.shape,
|
shape = AlertDialogDefaults.shape,
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(bottom = 10.dp)) {
|
Column(
|
||||||
if (title != null) {
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
) {
|
||||||
|
if (shouldAddVerticalPadding) {
|
||||||
Row {
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Spacer(modifier = Modifier.width(24.dp))
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.headlineSmall
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = iconTint,
|
||||||
|
modifier = Modifier.size(30.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title != null) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(isPlaced && canScrollBackward) {
|
AnimatedVisibility(isPlaced && canScrollBackward) {
|
||||||
@@ -110,25 +162,22 @@ fun MaterialDialog(
|
|||||||
.verticalScroll(scrollState)
|
.verticalScroll(scrollState)
|
||||||
.onPlaced { isPlaced = true }
|
.onPlaced { isPlaced = true }
|
||||||
) {
|
) {
|
||||||
if (text != null && title == null) {
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
Row {
|
Row(modifier = Modifier.padding(horizontal = 24.dp)) {
|
||||||
Spacer(modifier = Modifier.width(24.dp))
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = text,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text != null || title != null) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
if (alertItems.isNotEmpty()) {
|
if (alertItems.isNotEmpty()) {
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
@@ -158,7 +207,7 @@ fun MaterialDialog(
|
|||||||
alertItems = newItems.toImmutableList()
|
alertItems = newItems.toImmutableList()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
} else {
|
} else {
|
||||||
customContent?.invoke(this)
|
customContent?.invoke(this)
|
||||||
}
|
}
|
||||||
@@ -168,67 +217,77 @@ fun MaterialDialog(
|
|||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
if (confirmText != null || cancelText != null || neutralText != null) {
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
Column(
|
||||||
if (neutralText != null) {
|
modifier = Modifier
|
||||||
TextButton(
|
.padding(horizontal = 24.dp)
|
||||||
onClick = {
|
.padding(top = 10.dp),
|
||||||
neutralAction?.invoke() ?: kotlin.run {
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
if (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction) {
|
|
||||||
onDismissRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionInvokeDismiss == ActionInvokeDismiss.Always) {
|
|
||||||
onDismissRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
Text(text = neutralText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
if (cancelText != null) {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
cancelAction?.invoke() ?: kotlin.run {
|
|
||||||
if (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction) {
|
|
||||||
onDismissRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionInvokeDismiss == ActionInvokeDismiss.Always) {
|
|
||||||
onDismissRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(text = cancelText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(2.dp))
|
|
||||||
|
|
||||||
if (confirmText != null) {
|
if (confirmText != null) {
|
||||||
TextButton(
|
Button(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = {
|
onClick = {
|
||||||
confirmAction?.invoke() ?: kotlin.run {
|
val hadAction = confirmAction != null
|
||||||
if (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction) {
|
confirmAction?.invoke()
|
||||||
onDismissRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionInvokeDismiss == ActionInvokeDismiss.Always) {
|
if (actionInvokeDismiss == ActionInvokeDismiss.Always || (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction && !hadAction)) {
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = confirmContainerColor,
|
||||||
|
contentColor = confirmContentColor
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Text(text = confirmText)
|
Text(text = confirmText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
if (cancelText != null) {
|
||||||
|
OutlinedButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
val hadAction = cancelAction != null
|
||||||
|
cancelAction?.invoke()
|
||||||
|
|
||||||
|
if (actionInvokeDismiss == ActionInvokeDismiss.Always || (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction && !hadAction)) {
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
containerColor = cancelContainerColor,
|
||||||
|
contentColor = cancelContentColor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = cancelText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neutralText != null) {
|
||||||
|
TextButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onClick = {
|
||||||
|
val hadAction = neutralAction != null
|
||||||
|
neutralAction?.invoke()
|
||||||
|
|
||||||
|
if (actionInvokeDismiss == ActionInvokeDismiss.Always || (actionInvokeDismiss == ActionInvokeDismiss.IfNoAction && !hadAction)) {
|
||||||
|
onDismissRequest()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
containerColor = neutralContainerColor,
|
||||||
|
contentColor = neutralContentColor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = neutralText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldAddVerticalPadding) {
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,41 +312,40 @@ fun AlertItems(
|
|||||||
} else {
|
} else {
|
||||||
onItemClick?.invoke(index)
|
onItemClick?.invoke(index)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
.padding(horizontal = 10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
when (selectionType) {
|
when (selectionType) {
|
||||||
SelectionType.Multi -> {
|
SelectionType.Multi -> {
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
Checkbox(
|
Checkbox(
|
||||||
checked = item.isSelected,
|
checked = item.isSelected,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
onItemCheckedChanged?.invoke(index)
|
onItemCheckedChanged?.invoke(index)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionType.Single -> {
|
SelectionType.Single -> {
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
RadioButton(
|
RadioButton(
|
||||||
selected = item.isSelected,
|
selected = item.isSelected,
|
||||||
onClick = {
|
onClick = {
|
||||||
onItemClick?.invoke(index)
|
onItemClick?.invoke(index)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionType.None -> {
|
SelectionType.None -> {
|
||||||
Spacer(modifier = Modifier.width(26.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
text = item.title,
|
text = item.title,
|
||||||
style = MaterialTheme.typography.bodyLarge
|
style = MaterialTheme.typography.bodyLarge
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,3 +366,93 @@ sealed class SelectionType {
|
|||||||
data object Multi : SelectionType()
|
data object Multi : SelectionType()
|
||||||
data object None : SelectionType()
|
data object None : SelectionType()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FastPreview
|
||||||
|
@Composable
|
||||||
|
private fun MaterialDialogPreview() {
|
||||||
|
AppTheme(useDarkTheme = isSystemInDarkTheme(), useDynamicColors = true) {
|
||||||
|
MaterialDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
title = "Material Dialog",
|
||||||
|
text = "This is a preview of a Material dialog.",
|
||||||
|
confirmText = "Confirm",
|
||||||
|
cancelText = "Cancel",
|
||||||
|
icon = ImageVector.vectorResource(R.drawable.ic_info_round_24)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FastPreview
|
||||||
|
@Composable
|
||||||
|
private fun MaterialDialogWithListPreview() {
|
||||||
|
AppTheme(useDarkTheme = isSystemInDarkTheme(), useDynamicColors = true) {
|
||||||
|
MaterialDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
title = "Material Dialog",
|
||||||
|
text = "This is a preview of a Material dialog.",
|
||||||
|
confirmText = "Confirm",
|
||||||
|
cancelText = "Cancel",
|
||||||
|
items = listOf("Item 1", "Item 2", "Item 3").toImmutableList(),
|
||||||
|
selectionType = SelectionType.Single,
|
||||||
|
icon = ImageVector.vectorResource(R.drawable.ic_info_round_24)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FastPreview
|
||||||
|
@Composable
|
||||||
|
private fun MaterialDialogWithCustomContent() {
|
||||||
|
AppTheme(useDarkTheme = isSystemInDarkTheme(), useDynamicColors = true) {
|
||||||
|
MaterialDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
title = "Material Dialog",
|
||||||
|
confirmText = "Confirm",
|
||||||
|
cancelText = "Cancel",
|
||||||
|
icon = ImageVector.vectorResource(R.drawable.ic_info_round_24)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 20.dp)
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.weight(1f),
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
label = { Text(text = "Text") },
|
||||||
|
placeholder = { Text(text = "Text") },
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FastPreview
|
||||||
|
@Composable
|
||||||
|
private fun MaterialDialogWithOnlyCustomContent() {
|
||||||
|
AppTheme(useDarkTheme = isSystemInDarkTheme(), useDynamicColors = true) {
|
||||||
|
MaterialDialog(onDismissRequest = {}) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 20.dp)
|
||||||
|
) {
|
||||||
|
TextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.weight(1f),
|
||||||
|
value = "",
|
||||||
|
onValueChange = {},
|
||||||
|
label = { Text(text = "Text") },
|
||||||
|
placeholder = { Text(text = "Text") },
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package dev.meloda.fast.ui.components
|
package dev.meloda.fast.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -8,15 +9,17 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.meloda.fast.ui.R
|
import dev.meloda.fast.ui.R
|
||||||
|
import dev.meloda.fast.ui.common.FastPreview
|
||||||
|
import dev.meloda.fast.ui.theme.AppTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoItemsView(
|
fun NoItemsView(
|
||||||
@@ -49,11 +52,15 @@ fun NoItemsView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@FastPreview
|
||||||
@Composable
|
@Composable
|
||||||
private fun NoItemsViewPreview() {
|
private fun NoItemsViewPreview() {
|
||||||
|
AppTheme(useDarkTheme = isSystemInDarkTheme(), useDynamicColors = true) {
|
||||||
|
Surface {
|
||||||
NoItemsView(
|
NoItemsView(
|
||||||
customText = "Nothing here...",
|
customText = "Nothing here...",
|
||||||
buttonText = "Refresh"
|
buttonText = "Refresh"
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,14 @@ package dev.meloda.fast.ui.extensions
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> ProvidableCompositionLocal<T?>.getOrThrow(): T {
|
fun <T> ProvidableCompositionLocal<T?>.getOrThrow(): T {
|
||||||
return requireNotNull(current)
|
return requireNotNull(current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun Modifier.ifTrue(
|
||||||
|
condition: Boolean,
|
||||||
|
block: Modifier.() -> Modifier
|
||||||
|
): Modifier = if (condition) block() else this
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.koin.androidx.compose.koinViewModel
|
|||||||
import org.koin.core.parameter.ParametersDefinition
|
import org.koin.core.parameter.ParametersDefinition
|
||||||
import org.koin.core.qualifier.Qualifier
|
import org.koin.core.qualifier.Qualifier
|
||||||
|
|
||||||
|
@Suppress("ParamsComparedByRef")
|
||||||
@Composable
|
@Composable
|
||||||
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
|
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
package dev.meloda.fast.ui.model.api
|
|
||||||
|
|
||||||
import dev.meloda.fast.common.model.UiImage
|
|
||||||
import dev.meloda.fast.common.model.UiText
|
|
||||||
import dev.meloda.fast.ui.R
|
|
||||||
|
|
||||||
sealed class ConversationOption(
|
|
||||||
val title: UiText,
|
|
||||||
val icon: UiImage
|
|
||||||
) {
|
|
||||||
|
|
||||||
data object MarkAsRead : ConversationOption(
|
|
||||||
title = UiText.Resource(R.string.action_mark_as_read),
|
|
||||||
icon = UiImage.Resource(R.drawable.round_done_all_24)
|
|
||||||
)
|
|
||||||
|
|
||||||
data object Pin : ConversationOption(
|
|
||||||
title = UiText.Resource(R.string.action_pin),
|
|
||||||
icon = UiImage.Resource(R.drawable.pin_outline_24)
|
|
||||||
)
|
|
||||||
|
|
||||||
data object Unpin : ConversationOption(
|
|
||||||
title = UiText.Resource(R.string.action_unpin),
|
|
||||||
icon = UiImage.Resource(R.drawable.pin_off_outline_24)
|
|
||||||
)
|
|
||||||
|
|
||||||
data object Delete : ConversationOption(
|
|
||||||
title = UiText.Resource(R.string.action_delete),
|
|
||||||
icon = UiImage.Resource(R.drawable.round_delete_outline_24)
|
|
||||||
)
|
|
||||||
|
|
||||||
data object Archive : ConversationOption(
|
|
||||||
title = UiText.Resource(R.string.conversation_context_action_archive),
|
|
||||||
icon = UiImage.Resource(R.drawable.outline_archive_24)
|
|
||||||
)
|
|
||||||
|
|
||||||
data object Unarchive : ConversationOption(
|
|
||||||
title = UiText.Resource(R.string.conversation_context_action_unarchive),
|
|
||||||
icon = UiImage.Resource(R.drawable.outline_unarchive_24)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.meloda.fast.ui.model.api
|
package dev.meloda.fast.ui.model.vk
|
||||||
|
|
||||||
enum class ActionState {
|
enum class ActionState {
|
||||||
PHANTOM, CALL_IN_PROGRESS, NONE;
|
PHANTOM, CALL_IN_PROGRESS, NONE;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package dev.meloda.fast.ui.model.vk
|
||||||
|
|
||||||
|
import dev.meloda.fast.common.model.UiImage
|
||||||
|
import dev.meloda.fast.common.model.UiText
|
||||||
|
import dev.meloda.fast.ui.R
|
||||||
|
|
||||||
|
sealed class ConvoOption(
|
||||||
|
val title: UiText,
|
||||||
|
val icon: UiImage
|
||||||
|
) {
|
||||||
|
|
||||||
|
data object MarkAsRead : ConvoOption(
|
||||||
|
title = UiText.Resource(R.string.action_mark_as_read),
|
||||||
|
icon = UiImage.Resource(R.drawable.ic_done_all_round_24)
|
||||||
|
)
|
||||||
|
|
||||||
|
data object Pin : ConvoOption(
|
||||||
|
title = UiText.Resource(R.string.action_pin),
|
||||||
|
icon = UiImage.Resource(R.drawable.ic_keep_round_24)
|
||||||
|
)
|
||||||
|
|
||||||
|
data object Unpin : ConvoOption(
|
||||||
|
title = UiText.Resource(R.string.action_unpin),
|
||||||
|
icon = UiImage.Resource(R.drawable.ic_keep_off_round_24)
|
||||||
|
)
|
||||||
|
|
||||||
|
data object Delete : ConvoOption(
|
||||||
|
title = UiText.Resource(R.string.action_delete),
|
||||||
|
icon = UiImage.Resource(R.drawable.ic_delete_round_24)
|
||||||
|
)
|
||||||
|
|
||||||
|
data object Archive : ConvoOption(
|
||||||
|
title = UiText.Resource(R.string.convo_context_action_archive),
|
||||||
|
icon = UiImage.Resource(R.drawable.ic_archive_round_24)
|
||||||
|
)
|
||||||
|
|
||||||
|
data object Unarchive : ConvoOption(
|
||||||
|
title = UiText.Resource(R.string.convo_context_action_unarchive),
|
||||||
|
icon = UiImage.Resource(R.drawable.ic_unarchive_round_24)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package dev.meloda.fast.ui.model.vk
|
||||||
|
|
||||||
|
data class MentionIndex(
|
||||||
|
val id: Long,
|
||||||
|
val idPrefix: String,
|
||||||
|
val indexRange: IntRange
|
||||||
|
)
|
||||||
+11
-6
@@ -1,14 +1,18 @@
|
|||||||
package dev.meloda.fast.messageshistory.model
|
package dev.meloda.fast.ui.model.vk
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import dev.meloda.fast.common.model.UiImage
|
import dev.meloda.fast.common.model.UiImage
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
|
||||||
sealed class UiItem(
|
@Stable
|
||||||
|
sealed class MessageUiItem(
|
||||||
open val id: Long,
|
open val id: Long,
|
||||||
open val cmId: Long
|
open val cmId: Long
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class Message(
|
data class Message(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
override val cmId: Long,
|
override val cmId: Long,
|
||||||
@@ -29,16 +33,17 @@ sealed class UiItem(
|
|||||||
val isSelected: Boolean,
|
val isSelected: Boolean,
|
||||||
val isPinned: Boolean,
|
val isPinned: Boolean,
|
||||||
val isImportant: Boolean,
|
val isImportant: Boolean,
|
||||||
val attachments: List<VkAttachment>?,
|
val attachments: ImmutableList<VkAttachment>?,
|
||||||
val replyCmId: Long?,
|
val replyCmId: Long?,
|
||||||
val replyTitle: String?,
|
val replyTitle: String?,
|
||||||
val replySummary: String?
|
val replySummary: AnnotatedString?
|
||||||
) : UiItem(id, cmId)
|
) : MessageUiItem(id, cmId)
|
||||||
|
|
||||||
|
@Stable
|
||||||
data class ActionMessage(
|
data class ActionMessage(
|
||||||
override val id: Long,
|
override val id: Long,
|
||||||
override val cmId: Long,
|
override val cmId: Long,
|
||||||
val text: AnnotatedString,
|
val text: AnnotatedString,
|
||||||
val actionCmId: Long?
|
val actionCmId: Long?
|
||||||
) : UiItem(id, cmId)
|
) : MessageUiItem(id, cmId)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package dev.meloda.fast.ui.model.vk
|
||||||
|
|
||||||
|
enum class SendingStatus {
|
||||||
|
SENDING, SENT, FAILED
|
||||||
|
}
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
package dev.meloda.fast.ui.model.api
|
package dev.meloda.fast.ui.model.vk
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
@@ -8,7 +8,7 @@ import dev.meloda.fast.model.api.domain.VkMessage
|
|||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class UiConversation(
|
data class UiConvo(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
val lastMessageId: Long?,
|
val lastMessageId: Long?,
|
||||||
val avatar: UiImage?,
|
val avatar: UiImage?,
|
||||||
@@ -28,5 +28,5 @@ data class UiConversation(
|
|||||||
val interactionText: String?,
|
val interactionText: String?,
|
||||||
val isExpanded: Boolean,
|
val isExpanded: Boolean,
|
||||||
val isArchived: Boolean,
|
val isArchived: Boolean,
|
||||||
val options: ImmutableList<ConversationOption>,
|
val options: ImmutableList<ConvoOption>,
|
||||||
)
|
)
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package dev.meloda.fast.ui.model.api
|
package dev.meloda.fast.ui.model.vk
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import dev.meloda.fast.common.model.UiImage
|
import dev.meloda.fast.common.model.UiImage
|
||||||
@@ -11,6 +11,10 @@ import androidx.compose.runtime.mutableIntStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
|
import androidx.compose.ui.graphics.lerp
|
||||||
|
import androidx.compose.ui.graphics.luminance
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
import androidx.compose.ui.input.key.onKeyEvent
|
import androidx.compose.ui.input.key.onKeyEvent
|
||||||
@@ -121,3 +125,14 @@ fun isNeedToEnableDarkMode(darkMode: DarkMode): Boolean {
|
|||||||
|
|
||||||
return appForceDarkMode || (appBatterySaver && isSystemBatterySaver) || (!appBatterySaver && isSystemUsingDarkTheme && darkMode == DarkMode.FOLLOW_SYSTEM)
|
return appForceDarkMode || (appBatterySaver && isSystemBatterySaver) || (!appBatterySaver && isSystemUsingDarkTheme && darkMode == DarkMode.FOLLOW_SYSTEM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Color.lighten(amount: Float) = lerp(this, Color.White, amount.coerceIn(0f, 1f))
|
||||||
|
fun Color.darken(amount: Float) = lerp(this, Color.Black, amount.coerceIn(0f, 1f))
|
||||||
|
|
||||||
|
fun Color.isDark(
|
||||||
|
background: Color = Color.White,
|
||||||
|
threshold: Float = 0.5f
|
||||||
|
): Boolean {
|
||||||
|
val opaque = if (alpha < 1f) this.compositeOver(background) else this
|
||||||
|
return opaque.luminance() < threshold
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ class ImmutableList<T>(val values: List<T>) : Collection<T> {
|
|||||||
|
|
||||||
operator fun get(index: Int): T = values[index]
|
operator fun get(index: Int): T = values[index]
|
||||||
|
|
||||||
inline fun forEach(action: (T) -> Unit) {
|
|
||||||
for (element in values) action(element)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <R> map(transform: (T) -> R): ImmutableList<R> {
|
inline fun <R> map(transform: (T) -> R): ImmutableList<R> {
|
||||||
return values.map(transform).toImmutableList()
|
return values.map(transform).toImmutableList()
|
||||||
}
|
}
|
||||||
@@ -49,13 +45,15 @@ class ImmutableList<T>(val values: List<T>) : Collection<T> {
|
|||||||
if (elements.isNotEmpty()) copyOf(elements.asList()) else empty()
|
if (elements.isNotEmpty()) copyOf(elements.asList()) else empty()
|
||||||
|
|
||||||
fun <T> of(element: T) = ImmutableList(listOf(element))
|
fun <T> of(element: T) = ImmutableList(listOf(element))
|
||||||
|
|
||||||
|
fun <T> ImmutableList<T>?.orEmpty(): ImmutableList<T> = this ?: emptyImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<T> = values.listIterator()
|
override fun iterator(): Iterator<T> = values.listIterator()
|
||||||
|
|
||||||
|
val lastIndex: Int get() = this.size - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> emptyImmutableList(): ImmutableList<T> = ImmutableList(emptyList())
|
fun <T> emptyImmutableList(): ImmutableList<T> = ImmutableList(emptyList())
|
||||||
|
|
||||||
fun <T> immutableListOf(vararg elements: T) = ImmutableList(listOf(elements = elements))
|
fun <T> immutableListOf(vararg elements: T) = ImmutableList(listOf(elements = elements))
|
||||||
|
|
||||||
fun <T> ImmutableList<T>?.orEmpty(): ImmutableList<T> = this ?: emptyImmutableList()
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M16.67,13.13C18.04,14.06 19,15.32 19,17v3h4v-3C23,14.82 19.43,13.53 16.67,13.13z" />
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M9,8m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0" />
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M15,12c2.21,0 4,-1.79 4,-4c0,-2.21 -1.79,-4 -4,-4c-0.47,0 -0.91,0.1 -1.33,0.24C14.5,5.27 15,6.58 15,8s-0.5,2.73 -1.33,3.76C14.09,11.9 14.53,12 15,12z" />
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M9,13c-2.67,0 -8,1.34 -8,4v3h16v-3C17,14.34 11.67,13 9,13z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="48"
|
|
||||||
android:viewportHeight="48">
|
|
||||||
<path
|
|
||||||
android:fillColor="#ffffff"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:pathData="M24,0C10.7452,0 0,10.7452 0,24C0,37.2548 10.7452,48 24,48C37.2548,48 48,37.2548 48,24C48,10.7452 37.2548,0 24,0ZM32.25,13.8152C32.25,9.4191 28.7383,6 24.5,6C20.2617,6 16.75,9.4191 16.75,13.8152C16.75,18.0891 20.2617,21.6304 24.5,21.6304C28.7383,21.6304 32.25,18.0891 32.25,13.8152ZM9,34.5743C12.3906,39.5809 18.082,43 24.5,43C30.918,43 36.6094,39.5809 40,34.5743C39.8789,29.3234 29.5859,26.5149 24.5,26.5149C19.293,26.5149 9.1211,29.3234 9,34.5743Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M234,684Q285,645 348,622.5Q411,600 480,600Q549,600 612,622.5Q675,645 726,684Q761,643 780.5,591Q800,539 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,539 179.5,591Q199,643 234,684ZM480,520Q421,520 380.5,479.5Q340,439 340,380Q340,321 380.5,280.5Q421,240 480,240Q539,240 579.5,280.5Q620,321 620,380Q620,439 579.5,479.5Q539,520 480,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M234,684Q285,645 348,622.5Q411,600 480,600Q549,600 612,622.5Q675,645 726,684Q761,643 780.5,591Q800,539 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,539 179.5,591Q199,643 234,684ZM480,520Q421,520 380.5,479.5Q340,439 340,380Q340,321 380.5,280.5Q421,240 480,240Q539,240 579.5,280.5Q620,321 620,380Q620,439 579.5,479.5Q539,520 480,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q533,800 580,784.5Q627,769 666,740Q627,711 580,695.5Q533,680 480,680Q427,680 380,695.5Q333,711 294,740Q333,769 380,784.5Q427,800 480,800ZM480,440Q506,440 523,423Q540,406 540,380Q540,354 523,337Q506,320 480,320Q454,320 437,337Q420,354 420,380Q420,406 437,423Q454,440 480,440ZM480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380ZM480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,261Q120,247 124.5,234Q129,221 138,210L188,149Q199,135 215.5,127.5Q232,120 250,120L710,120Q728,120 744.5,127.5Q761,135 772,149L822,210Q831,221 835.5,234Q840,247 840,261L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM216,240L744,240L710,200Q710,200 710,200Q710,200 710,200L250,200Q250,200 250,200Q250,200 250,200L216,240ZM480,400Q463,400 451.5,411.5Q440,423 440,440L440,568L404,532Q393,521 376,521Q359,521 348,532Q337,543 337,560Q337,577 348,588L452,692Q464,704 480,704Q496,704 508,692L612,588Q623,577 623,560Q623,543 612,532Q601,521 584,521Q567,521 556,532L520,568L520,440Q520,423 508.5,411.5Q497,400 480,400Z"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user