load conversation from API instead of FakeData
This commit is contained in:
@@ -92,11 +92,12 @@ import xyz.magicalbits.smsremote.components.JetchatAppBar
|
|||||||
import xyz.magicalbits.smsremote.data.exampleUiState
|
import xyz.magicalbits.smsremote.data.exampleUiState
|
||||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.magicalbits.smsremote.data.exampleUiStateNew
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point for a conversation screen.
|
* Entry point for a conversation screen.
|
||||||
*
|
*
|
||||||
* @param uiState [ConversationUiState] that contains messages to display
|
* @param uiState [ConversationScreenState] that contains messages to display
|
||||||
* @param navigateToProfile User action when navigation to a profile is requested
|
* @param navigateToProfile User action when navigation to a profile is requested
|
||||||
* @param modifier [Modifier] to apply to this layout node
|
* @param modifier [Modifier] to apply to this layout node
|
||||||
* @param onNavIconPressed Sends an event up when the user clicks on the menu
|
* @param onNavIconPressed Sends an event up when the user clicks on the menu
|
||||||
@@ -104,7 +105,7 @@ import kotlinx.coroutines.launch
|
|||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ConversationContent(
|
fun ConversationContent(
|
||||||
uiState: ConversationUiState,
|
uiState: ConversationScreenState,
|
||||||
navigateToProfile: (String) -> Unit,
|
navigateToProfile: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onNavIconPressed: () -> Unit = { },
|
onNavIconPressed: () -> Unit = { },
|
||||||
@@ -167,8 +168,9 @@ fun ConversationContent(
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
ChannelNameBar(
|
ChannelNameBar(
|
||||||
channelName = uiState.channelName,
|
channelName = uiState.phoneNumber,
|
||||||
channelMembers = uiState.channelMembers,
|
// TODO remove?
|
||||||
|
channelMembers = 2,
|
||||||
onNavIconPressed = onNavIconPressed,
|
onNavIconPressed = onNavIconPressed,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
@@ -540,7 +542,7 @@ fun ClickableMessage(message: Message, isUserMe: Boolean, authorClicked: (String
|
|||||||
fun ConversationPreview() {
|
fun ConversationPreview() {
|
||||||
JetchatTheme {
|
JetchatTheme {
|
||||||
ConversationContent(
|
ConversationContent(
|
||||||
uiState = exampleUiState,
|
uiState = exampleUiStateNew,
|
||||||
navigateToProfile = { },
|
navigateToProfile = { },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-11
@@ -23,6 +23,8 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams
|
import android.view.ViewGroup.LayoutParams
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -31,11 +33,12 @@ import androidx.navigation.findNavController
|
|||||||
import xyz.magicalbits.smsremote.MainViewModel
|
import xyz.magicalbits.smsremote.MainViewModel
|
||||||
import xyz.magicalbits.smsremote.R
|
import xyz.magicalbits.smsremote.R
|
||||||
import xyz.magicalbits.smsremote.data.exampleUiState
|
import xyz.magicalbits.smsremote.data.exampleUiState
|
||||||
import xyz.magicalbits.smsremote.data.exampleUiState2
|
import xyz.magicalbits.smsremote.data.exampleUiStateNew
|
||||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||||
|
|
||||||
class ConversationFragment : Fragment() {
|
class ConversationFragment : Fragment() {
|
||||||
private val activityViewModel: MainViewModel by activityViewModels()
|
private val activityViewModel: MainViewModel by activityViewModels()
|
||||||
|
private val conversationViewModel: ConversationViewModel by activityViewModels()
|
||||||
|
|
||||||
var phoneNumber: String = ""
|
var phoneNumber: String = ""
|
||||||
|
|
||||||
@@ -43,8 +46,9 @@ class ConversationFragment : Fragment() {
|
|||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
// Consider using safe args plugin
|
// Consider using safe args plugin
|
||||||
val phoneNumber = arguments?.getString("phoneNumber")
|
val phoneNumber = arguments?.getString("phoneNumber")
|
||||||
// viewModel.setDeviceId(deviceId)
|
|
||||||
this.phoneNumber = phoneNumber!!
|
this.phoneNumber = phoneNumber!!
|
||||||
|
// update view model with latest messages
|
||||||
|
conversationViewModel.setConversationData(phoneNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@@ -55,18 +59,11 @@ class ConversationFragment : Fragment() {
|
|||||||
ComposeView(inflater.context).apply {
|
ComposeView(inflater.context).apply {
|
||||||
layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||||
|
|
||||||
val uiState =
|
|
||||||
if (phoneNumber == "+420123456789") {
|
|
||||||
exampleUiState
|
|
||||||
} else {
|
|
||||||
exampleUiState2
|
|
||||||
}
|
|
||||||
uiState.channelName = phoneNumber
|
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
val conversationData by conversationViewModel.conversationData.observeAsState()
|
||||||
JetchatTheme {
|
JetchatTheme {
|
||||||
ConversationContent(
|
ConversationContent(
|
||||||
uiState = uiState,
|
uiState = conversationData ?: exampleUiStateNew,
|
||||||
navigateToProfile = { user ->
|
navigateToProfile = { user ->
|
||||||
// Click callback
|
// Click callback
|
||||||
val bundle = bundleOf("userId" to user)
|
val bundle = bundleOf("userId" to user)
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package xyz.magicalbits.smsremote.conversation
|
||||||
|
|
||||||
|
import androidx.compose.runtime.toMutableStateList
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.magicalbits.smsremote.network.NetworkClient
|
||||||
|
|
||||||
|
class ConversationViewModel : ViewModel() {
|
||||||
|
private var phoneNumber: String = ""
|
||||||
|
private val _conversationData = MutableLiveData<ConversationScreenState>()
|
||||||
|
val conversationData: LiveData<ConversationScreenState> = _conversationData
|
||||||
|
|
||||||
|
fun setConversationData(phoneNumber: String?) {
|
||||||
|
if (phoneNumber != null) {
|
||||||
|
this.phoneNumber = phoneNumber
|
||||||
|
|
||||||
|
var messageDtoList: List<NetworkClient.SmsMessageDto> = listOf()
|
||||||
|
viewModelScope.launch {
|
||||||
|
val networkClient = NetworkClient()
|
||||||
|
messageDtoList = networkClient.getSmsMessagesByLocalPhoneNumber(phoneNumber)
|
||||||
|
}.invokeOnCompletion {
|
||||||
|
_conversationData.value =
|
||||||
|
ConversationScreenState(
|
||||||
|
phoneNumber = this.phoneNumber,
|
||||||
|
initialMessages = messageDtoList.map {
|
||||||
|
Message(
|
||||||
|
if (it.msg_type == "INCOMING") {
|
||||||
|
it.remote_phone_number
|
||||||
|
} else {
|
||||||
|
it.local_phone_number
|
||||||
|
},
|
||||||
|
it.content,
|
||||||
|
// FIXME convert to HH:MM AM/PM
|
||||||
|
it.ts_sent.toString(),
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
println("sims live: ${_conversationData.value!!.initialMessages}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ConversationScreenState(
|
||||||
|
val phoneNumber: String,
|
||||||
|
val initialMessages: List<Message>,
|
||||||
|
) {
|
||||||
|
private val _messages: MutableList<Message> = initialMessages.toMutableStateList()
|
||||||
|
|
||||||
|
val messages: List<Message> = _messages
|
||||||
|
|
||||||
|
fun addMessage(msg: Message) {
|
||||||
|
_messages.add(0, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
package xyz.magicalbits.smsremote.data
|
package xyz.magicalbits.smsremote.data
|
||||||
|
|
||||||
import xyz.magicalbits.smsremote.R
|
import xyz.magicalbits.smsremote.R
|
||||||
|
import xyz.magicalbits.smsremote.conversation.ConversationScreenState
|
||||||
import xyz.magicalbits.smsremote.conversation.ConversationUiState
|
import xyz.magicalbits.smsremote.conversation.ConversationUiState
|
||||||
import xyz.magicalbits.smsremote.conversation.Message
|
import xyz.magicalbits.smsremote.conversation.Message
|
||||||
import xyz.magicalbits.smsremote.device.DeviceScreenState
|
|
||||||
import xyz.magicalbits.smsremote.profile.ProfileScreenState
|
import xyz.magicalbits.smsremote.profile.ProfileScreenState
|
||||||
|
|
||||||
val initialMessages =
|
val initialMessages =
|
||||||
@@ -31,30 +31,16 @@ val initialMessages =
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val initialMessages2 =
|
|
||||||
listOf(
|
|
||||||
Message(
|
|
||||||
"uahguoidahfg",
|
|
||||||
"yolo",
|
|
||||||
"8:05 PM",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
val unreadMessages = initialMessages.filter { it.author != "me" }
|
val unreadMessages = initialMessages.filter { it.author != "me" }
|
||||||
|
|
||||||
val exampleUiState =
|
val exampleUiStateNew =
|
||||||
ConversationUiState(
|
ConversationScreenState(
|
||||||
initialMessages = initialMessages,
|
phoneNumber = "",
|
||||||
channelName = "Samsung A14",
|
initialMessages = mutableListOf()
|
||||||
channelMembers = 42,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val exampleUiState2 =
|
val exampleUiState =
|
||||||
ConversationUiState(
|
ConversationUiState("name", 123, listOf())
|
||||||
initialMessages = initialMessages2,
|
|
||||||
channelName = "iPhone XYZ",
|
|
||||||
channelMembers = 69,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example colleague profile
|
* Example colleague profile
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import io.ktor.client.plugins.auth.providers.BearerTokens
|
|||||||
import io.ktor.client.plugins.auth.providers.bearer
|
import io.ktor.client.plugins.auth.providers.bearer
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
class NetworkClient {
|
class NetworkClient {
|
||||||
// TODO make apiBaseUrl configurable with fallback to my real domain
|
// TODO make apiBaseUrl configurable with fallback to my real domain
|
||||||
@@ -37,6 +40,9 @@ class NetworkClient {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class SimCardDto(val device_access_key: String, val phone_number: String)
|
data class SimCardDto(val device_access_key: String, val phone_number: String)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SmsMessageDto(val content: String, val ts_received: Int, val ts_sent: Int, val msg_type: String, val local_phone_number: String, val remote_phone_number: String)
|
||||||
|
|
||||||
// GET /api/v1/devices
|
// GET /api/v1/devices
|
||||||
suspend fun getDevices(): List<DeviceDto> {
|
suspend fun getDevices(): List<DeviceDto> {
|
||||||
// TODO handle non-200 status codes
|
// TODO handle non-200 status codes
|
||||||
@@ -48,7 +54,20 @@ class NetworkClient {
|
|||||||
// GET /api/v1/sim-cards
|
// GET /api/v1/sim-cards
|
||||||
suspend fun getSimCardsByAccessKey(accessKey: String): List<SimCardDto> {
|
suspend fun getSimCardsByAccessKey(accessKey: String): List<SimCardDto> {
|
||||||
// TODO handle non-200 status codes
|
// TODO handle non-200 status codes
|
||||||
|
// TODO handle '{"msg":"Token has expired"}' messages
|
||||||
val data = networkClient.get("$apiBaseUrl/api/v1/sim-cards?access_key=$accessKey").bodyAsText()
|
val data = networkClient.get("$apiBaseUrl/api/v1/sim-cards?access_key=$accessKey").bodyAsText()
|
||||||
return Json.decodeFromString(data)
|
return Json.decodeFromString(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /api/v1/sms-messages
|
||||||
|
suspend fun getSmsMessagesByLocalPhoneNumber(phoneNumber: String): List<SmsMessageDto> {
|
||||||
|
// TODO handle non-200 status codes
|
||||||
|
// TODO handle '{"msg":"Token has expired"}' messages
|
||||||
|
// TODO extract encoder to a function here or to a utility class
|
||||||
|
val encodedPhoneNumber = withContext(Dispatchers.IO) {
|
||||||
|
URLEncoder.encode(phoneNumber, "UTF-8")
|
||||||
|
}
|
||||||
|
val data = networkClient.get("$apiBaseUrl/api/v1/sms-messages?local_phone_number=$encodedPhoneNumber").bodyAsText()
|
||||||
|
return Json.decodeFromString(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ def get_sms_messages_by_local_phone_number():
|
|||||||
return make_response(jsonify(msg=msg_403_not_primary), 403)
|
return make_response(jsonify(msg=msg_403_not_primary), 403)
|
||||||
|
|
||||||
local_phone_number = request.args.get("local_phone_number", None)
|
local_phone_number = request.args.get("local_phone_number", None)
|
||||||
|
# TODO set up access logging
|
||||||
|
#print(f"/api/v1/sms-messages - local_phone_number='{local_phone_number}'")
|
||||||
return make_response(jsonify([n.to_dict() for n in db.get_sms_messages_by_local_phone_number(cur, local_phone_number)]), 200)
|
return make_response(jsonify([n.to_dict() for n in db.get_sms_messages_by_local_phone_number(cur, local_phone_number)]), 200)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user