This commit is contained in:
2026-05-19 00:10:38 +02:00
parent db2290ba14
commit c68787cd01
93 changed files with 5855 additions and 0 deletions
@@ -0,0 +1,97 @@
package xyz.magicalbits.smsremote.device
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import xyz.magicalbits.smsremote.R
import xyz.magicalbits.smsremote.components.baselineHeight
@Composable
fun DeviceScreen(
deviceData: DeviceScreenState,
nestedScrollInteropConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(),
onPhoneNumberClicked: (String) -> Unit,
) {
val scrollState = rememberScrollState()
Box(
modifier = Modifier
.fillMaxSize()
.nestedScroll(nestedScrollInteropConnection),
) {
Surface {
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState).padding(horizontal = 16.dp)) {
Name(deviceData, Modifier.baselineHeight(32.dp))
Spacer(modifier = Modifier.height(8.dp))
HorizontalDivider()
Spacer(modifier = Modifier.height(8.dp))
deviceData.phoneNumbers.forEach {
PhoneNumber(
value = it,
modifier = Modifier
.baselineHeight(24.dp)
.clickable(onClick = { onPhoneNumberClicked(it) }),
style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.primary, fontWeight = FontWeight.Bold, fontFamily = FontFamily.Monospace)
)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
}
@Composable
private fun Name(
deviceData: DeviceScreenState,
modifier: Modifier
) {
Text(
text = deviceData.name,
modifier = modifier,
style = MaterialTheme.typography.headlineSmall
)
}
@Composable
private fun PhoneNumber(
value: String,
modifier: Modifier,
style: TextStyle
) {
Text(
text = value,
modifier = modifier,
style = style
)
}
@Composable
fun DeviceError() {
Text(stringResource(R.string.device_error))
}
@@ -0,0 +1,87 @@
package xyz.magicalbits.smsremote.device
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import xyz.magicalbits.smsremote.MainViewModel
import xyz.magicalbits.smsremote.R
import xyz.magicalbits.smsremote.components.JetchatAppBar
import xyz.magicalbits.smsremote.theme.JetchatTheme
import kotlin.getValue
import androidx.navigation.findNavController
import kotlinx.coroutines.launch
class DeviceFragment : Fragment() {
private val viewModel: DeviceViewModel by viewModels()
private val activityViewModel: MainViewModel by activityViewModels()
override fun onAttach(context: Context) {
super.onAttach(context)
// Consider using safe args plugin
val deviceName = arguments?.getString("deviceName")
viewModel.setDeviceId(deviceName)
}
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val rootView: View = inflater.inflate(R.layout.fragment_profile, container, false)
rootView.findViewById<ComposeView>(R.id.toolbar_compose_view).apply {
setContent {
JetchatTheme {
JetchatAppBar(
// Reset the minimum bounds that are passed to the root of a compose tree
modifier = Modifier.wrapContentSize(),
onNavIconPressed = { activityViewModel.openDrawer() },
title = { },
)
}
}
}
rootView.findViewById<ComposeView>(R.id.profile_compose_view).apply {
setContent {
val deviceData by viewModel.deviceData.observeAsState()
val nestedScrollInteropConnection = rememberNestedScrollInteropConnection()
JetchatTheme {
if (deviceData == null) {
DeviceError()
} else {
val navController: NavController = rootView.findNavController()
DeviceScreen(
deviceData = deviceData!!,
nestedScrollInteropConnection = nestedScrollInteropConnection,
onPhoneNumberClicked = {
val args = Bundle(1)
args.putString("phoneNumber", it)
navController.navigate(R.id.action_device_to_conversation, args)
},
)
}
}
}
}
return rootView
}
}
@@ -0,0 +1,35 @@
package xyz.magicalbits.smsremote.device
import androidx.compose.runtime.Immutable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import xyz.magicalbits.smsremote.data.a14Device
import xyz.magicalbits.smsremote.data.iPhoneDevice
class DeviceViewModel : ViewModel() {
private var deviceId: String = ""
private val _deviceData = MutableLiveData<DeviceScreenState>()
val deviceData: LiveData<DeviceScreenState> = _deviceData
fun setDeviceId(newDeviceId: String?) {
if (newDeviceId != null) {
deviceId = newDeviceId
}
// placeholder since there's no API reading logic yet
_deviceData.value =
if (deviceId == "Samsung A14") {
a14Device
} else {
iPhoneDevice
}
}
}
@Immutable
data class DeviceScreenState(
val deviceId: String,
val name: String,
val phoneNumbers: List<String>
)