Browse Source

Open Android file browser from channels and members

master
evgeny 6 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.Message
import com.utun.chat.data.ConversationTarget import com.utun.chat.data.ConversationTarget
import com.utun.chat.data.NativeLib 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.RadioOverlayService
import com.utun.chat.data.RadioAudioEngine import com.utun.chat.data.RadioAudioEngine
import com.utun.chat.data.RadioPttSource 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.components.ActiveCallBanner
import com.utun.chat.ui.screens.* import com.utun.chat.ui.screens.*
import com.utun.chat.viewmodel.ChatViewModel import com.utun.chat.viewmodel.ChatViewModel
import com.utun.chat.viewmodel.MemberFilesViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
class MainActivity : AppCompatActivity() { 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 overLock = false
private var inputScreen = "setup" private var inputScreen = "setup"
private var requestedFiles by mutableStateOf<FileLocation?>(null)
override fun dispatchTouchEvent(event: MotionEvent): Boolean { override fun dispatchTouchEvent(event: MotionEvent): Boolean {
val started = SystemClock.uptimeMillis() val started = SystemClock.uptimeMillis()
try { try {
@ -141,6 +145,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
readFilesIntent(intent)
lifecycleScope.launch { lifecycleScope.launch {
ChatApplication.instance.configProvider.radioVolumePttEnabled.collect { enabled -> ChatApplication.instance.configProvider.radioVolumePttEnabled.collect { enabled ->
if (!enabled) releaseVolumePtt("option-disabled") if (!enabled) releaseVolumePtt("option-disabled")
@ -189,6 +194,7 @@ class MainActivity : AppCompatActivity() {
return@VibeChatTheme return@VibeChatTheme
} }
val vm: ChatViewModel = viewModel(factory = ChatViewModel.Factory()) val vm: ChatViewModel = viewModel(factory = ChatViewModel.Factory())
val filesVm: MemberFilesViewModel = viewModel(factory = MemberFilesViewModel.Factory)
/* Pause GUI polling (health-check, recording/playback timers) when backgrounded, /* Pause GUI polling (health-check, recording/playback timers) when backgrounded,
refresh statuses on resume. */ refresh statuses on resume. */
@ -198,6 +204,17 @@ class MainActivity : AppCompatActivity() {
} }
var screen by rememberSaveable { mutableStateOf(Screen.ChannelList) } 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 showJoinDialog by rememberSaveable { mutableStateOf(false) }
var inviteChannelId by rememberSaveable { mutableStateOf<String?>(null) } var inviteChannelId by rememberSaveable { mutableStateOf<String?>(null) }
var videoMessage by remember { mutableStateOf<Message?>(null) } var videoMessage by remember { mutableStateOf<Message?>(null) }
@ -227,7 +244,7 @@ class MainActivity : AppCompatActivity() {
inputScreen = screen.name inputScreen = screen.name
LogManager.addLog("INFO", "MAIN", "UI screen=$screen") LogManager.addLog("INFO", "MAIN", "UI screen=$screen")
val unavailable = when (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.Dm -> vm.currentDm.value == null
Screen.VideoRecord -> videoTarget == null Screen.VideoRecord -> videoTarget == null
Screen.Call -> callState == 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) { when (screen) {
Screen.MemberList -> screen = Screen.Chat Screen.MemberList -> screen = Screen.Chat
Screen.VideoRecord -> screen = if (videoTarget is ConversationTarget.Pm) Screen.Dm else Screen.Chat Screen.VideoRecord -> screen = if (videoTarget is ConversationTarget.Pm) Screen.Dm else Screen.Chat
@ -368,6 +385,9 @@ class MainActivity : AppCompatActivity() {
viewModel = vm, viewModel = vm,
onSendMessage = { vm.sendMessage(it) }, onSendMessage = { vm.sendMessage(it) },
onMembersClick = { screen = Screen.MemberList }, onMembersClick = { screen = Screen.MemberList },
onFiles = { owner ->
filesVm.open(channel.id, owner); filesReturnScreen = Screen.Chat; screen = Screen.Files
},
onInvite = { showInvite(channel) }, onInvite = { showInvite(channel) },
onBack = { screen = Screen.ChannelList }, onBack = { screen = Screen.ChannelList },
onPlayVideo = { msg -> videoMessage = msg }, onPlayVideo = { msg -> videoMessage = msg },
@ -393,10 +413,17 @@ class MainActivity : AppCompatActivity() {
members = members, members = members,
selectedMember = selectedMember, selectedMember = selectedMember,
viewModel = vm, viewModel = vm,
onFiles = { owner ->
filesVm.open(channel.id, owner); filesReturnScreen = Screen.MemberList; screen = Screen.Files
},
onBack = { screen = Screen.Chat } onBack = { screen = Screen.Chat }
) )
} }
} }
Screen.Files -> {
val channel = vm.currentChannel.collectAsState().value
if (channel != null) MemberFilesScreen(channel, filesVm) { screen = filesReturnScreen }
}
Screen.Dm -> { Screen.Dm -> {
val dmMessages by vm.dmMessages.collectAsState() val dmMessages by vm.dmMessages.collectAsState()
val dm = vm.currentDm.collectAsState().value val dm = vm.currentDm.collectAsState().value
@ -552,6 +579,7 @@ class MainActivity : AppCompatActivity() {
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
setIntent(intent) setIntent(intent)
readFilesIntent(intent)
if (intent.getBooleanExtra(MicrophonePermission.EXTRA_REQUEST, false)) { if (intent.getBooleanExtra(MicrophonePermission.EXTRA_REQUEST, false)) {
intent.removeExtra(MicrophonePermission.EXTRA_REQUEST) intent.removeExtra(MicrophonePermission.EXTRA_REQUEST)
requestMicrophone("PTT") { requestMicrophone("PTT") {
@ -568,6 +596,20 @@ class MainActivity : AppCompatActivity() {
private fun incomingCallId(intent: Intent?): Long = private fun incomingCallId(intent: Intent?): Long =
intent?.getLongExtra(IncomingCallManager.EXTRA_CALL_ID, 0L) ?: 0L 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() { private fun showOverLockScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { 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 package com.utun.chat.ui.screens
import com.utun.chat.data.NativeLib
import com.utun.chat.R import com.utun.chat.R
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import android.os.SystemClock import android.os.SystemClock
@ -80,6 +82,7 @@ fun ChatScreen(
viewModel: ChatViewModel, viewModel: ChatViewModel,
onSendMessage: (String) -> Unit, onSendMessage: (String) -> Unit,
onMembersClick: () -> Unit, onMembersClick: () -> Unit,
onFiles: (Long) -> Unit = {},
onInvite: () -> Unit, onInvite: () -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
onPlayVideo: (Message) -> Unit = {}, onPlayVideo: (Message) -> Unit = {},
@ -181,6 +184,10 @@ fun ChatScreen(
Box { Box {
IconButton(onClick = { menu = true }) { Icon(Icons.Filled.MoreVert, stringResource(R.string.options)) } IconButton(onClick = { menu = true }) { Icon(Icons.Filled.MoreVert, stringResource(R.string.options)) }
DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) { 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.poll_create)) }, onClick = { menu = false; createPoll = true })
DropdownMenuItem(text = { Text(stringResource(R.string.invite)) }, DropdownMenuItem(text = { Text(stringResource(R.string.invite)) },
leadingIcon = { Icon(Icons.Filled.Link, null) }, onClick = { menu = false; onInvite() }) 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>, members: List<ChatMember>,
selectedMember: MemberDetail?, selectedMember: MemberDetail?,
viewModel: ChatViewModel, viewModel: ChatViewModel,
onFiles: (Long) -> Unit = {},
onBack: () -> Unit onBack: () -> Unit
) { ) {
var showEditDialog by remember { mutableStateOf(false) } var showEditDialog by remember { mutableStateOf(false) }
@ -124,6 +125,7 @@ fun MemberListScreen(
localSocketStates = localSockets, localSocketStates = localSockets,
isSelf = member?.isSelf == true, isSelf = member?.isSelf == true,
canEdit = member != null, canEdit = member != null,
onFiles = { onFiles(detail.nodeId) },
onConnect = { onConnect = {
Log.i("utun-gui", "Member detail connect: channel=${channel.id} node=${detail.nodeId}") Log.i("utun-gui", "Member detail connect: channel=${channel.id} node=${detail.nodeId}")
viewModel.connectMemberNode(channel.id, detail.nodeId) viewModel.connectMemberNode(channel.id, detail.nodeId)
@ -291,6 +293,7 @@ private fun MemberDetailCard(
localSocketStates: List<LocalSocketState>, localSocketStates: List<LocalSocketState>,
isSelf: Boolean = false, isSelf: Boolean = false,
canEdit: Boolean, canEdit: Boolean,
onFiles: () -> Unit,
onConnect: () -> Unit, onConnect: () -> Unit,
onEdit: () -> Unit onEdit: () -> Unit
) { ) {
@ -363,6 +366,7 @@ private fun MemberDetailCard(
} }
if (!isSelf) TextButton(onClick = onConnect, enabled = !detail.directUp) { Text(stringResource(R.string.connect)) } 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 = onEdit, enabled = canEdit) { Text(stringResource(R.string.edit)) }
TextButton(onClick = onFiles) { Text(stringResource(R.string.files_member)) }
} }
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))

Loading…
Cancel
Save