wip: wiring network client
This commit is contained in:
@@ -20,6 +20,8 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.compose)
|
alias(libs.plugins.compose)
|
||||||
|
// necessary for using T.serializer() on a data class
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -96,6 +98,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.glance.material3)
|
implementation(libs.androidx.glance.material3)
|
||||||
implementation(libs.kotlin.stdlib)
|
implementation(libs.kotlin.stdlib)
|
||||||
implementation(libs.kotlinx.coroutines.android)
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
|
|
||||||
@@ -115,6 +118,10 @@ dependencies {
|
|||||||
implementation(libs.androidx.compose.ui.viewbinding)
|
implementation(libs.androidx.compose.ui.viewbinding)
|
||||||
implementation(libs.androidx.compose.ui.googlefonts)
|
implementation(libs.androidx.compose.ui.googlefonts)
|
||||||
|
|
||||||
|
implementation(libs.ktor.client.auth)
|
||||||
|
implementation(libs.ktor.client.core)
|
||||||
|
implementation(libs.ktor.client.cio)
|
||||||
|
|
||||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||||
|
|
||||||
androidTestImplementation(libs.junit)
|
androidTestImplementation(libs.junit)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
|
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
|
|||||||
@@ -32,19 +32,24 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.compose.ui.viewinterop.AndroidViewBinding
|
import androidx.compose.ui.viewinterop.AndroidViewBinding
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import xyz.magicalbits.smsremote.components.JetchatDrawer
|
import xyz.magicalbits.smsremote.components.JetchatDrawer
|
||||||
import xyz.magicalbits.smsremote.databinding.ContentMainBinding
|
import xyz.magicalbits.smsremote.databinding.ContentMainBinding
|
||||||
|
import xyz.magicalbits.smsremote.network.NetworkClient
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main activity for the app.
|
* Main activity for the app.
|
||||||
*/
|
*/
|
||||||
class NavActivity : AppCompatActivity() {
|
class NavActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val viewModel: MainViewModel by viewModels()
|
private val viewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -53,6 +58,12 @@ class NavActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets -> insets }
|
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets -> insets }
|
||||||
|
|
||||||
|
var deviceDtoList: List<NetworkClient.DeviceDto> = listOf()
|
||||||
|
viewModel.viewModelScope.launch {
|
||||||
|
val networkClient = NetworkClient()
|
||||||
|
deviceDtoList = networkClient.getDevices()
|
||||||
|
}
|
||||||
|
|
||||||
setContentView(
|
setContentView(
|
||||||
ComposeView(this).apply {
|
ComposeView(this).apply {
|
||||||
consumeWindowInsets = false
|
consumeWindowInsets = false
|
||||||
@@ -79,15 +90,18 @@ class NavActivity : AppCompatActivity() {
|
|||||||
JetchatDrawer(
|
JetchatDrawer(
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
selectedMenu = selectedMenu,
|
selectedMenu = selectedMenu,
|
||||||
|
deviceDtoList = deviceDtoList,
|
||||||
onChatClicked = {
|
onChatClicked = {
|
||||||
findNavController().popBackStack(R.id.nav_device, false)
|
findNavController().popBackStack(R.id.nav_device, false)
|
||||||
val args = Bundle(1)
|
val args = Bundle(1)
|
||||||
args.putString("deviceName", it)
|
args.putString("deviceId", it.access_key)
|
||||||
|
args.putString("name", it.name)
|
||||||
|
args.putString("type", it.type)
|
||||||
findNavController().navigate(R.id.nav_device, args)
|
findNavController().navigate(R.id.nav_device, args)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
drawerState.close()
|
drawerState.close()
|
||||||
}
|
}
|
||||||
selectedMenu = it
|
selectedMenu = it.access_key
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
AndroidViewBinding(ContentMainBinding::inflate)
|
AndroidViewBinding(ContentMainBinding::inflate)
|
||||||
|
|||||||
@@ -56,11 +56,12 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import xyz.magicalbits.smsremote.R
|
import xyz.magicalbits.smsremote.R
|
||||||
|
import xyz.magicalbits.smsremote.network.NetworkClient
|
||||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||||
import xyz.magicalbits.smsremote.widget.WidgetReceiver
|
import xyz.magicalbits.smsremote.widget.WidgetReceiver
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun JetchatDrawerContent(onChatClicked: (String) -> Unit, selectedMenu: String = "iPhone XYZ") {
|
fun JetchatDrawerContent(onChatClicked: (NetworkClient.DeviceDto) -> Unit, selectedMenu: String = "", deviceDtoList: List<NetworkClient.DeviceDto> = listOf()) {
|
||||||
// Use windowInsetsTopHeight() to add a spacer which pushes the drawer content
|
// Use windowInsetsTopHeight() to add a spacer which pushes the drawer content
|
||||||
// below the status bar (y-axis)
|
// below the status bar (y-axis)
|
||||||
Column {
|
Column {
|
||||||
@@ -68,12 +69,13 @@ fun JetchatDrawerContent(onChatClicked: (String) -> Unit, selectedMenu: String =
|
|||||||
DrawerHeader()
|
DrawerHeader()
|
||||||
DividerItem()
|
DividerItem()
|
||||||
DrawerItemHeader("Devices")
|
DrawerItemHeader("Devices")
|
||||||
DeviceItem("Samsung A14", selectedMenu == "Samsung A14") {
|
|
||||||
onChatClicked("Samsung A14")
|
for (deviceDto in deviceDtoList) {
|
||||||
|
DeviceItem(deviceDto.name, selectedMenu == deviceDto.access_key) {
|
||||||
|
onChatClicked(deviceDto)
|
||||||
}
|
}
|
||||||
DeviceItem("iPhone XYZ", selectedMenu == "iPhone XYZ") {
|
|
||||||
onChatClicked("iPhone XYZ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
|
// DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
|
||||||
if (widgetAddingIsSupported(LocalContext.current)) {
|
if (widgetAddingIsSupported(LocalContext.current)) {
|
||||||
DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
|
DividerItem(modifier = Modifier.padding(horizontal = 28.dp))
|
||||||
|
|||||||
@@ -23,13 +23,15 @@ import androidx.compose.material3.ModalDrawerSheet
|
|||||||
import androidx.compose.material3.ModalNavigationDrawer
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import xyz.magicalbits.smsremote.network.NetworkClient
|
||||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun JetchatDrawer(
|
fun JetchatDrawer(
|
||||||
drawerState: DrawerState = rememberDrawerState(initialValue = Closed),
|
drawerState: DrawerState = rememberDrawerState(initialValue = Closed),
|
||||||
selectedMenu: String,
|
selectedMenu: String,
|
||||||
onChatClicked: (String) -> Unit,
|
deviceDtoList: List<NetworkClient.DeviceDto>,
|
||||||
|
onChatClicked: (NetworkClient.DeviceDto) -> Unit,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
JetchatTheme {
|
JetchatTheme {
|
||||||
@@ -44,6 +46,7 @@ fun JetchatDrawer(
|
|||||||
JetchatDrawerContent(
|
JetchatDrawerContent(
|
||||||
onChatClicked = onChatClicked,
|
onChatClicked = onChatClicked,
|
||||||
selectedMenu = selectedMenu,
|
selectedMenu = selectedMenu,
|
||||||
|
deviceDtoList = deviceDtoList,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.wrapContentSize
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
@@ -24,7 +23,6 @@ import xyz.magicalbits.smsremote.components.JetchatAppBar
|
|||||||
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
import xyz.magicalbits.smsremote.theme.JetchatTheme
|
||||||
import kotlin.getValue
|
import kotlin.getValue
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class DeviceFragment : Fragment() {
|
class DeviceFragment : Fragment() {
|
||||||
private val viewModel: DeviceViewModel by viewModels()
|
private val viewModel: DeviceViewModel by viewModels()
|
||||||
@@ -33,8 +31,10 @@ class DeviceFragment : Fragment() {
|
|||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
// Consider using safe args plugin
|
// Consider using safe args plugin
|
||||||
val deviceName = arguments?.getString("deviceName")
|
val deviceId = arguments?.getString("deviceId")
|
||||||
viewModel.setDeviceId(deviceName)
|
val name = arguments?.getString("name")
|
||||||
|
val type = arguments?.getString("type")
|
||||||
|
viewModel.setDeviceData(deviceId, name, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
|
||||||
@@ -65,6 +65,7 @@ class DeviceFragment : Fragment() {
|
|||||||
|
|
||||||
JetchatTheme {
|
JetchatTheme {
|
||||||
if (deviceData == null) {
|
if (deviceData == null) {
|
||||||
|
println("calling device error")
|
||||||
DeviceError()
|
DeviceError()
|
||||||
} else {
|
} else {
|
||||||
val navController: NavController = rootView.findNavController()
|
val navController: NavController = rootView.findNavController()
|
||||||
|
|||||||
@@ -4,25 +4,38 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import xyz.magicalbits.smsremote.data.a14Device
|
import androidx.lifecycle.viewModelScope
|
||||||
import xyz.magicalbits.smsremote.data.iPhoneDevice
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.magicalbits.smsremote.network.NetworkClient
|
||||||
|
|
||||||
class DeviceViewModel : ViewModel() {
|
class DeviceViewModel : ViewModel() {
|
||||||
private var deviceId: String = ""
|
private var deviceId: String = ""
|
||||||
private val _deviceData = MutableLiveData<DeviceScreenState>()
|
private val _deviceData = MutableLiveData<DeviceScreenState>()
|
||||||
val deviceData: LiveData<DeviceScreenState> = _deviceData
|
val deviceData: LiveData<DeviceScreenState> = _deviceData
|
||||||
|
|
||||||
fun setDeviceId(newDeviceId: String?) {
|
fun setDeviceData(newDeviceId: String?, name: String?, type: String?) {
|
||||||
if (newDeviceId != null) {
|
if (newDeviceId != null && name != null) {
|
||||||
deviceId = newDeviceId
|
deviceId = newDeviceId
|
||||||
}
|
|
||||||
|
var simCardDtoList: List<NetworkClient.SimCardDto> = listOf()
|
||||||
|
viewModelScope.launch {
|
||||||
|
val networkClient = NetworkClient()
|
||||||
|
simCardDtoList = networkClient.getSimCardsByAccessKey(deviceId)
|
||||||
|
println("sims: $simCardDtoList")
|
||||||
|
}.invokeOnCompletion {
|
||||||
|
// FIXME waiting for the response causes brief moment of DeviceError() before _deviceData is updated ...
|
||||||
|
// a solution: caching SIM phone numbers of all discovered devices locally on startup and updating them
|
||||||
|
// only on startup (implicit behavior) or with a pull-down refresh action (not done yet)
|
||||||
|
|
||||||
// placeholder since there's no API reading logic yet
|
// placeholder since there's no API reading logic yet
|
||||||
_deviceData.value =
|
_deviceData.value =
|
||||||
if (deviceId == "Samsung A14") {
|
DeviceScreenState(
|
||||||
a14Device
|
deviceId = deviceId,
|
||||||
} else {
|
name = name,
|
||||||
iPhoneDevice
|
phoneNumbers = simCardDtoList.map { it.phone_number },
|
||||||
|
)
|
||||||
|
println("sims live: ${_deviceData.value!!.phoneNumbers}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,12 @@
|
|||||||
<argument
|
<argument
|
||||||
android:name="deviceId"
|
android:name="deviceId"
|
||||||
app:argType="string" />
|
app:argType="string" />
|
||||||
|
<argument
|
||||||
|
android:name="name"
|
||||||
|
app:argType="string" />
|
||||||
|
<argument
|
||||||
|
android:name="type"
|
||||||
|
app:argType="string" />
|
||||||
<action
|
<action
|
||||||
android:id="@+id/action_device_to_conversation"
|
android:id="@+id/action_device_to_conversation"
|
||||||
app:destination="@id/nav_conversation"
|
app:destination="@id/nav_conversation"
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
uv run gunicorn app:app \
|
uv run gunicorn -b 0.0.0.0:5000 app:app \
|
||||||
--reload
|
--reload
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ gradle-versions = "0.54.0"
|
|||||||
hilt = "2.59.2"
|
hilt = "2.59.2"
|
||||||
hiltExt = "1.3.0"
|
hiltExt = "1.3.0"
|
||||||
horologist = "0.7.15"
|
horologist = "0.7.15"
|
||||||
|
ktor-client = "3.5.0"
|
||||||
jdkDesugar = "2.1.5"
|
jdkDesugar = "2.1.5"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlin = "2.3.21"
|
kotlin = "2.3.21"
|
||||||
@@ -147,6 +148,9 @@ kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collec
|
|||||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }
|
||||||
|
ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor-client"}
|
||||||
|
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-client"}
|
||||||
|
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor-client"}
|
||||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||||
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||||
play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "play-services-wearable" }
|
play-services-wearable = { module = "com.google.android.gms:play-services-wearable", version.ref = "play-services-wearable" }
|
||||||
|
|||||||
Reference in New Issue
Block a user