mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2025-12-24 00:07:48 -05:00
Kmp strings cleanup (#3669)
This commit is contained in:
@@ -176,7 +176,7 @@ class MeshServiceNotificationsImpl @Inject constructor(@ApplicationContext priva
|
||||
private fun createNotificationChannel(type: NotificationType) {
|
||||
if (notificationManager.getNotificationChannel(type.channelId) != null) return
|
||||
|
||||
val channelName = context.getString(type.channelNameRes)
|
||||
val channelName = getString(type.channelNameRes)
|
||||
val channel =
|
||||
NotificationChannel(type.channelId, channelName, type.importance).apply {
|
||||
lightColor = NOTIFICATION_LIGHT_COLOR
|
||||
@@ -259,7 +259,7 @@ class MeshServiceNotificationsImpl @Inject constructor(@ApplicationContext priva
|
||||
null
|
||||
}
|
||||
|
||||
cachedMessage = message ?: cachedMessage ?: context.getString(Res.string.no_local_stats)
|
||||
cachedMessage = message ?: cachedMessage ?: getString(Res.string.no_local_stats)
|
||||
|
||||
val notification =
|
||||
createServiceStateNotification(
|
||||
@@ -295,7 +295,7 @@ class MeshServiceNotificationsImpl @Inject constructor(@ApplicationContext priva
|
||||
|
||||
override fun showClientNotification(clientNotification: MeshProtos.ClientNotification) {
|
||||
val notification =
|
||||
createClientNotification(context.getString(Res.string.client_notification), clientNotification.message)
|
||||
createClientNotification(getString(Res.string.client_notification), clientNotification.message)
|
||||
notificationManager.notify(clientNotification.toString().hashCode(), notification)
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ class MeshServiceNotificationsImpl @Inject constructor(@ApplicationContext priva
|
||||
}
|
||||
|
||||
private fun createNewNodeSeenNotification(name: String, message: String?): Notification {
|
||||
val title = context.getString(Res.string.new_node_seen).format(name)
|
||||
val title = getString(Res.string.new_node_seen).format(name)
|
||||
val builder =
|
||||
commonBuilder(NotificationType.NewNode)
|
||||
.setCategory(Notification.CATEGORY_STATUS)
|
||||
@@ -393,9 +393,8 @@ class MeshServiceNotificationsImpl @Inject constructor(@ApplicationContext priva
|
||||
|
||||
private fun createLowBatteryNotification(node: NodeEntity, isRemote: Boolean): Notification {
|
||||
val type = if (isRemote) NotificationType.LowBatteryRemote else NotificationType.LowBatteryLocal
|
||||
val title = context.getString(Res.string.low_battery_title).format(node.shortName)
|
||||
val message =
|
||||
context.getString(Res.string.low_battery_message).format(node.longName, node.deviceMetrics.batteryLevel)
|
||||
val title = getString(Res.string.low_battery_title).format(node.shortName)
|
||||
val message = getString(Res.string.low_battery_message).format(node.longName, node.deviceMetrics.batteryLevel)
|
||||
|
||||
return commonBuilder(type)
|
||||
.setCategory(Notification.CATEGORY_STATUS)
|
||||
@@ -445,7 +444,7 @@ class MeshServiceNotificationsImpl @Inject constructor(@ApplicationContext priva
|
||||
}
|
||||
|
||||
private fun createReplyAction(contactKey: String): NotificationCompat.Action {
|
||||
val replyLabel = context.getString(Res.string.reply)
|
||||
val replyLabel = getString(Res.string.reply)
|
||||
val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build()
|
||||
|
||||
val replyIntent =
|
||||
|
||||
@@ -17,23 +17,13 @@
|
||||
|
||||
package com.meshtastic.core.strings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
fun Context.getString(stringResource: StringResource): String = runBlocking {
|
||||
fun getString(stringResource: StringResource): String = runBlocking {
|
||||
org.jetbrains.compose.resources.getString(stringResource)
|
||||
}
|
||||
|
||||
fun Context.getString(stringResource: StringResource, vararg formatArgs: Any): String = runBlocking {
|
||||
org.jetbrains.compose.resources.getString(stringResource, *formatArgs)
|
||||
}
|
||||
|
||||
fun Resources.getString(stringResource: StringResource): String = runBlocking {
|
||||
org.jetbrains.compose.resources.getString(stringResource)
|
||||
}
|
||||
|
||||
fun Resources.getString(stringResource: StringResource, vararg formatArgs: Any): String = runBlocking {
|
||||
fun getString(stringResource: StringResource, vararg formatArgs: Any): String = runBlocking {
|
||||
org.jetbrains.compose.resources.getString(stringResource, *formatArgs)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<string name="zh_TW" translatable="false">繁體中文</string>
|
||||
|
||||
<string name="some_username" translatable="false">SKH</string>
|
||||
<string name="sample_message" translatable="false">hey I found the cache, it is over here next to the big tiger. I\'m kinda scared.</string>
|
||||
<string name="sample_message" translatable="false">hey I found the cache, it is over here next to the big tiger. I'm kinda scared.</string>
|
||||
|
||||
<string name="default_mqtt_address" translatable="false">mqtt.meshtastic.org</string>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<string name="rebroadcast_mode_local_only">Local Only</string>
|
||||
<string name="rebroadcast_mode_local_only_desc">Ignores observed messages from foreign meshes that are open or those which it cannot decrypt. Only rebroadcasts message on the nodes local primary / secondary channels.</string>
|
||||
<string name="rebroadcast_mode_known_only">Known Only</string>
|
||||
<string name="rebroadcast_mode_known_only_desc">Ignores observed messages from foreign meshes like LOCAL ONLY, but takes it step further by also ignoring messages from nodes not already in the node\'s known list.</string>
|
||||
<string name="rebroadcast_mode_known_only_desc">Ignores observed messages from foreign meshes like LOCAL ONLY, but takes it step further by also ignoring messages from nodes not already in the node's known list.</string>
|
||||
<string name="rebroadcast_mode_none">None</string>
|
||||
<string name="rebroadcast_mode_none_desc">Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.</string>
|
||||
<string name="rebroadcast_mode_core_portnums_only">Core Portnums Only</string>
|
||||
@@ -147,7 +147,7 @@
|
||||
<string name="config_position_gps_update_interval_summary">How often should we try to get a GPS position (<10sec keeps GPS on).</string>
|
||||
<string name="config_position_flags_summary">Optional fields to include when assembling position messages. the more fields are included, the larger the message will be - leading to longer airtime and a higher risk of packet loss.</string>
|
||||
|
||||
<string name="config_power_is_power_saving_summary">Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don\'t use this setting if you want to use your device with the phone apps or are using a device without a user button.</string>
|
||||
<string name="config_power_is_power_saving_summary">Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio. Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.</string>
|
||||
|
||||
<string name="config_security_public_key">Generated from your public key and sent out to other nodes on the mesh to allow them to compute a shared secret key.</string>
|
||||
<string name="config_security_private_key">Used to create a shared key with a remote device.</string>
|
||||
@@ -181,7 +181,7 @@
|
||||
<string name="application_icon">application icon</string>
|
||||
<string name="unknown_username">Unknown Username</string>
|
||||
<string name="send">Send</string>
|
||||
<string name="warning_not_paired">You haven\'t yet paired a Meshtastic compatible radio with this phone. Please pair a device and set your username.\n\nThis open-source application is in development, if you find problems please post on our forum: https://github.com/orgs/meshtastic/discussions.\n\nFor more information see our web page - www.meshtastic.org.</string>
|
||||
<string name="warning_not_paired">You haven't yet paired a Meshtastic compatible radio with this phone. Please pair a device and set your username.\n\nThis open-source application is in development, if you find problems please post on our forum: https://github.com/orgs/meshtastic/discussions.\n\nFor more information see our web page - www.meshtastic.org.</string>
|
||||
<string name="you">You</string>
|
||||
<string name="analytics_okay">Allow analytics and crash reporting.</string>
|
||||
<string name="accept">Accept</string>
|
||||
@@ -198,14 +198,14 @@
|
||||
<string name="pairing_failed_try_again">Pairing failed, please select again</string>
|
||||
<string name="location_disabled">Location access is turned off, can not provide position to mesh.</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="new_node_seen">New Node Seen: %s</string>
|
||||
<string name="new_node_seen">New Node Seen: %1$s</string>
|
||||
<string name="disconnected">Disconnected</string>
|
||||
<string name="device_sleeping">Device sleeping</string>
|
||||
<string name="connected_count">Connected: %1$s online</string>
|
||||
<string name="ip_address">IP Address:</string>
|
||||
<string name="ip_port">Port:</string>
|
||||
<string name="connected">Connected</string>
|
||||
<string name="connected_to">Connected to radio (%s)</string>
|
||||
<string name="connected_to">Connected to radio (%1$s)</string>
|
||||
<string name="connection_status">Current connections:</string>
|
||||
<string name="wifi_ip">Wifi IP:</string>
|
||||
<string name="ethernet_ip">Ethernet IP:</string>
|
||||
@@ -246,7 +246,7 @@
|
||||
<string name="firmware_old">The radio firmware is too old to talk to this application. For more information on this see <a href="https://meshtastic.org/docs/getting-started/flashing-firmware">our Firmware Installation guide</a>.</string>
|
||||
<string name="okay">OK</string>
|
||||
<string name="must_set_region">You must set a region!</string>
|
||||
<string name="cant_change_no_radio">Couldn\'t change channel, because radio is not yet connected. Please try again.</string>
|
||||
<string name="cant_change_no_radio">Couldn't change channel, because radio is not yet connected. Please try again.</string>
|
||||
<string name="save_rangetest">Export rangetest packets</string>
|
||||
<string name="export_data_csv">Export all packets</string>
|
||||
<string name="reset">Reset</string>
|
||||
@@ -263,7 +263,7 @@
|
||||
<string name="provide_location_to_mesh">Provide phone location to mesh</string>
|
||||
<plurals name="delete_messages">
|
||||
<item quantity="one">Delete message?</item>
|
||||
<item quantity="other">Delete %s messages?</item>
|
||||
<item quantity="other">Delete %1$s messages?</item>
|
||||
</plurals>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="delete_for_everyone">Delete for everyone</string>
|
||||
@@ -297,15 +297,15 @@
|
||||
<string name="bluetooth_disabled">Bluetooth is disabled. Please enable it in your device settings.</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="firmware_version">Firmware version: %1$s</string>
|
||||
<string name="permission_missing_31">Meshtastic needs \"Nearby devices\" permissions enabled to find and connect to devices via Bluetooth. You can disable when not in use.</string>
|
||||
<string name="permission_missing_31">Meshtastic needs "Nearby devices" permissions enabled to find and connect to devices via Bluetooth. You can disable when not in use.</string>
|
||||
<string name="direct_message">Direct Message</string>
|
||||
<string name="nodedb_reset">NodeDB reset</string>
|
||||
<string name="delivery_confirmed">Delivery confirmed</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="ignore">Ignore</string>
|
||||
<string name="remove_ignored">Remove from ignored</string>
|
||||
<string name="ignore_add">Add \'%s\' to ignore list?</string>
|
||||
<string name="ignore_remove">Remove \'%s\' from ignore list?</string>
|
||||
<string name="ignore_add">Add '%1$s' to ignore list?</string>
|
||||
<string name="ignore_remove">Remove '%1$s' from ignore list?</string>
|
||||
<string name="map_select_download_region">Select download region</string>
|
||||
<string name="map_tile_download_estimate">Tile download estimate:</string>
|
||||
<string name="map_start_download">Start Download</string>
|
||||
@@ -318,10 +318,10 @@
|
||||
<string name="calculating">Calculating…</string>
|
||||
<string name="map_offline_manager">Offline Manager</string>
|
||||
<string name="map_cache_size">Current Cache size</string>
|
||||
<string name="map_cache_info">Cache Capacity: %1$.2f MB\nCache Usage: %2$.2f MB</string>
|
||||
<string name="map_cache_info">Cache Capacity: %1$d MB\nCache Usage: %2$d MB</string>
|
||||
<string name="map_clear_tiles">Clear Downloaded Tiles</string>
|
||||
<string name="map_tile_source">Tile Source</string>
|
||||
<string name="map_purge_success">SQL Cache purged for %s</string>
|
||||
<string name="map_purge_success">SQL Cache purged for %1$s</string>
|
||||
<string name="map_purge_fail">SQL Cache purge failed, see logcat for details</string>
|
||||
<string name="map_cache_manager">Cache Manager</string>
|
||||
<string name="map_download_complete">Download complete!</string>
|
||||
@@ -331,7 +331,7 @@
|
||||
<string name="waypoint_edit">Edit waypoint</string>
|
||||
<string name="waypoint_delete">Delete waypoint?</string>
|
||||
<string name="waypoint_new">New waypoint</string>
|
||||
<string name="waypoint_received">Received waypoint: %s</string>
|
||||
<string name="waypoint_received">Received waypoint: %1$s</string>
|
||||
<string name="error_duty_cycle">Duty Cycle limit reached. Cannot send messages right now, please try again later.</string>
|
||||
<string name="remove">Remove</string>
|
||||
<string name="remove_node_text">This node will be removed from your list until your node receives data from it again.</string>
|
||||
@@ -412,8 +412,8 @@
|
||||
<string name="favorite">Favorite</string>
|
||||
<string name="add_favorite">Add to favorites</string>
|
||||
<string name="remove_favorite">Remove from favorites</string>
|
||||
<string name="favorite_add">Add \'%s\' as a favorite node?</string>
|
||||
<string name="favorite_remove">Remove \'%s\' as a favorite node?</string>
|
||||
<string name="favorite_add">Add '%1$s' as a favorite node?</string>
|
||||
<string name="favorite_remove">Remove '%1$s' as a favorite node?</string>
|
||||
<string name="power_metrics_log">Power Metrics Log</string>
|
||||
<string name="channel_1">Channel 1</string>
|
||||
<string name="channel_2">Channel 2</string>
|
||||
@@ -422,10 +422,10 @@
|
||||
<string name="voltage">Voltage</string>
|
||||
<string name="are_you_sure">Are you sure?</string>
|
||||
<string name="router_role_confirmation_text"><![CDATA[I have read the <a href="https://meshtastic.org/docs/configuration/radio/device/#roles">Device Role Documentation</a> and the blog post about <a href="http://meshtastic.org/blog/choosing-the-right-device-role">Choosing The Right Device Role</a>.]]></string>
|
||||
<string name="i_know_what_i_m_doing">I know what I\'m doing.</string>
|
||||
<string name="i_know_what_i_m_doing">I know what I'm doing.</string>
|
||||
<string name="low_battery_message">Node %1$s has a low battery (%2$d%%)</string>
|
||||
<string name="meshtastic_low_battery_notifications">Low battery notifications</string>
|
||||
<string name="low_battery_title">Low battery: %s</string>
|
||||
<string name="low_battery_title">Low battery: %1$s</string>
|
||||
<string name="meshtastic_low_battery_temporary_remote_notifications">Low battery notifications (favorite nodes)</string>
|
||||
<string name="baro_pressure">Barometric Pressure</string>
|
||||
<string name="udp_enabled">Enabled</string>
|
||||
@@ -866,7 +866,7 @@
|
||||
<string name="critical_alerts_description">Select packets sent as critical will ignore the mute switch and Do Not Disturb settings in the OS notification center.</string>
|
||||
<string name="configure_notification_permissions">Configure notification permissions</string>
|
||||
<string name="phone_location">Phone Location</string>
|
||||
<string name="phone_location_description">Meshtastic uses your phone\'s location to enable a number of features. You can update your location permissions at any time from settings.</string>
|
||||
<string name="phone_location_description">Meshtastic uses your phone's location to enable a number of features. You can update your location permissions at any time from settings.</string>
|
||||
<string name="share_location">Share Location</string>
|
||||
<string name="share_location_description">Use your phone GPS to send locations to your node to instead of using a hardware GPS on your node.</string>
|
||||
<string name="distance_measurements">Distance Measurements</string>
|
||||
@@ -922,7 +922,7 @@
|
||||
<string name="channel_features">Channel Features</string>
|
||||
<string name="location_sharing">Location Sharing</string>
|
||||
<string name="periodic_position_broadcast">Periodic position broadcast</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node\'s configured gateway.</string>
|
||||
<string name="uplink_feature_description">Messages from the mesh will be sent to the public internet through any node's configured gateway.</string>
|
||||
<string name="downlink_feature_description">Messages from a public internet gateway are forwarded to the local mesh. Due to the zero-hop policy, traffic from the default MQTT server will not propagate further than this device.</string>
|
||||
<string name="icon_meanings">Icon Meanings</string>
|
||||
<string name="secondary_channel_position_feature">Disabling position on the primary channel allows periodic position broadcasts on the first secondary channel with the position enabled, otherwise manual position request required.</string>
|
||||
@@ -935,7 +935,7 @@
|
||||
<string name="eight_hours">8 Hours</string>
|
||||
<string name="one_day">24 Hours</string>
|
||||
<string name="two_days">48 Hours</string>
|
||||
<string name="last_heard_filter_label">Filter by Last Heard time: %s</string>
|
||||
<string name="last_heard_filter_label">Filter by Last Heard time: %1$s</string>
|
||||
<string name="dbm_value">%1$d dBm</string>
|
||||
<string name="error_no_app_to_handle_link">No application available to handle link.</string>
|
||||
<string name="system_settings">System Settings</string>
|
||||
@@ -949,6 +949,6 @@
|
||||
<string name="for_more_information_see_our_privacy_policy">For more information, see our privacy policy.</string>
|
||||
<string name="privacy_url" translatable="false">" https://meshtastic.org/docs/legal/privacy/"</string>
|
||||
<string name="unset">Unset - 0</string>
|
||||
<string name="relayed_by">Relayed by: %s</string>
|
||||
<string name="relayed_by">Relayed by: %1$s</string>
|
||||
<string name="preserve_favorites">Preserve Favorites?</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,16 +18,20 @@
|
||||
package org.meshtastic.feature.map
|
||||
|
||||
import android.Manifest // Added for Accompanist
|
||||
import android.content.Context
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Lens
|
||||
import androidx.compose.material.icons.filled.LocationDisabled
|
||||
@@ -36,17 +40,26 @@ import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material.icons.outlined.Layers
|
||||
import androidx.compose.material.icons.outlined.MyLocation
|
||||
import androidx.compose.material.icons.outlined.Tune
|
||||
import androidx.compose.material.icons.rounded.Check
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableDoubleStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -57,7 +70,6 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
@@ -65,8 +77,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.meshtastic.core.strings.getString
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.gpsDisabled
|
||||
@@ -106,6 +118,8 @@ import org.meshtastic.core.strings.show_waypoints
|
||||
import org.meshtastic.core.strings.toggle_my_position
|
||||
import org.meshtastic.core.strings.waypoint_delete
|
||||
import org.meshtastic.core.strings.you
|
||||
import org.meshtastic.core.ui.component.BasicListItem
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.util.showToast
|
||||
import org.meshtastic.feature.map.cluster.RadiusMarkerClusterer
|
||||
import org.meshtastic.feature.map.component.CacheLayout
|
||||
@@ -194,41 +208,6 @@ private fun cacheManagerCallback(onTaskComplete: () -> Unit, onTaskFailed: (Int)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.purgeTileSource(onResult: (String) -> Unit) {
|
||||
val cache = SqlTileWriterExt()
|
||||
val builder = MaterialAlertDialogBuilder(this)
|
||||
builder.setTitle(getString(Res.string.map_tile_source))
|
||||
val sources = cache.sources
|
||||
val sourceList = mutableListOf<String>()
|
||||
for (i in sources.indices) {
|
||||
sourceList.add(sources[i].source as String)
|
||||
}
|
||||
val selected: BooleanArray? = null
|
||||
val selectedList = mutableListOf<Int>()
|
||||
builder.setMultiChoiceItems(sourceList.toTypedArray(), selected) { _, i, b ->
|
||||
if (b) {
|
||||
selectedList.add(i)
|
||||
} else {
|
||||
selectedList.remove(i)
|
||||
}
|
||||
}
|
||||
builder.setPositiveButton(getString(Res.string.clear)) { _, _ ->
|
||||
for (x in selectedList) {
|
||||
val item = sources[x]
|
||||
val b = cache.purgeCache(item.source)
|
||||
onResult(
|
||||
if (b) {
|
||||
getString(Res.string.map_purge_success, item.source.toString())
|
||||
} else {
|
||||
getString(Res.string.map_purge_fail)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
builder.setNegativeButton(getString(Res.string.cancel)) { dialog, _ -> dialog.cancel() }
|
||||
builder.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Main composable for displaying the map view, including nodes, waypoints, and user location. It handles user
|
||||
* interactions for map manipulation, filtering, and offline caching.
|
||||
@@ -255,11 +234,13 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
|
||||
var showDownloadButton: Boolean by remember { mutableStateOf(false) }
|
||||
var showEditWaypointDialog by remember { mutableStateOf<Waypoint?>(null) }
|
||||
var showCacheManagerDialog by remember { mutableStateOf(false) }
|
||||
var showCurrentCacheInfo by remember { mutableStateOf(false) }
|
||||
var showPurgeTileSourceDialog by remember { mutableStateOf(false) }
|
||||
var showMapStyleDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val density = LocalDensity.current
|
||||
|
||||
val haptic = LocalHapticFeedback.current
|
||||
@@ -360,7 +341,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
id = u.id
|
||||
title = u.longName
|
||||
snippet =
|
||||
resources.getString(
|
||||
com.meshtastic.core.strings.getString(
|
||||
Res.string.map_node_popup_details,
|
||||
node.gpsString(),
|
||||
formatAgo(node.lastHeard),
|
||||
@@ -369,7 +350,11 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
)
|
||||
ourNode?.distanceStr(node, displayUnits)?.let { dist ->
|
||||
subDescription =
|
||||
resources.getString(Res.string.map_subDescription, ourNode.bearing(node).toString(), dist)
|
||||
com.meshtastic.core.strings.getString(
|
||||
Res.string.map_subDescription,
|
||||
ourNode.bearing(node).toString(),
|
||||
dist,
|
||||
)
|
||||
}
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
position = nodePosition
|
||||
@@ -390,16 +375,16 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
|
||||
fun showDeleteMarkerDialog(waypoint: Waypoint) {
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
builder.setTitle(resources.getString(Res.string.waypoint_delete))
|
||||
builder.setNeutralButton(resources.getString(Res.string.cancel)) { _, _ ->
|
||||
builder.setTitle(com.meshtastic.core.strings.getString(Res.string.waypoint_delete))
|
||||
builder.setNeutralButton(com.meshtastic.core.strings.getString(Res.string.cancel)) { _, _ ->
|
||||
Timber.d("User canceled marker delete dialog")
|
||||
}
|
||||
builder.setNegativeButton(resources.getString(Res.string.delete_for_me)) { _, _ ->
|
||||
builder.setNegativeButton(com.meshtastic.core.strings.getString(Res.string.delete_for_me)) { _, _ ->
|
||||
Timber.d("User deleted waypoint ${waypoint.id} for me")
|
||||
mapViewModel.deleteWaypoint(waypoint.id)
|
||||
}
|
||||
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
|
||||
builder.setPositiveButton(resources.getString(Res.string.delete_for_everyone)) { _, _ ->
|
||||
builder.setPositiveButton(com.meshtastic.core.strings.getString(Res.string.delete_for_everyone)) { _, _ ->
|
||||
Timber.d("User deleted waypoint ${waypoint.id} for everyone")
|
||||
mapViewModel.sendWaypoint(waypoint.copy { expire = 1 })
|
||||
mapViewModel.deleteWaypoint(waypoint.id)
|
||||
@@ -434,7 +419,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
}
|
||||
|
||||
fun getUsername(id: String?) = if (id == DataPacket.ID_LOCAL) {
|
||||
resources.getString(Res.string.you)
|
||||
com.meshtastic.core.strings.getString(Res.string.you)
|
||||
} else {
|
||||
mapViewModel.getUser(id).longName
|
||||
}
|
||||
@@ -485,30 +470,6 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(showCurrentCacheInfo) {
|
||||
if (!showCurrentCacheInfo) return@LaunchedEffect
|
||||
context.showToast(Res.string.calculating)
|
||||
val cacheManager = CacheManager(map)
|
||||
val cacheCapacity = cacheManager.cacheCapacity()
|
||||
val currentCacheUsage = cacheManager.currentCacheUsage()
|
||||
|
||||
val mapCacheInfoText =
|
||||
getString(
|
||||
Res.string.map_cache_info,
|
||||
cacheCapacity / (1024.0 * 1024.0),
|
||||
currentCacheUsage / (1024.0 * 1024.0),
|
||||
)
|
||||
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(resources.getString(Res.string.map_cache_manager))
|
||||
.setMessage(mapCacheInfoText)
|
||||
.setPositiveButton(resources.getString(Res.string.close)) { dialog, _ ->
|
||||
showCurrentCacheInfo = false
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
val mapEventsReceiver =
|
||||
object : MapEventsReceiver {
|
||||
override fun singleTapConfirmedHelper(p: GeoPoint): Boolean {
|
||||
@@ -564,7 +525,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
val tileCount: Int =
|
||||
CacheManager(this)
|
||||
.possibleTilesInArea(downloadRegionBoundingBox, zoomLevelMin.toInt(), zoomLevelMax.toInt())
|
||||
cacheEstimate = resources.getString(Res.string.map_cache_tiles, tileCount)
|
||||
cacheEstimate = com.meshtastic.core.strings.getString(Res.string.map_cache_tiles, tileCount)
|
||||
}
|
||||
|
||||
val boxOverlayListener =
|
||||
@@ -612,49 +573,9 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
}
|
||||
}
|
||||
|
||||
fun showMapStyleDialog() {
|
||||
val builder = MaterialAlertDialogBuilder(context)
|
||||
val mapStyles: Array<CharSequence> = CustomTileSource.mTileSources.values.toTypedArray()
|
||||
|
||||
val mapStyleInt = mapViewModel.mapStyleId
|
||||
builder.setSingleChoiceItems(mapStyles, mapStyleInt) { dialog, which ->
|
||||
Timber.d("Set mapStyleId pref to $which")
|
||||
mapViewModel.mapStyleId = which
|
||||
dialog.dismiss()
|
||||
map.setTileSource(loadOnlineTileSourceBase())
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
fun Context.showCacheManagerDialog() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(resources.getString(Res.string.map_offline_manager))
|
||||
.setItems(
|
||||
arrayOf<CharSequence>(
|
||||
getString(Res.string.map_cache_size),
|
||||
getString(Res.string.map_download_region),
|
||||
getString(Res.string.map_clear_tiles),
|
||||
getString(Res.string.cancel),
|
||||
),
|
||||
) { dialog, which ->
|
||||
when (which) {
|
||||
0 -> showCurrentCacheInfo = true
|
||||
1 -> {
|
||||
map.generateBoxOverlay()
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
||||
2 -> purgeTileSource { scope.launch { context.showToast(it) } }
|
||||
else -> dialog.dismiss()
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
DownloadButton(showDownloadButton && downloadRegionBoundingBox == null) { context.showCacheManagerDialog() }
|
||||
DownloadButton(showDownloadButton && downloadRegionBoundingBox == null) { showCacheManagerDialog = true }
|
||||
},
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
|
||||
@@ -685,7 +606,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
MapButton(
|
||||
onClick = ::showMapStyleDialog,
|
||||
onClick = { showMapStyleDialog = true },
|
||||
icon = Icons.Outlined.Layers,
|
||||
contentDescription = Res.string.map_style_selection,
|
||||
)
|
||||
@@ -800,6 +721,44 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
}
|
||||
}
|
||||
|
||||
if (showMapStyleDialog) {
|
||||
MapStyleDialog(
|
||||
selectedMapStyle = mapViewModel.mapStyleId,
|
||||
onDismiss = { showMapStyleDialog = false },
|
||||
onSelectMapStyle = {
|
||||
mapViewModel.mapStyleId = it
|
||||
map.setTileSource(loadOnlineTileSourceBase())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showCacheManagerDialog) {
|
||||
CacheManagerDialog(
|
||||
onClickOption = { option ->
|
||||
when (option) {
|
||||
CacheManagerOption.CurrentCacheSize -> {
|
||||
scope.launch { context.showToast(Res.string.calculating) }
|
||||
showCurrentCacheInfo = true
|
||||
}
|
||||
CacheManagerOption.DownloadRegion -> map.generateBoxOverlay()
|
||||
|
||||
CacheManagerOption.ClearTiles -> showPurgeTileSourceDialog = true
|
||||
CacheManagerOption.Cancel -> Unit
|
||||
}
|
||||
showCacheManagerDialog = false
|
||||
},
|
||||
onDismiss = { showCacheManagerDialog = false },
|
||||
)
|
||||
}
|
||||
|
||||
if (showCurrentCacheInfo) {
|
||||
CacheInfoDialog(mapView = map, onDismiss = { showCurrentCacheInfo = false })
|
||||
}
|
||||
|
||||
if (showPurgeTileSourceDialog) {
|
||||
PurgeTileSourceDialog(onDismiss = { showPurgeTileSourceDialog = false })
|
||||
}
|
||||
|
||||
if (showEditWaypointDialog != null) {
|
||||
EditWaypointDialog(
|
||||
waypoint = showEditWaypointDialog ?: return, // Safe call
|
||||
@@ -828,3 +787,159 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MapStyleDialog(selectedMapStyle: Int, onDismiss: () -> Unit, onSelectMapStyle: (Int) -> Unit) {
|
||||
val selected = remember { mutableStateOf(selectedMapStyle) }
|
||||
|
||||
MapsDialog(onDismiss = onDismiss) {
|
||||
CustomTileSource.mTileSources.values.forEachIndexed { index, style ->
|
||||
ListItem(
|
||||
text = style,
|
||||
trailingIcon = if (index == selected.value) Icons.Rounded.Check else null,
|
||||
onClick = {
|
||||
selected.value = index
|
||||
onSelectMapStyle(index)
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum class CacheManagerOption(val label: StringResource) {
|
||||
CurrentCacheSize(label = Res.string.map_cache_size),
|
||||
DownloadRegion(label = Res.string.map_download_region),
|
||||
ClearTiles(label = Res.string.map_clear_tiles),
|
||||
Cancel(label = Res.string.cancel),
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CacheManagerDialog(onClickOption: (CacheManagerOption) -> Unit, onDismiss: () -> Unit) {
|
||||
MapsDialog(title = stringResource(Res.string.map_offline_manager), onDismiss = onDismiss) {
|
||||
CacheManagerOption.entries.forEach { option ->
|
||||
ListItem(text = stringResource(option.label), trailingIcon = null) {
|
||||
onClickOption(option)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CacheInfoDialog(mapView: MapView, onDismiss: () -> Unit) {
|
||||
val (cacheCapacity, currentCacheUsage) =
|
||||
remember(mapView) {
|
||||
val cacheManager = CacheManager(mapView)
|
||||
cacheManager.cacheCapacity() to cacheManager.currentCacheUsage()
|
||||
}
|
||||
|
||||
MapsDialog(
|
||||
title = stringResource(Res.string.map_cache_manager),
|
||||
onDismiss = onDismiss,
|
||||
negativeButton = { TextButton(onClick = { onDismiss() }) { Text(text = stringResource(Res.string.close)) } },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
text =
|
||||
stringResource(
|
||||
Res.string.map_cache_info,
|
||||
cacheCapacity / (1024.0 * 1024.0),
|
||||
currentCacheUsage / (1024.0 * 1024.0),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun PurgeTileSourceDialog(onDismiss: () -> Unit) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val cache = SqlTileWriterExt()
|
||||
|
||||
val sourceList by derivedStateOf { cache.sources.map { it.source as String } }
|
||||
|
||||
val selected = remember { mutableStateListOf<Int>() }
|
||||
|
||||
MapsDialog(
|
||||
title = stringResource(Res.string.map_tile_source),
|
||||
positiveButton = {
|
||||
TextButton(
|
||||
enabled = selected.isNotEmpty(),
|
||||
onClick = {
|
||||
selected.forEach { selectedIndex ->
|
||||
val source = sourceList[selectedIndex]
|
||||
scope.launch {
|
||||
context.showToast(
|
||||
if (cache.purgeCache(source)) {
|
||||
getString(Res.string.map_purge_success, source)
|
||||
} else {
|
||||
getString(Res.string.map_purge_fail)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
onDismiss()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(Res.string.clear))
|
||||
}
|
||||
},
|
||||
negativeButton = { TextButton(onClick = onDismiss) { Text(text = stringResource(Res.string.cancel)) } },
|
||||
onDismiss = onDismiss,
|
||||
) {
|
||||
sourceList.forEachIndexed { index, source ->
|
||||
val isSelected = selected.contains(index)
|
||||
BasicListItem(
|
||||
text = source,
|
||||
trailingContent = { Checkbox(checked = isSelected, onCheckedChange = {}) },
|
||||
onClick = {
|
||||
if (isSelected) {
|
||||
selected.remove(index)
|
||||
} else {
|
||||
selected.add(index)
|
||||
}
|
||||
},
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MapsDialog(
|
||||
title: String? = null,
|
||||
onDismiss: () -> Unit,
|
||||
positiveButton: (@Composable () -> Unit)? = null,
|
||||
negativeButton: (@Composable () -> Unit)? = null,
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||
Surface(
|
||||
modifier = Modifier.wrapContentWidth().wrapContentHeight(),
|
||||
shape = MaterialTheme.shapes.large,
|
||||
color = AlertDialogDefaults.containerColor,
|
||||
tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||
) {
|
||||
Column {
|
||||
title?.let {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||
text = it,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { content() }
|
||||
if (positiveButton != null || negativeButton != null) {
|
||||
Row(Modifier.align(Alignment.End)) {
|
||||
positiveButton?.invoke()
|
||||
negativeButton?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,10 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -42,7 +45,13 @@ import androidx.compose.material.icons.rounded.LocationOn
|
||||
import androidx.compose.material.icons.rounded.Memory
|
||||
import androidx.compose.material.icons.rounded.Output
|
||||
import androidx.compose.material.icons.rounded.WavingHand
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -53,16 +62,15 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
import com.meshtastic.core.strings.getString
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.gpsDisabled
|
||||
import org.meshtastic.core.database.DatabaseConstants
|
||||
@@ -95,7 +103,6 @@ import org.meshtastic.core.strings.theme_system
|
||||
import org.meshtastic.core.ui.component.DropDownPreference
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.MainAppBar
|
||||
import org.meshtastic.core.ui.component.MultipleChoiceAlertDialog
|
||||
import org.meshtastic.core.ui.component.SwitchListItem
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.MODE_DYNAMIC
|
||||
@@ -106,7 +113,7 @@ import org.meshtastic.feature.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.feature.settings.radio.component.EditDeviceProfileDialog
|
||||
import org.meshtastic.feature.settings.radio.component.PacketResponseStateDialog
|
||||
import org.meshtastic.feature.settings.util.LanguageUtils
|
||||
import org.meshtastic.feature.settings.util.LanguageUtils.getLanguageMap
|
||||
import org.meshtastic.feature.settings.util.LanguageUtils.languageMap
|
||||
import org.meshtastic.proto.ClientOnlyProtos.DeviceProfile
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
@@ -260,7 +267,6 @@ fun SettingsScreen(
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
|
||||
TitledCard(title = stringResource(Res.string.app_settings), modifier = Modifier.padding(top = 16.dp)) {
|
||||
@@ -470,39 +476,54 @@ private fun AppVersionButton(
|
||||
|
||||
@Composable
|
||||
private fun LanguagePickerDialog(onDismiss: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val choices = remember {
|
||||
context
|
||||
.getLanguageMap()
|
||||
.map { (languageTag, languageName) -> languageName to { LanguageUtils.setAppLocale(languageTag) } }
|
||||
.toMap()
|
||||
SettingsDialog(title = stringResource(Res.string.preferences_language), onDismiss = onDismiss) {
|
||||
languageMap().forEach { (languageTag, languageName) ->
|
||||
ListItem(text = languageName, trailingIcon = null) {
|
||||
LanguageUtils.setAppLocale(languageTag)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MultipleChoiceAlertDialog(
|
||||
title = stringResource(Res.string.preferences_language),
|
||||
message = "",
|
||||
choices = choices,
|
||||
onDismissRequest = onDismiss,
|
||||
)
|
||||
private enum class ThemeOption(val label: StringResource, val mode: Int) {
|
||||
DYNAMIC(label = Res.string.dynamic, mode = MODE_DYNAMIC),
|
||||
LIGHT(label = Res.string.theme_light, mode = AppCompatDelegate.MODE_NIGHT_NO),
|
||||
DARK(label = Res.string.theme_dark, mode = AppCompatDelegate.MODE_NIGHT_YES),
|
||||
SYSTEM(label = Res.string.theme_system, mode = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM),
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ThemePickerDialog(onClickTheme: (Int) -> Unit, onDismiss: () -> Unit) {
|
||||
val resources = LocalResources.current
|
||||
val themeMap = remember {
|
||||
mapOf(
|
||||
resources.getString(Res.string.dynamic) to MODE_DYNAMIC,
|
||||
resources.getString(Res.string.theme_light) to AppCompatDelegate.MODE_NIGHT_NO,
|
||||
resources.getString(Res.string.theme_dark) to AppCompatDelegate.MODE_NIGHT_YES,
|
||||
resources.getString(Res.string.theme_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
|
||||
)
|
||||
.mapValues { (_, value) -> { onClickTheme(value) } }
|
||||
SettingsDialog(title = stringResource(Res.string.choose_theme), onDismiss = onDismiss) {
|
||||
ThemeOption.entries.forEach { option ->
|
||||
ListItem(text = stringResource(option.label), trailingIcon = null) {
|
||||
onClickTheme(option.mode)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SettingsDialog(title: String, onDismiss: () -> Unit, content: @Composable ColumnScope.() -> Unit) {
|
||||
BasicAlertDialog(onDismissRequest = onDismiss) {
|
||||
Surface(
|
||||
modifier = Modifier.wrapContentWidth().wrapContentHeight(),
|
||||
shape = MaterialTheme.shapes.large,
|
||||
color = AlertDialogDefaults.containerColor,
|
||||
tonalElevation = AlertDialogDefaults.TonalElevation,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 16.dp, top = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { content() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MultipleChoiceAlertDialog(
|
||||
title = stringResource(Res.string.choose_theme),
|
||||
message = "",
|
||||
choices = themeMap,
|
||||
onDismissRequest = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
|
||||
package org.meshtastic.feature.settings.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.meshtastic.core.strings.getString
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.fr_HT
|
||||
import org.meshtastic.core.strings.preferences_system_default
|
||||
@@ -47,33 +49,38 @@ object LanguageUtils {
|
||||
|
||||
/** Using locales_config.xml, maps language tags to their localized language names (e.g.: "en" -> "English") */
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
fun Context.getLanguageMap(): Map<String, String> {
|
||||
val languageTags = buildList {
|
||||
add(SYSTEM_DEFAULT)
|
||||
@Composable
|
||||
fun languageMap(): Map<String, String> {
|
||||
val resources = LocalResources.current
|
||||
val languageTags =
|
||||
remember(resources) {
|
||||
buildList {
|
||||
add(SYSTEM_DEFAULT)
|
||||
|
||||
try {
|
||||
resources.getXml(org.meshtastic.feature.settings.R.xml.locales_config).use { parser ->
|
||||
while (parser.eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
||||
val languageTag =
|
||||
parser.getAttributeValue("http://schemas.android.com/apk/res/android", "name")
|
||||
languageTag?.let { add(it) }
|
||||
try {
|
||||
resources.getXml(org.meshtastic.feature.settings.R.xml.locales_config).use { parser ->
|
||||
while (parser.eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
||||
val languageTag =
|
||||
parser.getAttributeValue("http://schemas.android.com/apk/res/android", "name")
|
||||
languageTag?.let { add(it) }
|
||||
}
|
||||
parser.next()
|
||||
}
|
||||
}
|
||||
parser.next()
|
||||
} catch (e: Exception) {
|
||||
Timber.e("Error parsing locale_config.xml: ${e.message}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e("Error parsing locale_config.xml: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
return languageTags.associateWith { languageTag ->
|
||||
when (languageTag) {
|
||||
SYSTEM_DEFAULT -> getString(Res.string.preferences_system_default)
|
||||
"fr-HT" -> getString(Res.string.fr_HT)
|
||||
"pt-BR" -> getString(Res.string.pt_BR)
|
||||
"zh-CN" -> getString(Res.string.zh_CN)
|
||||
"zh-TW" -> getString(Res.string.zh_TW)
|
||||
SYSTEM_DEFAULT -> stringResource(Res.string.preferences_system_default)
|
||||
"fr-HT" -> stringResource(Res.string.fr_HT)
|
||||
"pt-BR" -> stringResource(Res.string.pt_BR)
|
||||
"zh-CN" -> stringResource(Res.string.zh_CN)
|
||||
"zh-TW" -> stringResource(Res.string.zh_TW)
|
||||
else -> {
|
||||
Locale.forLanguageTag(languageTag).let { locale ->
|
||||
locale.getDisplayLanguage(locale).replaceFirstChar { char ->
|
||||
|
||||
Reference in New Issue
Block a user