diff --git a/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/CalculatorFragment.java b/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/CalculatorFragment.java deleted file mode 100644 index 62cc09de5..000000000 --- a/networksurvey/src/main/java/com/craxiom/networksurvey/fragments/CalculatorFragment.java +++ /dev/null @@ -1,273 +0,0 @@ -package com.craxiom.networksurvey.fragments; - -import android.os.Bundle; -import android.text.Editable; -import android.text.TextWatcher; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.fragment.app.Fragment; - -import com.craxiom.networksurvey.R; -import com.craxiom.networksurvey.util.CalculationUtils; -import com.craxiom.networksurvey.util.CellularUtils; - -import timber.log.Timber; - -/** - * A fragment to hold the LTE eNodeB ID calculator. - * - * @since 0.0.2 - */ -public class CalculatorFragment extends Fragment -{ - static final String TITLE = "Calculators"; - private static final String INVALID_CELL_ID_MESSAGE = "Invalid Cell ID. Valid Range is 0 - 268435455"; - private static final String INVALID_PCI_MESSAGE = "Invalid PCI. Valid Range is 0 - 503"; - private static final String INVALID_EARFCN_MESSAGE = "Invalid EARFCN. Valid Range is 0 - 262143"; - - private View view; - - private final TextWatcher lteCellIdTextWatcher = new TextWatcher() - { - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) - { - final String enteredText = s.toString(); - try - { - if (enteredText.isEmpty()) - { - Timber.v("The entered text for the LTE Cell ID is empty. Can't calculate the eNodeB ID."); - clearCellIdCalculatedValues(); - return; - } - - final int cellId; - try - { - cellId = Integer.parseInt(enteredText); - } catch (Exception e) - { - showToast(INVALID_CELL_ID_MESSAGE); - clearCellIdCalculatedValues(); - return; - } - - if (!CalculationUtils.isLteCellIdValid(cellId)) - { - showToast(INVALID_CELL_ID_MESSAGE); - clearCellIdCalculatedValues(); - return; - } - - // The Cell Identity is 28 bits long. The first 20 bits represent the Macro eNodeB ID. The last 8 bits - // represent the sector. Strip off the last 8 bits to get the Macro eNodeB ID. - int eNodebId = CalculationUtils.getEnodebIdFromCellId(cellId); - ((TextView) view.findViewById(R.id.calculatedEnbIdValue)).setText(String.valueOf(eNodebId)); - - int sectorId = CalculationUtils.getSectorIdFromCellId(cellId); - ((TextView) view.findViewById(R.id.calculatedSectorIdValue)).setText(String.valueOf(sectorId)); - } catch (Exception e) - { - Timber.w(e, "Unable to parse the provide LTE Cell ID as an Integer:%s", enteredText); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) - { - - } - - @Override - public void afterTextChanged(Editable s) - { - - } - }; - - private final TextWatcher ltePciTextWatcher = new TextWatcher() - { - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) - { - final String enteredText = s.toString(); - try - { - if (enteredText.isEmpty()) - { - Timber.v("The entered text for the LTE PCI is empty. Can't calculate the PSS and SSS."); - clearPciCalculatedValues(); - return; - } - - final int pci; - try - { - pci = Integer.parseInt(enteredText); - } catch (Exception e) - { - showToast(INVALID_PCI_MESSAGE); - clearPciCalculatedValues(); - return; - } - - if (pci < 0 || pci > 503) - { - showToast(INVALID_PCI_MESSAGE); - clearPciCalculatedValues(); - return; - } - - int primarySyncSequence = CalculationUtils.getPrimarySyncSequence(pci); - ((TextView) view.findViewById(R.id.calculatedPssValue)).setText(String.valueOf(primarySyncSequence)); - - int secondarySyncSequence = CalculationUtils.getSecondarySyncSequence(pci); - ((TextView) view.findViewById(R.id.calculatedSssValue)).setText(String.valueOf(secondarySyncSequence)); - } catch (Exception e) - { - Timber.w(e, "Unable to parse the provide LTE PCI as an Integer:%s", enteredText); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) - { - - } - - @Override - public void afterTextChanged(Editable s) - { - - } - }; - - private final TextWatcher lteEarfcnTextWatcher = new TextWatcher() - { - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) - { - final String enteredText = s.toString(); - try - { - if (enteredText.isEmpty()) - { - Timber.v("The entered text for the LTE EARFCN is empty. Can't calculate the Band."); - clearEarfcnCalculatedValues(); - return; - } - - final int earfcn; - try - { - earfcn = Integer.parseInt(enteredText); - } catch (Exception e) - { - showToast(INVALID_EARFCN_MESSAGE); - clearEarfcnCalculatedValues(); - return; - } - - if (earfcn < 0 || earfcn > 262143) - { - showToast(INVALID_EARFCN_MESSAGE); - clearEarfcnCalculatedValues(); - return; - } - - int band = CellularUtils.downlinkEarfcnToBand(earfcn); - if (band == -1) - { - ((TextView) view.findViewById(R.id.calculatedBandValue)).setText("Unknown"); - } else - { - ((TextView) view.findViewById(R.id.calculatedBandValue)).setText(String.valueOf(band)); - } - } catch (Exception e) - { - Timber.w(e, "Unable to parse the provide LTE EARFCN as an Integer:%s", enteredText); - } - } - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) - { - - } - - @Override - public void afterTextChanged(Editable s) - { - - } - }; - - public CalculatorFragment() - { - // Required empty public constructor - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) - { - // Inflate the layout for this fragment - view = inflater.inflate(R.layout.fragment_calculator, container, false); - - final EditText cellIdField = view.findViewById(R.id.lteCalculatorCellId); - cellIdField.addTextChangedListener(lteCellIdTextWatcher); - - final EditText pciField = view.findViewById(R.id.lteCalculatorPci); - pciField.addTextChangedListener(ltePciTextWatcher); - - final EditText earfcnField = view.findViewById(R.id.lteCalculatorEarfcn); - earfcnField.addTextChangedListener(lteEarfcnTextWatcher); - - return view; - } - - /** - * Sets the text in the Cell ID calculated TextView's to an empty string. - */ - private void clearCellIdCalculatedValues() - { - ((TextView) view.findViewById(R.id.calculatedEnbIdValue)).setText(""); - ((TextView) view.findViewById(R.id.calculatedSectorIdValue)).setText(""); - } - - /** - * Sets the text in the PCI calculated TextView's to an empty string. - */ - private void clearPciCalculatedValues() - { - ((TextView) view.findViewById(R.id.calculatedPssValue)).setText(""); - ((TextView) view.findViewById(R.id.calculatedSssValue)).setText(""); - } - - /** - * Sets the text in the Band calculated TextView to an empty string. - */ - private void clearEarfcnCalculatedValues() - { - ((TextView) view.findViewById(R.id.calculatedBandValue)).setText(""); - } - - /** - * Shows a short toast to the user with the provided message. - *

- * This method also logs a warning. - * - * @param toastMessage The message to show the user. - */ - private void showToast(String toastMessage) - { - Timber.d(toastMessage); - Toast.makeText(getActivity(), toastMessage, Toast.LENGTH_SHORT).show(); - } -} diff --git a/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/NsMainScreen.kt b/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/NsMainScreen.kt index 5c3193ee0..c5761e107 100644 --- a/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/NsMainScreen.kt +++ b/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/NsMainScreen.kt @@ -1,5 +1,7 @@ package com.craxiom.networksurvey.ui.main +import android.content.Intent +import android.net.Uri import androidx.activity.compose.BackHandler import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue @@ -7,6 +9,7 @@ import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.Scaffold import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavController import androidx.navigation.NavHostController @@ -27,6 +30,7 @@ fun MainCompose( appVersion: String ) { // TODO Is this needed? HandleBackPress(navController) + val context = LocalContext.current NsTheme { Scaffold { paddingValues -> @@ -37,6 +41,7 @@ fun MainCompose( appVersion = appVersion, drawerState = drawerState, menuItems = DrawerParams.drawerButtons, + externalLinks = DrawerParams.externalDrawerLinks, defaultPick = NavDrawerOption.None ) { onUserPickedOption -> when (onUserPickedOption) { @@ -69,6 +74,38 @@ fun MainCompose( popUpTo(NavDrawerOption.None.name) } } + + NavDrawerOption.UserManual -> { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://networksurvey.app/manual") + ) + context.startActivity(intent) + } + + NavDrawerOption.MessagingDocs -> { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://messaging.networksurvey.app/") + ) + context.startActivity(intent) + } + + NavDrawerOption.ReportAnIssue -> { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://github.com/christianrowlands/android-network-survey/issues/new/choose") + ) + context.startActivity(intent) + } + + NavDrawerOption.GitHub -> { + val intent = Intent( + Intent.ACTION_VIEW, + Uri.parse("https://github.com/christianrowlands/android-network-survey") + ) + context.startActivity(intent) + } } } } @@ -101,7 +138,6 @@ enum class NavRoutes { object DrawerParams { val drawerButtons = arrayListOf( - // TODO Update all of these AppDrawerItemInfo( NavDrawerOption.ServerConnection, R.string.grpc_connection_title, @@ -127,6 +163,33 @@ object DrawerParams { R.string.device_status_stream_description ) ) + + val externalDrawerLinks = arrayListOf( + AppDrawerItemInfo( + NavDrawerOption.UserManual, + R.string.manual, + R.drawable.ic_user_manual, + R.string.manual + ), + AppDrawerItemInfo( + NavDrawerOption.MessagingDocs, + R.string.messaging_docs, + R.drawable.ic_schema, + R.string.messaging_docs + ), + AppDrawerItemInfo( + NavDrawerOption.ReportAnIssue, + R.string.report_issue, + R.drawable.ic_bug, + R.string.report_issue + ), + AppDrawerItemInfo( + NavDrawerOption.GitHub, + R.string.github, + R.drawable.ic_github, + R.string.github + ) + ) } @Preview diff --git a/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerContent.kt b/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerContent.kt index 0e804d4a6..04ab2387b 100644 --- a/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerContent.kt +++ b/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerContent.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.DrawerState +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.Surface @@ -26,6 +27,7 @@ fun > AppDrawerContent( appVersion: String, drawerState: DrawerState, menuItems: List>, + externalLinks: List>, defaultPick: T, onClick: (T) -> Unit ) { @@ -58,6 +60,28 @@ fun > AppDrawerContent( onClick(navOption) } } + + item { + // TODO Figure out a way to prevent this from making the drawer wider + HorizontalDivider(thickness = 1.dp) + } + + items(externalLinks) { item -> + AppDrawerItem(item = item) { navOption -> + if (currentPick == navOption) { + coroutineScope.launch { + drawerState.close() + } + return@AppDrawerItem + } + + currentPick = navOption + coroutineScope.launch { + drawerState.close() + } + onClick(navOption) + } + } } Spacer(modifier = Modifier.weight(1f)) diff --git a/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerNav.kt b/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerNav.kt index e08115480..d80b032cb 100644 --- a/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerNav.kt +++ b/networksurvey/src/main/java/com/craxiom/networksurvey/ui/main/appdrawer/AppDrawerNav.kt @@ -1,16 +1,21 @@ package com.craxiom.networksurvey.ui.main.appdrawer +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material3.DrawerState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidViewBinding +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.compose.navigation +import com.craxiom.networksurvey.databinding.ContainerGrpcFragmentBinding +import com.craxiom.networksurvey.databinding.ContainerMqttFragmentBinding import com.craxiom.networksurvey.databinding.ContainerSettingsFragmentBinding import com.craxiom.networksurvey.fragments.SettingsFragment +import com.craxiom.networksurvey.ui.cellular.CalculatorScreen import com.craxiom.networksurvey.ui.main.HomeScreen import com.craxiom.networksurvey.ui.main.NavRoutes @@ -19,17 +24,20 @@ fun NavGraphBuilder.mainGraph( paddingValues: PaddingValues ) { navigation(startDestination = NavDrawerOption.None.name, route = NavRoutes.MainRoute.name) { + // TODO Need to add a header like the old display for all of these composable(NavDrawerOption.None.name) { HomeScreen(drawerState) } composable(NavDrawerOption.ServerConnection.name) { - // TODO finish this + GrpcFragmentInCompose(paddingValues) } composable(NavDrawerOption.MqttBrokerConnection.name) { - // TODO finish this + MqttFragmentInCompose(paddingValues) } composable(NavDrawerOption.CellularCalculators.name) { - // TODO finish this + Box(modifier = Modifier.padding(paddingValues = paddingValues)) { + CalculatorScreen(viewModel = viewModel()) + } } composable(NavDrawerOption.Settings.name) { @@ -44,7 +52,30 @@ enum class NavDrawerOption { MqttBrokerConnection, CellularCalculators, Settings, - // TODO Add the other options + + // External Links + UserManual, + MessagingDocs, + ReportAnIssue, + GitHub +} + +@Composable +fun GrpcFragmentInCompose(paddingValues: PaddingValues) { + AndroidViewBinding( + ContainerGrpcFragmentBinding::inflate, + modifier = Modifier.padding(paddingValues = paddingValues) + ) { + } +} + +@Composable +fun MqttFragmentInCompose(paddingValues: PaddingValues) { + AndroidViewBinding( + ContainerMqttFragmentBinding::inflate, + modifier = Modifier.padding(paddingValues = paddingValues) + ) { + } } @Composable diff --git a/networksurvey/src/main/res/layout/container_grpc_fragment.xml b/networksurvey/src/main/res/layout/container_grpc_fragment.xml new file mode 100644 index 000000000..8499f4a46 --- /dev/null +++ b/networksurvey/src/main/res/layout/container_grpc_fragment.xml @@ -0,0 +1,6 @@ + + diff --git a/networksurvey/src/main/res/layout/container_mqtt_fragment.xml b/networksurvey/src/main/res/layout/container_mqtt_fragment.xml new file mode 100644 index 000000000..5c6b963ba --- /dev/null +++ b/networksurvey/src/main/res/layout/container_mqtt_fragment.xml @@ -0,0 +1,6 @@ + +