Browse Source

Open Android file browser from channels and members

master
evgeny 4 days ago
parent
commit
4abfbc3e9c
  1. 48
      tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt
  2. 7
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt
  3. 4
      tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt

48
tools/chatgui-android/app/src/main/java/com/utun/chat/MainActivity.kt

@ -56,6 +56,8 @@ import com.utun.chat.data.LogManager
import com.utun.chat.data.Message
import com.utun.chat.data.ConversationTarget
import com.utun.chat.data.NativeLib
import com.utun.chat.data.FileLocation
import com.utun.chat.data.MemberFilesProtocol
import com.utun.chat.data.RadioOverlayService
import com.utun.chat.data.RadioAudioEngine
import com.utun.chat.data.RadioPttSource
@ -64,14 +66,16 @@ import com.utun.chat.ui.theme.VibeChatTheme
import com.utun.chat.ui.components.ActiveCallBanner
import com.utun.chat.ui.screens.*
import com.utun.chat.viewmodel.ChatViewModel
import com.utun.chat.viewmodel.MemberFilesViewModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.collect
class MainActivity : AppCompatActivity() {
enum class Screen { ChannelList, Chat, MemberList, Settings, Logs, VideoRecord, PhotoRecord, Call, Dm }
enum class Screen { ChannelList, Chat, MemberList, Files, Settings, Logs, VideoRecord, PhotoRecord, Call, Dm }
private var overLock = false
private var inputScreen = "setup"
private var requestedFiles by mutableStateOf<FileLocation?>(null)
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val started = SystemClock.uptimeMillis()
try {
@ -141,6 +145,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
readFilesIntent(intent)
lifecycleScope.launch {
ChatApplication.instance.configProvider.radioVolumePttEnabled.collect { enabled ->
if (!enabled) releaseVolumePtt("option-disabled")
@ -189,6 +194,7 @@ class MainActivity : AppCompatActivity() {
return@VibeChatTheme
}
val vm: ChatViewModel = viewModel(factory = ChatViewModel.Factory())
val filesVm: MemberFilesViewModel = viewModel(factory = MemberFilesViewModel.Factory)
/* Pause GUI polling (health-check, recording/playback timers) when backgrounded,
refresh statuses on resume. */
@ -198,6 +204,17 @@ class MainActivity : AppCompatActivity() {
}
var screen by rememberSaveable { mutableStateOf(Screen.ChannelList) }
var filesReturnScreen by rememberSaveable { mutableStateOf(Screen.Chat) }
val fileChannels by vm.channels.collectAsState()
LaunchedEffect(requestedFiles, fileChannels) {
requestedFiles?.let { target ->
val channel = fileChannels.find { it.id == target.group.toULong().toString() }
if (channel != null) {
requestedFiles = null; vm.selectChannel(channel); filesVm.openLocation(target)
filesReturnScreen = Screen.Chat; screen = Screen.Files
}
}
}
var showJoinDialog by rememberSaveable { mutableStateOf(false) }
var inviteChannelId by rememberSaveable { mutableStateOf<String?>(null) }
var videoMessage by remember { mutableStateOf<Message?>(null) }
@ -227,7 +244,7 @@ class MainActivity : AppCompatActivity() {
inputScreen = screen.name
LogManager.addLog("INFO", "MAIN", "UI screen=$screen")
val unavailable = when (screen) {
Screen.Chat, Screen.MemberList, Screen.PhotoRecord -> vm.currentChannel.value == null
Screen.Chat, Screen.MemberList, Screen.Files, Screen.PhotoRecord -> vm.currentChannel.value == null
Screen.Dm -> vm.currentDm.value == null
Screen.VideoRecord -> videoTarget == null
Screen.Call -> callState == null
@ -291,7 +308,7 @@ class MainActivity : AppCompatActivity() {
}
}
BackHandler(enabled = screen != Screen.ChannelList) {
BackHandler(enabled = screen != Screen.ChannelList && screen != Screen.Files) {
when (screen) {
Screen.MemberList -> screen = Screen.Chat
Screen.VideoRecord -> screen = if (videoTarget is ConversationTarget.Pm) Screen.Dm else Screen.Chat
@ -368,6 +385,9 @@ class MainActivity : AppCompatActivity() {
viewModel = vm,
onSendMessage = { vm.sendMessage(it) },
onMembersClick = { screen = Screen.MemberList },
onFiles = { owner ->
filesVm.open(channel.id, owner); filesReturnScreen = Screen.Chat; screen = Screen.Files
},
onInvite = { showInvite(channel) },
onBack = { screen = Screen.ChannelList },
onPlayVideo = { msg -> videoMessage = msg },
@ -393,10 +413,17 @@ class MainActivity : AppCompatActivity() {
members = members,
selectedMember = selectedMember,
viewModel = vm,
onFiles = { owner ->
filesVm.open(channel.id, owner); filesReturnScreen = Screen.MemberList; screen = Screen.Files
},
onBack = { screen = Screen.Chat }
)
}
}
Screen.Files -> {
val channel = vm.currentChannel.collectAsState().value
if (channel != null) MemberFilesScreen(channel, filesVm) { screen = filesReturnScreen }
}
Screen.Dm -> {
val dmMessages by vm.dmMessages.collectAsState()
val dm = vm.currentDm.collectAsState().value
@ -552,6 +579,7 @@ class MainActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
readFilesIntent(intent)
if (intent.getBooleanExtra(MicrophonePermission.EXTRA_REQUEST, false)) {
intent.removeExtra(MicrophonePermission.EXTRA_REQUEST)
requestMicrophone("PTT") {
@ -568,6 +596,20 @@ class MainActivity : AppCompatActivity() {
private fun incomingCallId(intent: Intent?): Long =
intent?.getLongExtra(IncomingCallManager.EXTRA_CALL_ID, 0L) ?: 0L
private fun readFilesIntent(intent: Intent) {
val group = intent.getLongExtra("files_group", 0)
if (group == 0L) return
val server = intent.getLongExtra("files_server", 0)
val owner = intent.getLongExtra("files_owner", 0)
val path = intent.getStringExtra("files_path") ?: ""
intent.removeExtra("files_group")
if (server == 0L || !MemberFilesProtocol.validPath(path) || owner == 0L && path.isNotEmpty()) {
LogManager.addLog("ERROR", "Files", "Invalid notification target group=$group server=$server owner=$owner path=$path"); return
}
requestedFiles = FileLocation(group, server, owner, path)
LogManager.addLog("INFO", "Files", "Notification opened location=$requestedFiles")
}
/** Показать активити поверх блокировки и включить экран (входящий звонок). */
private fun showOverLockScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {

7
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/ChatScreen.kt

@ -1,5 +1,7 @@
package com.utun.chat.ui.screens
import com.utun.chat.data.NativeLib
import com.utun.chat.R
import androidx.compose.ui.res.stringResource
import android.os.SystemClock
@ -80,6 +82,7 @@ fun ChatScreen(
viewModel: ChatViewModel,
onSendMessage: (String) -> Unit,
onMembersClick: () -> Unit,
onFiles: (Long) -> Unit = {},
onInvite: () -> Unit,
onBack: () -> Unit,
onPlayVideo: (Message) -> Unit = {},
@ -181,6 +184,10 @@ fun ChatScreen(
Box {
IconButton(onClick = { menu = true }) { Icon(Icons.Filled.MoreVert, stringResource(R.string.options)) }
DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) {
DropdownMenuItem(text = { Text(stringResource(R.string.files_title)) },
onClick = { menu = false; onFiles(0) })
DropdownMenuItem(text = { Text(stringResource(R.string.files_mine)) },
onClick = { menu = false; onFiles(NativeLib.getMyNodeId()) })
DropdownMenuItem(text = { Text(stringResource(R.string.poll_create)) }, onClick = { menu = false; createPoll = true })
DropdownMenuItem(text = { Text(stringResource(R.string.invite)) },
leadingIcon = { Icon(Icons.Filled.Link, null) }, onClick = { menu = false; onInvite() })

4
tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/MemberListScreen.kt

@ -59,6 +59,7 @@ fun MemberListScreen(
members: List<ChatMember>,
selectedMember: MemberDetail?,
viewModel: ChatViewModel,
onFiles: (Long) -> Unit = {},
onBack: () -> Unit
) {
var showEditDialog by remember { mutableStateOf(false) }
@ -124,6 +125,7 @@ fun MemberListScreen(
localSocketStates = localSockets,
isSelf = member?.isSelf == true,
canEdit = member != null,
onFiles = { onFiles(detail.nodeId) },
onConnect = {
Log.i("utun-gui", "Member detail connect: channel=${channel.id} node=${detail.nodeId}")
viewModel.connectMemberNode(channel.id, detail.nodeId)
@ -291,6 +293,7 @@ private fun MemberDetailCard(
localSocketStates: List<LocalSocketState>,
isSelf: Boolean = false,
canEdit: Boolean,
onFiles: () -> Unit,
onConnect: () -> Unit,
onEdit: () -> Unit
) {
@ -363,6 +366,7 @@ private fun MemberDetailCard(
}
if (!isSelf) TextButton(onClick = onConnect, enabled = !detail.directUp) { Text(stringResource(R.string.connect)) }
TextButton(onClick = onEdit, enabled = canEdit) { Text(stringResource(R.string.edit)) }
TextButton(onClick = onFiles) { Text(stringResource(R.string.files_member)) }
}
Spacer(Modifier.height(4.dp))

Loading…
Cancel
Save