Skip to content

Commit ad7cbf6

Browse files
committed
Forward chat
1 parent a77fbd1 commit ad7cbf6

File tree

29 files changed

+369
-116
lines changed

29 files changed

+369
-116
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ android {
4141
else -> 0
4242
}
4343

44-
val vCode = 394
44+
val vCode = 397
4545
versionCode = vCode - singleAbiNum
46-
versionName = "2.1.4"
46+
versionName = "2.1.5"
4747

4848
ndk {
4949
//noinspection ChromeOsAbiSupport

app/src/main/java/com/ismartcoding/plain/chat/PeerFileDownloader.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.ismartcoding.plain.chat
22

33
import android.content.Context
4+
import com.ismartcoding.lib.extensions.scanFileByConnection
45
import com.ismartcoding.lib.helpers.NetworkHelper
56
import com.ismartcoding.lib.logcat.LogCat
7+
import com.ismartcoding.plain.MainApp
68
import com.ismartcoding.plain.db.AppDatabase
79
import com.ismartcoding.plain.db.ChatItemDataUpdate
810
import com.ismartcoding.plain.db.DMessageFiles
@@ -59,6 +61,7 @@ object PeerFileDownloader {
5961
createNewFile()
6062
}
6163

64+
6265
var downloadedBytes = 0L
6366
val client = createUnsafeOkHttpClient()
6467
task.httpClient = client
@@ -72,7 +75,7 @@ object PeerFileDownloader {
7275
return null
7376
}
7477

75-
response.body?.byteStream()?.use { inputStream ->
78+
response.body.byteStream().use { inputStream ->
7679
localFile.outputStream().use { outputStream ->
7780
val buffer = ByteArray(8192)
7881
var lastProgressUpdate = System.currentTimeMillis()
@@ -105,6 +108,7 @@ object PeerFileDownloader {
105108
task.status = DownloadStatus.COMPLETED
106109
task.downloadedSize = downloadedBytes
107110
task.downloadSpeed = 0
111+
MainApp.instance.scanFileByConnection(localFile, null)
108112
updateMessageFileUri(task.messageId, messageFile.uri, chatFilePath.getFinalPath())
109113
return localFile.absolutePath
110114
} else {

app/src/main/java/com/ismartcoding/plain/features/ChatHelper.kt

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -60,55 +60,55 @@ object ChatHelper {
6060
value: Any?,
6161
) {
6262
AppDatabase.instance.chatDao().delete(id)
63-
when (value) {
64-
is DMessageFiles -> {
65-
value.items.forEach {
66-
File(it.uri.getFinalPath(context)).delete()
67-
}
68-
}
69-
70-
is DMessageImages -> {
71-
value.items.forEach {
72-
File(it.uri.getFinalPath(context)).delete()
73-
}
74-
}
75-
76-
is DMessageText -> {
77-
value.linkPreviews.forEach { preview ->
78-
preview.imageLocalPath?.let { path ->
79-
LinkPreviewHelper.deletePreviewImage(context, path)
80-
}
81-
}
82-
}
83-
}
63+
// when (value) {
64+
// is DMessageFiles -> {
65+
// value.items.forEach {
66+
// File(it.uri.getFinalPath(context)).delete()
67+
// }
68+
// }
69+
//
70+
// is DMessageImages -> {
71+
// value.items.forEach {
72+
// File(it.uri.getFinalPath(context)).delete()
73+
// }
74+
// }
75+
//
76+
// is DMessageText -> {
77+
// value.linkPreviews.forEach { preview ->
78+
// preview.imageLocalPath?.let { path ->
79+
// LinkPreviewHelper.deletePreviewImage(context, path)
80+
// }
81+
// }
82+
// }
83+
// }
8484
}
8585

8686
suspend fun deleteAllChatsByPeerAsync(context: Context, peerId: String) {
8787
val chatDao = AppDatabase.instance.chatDao()
8888
val chats = chatDao.getByChatId(peerId)
8989

9090
// Delete all associated files first
91-
for (chat in chats) {
92-
when (val value = chat.content.value) {
93-
is DMessageFiles -> {
94-
value.items.forEach {
95-
File(it.uri.getFinalPath(context)).delete()
96-
}
97-
}
98-
is DMessageImages -> {
99-
value.items.forEach {
100-
File(it.uri.getFinalPath(context)).delete()
101-
}
102-
}
103-
is DMessageText -> {
104-
value.linkPreviews.forEach { preview ->
105-
preview.imageLocalPath?.let { path ->
106-
LinkPreviewHelper.deletePreviewImage(context, path)
107-
}
108-
}
109-
}
110-
}
111-
}
91+
// for (chat in chats) {
92+
// when (val value = chat.content.value) {
93+
// is DMessageFiles -> {
94+
// value.items.forEach {
95+
// File(it.uri.getFinalPath(context)).delete()
96+
// }
97+
// }
98+
// is DMessageImages -> {
99+
// value.items.forEach {
100+
// File(it.uri.getFinalPath(context)).delete()
101+
// }
102+
// }
103+
// is DMessageText -> {
104+
// value.linkPreviews.forEach { preview ->
105+
// preview.imageLocalPath?.let { path ->
106+
// LinkPreviewHelper.deletePreviewImage(context, path)
107+
// }
108+
// }
109+
// }
110+
// }
111+
// }
112112

113113
// Delete all chat records for this peer using SQL query
114114
chatDao.deleteByPeerId(peerId)

app/src/main/java/com/ismartcoding/plain/ui/MainActivity.kt

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import androidx.activity.viewModels
2020
import androidx.appcompat.app.AlertDialog
2121
import androidx.appcompat.app.AppCompatActivity
2222
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.getValue
24+
import androidx.compose.runtime.setValue
2325
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
2426
import androidx.core.view.WindowCompat
2527
import androidx.core.view.WindowInsetsControllerCompat
@@ -86,12 +88,15 @@ import com.ismartcoding.plain.services.ScreenMirrorService
8688
import com.ismartcoding.plain.ui.helpers.DialogHelper
8789
import com.ismartcoding.plain.ui.helpers.FilePickHelper
8890
import com.ismartcoding.plain.ui.models.AudioPlaylistViewModel
91+
import com.ismartcoding.plain.ui.models.ChatListViewModel
8992
import com.ismartcoding.plain.ui.models.MainViewModel
9093
import com.ismartcoding.plain.ui.models.PomodoroViewModel
94+
import com.ismartcoding.plain.ui.page.chat.components.ForwardTarget
9195
import com.ismartcoding.plain.ui.nav.Routing
9296
import com.ismartcoding.plain.ui.nav.navigatePdf
9397
import com.ismartcoding.plain.ui.nav.navigateTextFile
9498
import com.ismartcoding.plain.ui.page.Main
99+
import com.ismartcoding.plain.ui.page.chat.components.ForwardTargetDialog
95100
import com.ismartcoding.plain.web.HttpServerManager
96101
import com.ismartcoding.plain.web.models.toModel
97102
import io.ktor.websocket.CloseReason
@@ -110,7 +115,11 @@ class MainActivity : AppCompatActivity() {
110115
private val mainVM: MainViewModel by viewModels()
111116
private val audioPlaylistVM: AudioPlaylistViewModel by viewModels()
112117
val pomodoroVM: PomodoroViewModel by viewModels()
118+
private val chatListVM: ChatListViewModel by viewModels()
113119
private val navControllerState = mutableStateOf<NavHostController?>(null)
120+
121+
private var showForwardTargetDialog by mutableStateOf(false)
122+
private var pendingFileUris by mutableStateOf<Set<Uri>?>(null)
114123

115124
private val screenCapture =
116125
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@@ -219,6 +228,36 @@ class MainActivity : AppCompatActivity() {
219228
Main(navControllerState, onLaunched = {
220229
handleIntent(intent)
221230
}, mainVM, audioPlaylistVM, pomodoroVM)
231+
232+
if (showForwardTargetDialog) {
233+
ForwardTargetDialog(
234+
chatListVM = chatListVM,
235+
onDismiss = {
236+
showForwardTargetDialog = false
237+
pendingFileUris = null
238+
},
239+
onTargetSelected = { target ->
240+
pendingFileUris?.let { uris ->
241+
when (target) {
242+
is ForwardTarget.Local -> {
243+
navControllerState.value?.navigate(Routing.Chat("local"))
244+
coIO {
245+
delay(1000)
246+
sendEvent(PickFileResultEvent(PickFileTag.SEND_MESSAGE, PickFileType.FILE, uris))
247+
}
248+
}
249+
is ForwardTarget.Peer -> {
250+
navControllerState.value?.navigate(Routing.Chat("peer:${target.peer.id}"))
251+
coIO {
252+
delay(1000)
253+
sendEvent(PickFileResultEvent(PickFileTag.SEND_MESSAGE, PickFileType.FILE, uris))
254+
}
255+
}
256+
}
257+
}
258+
}
259+
)
260+
}
222261
}
223262
}
224263

@@ -545,36 +584,30 @@ class MainActivity : AppCompatActivity() {
545584
),
546585
),
547586
)
548-
navControllerState.value?.navigate(Routing.Chat)
587+
navControllerState.value?.navigate(Routing.Chat("local"))
549588
}
550589
return
551590
}
552591

553592
val uri = intent.parcelable(Intent.EXTRA_STREAM) as? Uri ?: return
554-
DialogHelper.showConfirmDialog(
555-
"", LocaleHelper.getString(R.string.confirm_to_send_file_to_file_assistant),
556-
confirmButton = LocaleHelper.getString(R.string.ok) to {
557-
navControllerState.value?.navigate(Routing.Chat)
558-
coIO {
559-
delay(1000)
560-
sendEvent(PickFileResultEvent(PickFileTag.SEND_MESSAGE, PickFileType.FILE, setOf(uri)))
561-
}
562-
},
563-
dismissButton = LocaleHelper.getString(R.string.cancel) to {})
593+
coMain {
594+
DialogHelper.showLoading()
595+
withIO { chatListVM.loadPeers() }
596+
DialogHelper.hideLoading()
597+
pendingFileUris = setOf(uri)
598+
showForwardTargetDialog = true
599+
}
564600
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
565-
DialogHelper.showConfirmDialog(
566-
"", LocaleHelper.getString(R.string.confirm_to_send_files_to_file_assistant),
567-
confirmButton = LocaleHelper.getString(R.string.ok) to {
568-
val uris = intent.parcelableArrayList<Uri>(Intent.EXTRA_STREAM)
569-
if (uris != null) {
570-
navControllerState.value?.navigate(Routing.Chat)
571-
coIO {
572-
delay(1000)
573-
sendEvent(PickFileResultEvent(PickFileTag.SEND_MESSAGE, PickFileType.FILE, uris.toSet()))
574-
}
575-
}
576-
},
577-
dismissButton = LocaleHelper.getString(R.string.cancel) to {})
601+
val uris = intent.parcelableArrayList<Uri>(Intent.EXTRA_STREAM)
602+
if (uris != null) {
603+
coMain {
604+
DialogHelper.showLoading()
605+
withIO { chatListVM.loadPeers() }
606+
DialogHelper.hideLoading()
607+
pendingFileUris = uris.toSet()
608+
showForwardTargetDialog = true
609+
}
610+
}
578611
}
579612
}
580613

app/src/main/java/com/ismartcoding/plain/ui/base/PDialogListItem.kt

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -47,30 +47,36 @@ fun PDialogListItem(
4747
verticalAlignment = Alignment.CenterVertically,
4848
) {
4949
if (icon != null) {
50-
if (icon is ImageVector) {
51-
Icon(
52-
modifier = Modifier.padding(end = 16.dp),
53-
imageVector = icon,
54-
contentDescription = title,
55-
tint = MaterialTheme.colorScheme.onSurfaceVariant,
56-
)
57-
} else if (icon is Painter) {
58-
Image(
59-
modifier =
60-
Modifier
61-
.padding(end = 16.dp)
62-
.size(24.dp),
63-
painter = icon,
64-
contentDescription = title,
65-
)
66-
} else if (icon is String) {
67-
AsyncImage(
68-
model = icon,
69-
contentDescription = title,
70-
modifier = Modifier
71-
.size(24.dp),
72-
)
73-
HorizontalSpace(dp = 16.dp)
50+
when (icon) {
51+
is ImageVector -> {
52+
Icon(
53+
modifier = Modifier.padding(end = 16.dp),
54+
imageVector = icon,
55+
contentDescription = title,
56+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
57+
)
58+
}
59+
60+
is Painter -> {
61+
Image(
62+
modifier =
63+
Modifier
64+
.padding(end = 16.dp)
65+
.size(24.dp),
66+
painter = icon,
67+
contentDescription = title,
68+
)
69+
}
70+
71+
is String -> {
72+
AsyncImage(
73+
model = icon,
74+
contentDescription = title,
75+
modifier = Modifier
76+
.size(24.dp),
77+
)
78+
HorizontalSpace(dp = 16.dp)
79+
}
7480
}
7581
}
7682
Column(

app/src/main/java/com/ismartcoding/plain/ui/models/ChatViewModel.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,50 @@ class ChatViewModel : ISelectableViewModel<VChat>, ViewModel() {
225225
sendEvent(WebSocketEvent(EventType.MESSAGE_DELETED, json.toString()))
226226
}
227227
}
228+
229+
fun forwardMessage(messageId: String, targetPeer: DPeer, onResult: (Boolean) -> Unit = {}) {
230+
viewModelScope.launch(Dispatchers.IO) {
231+
val item = ChatHelper.getAsync(messageId) ?: return@launch
232+
val newItem = ChatHelper.sendAsync(
233+
message = item.content,
234+
fromId = "me",
235+
toId = targetPeer.id,
236+
peer = targetPeer
237+
)
238+
239+
val model = newItem.toModel().apply { data = getContentData() }
240+
sendEvent(WebSocketEvent(EventType.MESSAGE_CREATED, JsonHelper.jsonEncode(listOf(model))))
241+
242+
// Handle link previews for text messages
243+
if (newItem.content.type == DMessageType.TEXT.value) {
244+
sendEvent(FetchLinkPreviewsEvent(newItem))
245+
}
246+
247+
val success = PeerChatHelper.sendToPeerAsync(targetPeer, newItem.content)
248+
updateMessageStatus(newItem, success)
249+
onResult(success)
250+
}
251+
}
252+
253+
fun forwardMessageToLocal(messageId: String, onResult: (Boolean) -> Unit = {}) {
254+
viewModelScope.launch(Dispatchers.IO) {
255+
val item = ChatHelper.getAsync(messageId) ?: return@launch
256+
val newItem = ChatHelper.sendAsync(
257+
message = item.content,
258+
fromId = "me",
259+
toId = "local",
260+
peer = null
261+
)
262+
263+
val model = newItem.toModel().apply { data = getContentData() }
264+
sendEvent(WebSocketEvent(EventType.MESSAGE_CREATED, JsonHelper.jsonEncode(listOf(model))))
265+
266+
// Handle link previews for text messages
267+
if (newItem.content.type == DMessageType.TEXT.value) {
268+
sendEvent(FetchLinkPreviewsEvent(newItem))
269+
}
270+
271+
onResult(true)
272+
}
273+
}
228274
}

0 commit comments

Comments
 (0)