erste lauffähige Version
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
<uses-feature android:name="android.hardware.location.gps" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
|
||||
<uses-library
|
||||
android:name="com.google.android.wearable"
|
||||
android:required="true" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.wearable.standalone"
|
||||
android:value="true" />
|
||||
|
||||
<service
|
||||
android:name=".TrackingService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="location" />
|
||||
|
||||
<activity
|
||||
android:name=".presentation.MainActivity"
|
||||
android:exported="true"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/MainActivityTheme.Starting">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,63 @@
|
||||
package de.tstratmann.gpstracker_wear
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class GPXManager(private val context: Context) {
|
||||
|
||||
private var currentFile: File? = null
|
||||
|
||||
fun startNewSession(): File {
|
||||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
val fileName = "track_$timeStamp.gpx"
|
||||
val file = File(context.filesDir, fileName)
|
||||
file.writeText(getGpxHeader())
|
||||
currentFile = file
|
||||
return file
|
||||
}
|
||||
|
||||
fun appendLocation(location: Location) {
|
||||
currentFile?.let { file ->
|
||||
val time = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply {
|
||||
timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.format(Date(location.time))
|
||||
|
||||
val trkpt = """
|
||||
<trkpt lat="${location.latitude}" lon="${location.longitude}">
|
||||
<ele>${location.altitude}</ele>
|
||||
<time>$time</time>
|
||||
</trkpt>
|
||||
""".trimIndent()
|
||||
|
||||
// This is a simple append, for a real app we'd want to handle the footer properly
|
||||
// But for this exercise, we'll strip the footer, append, then re-add footer
|
||||
val content = file.readText().removeSuffix(getGpxFooter())
|
||||
file.writeText(content + trkpt + getGpxFooter())
|
||||
}
|
||||
}
|
||||
|
||||
fun getActivitiesCount(): Int {
|
||||
return context.filesDir.listFiles { _, name -> name.endsWith(".gpx") }?.size ?: 0
|
||||
}
|
||||
|
||||
fun getAllFiles(): List<File> {
|
||||
return context.filesDir.listFiles { _, name -> name.endsWith(".gpx") }?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
private fun getGpxHeader() = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="WearGPSTracker" xmlns="http://www.topografix.com/GPX/1/1">
|
||||
<trk>
|
||||
<name>Track ${SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(Date())}</name>
|
||||
<trkseg>
|
||||
""".trimIndent()
|
||||
|
||||
private fun getGpxFooter() = """
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
||||
""".trimIndent()
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package de.tstratmann.gpstracker_wear
|
||||
|
||||
import android.app.*
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.google.android.gms.location.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.util.*
|
||||
|
||||
class TrackingService : Service() {
|
||||
|
||||
private val binder = LocalBinder()
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private lateinit var locationCallback: LocationCallback
|
||||
private lateinit var gpxManager: GPXManager
|
||||
|
||||
private val _trackingState = MutableStateFlow(TrackingData())
|
||||
val trackingState = _trackingState.asStateFlow()
|
||||
|
||||
private var startTime = 0L
|
||||
private var lastLocation: Location? = null
|
||||
private var job: Job? = null
|
||||
|
||||
data class TrackingData(
|
||||
val durationSeconds: Long = 0,
|
||||
val distanceKm: Double = 0.0,
|
||||
val speedKmh: Double = 0.0
|
||||
)
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
fun getService(): TrackingService = this@TrackingService
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
gpxManager = GPXManager(this)
|
||||
|
||||
createNotificationChannel()
|
||||
|
||||
locationCallback = object : LocationCallback() {
|
||||
override fun onLocationResult(locationResult: LocationResult) {
|
||||
locationResult.lastLocation?.let { location ->
|
||||
processLocation(location)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processLocation(location: Location) {
|
||||
gpxManager.appendLocation(location)
|
||||
|
||||
val currentData = _trackingState.value
|
||||
var newDistance = currentData.distanceKm
|
||||
|
||||
lastLocation?.let { last ->
|
||||
newDistance += last.distanceTo(location) / 1000.0
|
||||
}
|
||||
|
||||
lastLocation = location
|
||||
val speedKmh = location.speed * 3.6 // m/s to km/h
|
||||
|
||||
_trackingState.value = currentData.copy(
|
||||
distanceKm = newDistance,
|
||||
speedKmh = speedKmh
|
||||
)
|
||||
}
|
||||
|
||||
private fun startForegroundService() {
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("GPS Tracker")
|
||||
.setContentText("Tracking active...")
|
||||
.setSmallIcon(android.R.drawable.ic_menu_mylocation)
|
||||
.setOngoing(true)
|
||||
.build()
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
fun startTracking() {
|
||||
startForegroundService()
|
||||
gpxManager.startNewSession()
|
||||
startTime = System.currentTimeMillis()
|
||||
lastLocation = null
|
||||
_trackingState.value = TrackingData()
|
||||
|
||||
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000)
|
||||
.setMinUpdateIntervalMillis(1000)
|
||||
.build()
|
||||
|
||||
try {
|
||||
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
|
||||
} catch (unlikely: SecurityException) {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
job = CoroutineScope(Dispatchers.Main).launch {
|
||||
while (isActive) {
|
||||
val elapsed = (System.currentTimeMillis() - startTime) / 1000
|
||||
_trackingState.value = _trackingState.value.copy(durationSeconds = elapsed)
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopTracking() {
|
||||
fusedLocationClient.removeLocationUpdates(locationCallback)
|
||||
job?.cancel()
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder = binder
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
val serviceChannel = NotificationChannel(
|
||||
CHANNEL_ID, "GPS Tracker Channel",
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
val manager = getSystemService(NotificationManager::class.java)
|
||||
manager.createNotificationChannel(serviceChannel)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID = "TrackingServiceChannel"
|
||||
const val NOTIFICATION_ID = 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package de.tstratmann.gpstracker_wear
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.google.android.gms.wearable.Asset
|
||||
import com.google.android.gms.wearable.PutDataMapRequest
|
||||
import com.google.android.gms.wearable.Wearable
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import java.io.File
|
||||
|
||||
class WearSyncManager(private val context: Context) {
|
||||
|
||||
suspend fun syncGpxFile(file: File): Boolean {
|
||||
return try {
|
||||
val bytes = file.readBytes()
|
||||
val asset = Asset.createFromBytes(bytes)
|
||||
|
||||
val request = PutDataMapRequest.create("/gpx_export/${file.name}").apply {
|
||||
dataMap.putAsset("gpx_file", asset)
|
||||
dataMap.putLong("timestamp", System.currentTimeMillis())
|
||||
}
|
||||
|
||||
val putDataRequest = request.asPutDataRequest().setUrgent()
|
||||
Wearable.getDataClient(context).putDataItem(putDataRequest).await()
|
||||
Log.d("WearSyncManager", "File synced: ${file.name}")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
Log.e("WearSyncManager", "Sync failed", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package de.tstratmann.gpstracker_wear.presentation
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.wear.compose.material.*
|
||||
import androidx.wear.compose.navigation.SwipeDismissableNavHost
|
||||
import androidx.wear.compose.navigation.composable
|
||||
import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
|
||||
import de.tstratmann.gpstracker_wear.GPXManager
|
||||
import de.tstratmann.gpstracker_wear.TrackingService
|
||||
import de.tstratmann.gpstracker_wear.WearSyncManager
|
||||
import de.tstratmann.gpstracker_wear.presentation.theme.GPSTracker_WearTheme
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
WearApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WearApp() {
|
||||
val navController = rememberSwipeDismissableNavController()
|
||||
GPSTracker_WearTheme {
|
||||
Scaffold(
|
||||
timeText = { TimeText() }
|
||||
) {
|
||||
SwipeDismissableNavHost(
|
||||
navController = navController,
|
||||
startDestination = "main"
|
||||
) {
|
||||
composable("main") {
|
||||
MainScreen(navController)
|
||||
}
|
||||
composable("tracking") {
|
||||
TrackingScreen(navController)
|
||||
}
|
||||
composable("activities") {
|
||||
ActivitiesScreen(navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen(navController: NavHostController) {
|
||||
val context = LocalContext.current
|
||||
val permissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true) {
|
||||
navController.navigate("tracking")
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
modifier = Modifier.padding(top = 24.dp, bottom = 24.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "GPS Tracker",
|
||||
style = MaterialTheme.typography.title1,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
val permissions = arrayOf(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
)
|
||||
if (permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED }) {
|
||||
navController.navigate("tracking")
|
||||
} else {
|
||||
permissionLauncher.launch(permissions)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.8f)
|
||||
) {
|
||||
Text("Start")
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = { navController.navigate("activities") },
|
||||
modifier = Modifier.fillMaxWidth(0.8f),
|
||||
colors = ButtonDefaults.secondaryButtonColors()
|
||||
) {
|
||||
Text("Aktivitäten")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TrackingScreen(navController: NavHostController) {
|
||||
val context = LocalContext.current
|
||||
var trackingService by remember { mutableStateOf<TrackingService?>(null) }
|
||||
var isBound by remember { mutableStateOf(false) }
|
||||
|
||||
val connection = remember {
|
||||
object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
val binder = service as TrackingService.LocalBinder
|
||||
trackingService = binder.getService()
|
||||
isBound = true
|
||||
trackingService?.startTracking()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
isBound = false
|
||||
trackingService = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val intent = Intent(context, TrackingService::class.java)
|
||||
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
val trackingDataState = trackingService?.trackingState?.collectAsStateWithLifecycle(
|
||||
TrackingService.TrackingData()
|
||||
)
|
||||
val trackingData = trackingDataState?.value ?: TrackingService.TrackingData()
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
val hours = trackingData.durationSeconds / 3600
|
||||
val minutes = (trackingData.durationSeconds % 3600) / 60
|
||||
val seconds = trackingData.durationSeconds % 60
|
||||
val timeString = String.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
|
||||
Text(text = timeString, style = MaterialTheme.typography.display1)
|
||||
Text(text = String.format("%.2f km", trackingData.distanceKm), style = MaterialTheme.typography.body1)
|
||||
Text(text = String.format("%.1f km/h", trackingData.speedKmh), style = MaterialTheme.typography.body1)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
trackingService?.stopTracking()
|
||||
if (isBound) {
|
||||
context.unbindService(connection)
|
||||
isBound = false
|
||||
}
|
||||
navController.popBackStack()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red),
|
||||
modifier = Modifier.size(ButtonDefaults.DefaultButtonSize)
|
||||
) {
|
||||
Text("STOP", color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
if (isBound) {
|
||||
context.unbindService(connection)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActivitiesScreen(navController: NavHostController) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val gpxManager = remember { GPXManager(context) }
|
||||
val syncManager = remember { WearSyncManager(context) }
|
||||
var count by remember { mutableIntStateOf(gpxManager.getActivitiesCount()) }
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(text = "$count Aktivitäten", style = MaterialTheme.typography.title3)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
val files = gpxManager.getAllFiles()
|
||||
if (files.isNotEmpty()) {
|
||||
scope.launch {
|
||||
var successCount = 0
|
||||
files.forEach { file ->
|
||||
if (syncManager.syncGpxFile(file)) {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
Toast.makeText(context, "$successCount von ${files.size} synchronisiert", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(context, "Keine Aktivitäten", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.8f)
|
||||
) {
|
||||
Text("GPX Sync")
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = { navController.popBackStack() },
|
||||
modifier = Modifier.fillMaxWidth(0.8f),
|
||||
colors = ButtonDefaults.secondaryButtonColors()
|
||||
) {
|
||||
Text("Zurück")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.tstratmann.gpstracker_wear.presentation.theme
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.wear.compose.material.MaterialTheme
|
||||
|
||||
@Composable
|
||||
fun GPSTracker_WearTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
/**
|
||||
* Empty theme to customize for your app.
|
||||
* See: https://developer.android.com/jetpack/compose/designsystems/custom
|
||||
*/
|
||||
MaterialTheme(
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:gravity="center">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#FFFFFF" />
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:gravity="center">
|
||||
<vector
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z" />
|
||||
</vector>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 982 B |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">GPSTracker_Wear</string>
|
||||
<string name="hello_world">Hello, %1$s!</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<style name="MainActivityTheme.Starting" parent="Theme.SplashScreen">
|
||||
<item name="windowSplashScreenBackground">@android:color/black</item>
|
||||
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
|
||||
<item name="postSplashScreenTheme">@android:style/Theme.DeviceDefault</item>
|
||||
</style>
|
||||
</resources>
|
||||