diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d508831..e7d3022 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ No programming experience is required, translations can be done directly in the ## Notes -- For larger changes, please open an Issue in github first to discuss. +- For larger changes, please open an Issue in GitHub first to discuss. - All derivative work must remain open-source under GPLv3. Thanks for helping to improve MBCompass! \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8bbe125..e728bca 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,10 +108,8 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.runtime.compose) - // Dagger Hilt implementation(libs.hilt.android) - implementation(libs.androidx.coordinatorlayout) ksp(libs.hilt.android.compiler) // Android UI ViewBinding diff --git a/app/src/main/java/com/mubarak/mbcompass/core/location/LocationHelper.kt b/app/src/main/java/com/mubarak/mbcompass/core/location/LocationHelper.kt index 3ee8ac6..210991b 100644 --- a/app/src/main/java/com/mubarak/mbcompass/core/location/LocationHelper.kt +++ b/app/src/main/java/com/mubarak/mbcompass/core/location/LocationHelper.kt @@ -6,7 +6,6 @@ import android.content.pm.PackageManager import android.location.Location import android.location.LocationManager import android.os.Build -import android.os.Bundle import android.os.SystemClock import android.util.Log import androidx.core.content.ContextCompat @@ -190,7 +189,7 @@ object LocationHelper { - // Calculate distance between two points (Haversine formula) + // Calculate distance between two points fun calculateDistance(previousLocation: Location?, location: Location): Float { var distance = 0f if (previousLocation != null) { diff --git a/app/src/main/java/com/mubarak/mbcompass/core/permission/PermissionHandler.kt b/app/src/main/java/com/mubarak/mbcompass/core/permission/PermissionHandler.kt index 28e862c..36a5aed 100644 --- a/app/src/main/java/com/mubarak/mbcompass/core/permission/PermissionHandler.kt +++ b/app/src/main/java/com/mubarak/mbcompass/core/permission/PermissionHandler.kt @@ -20,7 +20,6 @@ class PermissionHandler( private val context: Context get() = fragment.requireContext() - fun requestLocationPermission( launcher: ActivityResultLauncher, onGranted: () -> Unit, diff --git a/app/src/main/java/com/mubarak/mbcompass/data/AppPreferences.kt b/app/src/main/java/com/mubarak/mbcompass/data/AppPreferences.kt index 3926a18..46ff75d 100644 --- a/app/src/main/java/com/mubarak/mbcompass/data/AppPreferences.kt +++ b/app/src/main/java/com/mubarak/mbcompass/data/AppPreferences.kt @@ -25,8 +25,6 @@ import kotlinx.coroutines.runBlocking object AppPreferences { - private const val TAG = "AppPreferences" - private val Context.dataStore: DataStore by preferencesDataStore(name = "user_preferences") private lateinit var prefDataStore: DataStore diff --git a/app/src/main/java/com/mubarak/mbcompass/features/compass/NavigationScreen.kt b/app/src/main/java/com/mubarak/mbcompass/features/compass/NavigationScreen.kt index 62c1386..812ec2c 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/compass/NavigationScreen.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/compass/NavigationScreen.kt @@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -38,10 +37,8 @@ import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SmallFloatingActionButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar @@ -68,6 +65,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.location.LocationManagerCompat +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner @@ -76,10 +74,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.mubarak.mbcompass.MainViewModel import com.mubarak.mbcompass.R -import com.mubarak.mbcompass.core.sensors.AndroidSensorEventListener -import com.mubarak.mbcompass.core.sensors.SensorViewModel import com.mubarak.mbcompass.core.location.AndroidLocationManager import com.mubarak.mbcompass.core.location.TAG +import com.mubarak.mbcompass.core.sensors.AndroidSensorEventListener +import com.mubarak.mbcompass.core.sensors.SensorViewModel import com.mubarak.mbcompass.features.settings.SettingsViewModel import com.mubarak.mbcompass.utils.Azimuth import com.mubarak.mbcompass.utils.CardinalDirection diff --git a/app/src/main/java/com/mubarak/mbcompass/features/map/MapFragment.kt b/app/src/main/java/com/mubarak/mbcompass/features/map/MapFragment.kt index 1171e53..8d858ef 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/map/MapFragment.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/map/MapFragment.kt @@ -154,8 +154,6 @@ class MapFragment : Fragment() { currentBestLocation = LocationHelper.getLastKnownLocation(requireContext()) trackingState = AppPreferences.loadTrackingState() permissionHandler = PermissionHandler(this) - - Log.d(TAG, "onCreate - NO permission request on launch") } override fun onCreateView( @@ -310,6 +308,7 @@ class MapFragment : Fragment() { // don't show start track button on TrackFragment val isViewOnlyMode = trackUriToDisplay != null btnStart.isVisible = !isViewOnlyMode + locationButton.isVisible = !isViewOnlyMode btnStart.setOnClickListener { handleStartButton() @@ -342,7 +341,9 @@ class MapFragment : Fragment() { permissionHandler.requestLocationPermission( launcher = locationPermissionLauncher, onGranted = { onLocationPermissionGranted() }, - onDenied = { /* User declined */ } + onDenied = { + Toast.makeText(requireContext(), R.string.location_permission_required, Toast.LENGTH_SHORT).show() + } ) } } @@ -364,7 +365,7 @@ class MapFragment : Fragment() { permissionHandler.requestLocationPermission( launcher = locationPermissionLauncher, onGranted = { startTracking(resume) }, - onDenied = { /* Cancelled */ } + onDenied = { Toast.makeText(requireContext(), R.string.location_permission_title, Toast.LENGTH_SHORT).show() } ) return } diff --git a/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsScreen.kt b/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsScreen.kt index 577914e..4a28761 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsScreen.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsScreen.kt @@ -66,12 +66,12 @@ import androidx.core.net.toUri import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.mubarak.mbcompass.R -import com.mubarak.mbcompass.features.settings.SettingsViewModel import com.mubarak.mbcompass.ui.theme.MBCompassTheme import com.mubarak.mbcompass.ui.theme.MBShapeDefaults.bottomListItemShape import com.mubarak.mbcompass.ui.theme.MBShapeDefaults.middleListItemShape import com.mubarak.mbcompass.ui.theme.MBShapeDefaults.singleListItemShape import com.mubarak.mbcompass.ui.theme.MBShapeDefaults.topListItemShape +import com.mubarak.mbcompass.ui.theme.ThemeConfig import com.mubarak.mbcompass.ui.theme.iconDefaultSize import com.mubarak.mbcompass.ui.theme.spacingMedium import com.mubarak.mbcompass.ui.theme.spacingSmall @@ -79,7 +79,6 @@ import com.mubarak.mbcompass.utils.Const.APP_PAGE import com.mubarak.mbcompass.utils.Const.AUTHOR_EMAIL import com.mubarak.mbcompass.utils.Const.LICENSE_PAGE import com.mubarak.mbcompass.utils.Const.SUPPORT_PAGE -import com.mubarak.mbcompass.ui.theme.ThemeConfig @Composable fun SettingsScreen( diff --git a/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsViewModel.kt b/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsViewModel.kt index 1b64dd6..51f83aa 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/settings/SettingsViewModel.kt @@ -30,7 +30,7 @@ class SettingsViewModel @Inject constructor( }.catch { Log.d("SettingsViewModel", "Error getting user preference", it) emit(SettingsUiState()) - }.stateIn(viewModelScope, SharingStarted.Companion.WhileSubscribed(5_000), SettingsUiState()) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), SettingsUiState()) fun setTheme(theme: String) { viewModelScope.launch { diff --git a/app/src/main/java/com/mubarak/mbcompass/features/track/ElevationChart.kt b/app/src/main/java/com/mubarak/mbcompass/features/track/ElevationChart.kt new file mode 100644 index 0000000..f035463 --- /dev/null +++ b/app/src/main/java/com/mubarak/mbcompass/features/track/ElevationChart.kt @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.mubarak.mbcompass.features.track + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.mubarak.mbcompass.features.tracks.model.WayPoint +import kotlin.math.* + +@Composable +fun ElevationChart( + waypoints: List, + modifier: Modifier = Modifier, + useMetric: Boolean = true +) { + val validPoints = remember(waypoints) { + waypoints.filter { it.altitude != 0.0 } + } + + if (validPoints.size < 2) { + Box(modifier = modifier, contentAlignment = Alignment.Center) { + Text( + text = "No elevation data", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + return + } + + val elevations = remember(validPoints) { + validPoints.map { it.altitude.toFloat() } + } + + val distances = remember(validPoints) { + var cum = 0f + val list = mutableListOf(0f) + for (i in 1 until validPoints.size) { + cum += calcDistance(validPoints[i - 1], validPoints[i]) + list.add(cum) + } + list + } + + val rawMin = elevations.min() + val rawMax = elevations.max() + val rawRange = (rawMax - rawMin).coerceAtLeast(1f) + val pad = rawRange * 0.15f + val minE = rawMin - pad + val maxE = rawMax + pad + val eRange = maxE - minE + + val totalDist = distances.last().coerceAtLeast(1f) + + val primaryColor = MaterialTheme.colorScheme.primary + val labelColor = MaterialTheme.colorScheme.onSurfaceVariant + + val leftPadDp = 44.dp + val bottomPadDp = 20.dp + val topPadDp = 8.dp + + BoxWithConstraints(modifier = modifier) { + val density = LocalDensity.current + + val lp = with(density) { leftPadDp.toPx() } + val bp = with(density) { bottomPadDp.toPx() } + val tp = with(density) { topPadDp.toPx() } + + val totalW = with(density) { maxWidth.toPx() } + val totalH = with(density) { maxHeight.toPx() } + + val chartW = totalW - lp + val chartH = totalH - bp - tp + + fun x(d: Float) = lp + (d / totalDist) * chartW + fun y(e: Float) = tp + chartH - ((e - minE) / eRange) * chartH + + Canvas(modifier = Modifier.fillMaxSize()) { + + val pts = elevations.indices.map { i -> + Offset(x(distances[i]), y(elevations[i])) + } + + val splinePath = catmullRomToBezier(pts) + + // fill path + val fillPath = Path().apply { + addPath(splinePath) + lineTo(pts.last().x, tp + chartH) + lineTo(pts.first().x, tp + chartH) + close() + } + + // gradient fil + drawPath( + path = fillPath, + brush = Brush.verticalGradient( + colorStops = arrayOf( + 0.0f to primaryColor.copy(alpha = 0.25f), + 0.6f to primaryColor.copy(alpha = 0.10f), + 1.0f to primaryColor.copy(alpha = 0.0f) + ), + startY = tp, + endY = tp + chartH + ) + ) + + drawPath( + path = splinePath, + color = primaryColor, + style = Stroke( + width = 2.5.dp.toPx(), + cap = StrokeCap.Round, + join = StrokeJoin.Round + ) + ) + + // baseline + drawLine( + color = primaryColor.copy(alpha = 0.15f), + start = Offset(lp, tp + chartH), + end = Offset(totalW, tp + chartH), + strokeWidth = 1.dp.toPx() + ) + + listOf(pts.first(), pts.last()).forEach { p -> + drawCircle(color = primaryColor, radius = 3.dp.toPx(), center = p) + drawCircle( + color = Color.White, + radius = 1.5.dp.toPx(), + center = p + ) + } + } + + // Yaxis labels + fun formatAlt(v: Float) = if (useMetric) "%.0f m".format(v) + else "%.0f ft".format(v * 3.28084f) + + Text( + text = formatAlt(rawMax), + fontSize = 9.sp, + color = labelColor, + modifier = Modifier + .align(Alignment.TopStart) + .padding(top = topPadDp, start = 2.dp) + ) + Text( + text = formatAlt(rawMin), + fontSize = 9.sp, + color = labelColor, + modifier = Modifier + .align(Alignment.BottomStart) + .padding(bottom = bottomPadDp + 4.dp, start = 2.dp) + ) + + // Xaxis labels + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomStart) + .padding(start = leftPadDp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + val steps = 3 + repeat(steps + 1) { i -> + val d = totalDist * i / steps + val label = if (useMetric) { + if (d < 1000f) "%.0f m".format(d) + else "%.1f km".format(d / 1000f) + } else { + "%.2f mi".format(d / 1609.34f) + } + Text(text = label, fontSize = 9.sp, color = labelColor) + } + } + } +} + + +// convert it to bezier https://cubic-bezier.com/ for smooth spine +private fun catmullRomToBezier(pts: List): Path { + val path = Path() + if (pts.size < 2) return path + + path.moveTo(pts[0].x, pts[0].y) + + for (i in 0 until pts.size - 1) { + val p0 = if (i == 0) pts[0] else pts[i - 1] + val p1 = pts[i] + val p2 = pts[i + 1] + val p3 = if (i + 2 < pts.size) pts[i + 2] else pts[i + 1] + + val cp1x = p1.x + (p2.x - p0.x) / 6f + val cp1y = p1.y + (p2.y - p0.y) / 6f + val cp2x = p2.x - (p3.x - p1.x) / 6f + val cp2y = p2.y - (p3.y - p1.y) / 6f + + path.cubicTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y) + } + + return path +} + +// calculate distance using Haversine formula +private fun calcDistance(p1: WayPoint, p2: WayPoint): Float { + val R = 6_371_000f + val lat1 = Math.toRadians(p1.latitude) + val lat2 = Math.toRadians(p2.latitude) + val dLat = Math.toRadians(p2.latitude - p1.latitude) + val dLon = Math.toRadians(p2.longitude - p1.longitude) + + val a = sin(dLat / 2).pow(2) + cos(lat1) * cos(lat2) * sin(dLon / 2).pow(2) + return (R * 2 * atan2(sqrt(a), sqrt(1 - a))).toFloat() +} \ No newline at end of file diff --git a/app/src/main/java/com/mubarak/mbcompass/features/track/TrackScreen.kt b/app/src/main/java/com/mubarak/mbcompass/features/track/TrackScreen.kt index 5f4a5a8..309e844 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/track/TrackScreen.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/track/TrackScreen.kt @@ -11,7 +11,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import com.mubarak.mbcompass.R import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -19,6 +18,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.fragment.app.FragmentActivity +import com.mubarak.mbcompass.R import com.mubarak.mbcompass.databinding.FragmentTrackContainerBinding @OptIn(ExperimentalMaterial3Api::class) diff --git a/app/src/main/java/com/mubarak/mbcompass/features/track/TrackStatsSheet.kt b/app/src/main/java/com/mubarak/mbcompass/features/track/TrackStatsSheet.kt new file mode 100644 index 0000000..80cf96b --- /dev/null +++ b/app/src/main/java/com/mubarak/mbcompass/features/track/TrackStatsSheet.kt @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package com.mubarak.mbcompass.features.track + +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.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.SheetState +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.mubarak.mbcompass.R +import com.mubarak.mbcompass.features.tracks.model.Track +import com.mubarak.mbcompass.utils.DateTimeFormatter +import com.mubarak.mbcompass.utils.LengthUnitHelper + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TrackStatsBottomSheet( + track: Track, + sheetState: SheetState, + onDismissRequest: () -> Unit, + onShareClick: () -> Unit, + onDeleteClick: () -> Unit, + modifier: Modifier = Modifier +) { + ModalBottomSheet( + onDismissRequest = onDismissRequest, + sheetState = sheetState, + modifier = modifier, + containerColor = MaterialTheme.colorScheme.surface, + tonalElevation = 0.dp, + dragHandle = { + Box( + modifier = Modifier + .padding(vertical = 16.dp) + .width(36.dp) + .height(4.dp) + .clip(RoundedCornerShape(2.dp)) + .background(MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)) + ) + } + ) { + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues( + start = 24.dp, + end = 24.dp, + bottom = 40.dp + ), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + + item { + Column { + Text( + text = track.name, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(Modifier.height(4.dp)) + Text( + text = DateTimeFormatter.formatDateTimeString(track.recordingStart), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + + item { + Spacer(Modifier.height(12.dp)) + StatsGrid(track = track) + } + + item { + ElevationCard(track = track) + } + + if (track.maxAltitude > 0.0) { + item { + AltitudeRangeCard( + minAltitude = track.minAltitude, + maxAltitude = track.maxAltitude + ) + } + } + + if (track.recordingStop != 0L) { + item { + RecordingInfoCard( + startTime = track.recordingStart, + endTime = track.recordingStop + ) + } + } + + item { + Spacer(Modifier.height(16.dp)) + ActionButtons( + onShareClick = onShareClick, + onDeleteClick = onDeleteClick + ) + } + } + } + +} + +@Composable +private fun StatsGrid(track: Track) { + val avgSpeed = if (track.duration > 0) + (track.length / 1000.0) / (track.duration / 3_600_000.0) + else 0.0 + + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + StatTile( + icon = R.drawable.ic_duration24px, + label = "Duration", + value = DateTimeFormatter.formatDurationTime(track.duration), + modifier = Modifier.weight(1f) + ) + StatTile( + icon = R.drawable.ic_distance_24px, + label = "Distance", + value = LengthUnitHelper.convertDistanceToString(track.length), + modifier = Modifier.weight(1f) + ) + } + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + StatTile( + icon = R.drawable.location_icon24px, + label = "Points", + value = track.wayPoints.size.toString(), + modifier = Modifier.weight(1f) + ) + StatTile( + icon = R.drawable.ic_speed_24px, + label = "Avg Speed", + value = "%.1f km/h".format(avgSpeed), + modifier = Modifier.weight(1f) + ) + } + } +} + +@Composable +private fun StatTile( + icon: Int, + label: String, + value: String, + modifier: Modifier = Modifier +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(16.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow, + tonalElevation = 0.dp + ) { + Column( + modifier = Modifier.padding(horizontal = 20.dp, vertical = 18.dp) + ) { + Icon( + painter = painterResource(id = icon), + contentDescription = label, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.primary + ) + Spacer(Modifier.height(12.dp)) + Text( + text = value, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(Modifier.height(2.dp)) + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +@Composable +private fun ElevationCard(track: Track) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow + ) { + Column(modifier = Modifier.padding(20.dp)) { + Text( + text = "Elevation", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(Modifier.height(16.dp)) + + ElevationChart( + waypoints = track.wayPoints, + useMetric = true, + modifier = Modifier + .fillMaxWidth() + .height(120.dp) + ) + + if (track.positiveElevation != 0.0 || track.negativeElevation != 0.0) { + Spacer(Modifier.height(20.dp)) + HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant) + Spacer(Modifier.height(20.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + ElevationStat( + icon = R.drawable.arrow_back_24px, + iconRotation = 90f, + label = "Ascent", + value = "+%.0f m".format(track.positiveElevation), + modifier = Modifier.weight(1f) + ) + ElevationStat( + icon = R.drawable.arrow_back_24px, + iconRotation = 270f, + label = "Descent", + value = "−%.0f m".format(kotlin.math.abs(track.negativeElevation)), + modifier = Modifier.weight(1f) + ) + } + } + } + } +} + +@Composable +private fun ElevationStat( + icon: Int, + iconRotation: Float, + label: String, + value: String, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Box( + modifier = Modifier + .size(36.dp) + .clip(RoundedCornerShape(10.dp)) + .background(MaterialTheme.colorScheme.secondaryContainer), + contentAlignment = Alignment.Center + ) { + Icon( + painter = painterResource(id = icon), + contentDescription = label, + modifier = Modifier + .size(18.dp) + .graphicsLayer { rotationZ = iconRotation }, + tint = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + Column { + Text( + text = value, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +@Composable +private fun AltitudeRangeCard(minAltitude: Double, maxAltitude: Double) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 18.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + AltitudeTile( + icon = R.drawable.altitude_low_24px, + label = "Lowest", + value = "%.0f m".format(minAltitude), + modifier = Modifier.weight(1f) + ) + VerticalDivider( + modifier = Modifier.height(52.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + AltitudeTile( + icon = R.drawable.altitude_high_24px, + label = "Highest", + value = "%.0f m".format(maxAltitude), + modifier = Modifier.weight(1f) + ) + } + } +} + +@Composable +private fun AltitudeTile( + icon: Int, + label: String, + value: String, + modifier: Modifier = Modifier +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + painter = painterResource(id = icon), + contentDescription = label, + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.primary + ) + Column { + Text( + text = value, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +@Composable +private fun RecordingInfoCard(startTime: Long, endTime: Long) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surfaceContainerLow + ) { + Column(modifier = Modifier.padding(horizontal = 20.dp, vertical = 18.dp)) { + Text( + text = "Recording", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface + ) + Spacer(Modifier.height(12.dp)) + RecordingRow(label = "Started", value = DateTimeFormatter.formatDateTimeString(startTime)) + Spacer(Modifier.height(6.dp)) + RecordingRow(label = "Ended", value = DateTimeFormatter.formatDateTimeString(endTime)) + } + } +} + +@Composable +private fun RecordingRow(label: String, value: String) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = label, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + Text( + text = value, + style = MaterialTheme.typography.bodySmall, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onSurface + ) + } +} + +@Composable +private fun ActionButtons( + onShareClick: () -> Unit, + onDeleteClick: () -> Unit +) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + Button( + onClick = onShareClick, + modifier = Modifier + .weight(1f) + .height(52.dp), + shape = RoundedCornerShape(16.dp) + ) { + Icon( + painter = painterResource(R.drawable.share_24px), + contentDescription = "Share", + modifier = Modifier.size(18.dp) + ) + Spacer(Modifier.width(8.dp)) + Text("Share", style = MaterialTheme.typography.labelLarge) + } + + OutlinedButton( + onClick = onDeleteClick, + modifier = Modifier + .weight(1f) + .height(52.dp), + shape = RoundedCornerShape(16.dp), + colors = ButtonDefaults.outlinedButtonColors( + contentColor = MaterialTheme.colorScheme.error + ), + border = androidx.compose.foundation.BorderStroke( + 1.dp, + MaterialTheme.colorScheme.error.copy(alpha = 0.5f) + ) + ) { + Icon( + painter = painterResource(R.drawable.delete_24px), + contentDescription = "Delete", + modifier = Modifier.size(18.dp) + ) + Spacer(Modifier.width(8.dp)) + Text("Delete", style = MaterialTheme.typography.labelLarge) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mubarak/mbcompass/features/track/TrackViewFragment.kt b/app/src/main/java/com/mubarak/mbcompass/features/track/TrackViewFragment.kt index f0a1750..97098db 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/track/TrackViewFragment.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/track/TrackViewFragment.kt @@ -2,7 +2,6 @@ package com.mubarak.mbcompass.features.track -import com.mubarak.mbcompass.R import android.app.Activity import android.app.AlertDialog import android.content.Intent @@ -12,22 +11,35 @@ import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View +import android.view.View.GONE +import android.view.View.VISIBLE import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageButton import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.core.content.FileProvider import androidx.core.net.toFile import androidx.core.net.toUri -import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import com.mubarak.mbcompass.R import com.mubarak.mbcompass.data.TrackRepository import com.mubarak.mbcompass.databinding.FragmentTrackViewBinding import com.mubarak.mbcompass.features.map.MapFragment import com.mubarak.mbcompass.features.tracks.model.Track -import com.mubarak.mbcompass.utils.DateTimeFormatter -import com.mubarak.mbcompass.utils.LengthUnitHelper import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -61,7 +73,8 @@ class TrackViewFragment : Fragment() { private var track: Track? = null private var trackUri: String? = null - // activity result launcher for saving GPX + private var fab: ImageButton? = null + private val saveGpxLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult(), this::handleSaveGpxResult @@ -86,17 +99,31 @@ class TrackViewFragment : Fragment() { return } - loadTrack(trackUri!!) setupMapFragment(trackUri!!) - setupActionButtons() + loadTrackAndSetupBottomSheet(trackUri!!) + setupFAB() } override fun onDestroyView() { super.onDestroyView() + fab = null _binding = null } - private fun loadTrack(trackUri: String) { +// reuse existing map fragment + private fun setupMapFragment(trackUri: String) { + val fragmentManager = childFragmentManager + val existingFragment = fragmentManager.findFragmentById(R.id.map_container) as? MapFragment + + if (existingFragment == null) { + fragmentManager.beginTransaction() + .replace(R.id.map_container, MapFragment.newInstance(trackUri)) + .commitNow() + + Log.d(TAG, "MapFragment added for track: $trackUri") + } + } + private fun loadTrackAndSetupBottomSheet(trackUri: String) { lifecycleScope.launch { try { val loadedTrack = withContext(Dispatchers.IO) { @@ -104,100 +131,109 @@ class TrackViewFragment : Fragment() { } track = loadedTrack - displayTrackStatistics(loadedTrack) + setupBottomSheet(loadedTrack) } catch (e: Exception) { Log.e(TAG, "Error loading track", e) Toast.makeText( requireContext(), - "Error loading track", + "Error loading track: ${e.message}", Toast.LENGTH_LONG ).show() } } } - private fun setupMapFragment(trackUri: String) { - val fragmentManager = childFragmentManager - val existingFragment = fragmentManager.findFragmentById(R.id.map_container) as? MapFragment - - if (existingFragment == null) { - fragmentManager.beginTransaction() - .replace(R.id.map_container, MapFragment.newInstance(trackUri)) - .commitNow() - - Log.d(TAG, "MapFragment added for track: $trackUri") - } - } + @OptIn(ExperimentalMaterial3Api::class) + private fun setupBottomSheet(track: Track) { + val composeView = binding.bottomSheetCompose as ComposeView - private fun displayTrackStatistics(track: Track) { - with(binding) { - // Track name and date - trackName.text = track.name - trackDate.text = DateTimeFormatter.formatDateTimeString(track.recordingStart) + composeView.apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - trackDuration.text = DateTimeFormatter.formatDurationTime(track.duration) - trackDistance.text = LengthUnitHelper.convertDistanceToString(track.length) + setContent { + var showBottomSheet by remember { mutableStateOf(false) } + val sheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = false + ) - trackWaypoints.text = track.wayPoints.size.toString() + MaterialTheme { + if (showBottomSheet) { + TrackStatsBottomSheet( + track = track, + sheetState = sheetState, + onDismissRequest = { + showBottomSheet = false + fab?.visibility = VISIBLE + }, + onShareClick = { + showBottomSheet = false + showShareOptions() + }, + onDeleteClick = { + showBottomSheet = false + showDeleteConfirmation() + } + ) + } - // Average speed calculation - trackAvgSpeed.text = if (track.duration > 0) { - val speedKmh = (track.length / 1000.0) / (track.duration / 3600000.0) - String.format("%.1f km/h", speedKmh) - } else { - "0.0 km/h" - } + // update FAB visibility based on sheet state + LaunchedEffect(showBottomSheet) { + if (showBottomSheet) { + fab?.visibility = GONE + } else { + fab?.visibility = VISIBLE + } + } + } - if (track.positiveElevation != 0.0 || track.negativeElevation != 0.0) { - elevationCard.isVisible = true - trackUphill.text = String.format("+%.0f m", track.positiveElevation) - trackDownhill.text = String.format("%.0f m", kotlin.math.abs(track.negativeElevation)) - } else { - elevationCard.isVisible = false + DisposableEffect(Unit) { + fab?.setOnClickListener { + showBottomSheet = true + } + onDispose { } + } } + } + } - if (track.maxAltitude > 0.0) { - altitudeRangeCard.isVisible = true - trackMinAltitude.text = String.format("%.0f m", track.minAltitude) - trackMaxAltitude.text = String.format("%.0f m", track.maxAltitude) - } else { - altitudeRangeCard.isVisible = false - } - // Recording details - if (track.recordingStop != 0L) { - recordingInfoCard.isVisible = true - trackRecordingStart.text = "Started: ${DateTimeFormatter.formatDateTimeString(track.recordingStart)}" - trackRecordingStop.text = "Ended: ${DateTimeFormatter.formatDateTimeString(track.recordingStop)}" - } else { - recordingInfoCard.isVisible = false + private fun setupFAB() { + fab = ImageButton(requireContext()).apply { + setImageResource(R.drawable.info_24px) + setBackgroundResource(R.drawable.fab_backgnd) + layoutParams = ViewGroup.MarginLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(0, 0, 48, 48) } } - Log.d(TAG, "Displayed stats for track: ${track.name} (${track.wayPoints.size} waypoints)") - } + (binding.root as ViewGroup).addView(fab) - private fun setupActionButtons() { - binding.fabShare.setOnClickListener { - showShareOptions() - } + // align it top end + val layoutParams = fab!!.layoutParams as FrameLayout.LayoutParams + layoutParams.gravity = android.view.Gravity.TOP or android.view.Gravity.END + layoutParams.setMargins(0, 16, + resources.getDimensionPixelSize(R.dimen.fab_margin), + resources.getDimensionPixelSize(R.dimen.fab_margin) + ) + fab!!.layoutParams = layoutParams } - private fun showShareOptions() { - val options = arrayOf("Save GPX to file", "Share GPX via apps", "Delete track") + val options = arrayOf("Save GPX to file", "Share GPX via apps") AlertDialog.Builder(requireContext()) - .setTitle("Track Options") + .setTitle(R.string.share_track) .setItems(options) { _, which -> when (which) { 0 -> openSaveGpxDialog() 1 -> shareGpxViaShareSheet() - 2 -> showDeleteConfirmation() } } - .setNegativeButton("Cancel", null) + .setNegativeButton(R.string.cancel, null) .show() } @@ -212,7 +248,7 @@ class TrackViewFragment : Fragment() { try { saveGpxLauncher.launch(intent) } catch (e: Exception) { - Log.e(TAG, "Unable to save GPX.", e) + Log.e(TAG, getString(R.string.toast_error_saving_gpx), e) Toast.makeText(requireContext(), "Please install a file manager app", Toast.LENGTH_LONG).show() } } @@ -227,10 +263,10 @@ class TrackViewFragment : Fragment() { lifecycleScope.launch { try { copyFile(sourceUri, targetUri) - Toast.makeText(requireContext(), "GPX file saved successfully", Toast.LENGTH_LONG).show() + Toast.makeText(requireContext(), R.string.toast_gpx_saved, Toast.LENGTH_LONG).show() } catch (e: Exception) { Log.e(TAG, "Error saving GPX", e) - Toast.makeText(requireContext(), "Error saving GPX file", Toast.LENGTH_LONG).show() + Toast.makeText(requireContext(), R.string.toast_error_saving_gpx, Toast.LENGTH_LONG).show() } } } @@ -282,21 +318,22 @@ class TrackViewFragment : Fragment() { } catch (e: Exception) { Log.e(TAG, "Error sharing GPX", e) - Toast.makeText(requireContext(), "Error sharing GPX file", Toast.LENGTH_LONG).show() + Toast.makeText(requireContext(), R.string.toast_error_saving_gpx, Toast.LENGTH_LONG).show() } } } + private fun showDeleteConfirmation() { track?.let { track -> AlertDialog.Builder(requireContext()) - .setTitle("Delete Track") + .setTitle(R.string.delete_track_title) .setMessage("Are you sure you want to delete this track?\n\n- ${track.name}") .setIcon(R.drawable.delete_24px) - .setPositiveButton("Delete") { _, _ -> + .setPositiveButton(R.string.delete) { _, _ -> deleteTrack() } - .setNegativeButton("Cancel", null) + .setNegativeButton(R.string.cancel, null) .show() } } @@ -309,9 +346,12 @@ class TrackViewFragment : Fragment() { trackRepository.deleteTrack(track.getTrackId()) } + Toast.makeText(requireContext(), R.string.track_deleted, Toast.LENGTH_SHORT).show() + requireActivity().onBackPressed() + } catch (e: Exception) { Log.e(TAG, "Error deleting track", e) - Toast.makeText(requireContext(), "Error deleting track", Toast.LENGTH_LONG).show() + Toast.makeText(requireContext(), R.string.toast_error_deleting_track, Toast.LENGTH_LONG).show() } } } diff --git a/app/src/main/java/com/mubarak/mbcompass/features/tracks/GpxBuilder.kt b/app/src/main/java/com/mubarak/mbcompass/features/tracks/GpxBuilder.kt index be7d3cb..0f1a0b9 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/tracks/GpxBuilder.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/tracks/GpxBuilder.kt @@ -65,7 +65,7 @@ object GpxBuilder { } } - if (track.wayPoints.size > 0) { + if (track.wayPoints.isNotEmpty()) { track.wayPoints[track.wayPoints.size - 1].isStopOver = LocationHelper.isStopOver(previousLocation, location) } diff --git a/app/src/main/java/com/mubarak/mbcompass/features/tracks/TrackerService.kt b/app/src/main/java/com/mubarak/mbcompass/features/tracks/TrackerService.kt index 8b3a192..d464d28 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/tracks/TrackerService.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/tracks/TrackerService.kt @@ -288,9 +288,9 @@ class TrackerService : Service(), SensorEventListener { private fun stopForegroundCompat(removeNotification: Boolean) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val flags = if (removeNotification) { - Service.STOP_FOREGROUND_REMOVE + STOP_FOREGROUND_REMOVE } else { - Service.STOP_FOREGROUND_DETACH + STOP_FOREGROUND_DETACH } stopForeground(flags) } else { @@ -331,7 +331,7 @@ class TrackerService : Service(), SensorEventListener { ) } - // Auto-save temp track every 2 minutes + // Auto-save temp track val now = GregorianCalendar.getInstance().time if (now.time - lastTempSaveTime.time > TrackingConstants.SAVE_TEMP_TRACK_INTERVAL) { lastTempSaveTime = now @@ -469,7 +469,7 @@ class TrackerService : Service(), SensorEventListener { } } - // Used for testing TODO: move it to a separate class + // TODO: move it to a separate class private fun createNotification(): Notification { val intent = Intent(this, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity( diff --git a/app/src/main/java/com/mubarak/mbcompass/features/tracks/TracksScreen.kt b/app/src/main/java/com/mubarak/mbcompass/features/tracks/TracksScreen.kt index a3ea520..09d0647 100644 --- a/app/src/main/java/com/mubarak/mbcompass/features/tracks/TracksScreen.kt +++ b/app/src/main/java/com/mubarak/mbcompass/features/tracks/TracksScreen.kt @@ -158,7 +158,7 @@ private fun TracksList( // delete confirmation dialog trackToDelete?.let { track -> AlertDialog( - onDismissRequest = { trackToDelete = null }, + onDismissRequest = {}, title = { Text(stringResource(R.string.delete_track_title)) }, text = { Text( @@ -172,14 +172,13 @@ private fun TracksList( TextButton( onClick = { onDeleteTrack(track) - trackToDelete = null } ) { Text(stringResource(R.string.delete)) } }, dismissButton = { - TextButton(onClick = { trackToDelete = null }) { + TextButton(onClick = {}) { Text(stringResource(R.string.cancel)) } } diff --git a/app/src/main/java/com/mubarak/mbcompass/navigation/TopLevelNavItem.kt b/app/src/main/java/com/mubarak/mbcompass/navigation/TopLevelNavItem.kt index 596cb20..5c79e5f 100644 --- a/app/src/main/java/com/mubarak/mbcompass/navigation/TopLevelNavItem.kt +++ b/app/src/main/java/com/mubarak/mbcompass/navigation/TopLevelNavItem.kt @@ -31,6 +31,6 @@ val TopLevelDestination = listOf( TopLevelRoute(NavigationRoute, R.string.navigation, R.drawable.ic_nav_24px), TopLevelRoute(MapRoute(), R.string.map, R.drawable.ic_map_24px), TopLevelRoute(TracksRoute, R.string.tracks, R.drawable.ic_tracks_24px), - TopLevelRoute(SettingsRoute, R.string.settings, R.drawable.ic_settings_24px), + TopLevelRoute(SettingsRoute, R.string.settings, R.drawable.settings_24px), ) diff --git a/app/src/main/java/com/mubarak/mbcompass/ui/FragmentNotification.kt b/app/src/main/java/com/mubarak/mbcompass/ui/FragmentNotification.kt index a5a3e81..764190d 100644 --- a/app/src/main/java/com/mubarak/mbcompass/ui/FragmentNotification.kt +++ b/app/src/main/java/com/mubarak/mbcompass/ui/FragmentNotification.kt @@ -8,7 +8,6 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.TextView import android.widget.Toast -import androidx.compose.material3.Snackbar import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.mubarak.mbcompass.R diff --git a/app/src/main/java/com/mubarak/mbcompass/ui/theme/Theme.kt b/app/src/main/java/com/mubarak/mbcompass/ui/theme/Theme.kt index 9f43ea8..d001fd6 100644 --- a/app/src/main/java/com/mubarak/mbcompass/ui/theme/Theme.kt +++ b/app/src/main/java/com/mubarak/mbcompass/ui/theme/Theme.kt @@ -15,7 +15,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import com.mubarak.mbcompass.features.settings.SettingsViewModel -import com.mubarak.mbcompass.ui.theme.ThemeConfig private val lightScheme = lightColorScheme( primary = primaryLight, diff --git a/app/src/main/java/com/mubarak/mbcompass/ui/theme/Type.kt b/app/src/main/java/com/mubarak/mbcompass/ui/theme/Type.kt index 2ad3879..467998d 100644 --- a/app/src/main/java/com/mubarak/mbcompass/ui/theme/Type.kt +++ b/app/src/main/java/com/mubarak/mbcompass/ui/theme/Type.kt @@ -9,9 +9,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp -// Default Material 3 typography values -val baseline = Typography() - // Set of Material typography styles to start with val Typography = Typography( bodyLarge = TextStyle( diff --git a/app/src/main/res/drawable-hdpi/my_arrow_nav.webp b/app/src/main/res/drawable-hdpi/my_arrow_nav.webp deleted file mode 100644 index 18031db..0000000 Binary files a/app/src/main/res/drawable-hdpi/my_arrow_nav.webp and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/my_arrow_nav.webp b/app/src/main/res/drawable-mdpi/my_arrow_nav.webp deleted file mode 100644 index 8e66385..0000000 Binary files a/app/src/main/res/drawable-mdpi/my_arrow_nav.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/my_arrow_nav.webp b/app/src/main/res/drawable-xhdpi/my_arrow_nav.webp deleted file mode 100644 index f6cdd1a..0000000 Binary files a/app/src/main/res/drawable-xhdpi/my_arrow_nav.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/my_arrow_nav.webp b/app/src/main/res/drawable-xxhdpi/my_arrow_nav.webp deleted file mode 100644 index 450f300..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/my_arrow_nav.webp and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/my_arrow_nav.webp b/app/src/main/res/drawable-xxxhdpi/my_arrow_nav.webp deleted file mode 100644 index ab30bb7..0000000 Binary files a/app/src/main/res/drawable-xxxhdpi/my_arrow_nav.webp and /dev/null differ diff --git a/app/src/main/res/drawable/card_background.xml b/app/src/main/res/drawable/card_background.xml deleted file mode 100644 index 2365da2..0000000 --- a/app/src/main/res/drawable/card_background.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_24px.xml b/app/src/main/res/drawable/ic_settings_24px.xml deleted file mode 100644 index b4a8268..0000000 --- a/app/src/main/res/drawable/ic_settings_24px.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/info_24px.xml b/app/src/main/res/drawable/info_24px.xml new file mode 100644 index 0000000..431db81 --- /dev/null +++ b/app/src/main/res/drawable/info_24px.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/map_fill_icon_24px.xml b/app/src/main/res/drawable/map_fill_icon_24px.xml deleted file mode 100644 index d3e25c4..0000000 --- a/app/src/main/res/drawable/map_fill_icon_24px.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/save_24dp.xml b/app/src/main/res/drawable/save_24dp.xml index edb4f1f..195a31c 100644 --- a/app/src/main/res/drawable/save_24dp.xml +++ b/app/src/main/res/drawable/save_24dp.xml @@ -7,5 +7,5 @@ android:viewportHeight="960"> + android:fillColor="@android:color/white"/> diff --git a/app/src/main/res/drawable/settings_24px.xml b/app/src/main/res/drawable/settings_24px.xml index 63cad59..abed04b 100644 --- a/app/src/main/res/drawable/settings_24px.xml +++ b/app/src/main/res/drawable/settings_24px.xml @@ -1,12 +1,9 @@ - - + android:viewportHeight="960"> + android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/> diff --git a/app/src/main/res/drawable/share_24px.xml b/app/src/main/res/drawable/share_24px.xml new file mode 100644 index 0000000..23ab377 --- /dev/null +++ b/app/src/main/res/drawable/share_24px.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml index 7f025af..15c1c31 100644 --- a/app/src/main/res/layout/fragment_map.xml +++ b/app/src/main/res/layout/fragment_map.xml @@ -10,35 +10,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - - - - - - - - - - + android:layout_height="match_parent"> + android:layout_height="match_parent"/> - + + android:layout_height="match_parent"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -