mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6ec5c7a8e | ||
|
|
2f49559600 | ||
|
|
57803988d4 | ||
|
|
bcde51ad56 | ||
|
|
cc522bb0b7 | ||
|
|
e4c2a72fad | ||
|
|
0627a32df7 | ||
|
|
5df2dace77 | ||
|
|
cb30a3de69 | ||
|
|
4aac815b2e | ||
|
|
d915b0ce45 | ||
|
|
746b0b7325 | ||
|
|
de370751a0 | ||
|
|
e1948a823f | ||
|
|
738b99ff9e | ||
|
|
7959b27d38 | ||
|
|
83c33eeea3 | ||
|
|
b168eb7c04 | ||
|
|
f4b8391fa3 | ||
|
|
d1f67bb64d | ||
|
|
7d02ac8b75 | ||
|
|
6ff59be6ab | ||
|
|
dbeaa9cf5e | ||
|
|
7bfb8c8669 | ||
|
|
3fbabfa0ed | ||
|
|
0fd7568536 | ||
|
|
02618dbcb9 | ||
|
|
dc9514dcfa | ||
|
|
74f557524f | ||
|
|
324112ca8b | ||
|
|
15d6721705 | ||
|
|
1f9ef552c1 | ||
|
|
608d72b856 | ||
|
|
08eef32e59 | ||
|
|
894712c38d | ||
|
|
ef1b2e1be7 | ||
|
|
93a1ff7485 | ||
|
|
8a2dae4173 | ||
|
|
a1a0546b74 | ||
|
|
c616f0203a | ||
|
|
08ac6fe117 | ||
|
|
46c2e92684 | ||
|
|
b2a95adb87 | ||
|
|
9d629c392b | ||
|
|
0eea09542f | ||
|
|
5c72a18f06 | ||
|
|
5cc1f518c7 | ||
|
|
3f393fd56f | ||
|
|
703d8d69e8 | ||
|
|
89e5bbd2bb |
No files matched your search
+18
-14
@@ -1,20 +1,24 @@
|
||||
Gearslip 0.3.12 gives the car screen a fresh look and fixes the wireless loop 0.3.02 caused on some head units.
|
||||
Gearslip 0.3.31 brings saved places, a new music player and sixteen languages.
|
||||
|
||||
## New
|
||||
- Navigation cards sit down the left side, with a bigger turn card that shows your lane. The map keeps the right side.
|
||||
- The car screen and the phone app take their colors from your phone's Material You palette.
|
||||
- The car's settings sit on the phone's home screen, each group on its own screen.
|
||||
- Put the home screen's player left or right of the map.
|
||||
- The browser shows the page title, and it has an address bar, forward, reload, and a start page.
|
||||
- If Gearslip lacks call permissions, the car's Phone screen asks for them on the phone.
|
||||
- Save Home, Work and three places of your own under Saved places in the Car card on the phone's home screen. Say "navigate home" or "take me to work", and your map app gets the address.
|
||||
- Calls show on the car's own screens, and the car's phone button answers them.
|
||||
- A new music player: Now playing with a big cover, Browse with covers, and Up next. Beside the map, the player floats as a card; drag its edge to resize it.
|
||||
- Lyrics come from BetterLyrics, LRCLIB or KuGou, whichever has the song.
|
||||
- A Dark mode setting: black, black and colour, deep colour or grey. Black is the default.
|
||||
- Gearslip speaks Arabic, Chinese, Czech, Dutch, French, German, Italian, Japanese, Korean, Polish, Portuguese, Russian, Spanish, Swedish, Turkish and Ukrainian. Most started as machine translations, so fix your language on [Weblate](https://hosted.weblate.org/engage/gearslip/).
|
||||
- With experimental features on, car apps get the car's speed, fuel, range and compass, and cars with an instrument cluster screen get a turn card there.
|
||||
- A Manage storage screen in App info clears lyrics, voice models and old logs.
|
||||
|
||||
## Changed
|
||||
- Gearslip uses Car App Library 1.8. Map apps show the right turn arrows and route times, and Vela works fully.
|
||||
- Setup ends with recommended apps, then the button picker, then usage notes.
|
||||
- The nav bar floats on every screen.
|
||||
- The car's settings all sit under the Car card on the phone's home screen.
|
||||
- The Light app theme makes the phone app light too.
|
||||
- With the player beside the map, the map app's own buttons fold behind one button, so everything fits.
|
||||
- The USB audio choice is now called Through Gearslip.
|
||||
- Setup and Settings explain that some newer cars reject the built-in certificate and need your own.
|
||||
|
||||
## Fixed
|
||||
- Some head units, such as ZLink adapters, sent their Wi-Fi details twice. Gearslip rejoined the network each time and dropped the connection. It now ignores the repeat.
|
||||
- Gearslip no longer crashes when a wireless connection ends.
|
||||
- Opening a second music app without playing anything keeps the player on the song that's still playing.
|
||||
- Vela's speed sign is full size again.
|
||||
- RF Sentinel and other non-media car apps get their own tile in the launcher.
|
||||
- Older head units that take only AAC audio no longer drop the connection when music starts.
|
||||
- Long screens such as Settings, Messages and Vehicle data scroll all the way down.
|
||||
- Settings opens faster.
|
||||
@@ -88,6 +88,9 @@ We are not required to use or include any Contribution.
|
||||
Most contributors sign on GitHub: the CLA Assistant bot comments on your first pull request,
|
||||
and you sign there with one click. Your GitHub account then serves as your signature.
|
||||
|
||||
Translators sign on Weblate: it shows this agreement before your first translation, and
|
||||
accepting it there serves as your signature.
|
||||
|
||||
To sign by hand instead, fill in the following and send it to us on Matrix. The address is
|
||||
on the [Seb3thehacker GitHub profile](https://github.com/Seb3thehacker).
|
||||
|
||||
|
||||
@@ -38,6 +38,19 @@ branch on `experimental`, and open your pull request against it.
|
||||
- Gearslip asks for no adb, root, or Shizuku. A feature must work with ordinary Android
|
||||
permissions and on-device consent dialogs.
|
||||
|
||||
## Translating
|
||||
|
||||
Translations happen on [Weblate](https://hosted.weblate.org/engage/gearslip/), not in pull
|
||||
requests. Pick your language, or start a new one, and translate in the browser. Weblate
|
||||
sends the finished work to this repository as a pull request.
|
||||
|
||||
Keep the tone of the English: short, plain, and friendly. Leave placeholders such as `%1$s`
|
||||
and `%1$d` in place; Gearslip fills them with names and numbers. Untranslated strings show
|
||||
in English, so a partial translation is still useful.
|
||||
|
||||
Before your first translation, Weblate asks you to accept the
|
||||
[Contributor License Agreement](CLA.md). That counts as signing it.
|
||||
|
||||
## Writing
|
||||
|
||||
Text that users read, such as screens, release notes, and docs, follows Strunk's
|
||||
|
||||
@@ -9,13 +9,20 @@ An Android Auto alternative built for everyone.
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/ImranR98/Obtainium/main/assets/graphics/badge_obtainium.png" alt="Get it on Obtainium" height="80">](https://apps.obtainium.imranr.dev/redirect?r=obtainium://add/https://github.com/twinksoftware/gearslip)
|
||||
|
||||
[](https://hosted.weblate.org/engage/gearslip/)
|
||||
|
||||
Gearslip speaks English, Arabic, Chinese, Czech, Dutch, French, German, Italian, Japanese,
|
||||
Korean, Polish, Portuguese, Russian, Spanish, Swedish, Turkish, and Ukrainian. Most of these
|
||||
started as machine translations, so some read awkwardly.
|
||||
[Fix or add your language on Weblate](https://hosted.weblate.org/engage/gearslip/).
|
||||
|
||||
Questions, ideas?
|
||||
[Join the discussion on Matrix](https://matrix.to/#/!mf0_mgBd5KqKpc9FfX_wHp1jFCcwAtPrndIn_oGIyMk?via=matrix.org).
|
||||
|
||||
## Which cars work
|
||||
|
||||
Most cars work. Starting around 2020, some cars added newer security standards that reject
|
||||
Gearslip's certificate, and Gearslip can't run in those cars. [CARS.md](CARS.md) lists the
|
||||
Most cars work. Starting around 2020, some cars added newer security checks that reject
|
||||
the aasdk certificate, and in those cars Gearslip needs a certificate of your own. [CARS.md](CARS.md) lists the
|
||||
cars drivers have confirmed.
|
||||
|
||||
## What Gearslip is
|
||||
@@ -90,13 +97,15 @@ screen-sharing mode.
|
||||
Gearslip's own player. TIDAL lets only approved apps browse its library.
|
||||
- **Calls use the car's Bluetooth,** as they do in Android Auto. Pair the phone with the
|
||||
car to hear calls through its speakers.
|
||||
- **Music reaches the car over Bluetooth or USB.** Setup asks which. Bluetooth is the
|
||||
default: Android never asks for permission, and your map app's voice reaches the car
|
||||
too. USB sounds a little clearer, with no delay, but each drive Android asks you to
|
||||
share your screen, which lets Gearslip send the sound. Gearslip never reads the screen; capturing playback is
|
||||
the only way an ordinary app can pass another app's audio along.
|
||||
- **Music reaches the car over Bluetooth or through Gearslip.** Setup asks which.
|
||||
Bluetooth is the default: Android never asks for permission, and your map app's voice
|
||||
reaches the car too. Through Gearslip, the sound travels over the car connection, cable
|
||||
or wireless. It sounds a little clearer, with no delay, but each drive Android asks you
|
||||
to share your screen, which lets Gearslip send the sound. Gearslip never reads the
|
||||
screen; capturing playback is the only way an ordinary app can pass another app's audio
|
||||
along.
|
||||
- **Gearslip needs no adb, root, or Shizuku.** Every feature relies on an ordinary
|
||||
Android permission or an on-device consent dialog. For USB audio, setup offers two
|
||||
Android permission or an on-device consent dialog. For audio through Gearslip, setup offers two
|
||||
optional adb commands: one removes the audio dialog for good, and the other keeps
|
||||
notifications visible while audio plays.
|
||||
- **Updates come from GitHub.** Once a day, Gearslip checks this repository's latest
|
||||
@@ -126,7 +135,7 @@ turn them on, in setup or in Settings.
|
||||
those boxes cover: the Android version and whether the phone runs GrapheneOS, the phone
|
||||
model, whether a car connected that day, and each car's head unit, model, year, screen
|
||||
size, and connection result, with whether that drive used the cable or wireless and
|
||||
whether music went over USB or Bluetooth. If you tick it, how each drive went: how far
|
||||
whether music went through Gearslip or over Bluetooth. If you tick it, how each drive went: how far
|
||||
the connection got, how long it lasted, and how it ended. Settings shows the exact note. Untick a phone detail, and
|
||||
the server drops the copy it kept.
|
||||
- **What they leave out.** Your name, accounts, location, contacts, messages, and
|
||||
@@ -153,6 +162,9 @@ Bug reports from real cars help most, and pull requests are welcome.
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md) explains what to put in a report, which branch to work
|
||||
on, and the one-time agreement you sign before your first pull request merges.
|
||||
|
||||
To help translate Gearslip into your language, join
|
||||
[Gearslip on Weblate](https://hosted.weblate.org/engage/gearslip/). No coding needed.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
|
||||
@@ -38,9 +38,9 @@ android {
|
||||
applicationId = "app.seb3thehacker.gearslip"
|
||||
minSdk = 31
|
||||
targetSdk = 37
|
||||
versionCode = 12
|
||||
versionCode = 13
|
||||
// -PgearslipVersionName=0.1.99-test makes a build that thinks it's older, to try the updater.
|
||||
versionName = project.findProperty("gearslipVersionName")?.toString() ?: "0.3.12-alpha"
|
||||
versionName = project.findProperty("gearslipVersionName")?.toString() ?: "0.3.31-alpha"
|
||||
manifestPlaceholders["appLabel"] = "Gearslip"
|
||||
// Where opted-in phones send their daily usage note: the stats-worker/ Worker's address,
|
||||
// set as gearslipStatsUrl in gradle.properties. Left empty, nothing is ever sent.
|
||||
@@ -74,6 +74,9 @@ android {
|
||||
applicationIdSuffix = ".dev"
|
||||
signingConfigs.findByName("release")?.let { signingConfig = it }
|
||||
manifestPlaceholders["appLabel"] = "Gearslip Debug"
|
||||
// English (XA) and Arabic (XB) under the phone's languages: stretched, accented and
|
||||
// right-to-left text, which shows what's still hardcoded and what doesn't fit.
|
||||
isPseudoLocalesEnabled = true
|
||||
// Shrunk with R8 like a release, so a debug install behaves like what ships (a keep
|
||||
// rule missing for release shows up here first). -PnoR8 skips it for a quicker build.
|
||||
if (!project.hasProperty("noR8")) {
|
||||
@@ -88,6 +91,18 @@ android {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
// Lists each language that has a translation in the app's locale config, so Android 13+
|
||||
// offers it under the system's per-app languages. English is the unqualified default
|
||||
// (res/resources.properties).
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
lint {
|
||||
// Weblate fills languages in a string at a time; the rest fall back to English.
|
||||
disable += "MissingTranslation"
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:label="${appLabel}"
|
||||
android:supportsRtl="true"
|
||||
android:manageSpaceActivity="app.seb3thehacker.gearslip.ui.ManageSpaceActivity"
|
||||
android:theme="@style/Theme.Gearslip">
|
||||
|
||||
<provider
|
||||
@@ -162,6 +163,12 @@
|
||||
activity so Settings can switch off just this (AppSettings.setGearslipEnabled) and leave
|
||||
the head unit to Android Auto, without disabling the screen the switch is on.
|
||||
-->
|
||||
<!-- App info › Storage › Manage space: clears lyrics, voice models and logs one at a time. -->
|
||||
<activity
|
||||
android:name=".ui.ManageSpaceActivity"
|
||||
android:exported="true"
|
||||
android:label="Gearslip storage" />
|
||||
|
||||
<activity
|
||||
android:name=".PermissionActivity"
|
||||
android:exported="false"
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package app.seb3thehacker.gearslip
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import app.seb3thehacker.gearslip.car.ClusterScreen
|
||||
|
||||
/**
|
||||
* Gearslip's picture on the car's instrument-cluster screen: a second video sink, for a car that
|
||||
* offers one, showing the turn card ([ClusterScreen]). The same exchange as the main screen's,
|
||||
* on its own channel, with its own encoder and its own private display:
|
||||
*
|
||||
* -> CHANNEL_OPEN_REQUEST <- CHANNEL_OPEN_RESPONSE
|
||||
* -> Setup { H.264 } <- Config { status, max_unacked, configuration_indices }
|
||||
* -> VideoFocusRequest <- VideoFocusNotification { projected }
|
||||
* -> Start, CodecConfig, Data... <- Ack
|
||||
*
|
||||
* No touch: a cluster has none. Experimental, behind [car.CarSettings.clusterVideoEnabled], and
|
||||
* untested in a car: none in the notes has a cluster sink.
|
||||
*/
|
||||
internal class ClusterVideo(
|
||||
private val context: Context,
|
||||
private val service: ServiceDiscovery.VideoService,
|
||||
private val send: (messageId: Int, body: ByteArray) -> Unit,
|
||||
) {
|
||||
private val log = GearslipLog.tagged("CLUSTER")
|
||||
private val main = Handler(Looper.getMainLooper())
|
||||
|
||||
val channelId get() = service.serviceId
|
||||
|
||||
private var configIndex = 0
|
||||
@Volatile private var started = false
|
||||
@Volatile private var running = true
|
||||
@Volatile private var source: VideoSource? = null
|
||||
private var projector: ScreenProjector? = null
|
||||
|
||||
private val ackLock = Object()
|
||||
@Volatile private var inFlight = 0
|
||||
@Volatile private var maxUnacked = 1
|
||||
@Volatile private var lastAckAt = 0L
|
||||
private var frames = 0
|
||||
|
||||
fun onMessage(messageId: Int, body: ByteArray) {
|
||||
when (messageId) {
|
||||
GearslipRunner.MSG_CHANNEL_OPEN_RESPONSE -> {
|
||||
val status = Protobuf.readInt32Field(body, 1) ?: 0
|
||||
log.i("<- ChannelOpenResponse: status=$status")
|
||||
if (status != 0) return
|
||||
send(GearslipRunner.MSG_MEDIA_SETUP, Protobuf.varintField(1, GearslipRunner.CODEC_H264_BP.toLong()))
|
||||
log.i("-> Setup(codec=VIDEO_H264_BP)")
|
||||
}
|
||||
|
||||
GearslipRunner.MSG_MEDIA_CONFIG -> {
|
||||
val fields = Wire.fields(body)
|
||||
Wire.varint(fields, 2)?.toInt()?.let { maxUnacked = maxOf(1, it) }
|
||||
val offered = fields.filter { it.number == 3 && it.wireType == 0 }.map { it.varint.toInt() }
|
||||
// The cluster gets the smallest picture it offers: a turn card needs no more.
|
||||
configIndex = offered.minByOrNull { service.configs.getOrNull(it)?.pixelCount() ?: Int.MAX_VALUE } ?: 0
|
||||
log.i("<- Config status=${Wire.varint(fields, 1)} max_unacked=$maxUnacked offered=$offered - using $configIndex")
|
||||
val focus = Protobuf.varintField(2, GearslipRunner.VIDEO_FOCUS_PROJECTED.toLong()) + Protobuf.varintField(3, 1L)
|
||||
send(GearslipRunner.MSG_VIDEO_FOCUS_REQUEST, focus)
|
||||
log.i("-> VideoFocusRequest(mode=PROJECTED)")
|
||||
}
|
||||
|
||||
GearslipRunner.MSG_VIDEO_FOCUS_NOTIFICATION -> {
|
||||
val mode = Wire.varint(Wire.fields(body), 1)?.toInt() ?: GearslipRunner.VIDEO_FOCUS_PROJECTED
|
||||
log.i("<- VideoFocusNotification: mode=$mode")
|
||||
val projected = mode == GearslipRunner.VIDEO_FOCUS_PROJECTED || mode == GearslipRunner.VIDEO_FOCUS_PROJECTED_NO_INPUT
|
||||
if (projected && !started) startStream()
|
||||
}
|
||||
|
||||
GearslipRunner.MSG_MEDIA_ACK -> {
|
||||
val acked = Wire.varint(Wire.fields(body), 2)?.toInt() ?: 1
|
||||
synchronized(ackLock) {
|
||||
inFlight = maxOf(0, inFlight - maxOf(1, acked))
|
||||
lastAckAt = System.currentTimeMillis()
|
||||
ackLock.notifyAll()
|
||||
}
|
||||
}
|
||||
|
||||
else -> log.i("<- unhandled message id=$messageId (${body.size} bytes)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun startStream() {
|
||||
val config = service.configs.getOrNull(configIndex) ?: service.configs.firstOrNull() ?: return
|
||||
val (width, height) = config.pixelSize() ?: run {
|
||||
log.w("unsupported cluster resolution ${config.resolutionName}")
|
||||
return
|
||||
}
|
||||
started = true
|
||||
send(
|
||||
GearslipRunner.MSG_MEDIA_START,
|
||||
Protobuf.varintField(1, GearslipRunner.SESSION_ID.toLong()) + Protobuf.varintField(2, configIndex.toLong()),
|
||||
)
|
||||
log.i("-> Start(config=$configIndex -> $config)")
|
||||
lastAckAt = System.currentTimeMillis()
|
||||
val video = VideoSource(
|
||||
width = width,
|
||||
height = height,
|
||||
frameRate = FRAME_RATE,
|
||||
mode = VideoSource.Mode.SURFACE,
|
||||
wireless = true, // a small share of the link: the main screen comes first
|
||||
onCodecConfig = { csd ->
|
||||
send(GearslipRunner.MSG_MEDIA_CODEC_CONFIG, csd)
|
||||
log.i("-> CodecConfig (${csd.size} bytes)")
|
||||
},
|
||||
onFrame = { data, pts, _ -> sendFrame(data, pts) },
|
||||
)
|
||||
source = video
|
||||
runCatching { video.start() }.onFailure {
|
||||
log.e("cluster encoder failed to start", it)
|
||||
return
|
||||
}
|
||||
val surface = video.inputSurface ?: return
|
||||
val density = config.density.takeIf { it > 0 } ?: 160
|
||||
main.post {
|
||||
if (!running) return@post
|
||||
projector = ScreenProjector(context).also {
|
||||
it.start(surface, width, height, density, name = "gearslip-cluster") {
|
||||
// The margins the car crops off are kept clear, as on the main screen.
|
||||
ClusterScreen(config.widthMargin, config.heightMargin)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** As on the main screen: wait for the car's acks rather than drop a frame. */
|
||||
private fun sendFrame(data: ByteArray, presentationTimeUs: Long) {
|
||||
synchronized(ackLock) {
|
||||
while (running && inFlight >= maxUnacked) {
|
||||
val since = System.currentTimeMillis() - lastAckAt
|
||||
if (since > GearslipRunner.ACK_STALL_MS) {
|
||||
inFlight = 0
|
||||
lastAckAt = System.currentTimeMillis()
|
||||
break
|
||||
}
|
||||
ackLock.wait(GearslipRunner.ACK_STALL_MS - since + 1)
|
||||
}
|
||||
}
|
||||
if (!running) return
|
||||
val timestamp = ByteArray(8)
|
||||
for (i in 0 until 8) timestamp[i] = ((presentationTimeUs shr ((7 - i) * 8)) and 0xFF).toByte()
|
||||
synchronized(ackLock) { inFlight++ }
|
||||
runCatching { send(GearslipRunner.MSG_MEDIA_DATA, timestamp + data) }.onFailure { running = false }
|
||||
if (++frames <= 2) log.i("-> Data frame #$frames (${data.size} bytes)")
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
running = false
|
||||
synchronized(ackLock) { ackLock.notifyAll() }
|
||||
main.post {
|
||||
projector?.stop()
|
||||
projector = null
|
||||
}
|
||||
source?.stop()
|
||||
source = null
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// A turn card changes a few times a minute; the encoder repeats the last frame between.
|
||||
const val FRAME_RATE = 15
|
||||
}
|
||||
}
|
||||
@@ -320,6 +320,8 @@ class GearslipActivity : ComponentActivity(), Projection {
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent?) {
|
||||
// Debug builds only: opens the audio choice for a look, without turning down a real prompt.
|
||||
if (BuildConfig.DEBUG && intent?.getBooleanExtra("show_audio_choice", false) == true) offerBluetooth.value = true
|
||||
if (intent?.action == app.seb3thehacker.gearslip.wireless.WirelessAuto.ACTION_CAR_NEARBY) {
|
||||
connectToNearbyCar(intent.getStringExtra(app.seb3thehacker.gearslip.wireless.WirelessAuto.EXTRA_ADDRESS))
|
||||
return
|
||||
|
||||
@@ -96,6 +96,8 @@ class GearslipRunner(
|
||||
@Volatile private var inputKeycodes: List<Int> = emptyList()
|
||||
@Volatile private var sensorChannelId: Int = -1
|
||||
@Volatile private var mediaStatus: CarMediaStatus? = null
|
||||
@Volatile private var phoneStatus: app.seb3thehacker.gearslip.call.CarPhoneStatus? = null
|
||||
@Volatile private var clusterVideo: ClusterVideo? = null
|
||||
@Volatile private var clusterNav: ClusterNav? = null
|
||||
private var sensorTypes: List<Int> = emptyList()
|
||||
@Volatile private var audioLink: AudioLink? = null
|
||||
@@ -184,12 +186,17 @@ class GearslipRunner(
|
||||
guidanceLink?.close()
|
||||
guidanceLink = null
|
||||
CarSensors.clear()
|
||||
app.seb3thehacker.gearslip.car.CarData.clear()
|
||||
CarMic.current = null
|
||||
CarBluetooth.onCarDisconnected()
|
||||
BluetoothAudioCheck.onCarDisconnected()
|
||||
LocationCheck.onCarDisconnected()
|
||||
mediaStatus?.stop()
|
||||
mediaStatus = null
|
||||
phoneStatus?.stop()
|
||||
phoneStatus = null
|
||||
clusterVideo?.stop()
|
||||
clusterVideo = null
|
||||
clusterNav?.stop()
|
||||
clusterNav = null
|
||||
lastSensorNight = null
|
||||
@@ -237,6 +244,10 @@ class GearslipRunner(
|
||||
clusterNav?.onMessage(messageId, body)
|
||||
} else if (frame.channel == mediaStatus?.channelId) {
|
||||
mediaStatus?.onMessage(messageId, body)
|
||||
} else if (frame.channel == phoneStatus?.channelId) {
|
||||
phoneStatus?.onMessage(messageId, body)
|
||||
} else if (frame.channel == clusterVideo?.channelId) {
|
||||
clusterVideo?.onMessage(messageId, body)
|
||||
} else if (frame.channel == guidanceLink?.channelId) {
|
||||
guidanceLink?.onMessage(messageId, body)
|
||||
} else if (frame.channel == CarBluetooth.channelId) {
|
||||
@@ -462,6 +473,7 @@ class GearslipRunner(
|
||||
log.i("head unit: $info")
|
||||
SessionReport.headUnit(info.toString())
|
||||
SessionReport.headUnitInfo = info
|
||||
app.seb3thehacker.gearslip.car.CarData.onModel(info.carModel, info.carYear)
|
||||
vehicleProfile = runCatching { vehicleProfileFor(info) }.getOrNull()
|
||||
val profile = vehicleProfile
|
||||
SessionReport.profile(profile?.name)
|
||||
@@ -512,6 +524,8 @@ class GearslipRunner(
|
||||
startSensorChannel(serviceDiscoveryResponse)
|
||||
startMicChannel(serviceDiscoveryResponse)
|
||||
startMediaStatusChannel(serviceDiscoveryResponse)
|
||||
startPhoneStatusChannel(serviceDiscoveryResponse)
|
||||
startClusterVideo(serviceDiscoveryResponse)
|
||||
startClusterNavChannel(serviceDiscoveryResponse)
|
||||
startBluetooth(serviceDiscoveryResponse)
|
||||
startVendorChannel(serviceDiscoveryResponse)
|
||||
@@ -628,6 +642,32 @@ class GearslipRunner(
|
||||
openChannel(channel)
|
||||
}
|
||||
|
||||
/** The turn card on the instrument cluster, for a car with a cluster screen: experimental, off by default. */
|
||||
private fun startClusterVideo(serviceDiscoveryResponse: ByteArray) {
|
||||
val sink = ServiceDiscovery.findClusterVideoService(serviceDiscoveryResponse) ?: return
|
||||
log.i("cluster video sink: channel=${sink.serviceId} display=${sink.displayId} type=${sink.displayType}")
|
||||
if (!CarSettings.experimentalFeaturesEnabled.value || !CarSettings.clusterVideoEnabled.value) {
|
||||
log.i("cluster screen is off in the car's Settings (experimental) - leaving that sink alone")
|
||||
return
|
||||
}
|
||||
val context = app.seb3thehacker.gearslip.car.CarEnvironment.appContext ?: return
|
||||
clusterVideo = ClusterVideo(context, sink) { id, body -> send(id, body, encrypted = true, channel = sink.serviceId) }
|
||||
openChannel(sink.serviceId)
|
||||
}
|
||||
|
||||
/** Calls, for the car's own screens: the cluster, the steering wheel display. */
|
||||
private fun startPhoneStatusChannel(serviceDiscoveryResponse: ByteArray) {
|
||||
val channel = ServiceDiscovery.findPhoneStatusChannel(serviceDiscoveryResponse) ?: run {
|
||||
log.i("no phone status channel - the car's own screens won't show calls")
|
||||
return
|
||||
}
|
||||
log.i("phone status channel: $channel")
|
||||
phoneStatus = app.seb3thehacker.gearslip.call.CarPhoneStatus(channel) { id, body ->
|
||||
send(id, body, encrypted = true, channel = channel)
|
||||
}
|
||||
openChannel(channel)
|
||||
}
|
||||
|
||||
/** Bluetooth pairing, for calls: the channel only opens if the driver asks to pair. */
|
||||
/**
|
||||
* GM's own channel ("com.gm.vip"), opened in GM cars only. Issue #18: a 2021 Bolt ends every
|
||||
@@ -766,6 +806,7 @@ class GearslipRunner(
|
||||
|
||||
MSG_SENSOR_EVENT_INDICATION -> {
|
||||
CarSensors.onEvent(body)
|
||||
app.seb3thehacker.gearslip.car.CarData.onEvent(body)
|
||||
logSensorEvent(body)
|
||||
onNightSensor(body)
|
||||
}
|
||||
@@ -1458,7 +1499,7 @@ class GearslipRunner(
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
internal companion object {
|
||||
/** The stats window adaptive bitrate reads, matching the watchdog's sleep. */
|
||||
const val ADAPT_WINDOW_MS = 5_000L
|
||||
|
||||
|
||||
@@ -51,8 +51,17 @@ object ServiceDiscovery {
|
||||
"$resolutionName @ $frameRateName, density=$density, margin=${widthMargin}x$heightMargin"
|
||||
}
|
||||
|
||||
/** [displayId] is MediaSinkService field 6: 0, the default, is the main screen. */
|
||||
class VideoService(val serviceId: Int, val codecType: Int, val configs: List<VideoConfig>, val displayId: Int = 0) {
|
||||
/**
|
||||
* [displayId] is MediaSinkService field 6: 0, the default, is the main screen. [displayType]
|
||||
* is field 7 (0 main, 1 instrument cluster, 2 auxiliary), or null when the car leaves it out.
|
||||
*/
|
||||
class VideoService(
|
||||
val serviceId: Int,
|
||||
val codecType: Int,
|
||||
val configs: List<VideoConfig>,
|
||||
val displayId: Int = 0,
|
||||
val displayType: Int? = null,
|
||||
) {
|
||||
val codecName: String get() = CODECS[codecType] ?: "unknown($codecType)"
|
||||
}
|
||||
|
||||
@@ -116,6 +125,12 @@ object ServiceDiscovery {
|
||||
*/
|
||||
fun findMediaStatusChannel(response: ByteArray): Int? = findChannelWith(response, 9)
|
||||
|
||||
/**
|
||||
* The channel the car takes call status on (PhoneStatusService, Service field 10), for its
|
||||
* cluster and phone screens. It carries no settings. Null when the car has none.
|
||||
*/
|
||||
fun findPhoneStatusChannel(response: ByteArray): Int? = findChannelWith(response, 10)
|
||||
|
||||
class NavStatusService(val serviceId: Int, val minimumIntervalMs: Int)
|
||||
|
||||
/** The cluster advertises its minimum update interval in NavigationStatusService field 1. */
|
||||
@@ -277,6 +292,19 @@ object ServiceDiscovery {
|
||||
return sinks.firstOrNull { it.displayId == 0 } ?: sinks.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* The instrument cluster's video sink: one marked as a cluster, or, from a car that marks
|
||||
* none, a second sink beside the main screen's. Null when the car has no cluster screen.
|
||||
*/
|
||||
fun findClusterVideoService(response: ByteArray): VideoService? {
|
||||
val sinks = videoServices(response)
|
||||
val main = findVideoService(response) ?: return null
|
||||
return sinks.firstOrNull { it.displayType == DISPLAY_TYPE_CLUSTER && it.serviceId != main.serviceId }
|
||||
?: sinks.firstOrNull { it.displayType == null && it.serviceId != main.serviceId && it.displayId != main.displayId }
|
||||
}
|
||||
|
||||
private const val DISPLAY_TYPE_CLUSTER = 1
|
||||
|
||||
private fun videoServices(response: ByteArray): List<VideoService> = buildList {
|
||||
for (channel in Wire.allBytes(Wire.fields(response), 1)) {
|
||||
val service = Wire.fields(channel)
|
||||
@@ -298,7 +326,7 @@ object ServiceDiscovery {
|
||||
heightMargin = Wire.varint(f, 4)?.toInt() ?: 0,
|
||||
)
|
||||
}
|
||||
add(VideoService(id, codecType, configs, Wire.varint(sinkFields, 6)?.toInt() ?: 0))
|
||||
add(VideoService(id, codecType, configs, Wire.varint(sinkFields, 6)?.toInt() ?: 0, Wire.varint(sinkFields, 7)?.toInt()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,17 @@ internal class AacEncoder(private val sampleRate: Int, private val channels: Int
|
||||
runCatching { codec.release() }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val INPUT_WAIT_US = 10_000L
|
||||
val FREQUENCIES = listOf(96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350)
|
||||
companion object {
|
||||
private const val INPUT_WAIT_US = 10_000L
|
||||
private val FREQUENCIES = listOf(96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350)
|
||||
|
||||
/** The two-byte AudioSpecificConfig for AAC-LC: object type 2, rate index, channel count. */
|
||||
fun audioSpecificConfig(sampleRate: Int, channels: Int): ByteArray {
|
||||
val freq = FREQUENCIES.indexOf(sampleRate).let { if (it < 0) 3 else it }
|
||||
return byteArrayOf(
|
||||
((2 shl 3) or (freq shr 1)).toByte(),
|
||||
(((freq and 1) shl 7) or (channels shl 3)).toByte(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,10 @@ class AudioLink(
|
||||
private var bytes = 0L
|
||||
private var dropped = 0
|
||||
@Volatile private var configTimer: Thread? = null
|
||||
/** Underflows the car reported this drive, and the silence still to slip in ahead of the next chunk. */
|
||||
private var underflows = 0
|
||||
@Volatile private var padMs = 0
|
||||
@Volatile private var pendingPadMs = 0
|
||||
private val ackLock = Object()
|
||||
/** Only for a sink that takes AAC and nothing else; PCM sinks get the capture as it is. */
|
||||
@Volatile private var aac: AacEncoder? = null
|
||||
@@ -180,6 +184,8 @@ class AudioLink(
|
||||
}
|
||||
}
|
||||
|
||||
MSG_UNDERFLOW -> onUnderflow()
|
||||
|
||||
MSG_ACK -> {
|
||||
val acked = Wire.varint(Wire.fields(body), 2)?.toInt() ?: 1
|
||||
synchronized(ackLock) {
|
||||
@@ -221,6 +227,26 @@ class AudioLink(
|
||||
if (had && (state == FOCUS_STATE_LOSS || state == FOCUS_STATE_LOSS_TRANSIENT)) onTakenBack()
|
||||
}
|
||||
|
||||
/**
|
||||
* The car ran out of sound to play (AudioUnderflowNotification, no body): its buffer emptied
|
||||
* before the next chunk arrived, which the driver hears as a gap. Capture runs in real time, so
|
||||
* the car's buffer only ever holds what the window lets through. A little silence slipped in
|
||||
* ahead of the next chunk puts it that far ahead, so the next slow patch is soaked up rather
|
||||
* than heard. Capped, as each step adds a little delay to the music.
|
||||
*/
|
||||
private fun onUnderflow() {
|
||||
underflows++
|
||||
val step = if (started && padMs < MAX_PAD_MS) PAD_STEP_MS else 0
|
||||
padMs += step
|
||||
pendingPadMs += step
|
||||
if (underflows <= 3 || underflows % 20 == 0) {
|
||||
GearslipLog.w("$tag: <- AudioUnderflow ($underflows this drive) - running ${padMs}ms ahead now")
|
||||
}
|
||||
}
|
||||
|
||||
/** Silence for [ms], whole frames of this sink's format. */
|
||||
private fun silence(ms: Int): ByteArray = ByteArray(sampleRate * ms / 1000 * channels * 2)
|
||||
|
||||
override fun start() {
|
||||
if (!ready || started) return
|
||||
if (asksFocus && !focusRequested) {
|
||||
@@ -251,6 +277,12 @@ class AudioLink(
|
||||
MSG_START,
|
||||
Protobuf.varintField(1, sessionId.toLong()) + Protobuf.varintField(2, configIndex.toLong()),
|
||||
)
|
||||
// Raw AAC-LC has no header of its own: the car needs the codec config before the first frame.
|
||||
if (service.codecType == ServiceDiscovery.CODEC_AAC_LC) {
|
||||
val asc = AacEncoder.audioSpecificConfig(sampleRate, channels)
|
||||
sendOnChannel(MSG_CODEC_CONFIG, asc)
|
||||
GearslipLog.hex("$tag: -> CodecConfig (AudioSpecificConfig)", asc)
|
||||
}
|
||||
inFlight = 0
|
||||
lastAckAt = System.currentTimeMillis()
|
||||
if (aacSink && aac == null) {
|
||||
@@ -282,10 +314,23 @@ class AudioLink(
|
||||
GearslipLog.i("$tag: -> Stop, AudioFocusRequest(RELEASE) after $chunks chunks / $bytes bytes")
|
||||
chunks = 0
|
||||
bytes = 0
|
||||
// A new stream starts with the car's buffer empty again: earn the lead back if it's needed.
|
||||
padMs = 0
|
||||
pendingPadMs = 0
|
||||
}
|
||||
|
||||
override fun write(pcm: ByteArray, size: Int) {
|
||||
if (!started) return
|
||||
val pad = pendingPadMs
|
||||
if (pad > 0) {
|
||||
pendingPadMs = 0
|
||||
val quiet = silence(pad)
|
||||
writeNow(quiet, quiet.size)
|
||||
}
|
||||
writeNow(pcm, size)
|
||||
}
|
||||
|
||||
private fun writeNow(pcm: ByteArray, size: Int) {
|
||||
val encoder = aac
|
||||
if (encoder == null) {
|
||||
send(pcm.copyOf(size))
|
||||
@@ -364,12 +409,16 @@ class AudioLink(
|
||||
const val MSG_FOCUS_RESPONSE = 19
|
||||
private const val MSG_CHANNEL_OPEN_RESPONSE = 8
|
||||
private const val MSG_DATA = 0
|
||||
private const val MSG_CODEC_CONFIG = 1
|
||||
private const val MSG_SETUP = 32768
|
||||
private const val MSG_START = 32769
|
||||
private const val MSG_STOP = 32770
|
||||
private const val MSG_CONFIG = 32771
|
||||
private const val MSG_ACK = 32772
|
||||
private const val MSG_SETUP_ANSWER = 255
|
||||
private const val MSG_UNDERFLOW = 32779 // 0x800B, car to phone, no body
|
||||
private const val PAD_STEP_MS = 60
|
||||
private const val MAX_PAD_MS = 240
|
||||
private const val STATUS_READY = 2
|
||||
private const val FOCUS_GAIN = 1
|
||||
private const val FOCUS_GAIN_TRANSIENT_MAY_DUCK = 3
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package app.seb3thehacker.gearslip.call
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.view.KeyEvent
|
||||
import app.seb3thehacker.gearslip.GearslipLog
|
||||
import app.seb3thehacker.gearslip.Protobuf
|
||||
import app.seb3thehacker.gearslip.Wire
|
||||
import app.seb3thehacker.gearslip.car.CarKeys
|
||||
|
||||
/**
|
||||
* Tells the car about calls, so its own screens show them: the instrument cluster, the steering
|
||||
* wheel display, the car's phone page. Android Auto does the same over the car's
|
||||
* PhoneStatusService (Service field 10):
|
||||
*
|
||||
* -> PhoneStatusUpdate { repeated PhoneCall calls = 1 }
|
||||
* PhoneCall { state = 1 (1 in call, 4 incoming, 6 muted); duration_seconds = 2;
|
||||
* number = 3; name = 4 }
|
||||
* <- PhoneStatusInput { input = 1 { action = 1 (7 call) }; caller = 2; name = 3 }
|
||||
*
|
||||
* Read once a second from [CarCalls]. An update goes when the call changes, and every second
|
||||
* while one is going, so the car's timer counts along; no calls goes once, as an empty list.
|
||||
* The car's own call button answers through [CarKeys], like the steering wheel's.
|
||||
*/
|
||||
class CarPhoneStatus(
|
||||
val channelId: Int,
|
||||
private val sendOnChannel: (messageId: Int, body: ByteArray) -> Unit,
|
||||
) {
|
||||
@Volatile private var running = false
|
||||
private var sent: String? = null
|
||||
|
||||
fun onMessage(messageId: Int, body: ByteArray) {
|
||||
when (messageId) {
|
||||
MSG_CHANNEL_OPEN_RESPONSE -> {
|
||||
val status = Protobuf.readInt32Field(body, 1)
|
||||
GearslipLog.i("phone status: <- ChannelOpenResponse: status=$status")
|
||||
if (status == 0) start()
|
||||
}
|
||||
MSG_INPUT -> onInput(body)
|
||||
else -> GearslipLog.i("phone status: <- unhandled message id=$messageId (${body.size} bytes)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun start() {
|
||||
if (running) return
|
||||
running = true
|
||||
Thread({
|
||||
while (running) {
|
||||
runCatching { tick() }.onFailure { GearslipLog.w("phone status: ${it.message}") }
|
||||
Thread.sleep(TICK_MS)
|
||||
}
|
||||
}, "gearslip-phone-status").start()
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
running = false
|
||||
}
|
||||
|
||||
private fun tick() {
|
||||
val call = when (val state = CarCalls.state.value) {
|
||||
CallUiState.None -> null
|
||||
is CallUiState.Ringing -> Call(STATE_INCOMING, 0, state.caller)
|
||||
is CallUiState.Active -> Call(
|
||||
if (CarCalls.muted.value) STATE_MUTED else STATE_IN_CALL,
|
||||
((SystemClock.uptimeMillis() - state.since) / 1000).coerceAtLeast(0),
|
||||
state.caller,
|
||||
)
|
||||
}
|
||||
// Only a call that's going changes from one second to the next.
|
||||
val key = call?.let { "${it.state}|${it.caller?.number}|${it.caller?.name}|${if (it.state == STATE_INCOMING) 0 else it.seconds}" } ?: ""
|
||||
if (key == sent) return
|
||||
val changed = call?.state != lastState
|
||||
sent = key
|
||||
lastState = call?.state
|
||||
val body = call?.let { Protobuf.bytesField(1, it.encode()) } ?: ByteArray(0)
|
||||
sendOnChannel(MSG_STATUS, body)
|
||||
if (changed) GearslipLog.i("phone status: -> ${call?.let { STATE_NAMES[it.state] } ?: "no calls"}")
|
||||
}
|
||||
|
||||
private var lastState: Int? = null
|
||||
|
||||
private class Call(val state: Int, val seconds: Long, val caller: Caller?) {
|
||||
fun encode(): ByteArray {
|
||||
var body = Protobuf.varintField(1, state.toLong()) + Protobuf.varintField(2, seconds)
|
||||
caller?.number?.takeIf { it.isNotBlank() }?.let { body += Protobuf.stringField(3, it) }
|
||||
caller?.name?.takeIf { it.isNotBlank() }?.let { body += Protobuf.stringField(4, it) }
|
||||
return body
|
||||
}
|
||||
}
|
||||
|
||||
/** The car's own phone buttons. Only "call" does anything: it answers, as the wheel's does. */
|
||||
private fun onInput(body: ByteArray) {
|
||||
val action = Wire.bytes(Wire.fields(body), 1)?.let { Wire.varint(Wire.fields(it), 1)?.toInt() }
|
||||
GearslipLog.i("phone status: <- input action=${action ?: "?"}")
|
||||
if (action == ACTION_CALL) CarKeys.onKey(KeyEvent.KEYCODE_CALL, down = true, longPress = false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TICK_MS = 1_000L
|
||||
private const val MSG_CHANNEL_OPEN_RESPONSE = 8
|
||||
private const val MSG_STATUS = 32769 // 0x8001
|
||||
private const val MSG_INPUT = 32770 // 0x8002
|
||||
private const val STATE_IN_CALL = 1
|
||||
private const val STATE_INCOMING = 4
|
||||
private const val STATE_MUTED = 6
|
||||
private const val ACTION_CALL = 7
|
||||
private val STATE_NAMES = mapOf(STATE_IN_CALL to "in a call", STATE_INCOMING to "ringing", STATE_MUTED to "in a call, muted")
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,12 @@ object AssistantCommands {
|
||||
heard: String,
|
||||
): String? {
|
||||
// "use Organic Maps to get to X", "open OsmAnd and navigate to X": the app comes first.
|
||||
var text = heard
|
||||
// "take me home", "go home": no "to", so no trigger would catch them.
|
||||
var text = if (Regex("^(?:(?:take|bring|get|drive) me|go|drive|head|get|directions|go back|head back) home$").matches(heard)) {
|
||||
"navigate to home"
|
||||
} else {
|
||||
heard
|
||||
}
|
||||
var leadingApp: String? = null
|
||||
Regex("^(?:use|using|with|open|in|on) (.+?) (?:to |and )(.+)$").find(text)?.let { m ->
|
||||
if (after(m.groupValues[2], NAVIGATE_TRIGGERS) != null) {
|
||||
@@ -135,6 +140,20 @@ object AssistantCommands {
|
||||
val (place, nearby) = plainPlace(destination)
|
||||
destination = place
|
||||
if (destination.isBlank()) return "Where to?"
|
||||
// "home", "work", or a name from Saved places: its address is the search.
|
||||
var spoken: String? = null
|
||||
SavedPlaces.match(context, destination)?.let { (slot, saved) ->
|
||||
val name = when (slot) {
|
||||
SavedPlaces.HOME -> "home"
|
||||
SavedPlaces.WORK -> "work"
|
||||
else -> saved.label
|
||||
}
|
||||
if (saved.address.isBlank()) {
|
||||
return "You haven't saved a $name address yet. Add it under Saved places in Gearslip's settings on your phone."
|
||||
}
|
||||
destination = saved.address
|
||||
spoken = if (slot == SavedPlaces.HOME) "home" else "to $name"
|
||||
}
|
||||
|
||||
val chosen = chosenApp ?: when {
|
||||
navApps.size == 1 -> navApps.first()
|
||||
@@ -144,6 +163,7 @@ object AssistantCommands {
|
||||
|
||||
CarServices.connectNav(template, frame, destination)
|
||||
navigator.home()
|
||||
spoken?.let { return "Navigating $it with ${chosen.label}." }
|
||||
return if (nearby) "Finding the nearest $destination with ${chosen.label}." else "Navigating to $destination with ${chosen.label}."
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ object BluetoothAudioCheck {
|
||||
if (!armed) return
|
||||
val context = appContext ?: return
|
||||
if (CarSettings.pipeAudio.value) {
|
||||
set(false, "USB audio is on")
|
||||
set(false, "audio goes through Gearslip")
|
||||
return
|
||||
}
|
||||
if (!CarSettings.bluetoothWarning.value) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -45,32 +47,28 @@ internal fun BluetoothPairOverlay() {
|
||||
val locationOff by LocationCheck.off.collectAsState()
|
||||
val locationDismissed by LocationCheck.carDismissed.collectAsState()
|
||||
if (missing && bluetoothAudio && warn && !dismissed) PairCard(
|
||||
title = "Bluetooth isn't connected",
|
||||
body = "Music plays over Bluetooth, but your phone isn't connected to the car. " +
|
||||
"Connect it from the car's Bluetooth menu or your phone's.",
|
||||
secondary = "Don't show again" to BluetoothAudioCheck::neverAgain,
|
||||
primary = "OK" to BluetoothAudioCheck::dismissOnCar,
|
||||
title = stringResource(R.string.home_bt_title),
|
||||
body = stringResource(R.string.overlay_bt_body),
|
||||
secondary = stringResource(R.string.overlay_dont_show) to BluetoothAudioCheck::neverAgain,
|
||||
primary = stringResource(R.string.overlay_ok) to BluetoothAudioCheck::dismissOnCar,
|
||||
) else if (locationOff && !locationDismissed) PairCard(
|
||||
title = "Location is off",
|
||||
body = "Map apps can't show where you are or give directions. Turn on location " +
|
||||
"in your phone's quick settings when you're parked.",
|
||||
secondary = "Don't show again" to LocationCheck::neverAgain,
|
||||
primary = "OK" to LocationCheck::dismissOnCar,
|
||||
title = stringResource(R.string.overlay_location_off),
|
||||
body = stringResource(R.string.overlay_location_body),
|
||||
secondary = stringResource(R.string.overlay_dont_show) to LocationCheck::neverAgain,
|
||||
primary = stringResource(R.string.overlay_ok) to LocationCheck::dismissOnCar,
|
||||
)
|
||||
}
|
||||
CarBluetooth.Prompt.ASK -> PairCard(
|
||||
title = "Pair with this car?",
|
||||
body = (if (bluetoothAudio) "Calls and music play" else "Calls play") +
|
||||
" through the car's speakers once your phone and the car are paired. Your phone " +
|
||||
"then asks you to confirm a code.",
|
||||
secondary = "Not now" to CarBluetooth::notNow,
|
||||
primary = "Pair" to CarBluetooth::pair,
|
||||
title = stringResource(R.string.overlay_pair_title),
|
||||
body = stringResource(if (bluetoothAudio) R.string.overlay_pair_body_music else R.string.overlay_pair_body),
|
||||
secondary = stringResource(R.string.worked_not_now) to CarBluetooth::notNow,
|
||||
primary = stringResource(R.string.overlay_pair) to CarBluetooth::pair,
|
||||
)
|
||||
CarBluetooth.Prompt.PAIRING -> PairCard(
|
||||
title = "Confirm on your phone",
|
||||
body = "Check the code on your phone and tap Pair. The car may ask too.",
|
||||
title = stringResource(R.string.overlay_confirm_title),
|
||||
body = stringResource(R.string.overlay_confirm_body),
|
||||
secondary = null,
|
||||
primary = "Hide" to CarBluetooth::hide,
|
||||
primary = stringResource(R.string.carset_hide) to CarBluetooth::hide,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -63,7 +65,7 @@ private fun IncomingCall(s: CallUiState.Ringing, onAnswer: () -> Unit, onDecline
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
"Incoming call",
|
||||
stringResource(R.string.call_incoming),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -76,13 +78,13 @@ private fun IncomingCall(s: CallUiState.Ringing, onAnswer: () -> Unit, onDecline
|
||||
)
|
||||
Spacer(Modifier.height(56.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(48.dp)) {
|
||||
CallButton(Icons.Filled.Close, "Decline", MaterialTheme.colorScheme.error, enabled = s.canDecline, onClick = onDecline)
|
||||
CallButton(Icons.Filled.Call, "Answer", MaterialTheme.colorScheme.primary, enabled = true, onClick = onAnswer)
|
||||
CallButton(Icons.Filled.Close, stringResource(R.string.call_decline), MaterialTheme.colorScheme.error, enabled = s.canDecline, onClick = onDecline)
|
||||
CallButton(Icons.Filled.Call, stringResource(R.string.call_answer), MaterialTheme.colorScheme.primary, enabled = true, onClick = onAnswer)
|
||||
}
|
||||
if (!s.canDecline) {
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text(
|
||||
"Already ringing out loud - answer here, or let it ring.",
|
||||
stringResource(R.string.call_ringing),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
@@ -11,6 +13,8 @@ import androidx.compose.ui.input.pointer.PointerEventPass
|
||||
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
||||
import kotlin.math.hypot
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -73,10 +77,28 @@ fun CarAppStage(
|
||||
if (status.surfaceRequested) {
|
||||
AppSurface(connection, frame, template, Modifier.fillMaxSize())
|
||||
}
|
||||
// The docked player floats over one side of the map: the chrome keeps clear of it,
|
||||
// and the app hears that side is covered, so its map centres in the rest.
|
||||
val panel = LocalSidePanelInset.current
|
||||
val panelAtStart = LocalCardsAtEnd.current
|
||||
TemplateChrome(
|
||||
template,
|
||||
Modifier.fillMaxSize().padding(top = if (embedded) 0.dp else CHROME_BAR, bottom = navBar),
|
||||
Modifier.fillMaxSize().padding(
|
||||
top = if (embedded) 0.dp else CHROME_BAR,
|
||||
bottom = navBar,
|
||||
start = if (panelAtStart) panel else 0.dp,
|
||||
end = if (panelAtStart) 0.dp else panel,
|
||||
),
|
||||
)
|
||||
if (panel > 0.dp) {
|
||||
Box(
|
||||
Modifier
|
||||
.align(if (panelAtStart) Alignment.CenterStart else Alignment.CenterEnd)
|
||||
.fillMaxHeight()
|
||||
.width(panel)
|
||||
.covers("side-panel", if (panelAtStart) CoverEdge.START else CoverEdge.END),
|
||||
)
|
||||
}
|
||||
BottomCover(navBar, Modifier.align(Alignment.BottomCenter))
|
||||
}
|
||||
if (!embedded) ChromeBar(status, onDisconnect, Modifier.align(Alignment.TopCenter))
|
||||
@@ -106,12 +128,16 @@ private fun AppSurface(
|
||||
val measured = bounds?.takeIf { width > 0 && height > 0 }?.let { cover?.visibleWithin(it, width, height) }
|
||||
// The card column moves the map's centre but isn't taken out of the stable area: apps size
|
||||
// their own overlays from it, and Vela's speedometer shrank to fit the strip right of the cards.
|
||||
val measuredStable = bounds?.takeIf { width > 0 && height > 0 }?.let { cover?.visibleWithin(it, width, height, skip = CoverEdge.START) }
|
||||
LaunchedEffect(template, size, measured, measuredStable) {
|
||||
val cardsAtEnd = LocalCardsAtEnd.current
|
||||
val cardEdge = cardEdge
|
||||
val measuredStable = bounds?.takeIf { width > 0 && height > 0 }?.let { cover?.visibleWithin(it, width, height, skip = cardEdge) }
|
||||
LaunchedEffect(template, size, measured, measuredStable, cardsAtEnd) {
|
||||
if (width > 0 && height > 0) {
|
||||
val visible = visibleAreaFor(template, width, height, measured)
|
||||
val stable = if (!isContentTemplate(template)) {
|
||||
visibleAreaFor(template, width, height, measuredStable)
|
||||
val visible = visibleAreaFor(template, width, height, measured, cardsAtEnd)
|
||||
// Waze seems to centre its car in the stable area rather than the visible one, so it
|
||||
// gets the cards taken out of that too. Test: see whether its car moves out from under them.
|
||||
val stable = if (!isContentTemplate(template) && connection.appPackage != "com.waze") {
|
||||
visibleAreaFor(template, width, height, measuredStable, cardsAtEnd)
|
||||
} else {
|
||||
visible
|
||||
}
|
||||
@@ -227,14 +253,14 @@ private fun ChromeBar(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
status.app ?: "Car app",
|
||||
status.app ?: stringResource(R.string.stage_car_app),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
GsIconButton(
|
||||
androidx.compose.material.icons.Icons.Filled.Close,
|
||||
"Close ${status.app ?: "the app"}",
|
||||
status.app?.let { stringResource(R.string.stage_close, it) } ?: stringResource(R.string.stage_close_app),
|
||||
onDisconnect,
|
||||
size = 36.dp,
|
||||
iconSize = 20.dp,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Call
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
@@ -13,7 +15,9 @@ import app.seb3thehacker.gearslip.ProjectedContent
|
||||
/** One tile in the launcher and the screen it opens. */
|
||||
class CarApp(
|
||||
val id: String,
|
||||
/** English, for matching what a driver says; [labelRes] is what the screen shows. */
|
||||
val label: String,
|
||||
@param:StringRes val labelRes: Int,
|
||||
val icon: ImageVector,
|
||||
val content: @Composable () -> Unit,
|
||||
)
|
||||
@@ -22,16 +26,16 @@ class CarApp(
|
||||
object CarApps {
|
||||
|
||||
val all: List<CarApp> = listOf(
|
||||
CarApp("web", "Browser", Icons.Filled.Search) {
|
||||
CarApp("web", "Browser", R.string.app_browser, Icons.Filled.Search) {
|
||||
WebApp(content = ProjectedContent.resolve(LocalContext.current))
|
||||
},
|
||||
CarApp("phone", "Screen sharing", Icons.Filled.Share) {
|
||||
CarApp("phone", "Screen sharing", R.string.app_screen_sharing, Icons.Filled.Share) {
|
||||
PhoneAppsScreen()
|
||||
},
|
||||
CarApp("dialer", "Phone", Icons.Filled.Call) {
|
||||
CarApp("dialer", "Phone", R.string.carset_phone, Icons.Filled.Call) {
|
||||
PhoneDialerScreen()
|
||||
},
|
||||
CarApp("settings", "Settings", Icons.Filled.Settings) {
|
||||
CarApp("settings", "Settings", R.string.settings_title, Icons.Filled.Settings) {
|
||||
CarSettingsScreen()
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
@@ -111,7 +113,7 @@ object CarBluetooth {
|
||||
// If the car never answers on its channel, pair from the phone anyway.
|
||||
main.postDelayed({ startBond("the car didn't answer the pairing request") }, CAR_REPLY_WAIT_MS)
|
||||
main.postDelayed({
|
||||
if (pairing) finish("Pairing didn't finish. Pair from the car's Bluetooth menu instead.")
|
||||
if (pairing) finish(R.string.bt_pair_unfinished)
|
||||
}, GIVE_UP_MS)
|
||||
}
|
||||
|
||||
@@ -168,7 +170,7 @@ object CarBluetooth {
|
||||
bondStarted = true
|
||||
val started = runCatching { device.createBond() }.getOrDefault(false)
|
||||
log.i("pairing from the phone ($why): ${if (started) "started" else "refused"}")
|
||||
if (!started) finish("Pairing didn't start. Pair from the car's Bluetooth menu instead.")
|
||||
if (!started) finish(R.string.bt_pair_not_started)
|
||||
}
|
||||
|
||||
private fun watchBonding(device: BluetoothDevice) {
|
||||
@@ -184,8 +186,8 @@ object CarBluetooth {
|
||||
log.i("bond state $previous -> $state")
|
||||
when {
|
||||
state == BluetoothDevice.BOND_BONDING -> bondStarted = true
|
||||
state == BluetoothDevice.BOND_BONDED -> finish("Paired. Calls now play through the car.")
|
||||
previous == BluetoothDevice.BOND_BONDING -> finish("Pairing didn't finish. Pair from the car's Bluetooth menu instead.")
|
||||
state == BluetoothDevice.BOND_BONDED -> finish(R.string.bt_paired)
|
||||
previous == BluetoothDevice.BOND_BONDING -> finish(R.string.bt_pair_unfinished)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,12 +195,12 @@ object CarBluetooth {
|
||||
receiver = r
|
||||
}
|
||||
|
||||
private fun finish(message: String) {
|
||||
private fun finish(@StringRes message: Int) {
|
||||
pairing = false
|
||||
stopWatching()
|
||||
main.removeCallbacksAndMessages(null)
|
||||
_prompt.value = Prompt.NONE
|
||||
CarToasts.show(this, message, CarToast.LENGTH_LONG)
|
||||
CarToasts.show(this, appContext?.getString(message) ?: return, CarToast.LENGTH_LONG)
|
||||
}
|
||||
|
||||
private fun stopWatching() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
@@ -60,7 +62,7 @@ fun CarDashboardScreen() {
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
ScreenHeader("Today")
|
||||
ScreenHeader(stringResource(R.string.dash_today))
|
||||
WeatherCard(context)
|
||||
AgendaCard(context)
|
||||
NotificationsSection()
|
||||
@@ -84,9 +86,9 @@ private fun WeatherCard(context: Context) {
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
when (val s = state) {
|
||||
WeatherState.Loading -> Text("Getting weather…", style = MaterialTheme.typography.bodyLarge)
|
||||
WeatherState.Loading -> Text(stringResource(R.string.dash_getting_weather), style = MaterialTheme.typography.bodyLarge)
|
||||
is WeatherState.Unavailable -> Text(
|
||||
s.reason, style = MaterialTheme.typography.bodyLarge,
|
||||
stringResource(s.reason), style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
is WeatherState.Ready -> {
|
||||
@@ -94,7 +96,7 @@ private fun WeatherCard(context: Context) {
|
||||
WeatherIcon(w.code, w.isDay, size = 44.dp)
|
||||
Column {
|
||||
Text(
|
||||
"${w.temp}${w.tempUnit} · ${Weather.describe(w.code)}",
|
||||
"${w.temp}${w.tempUnit} · ${Weather.condition(w.code)}",
|
||||
style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(w.place, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
@@ -112,12 +114,12 @@ private fun AgendaCard(context: Context) {
|
||||
}
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Coming up", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Text(stringResource(R.string.dash_coming_up), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
val list = events
|
||||
when {
|
||||
list == null -> Text("Loading calendar…", style = MaterialTheme.typography.bodyMedium)
|
||||
list == null -> Text(stringResource(R.string.dash_loading_calendar), style = MaterialTheme.typography.bodyMedium)
|
||||
list.isEmpty() -> Text(
|
||||
"Nothing in the next week.",
|
||||
stringResource(R.string.dash_nothing_week),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -135,7 +137,7 @@ private fun EventRow(event: AgendaEvent) {
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
eventWhen(event),
|
||||
eventWhen(event, stringResource(R.string.dash_all_day)),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
@@ -149,14 +151,14 @@ private fun EventRow(event: AgendaEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
/** "Today" events show just a time; later days get a weekday so a week's list stays readable. */
|
||||
private fun eventWhen(event: AgendaEvent): String {
|
||||
/** stringResource(R.string.dash_today) events show just a time; later days get a weekday so a week's list stays readable. */
|
||||
private fun eventWhen(event: AgendaEvent, allDay: String): String {
|
||||
val zone = if (event.allDay) TimeZone.getTimeZone("UTC") else TimeZone.getDefault()
|
||||
val start = Calendar.getInstance(zone).apply { timeInMillis = event.startMillis }
|
||||
val today = Calendar.getInstance()
|
||||
val sameDay = start.get(Calendar.YEAR) == today.get(Calendar.YEAR) &&
|
||||
start.get(Calendar.DAY_OF_YEAR) == today.get(Calendar.DAY_OF_YEAR)
|
||||
val time = if (event.allDay) "All day" else DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(event.startMillis))
|
||||
val time = if (event.allDay) allDay else DateFormat.getTimeInstance(DateFormat.SHORT).format(Date(event.startMillis))
|
||||
if (sameDay || (event.allDay && start.before(today))) return time
|
||||
return SimpleDateFormat("EEE", Locale.getDefault()).apply { timeZone = zone }.format(Date(event.startMillis)) + " " + time
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import app.seb3thehacker.gearslip.Wire
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
* The car's sensors, read into real values for the car apps that ask (a map's speed, an EV app's
|
||||
* range). [CarSensors] keeps the raw readings for the Vehicle data screen; this keeps the few with
|
||||
* a known meaning, decoded the way Android Auto's SensorBatch scales them:
|
||||
*
|
||||
* gps_location = 1 { latitude_e7 = 2; longitude_e7 = 3; accuracy_e3 = 4; altitude_e2 = 5;
|
||||
* speed_e3 = 6; bearing_e6 = 7 }
|
||||
* compass = 2 { bearing_e6 = 1 } speed = 3 { speed_e3 = 1 } (metres per second)
|
||||
* odometer = 5 { kms_e1 = 1 } fuel = 6 { level = 1 (percent); range = 2 (km); low = 3 }
|
||||
*
|
||||
* Each value is null until the car first sends it, and the whole lot clears when it disconnects.
|
||||
*/
|
||||
object CarData {
|
||||
|
||||
class Location(
|
||||
val latitude: Double,
|
||||
val longitude: Double,
|
||||
val accuracyMeters: Float?,
|
||||
val altitudeMeters: Double?,
|
||||
val speedMps: Float?,
|
||||
val bearingDegrees: Float?,
|
||||
)
|
||||
|
||||
data class Snapshot(
|
||||
val speedMps: Float? = null,
|
||||
val odometerKm: Float? = null,
|
||||
val fuelPercent: Int? = null,
|
||||
val rangeKm: Int? = null,
|
||||
val fuelLow: Boolean? = null,
|
||||
val compassDegrees: Float? = null,
|
||||
val location: Location? = null,
|
||||
/** When each kind last changed, by its SensorBatch field number. */
|
||||
val updatedAt: Map<Int, Long> = emptyMap(),
|
||||
)
|
||||
|
||||
/** What the car says it is, from its service discovery: model and year. It names no maker. */
|
||||
class Model(val name: String, val year: Int?)
|
||||
|
||||
private val flow = MutableStateFlow(Snapshot())
|
||||
val state: StateFlow<Snapshot> = flow
|
||||
|
||||
@Volatile var model: Model? = null
|
||||
private set
|
||||
|
||||
fun onModel(name: String, year: String) {
|
||||
model = name.takeIf { it.isNotBlank() }?.let { Model(it, year.toIntOrNull()?.takeIf { y -> y > 0 }) }
|
||||
}
|
||||
|
||||
fun onEvent(body: ByteArray) {
|
||||
val now = System.currentTimeMillis()
|
||||
val batch = Wire.fields(body)
|
||||
var s = flow.value
|
||||
val touched = HashSet<Int>()
|
||||
// Each kind may come several times in one batch: the last is the newest.
|
||||
Wire.allBytes(batch, LOCATION).lastOrNull()?.let { bytes ->
|
||||
val f = Wire.fields(bytes)
|
||||
val lat = Wire.varint(f, 2)?.toInt()
|
||||
val lon = Wire.varint(f, 3)?.toInt()
|
||||
if (lat != null && lon != null) {
|
||||
s = s.copy(
|
||||
location = Location(
|
||||
lat / 1e7, lon / 1e7,
|
||||
Wire.varint(f, 4)?.let { it / 1e3f },
|
||||
Wire.varint(f, 5)?.let { it.toInt() / 1e2 },
|
||||
Wire.varint(f, 6)?.let { it.toInt() / 1e3f },
|
||||
Wire.varint(f, 7)?.let { it.toInt() / 1e6f },
|
||||
),
|
||||
)
|
||||
touched += LOCATION
|
||||
}
|
||||
}
|
||||
Wire.allBytes(batch, COMPASS).lastOrNull()?.let { bytes ->
|
||||
Wire.varint(Wire.fields(bytes), 1)?.let { s = s.copy(compassDegrees = it.toInt() / 1e6f); touched += COMPASS }
|
||||
}
|
||||
Wire.allBytes(batch, SPEED).lastOrNull()?.let { bytes ->
|
||||
Wire.varint(Wire.fields(bytes), 1)?.let { s = s.copy(speedMps = it.toInt() / 1e3f); touched += SPEED }
|
||||
}
|
||||
Wire.allBytes(batch, ODOMETER).lastOrNull()?.let { bytes ->
|
||||
Wire.varint(Wire.fields(bytes), 1)?.let { s = s.copy(odometerKm = it.toInt() / 10f); touched += ODOMETER }
|
||||
}
|
||||
Wire.allBytes(batch, FUEL).lastOrNull()?.let { bytes ->
|
||||
val f = Wire.fields(bytes)
|
||||
s = s.copy(
|
||||
fuelPercent = Wire.varint(f, 1)?.toInt() ?: s.fuelPercent,
|
||||
rangeKm = Wire.varint(f, 2)?.toInt() ?: s.rangeKm,
|
||||
fuelLow = Wire.varint(f, 3)?.let { it != 0L } ?: s.fuelLow,
|
||||
)
|
||||
touched += FUEL
|
||||
}
|
||||
if (touched.isEmpty()) return
|
||||
flow.value = s.copy(updatedAt = s.updatedAt + touched.associateWith { now })
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
flow.value = Snapshot()
|
||||
model = null
|
||||
}
|
||||
|
||||
const val LOCATION = 1
|
||||
const val COMPASS = 2
|
||||
const val SPEED = 3
|
||||
const val ODOMETER = 5
|
||||
const val FUEL = 6
|
||||
}
|
||||
@@ -64,7 +64,9 @@ object CarEnvironment {
|
||||
}
|
||||
|
||||
@Volatile private var sensorNight: Boolean? = null
|
||||
private var appContext: Context? = null
|
||||
/** Set once the car UI starts; the cluster screen draws with it too. */
|
||||
var appContext: Context? = null
|
||||
private set
|
||||
private var running = false
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -12,6 +13,8 @@ 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 app.seb3thehacker.gearslip.car.theme.gsPanelShadow
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
@@ -64,7 +67,9 @@ import app.seb3thehacker.gearslip.media.NowPlaying
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* The home screen: the map on the left, the docked player on the right when asked for.
|
||||
* The home screen: the map, with the docked player floating over one side of it when asked for.
|
||||
* With the player on the left, the map app's cards move to the map's far edge, so the player, the
|
||||
* map and the cards each keep their own space.
|
||||
*
|
||||
* The map is whatever navigation app was used last, brought back on its own. The player sits in
|
||||
* the nav bar's pill until its dock button moves it here; the column closes itself back to a
|
||||
@@ -94,26 +99,56 @@ fun CarHome() {
|
||||
}
|
||||
val splitScreen = phase == CarMedia.Phase.READY && now.isActive && panel != SidePanel.NONE
|
||||
val playerLeft by CarSettings.playerLeft.collectAsStateWithLifecycle()
|
||||
// What the card showed last, so it still has something to show while it slides away.
|
||||
val shown = remember { androidx.compose.runtime.mutableStateOf(SidePanel.CONTROLS) }
|
||||
if (panel != SidePanel.NONE) shown.value = panel
|
||||
|
||||
// No padding anywhere, split screen or not: the player sits flush against the map.
|
||||
Row(Modifier.fillMaxSize()) {
|
||||
val side = Modifier.weight(1f).fillMaxHeight().padding(bottom = LocalNavBarInset.current)
|
||||
if (splitScreen && playerLeft) {
|
||||
if (panel == SidePanel.LYRICS) SideLyrics(now, side) else DockedPlayer(now, side)
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.weight(if (splitScreen) 2.1f else 1f)
|
||||
.fillMaxHeight(),
|
||||
// The map runs edge to edge underneath; the player floats over one side of it as a card, with
|
||||
// a gap all round. The map's surface keeps its size when the card comes and goes, so the app
|
||||
// only re-centres its map instead of drawing onto a new surface.
|
||||
BoxWithConstraints(Modifier.fillMaxSize()) {
|
||||
val column = (maxWidth / PLAYER_SHARE).coerceIn(PLAYER_MIN_WIDTH, PLAYER_MAX_WIDTH)
|
||||
androidx.compose.runtime.CompositionLocalProvider(
|
||||
LocalCardsAtEnd provides (splitScreen && playerLeft),
|
||||
LocalSidePanelInset provides if (splitScreen) column else 0.dp,
|
||||
) {
|
||||
MapPane(navStatus, frame)
|
||||
}
|
||||
if (splitScreen && !playerLeft) {
|
||||
if (panel == SidePanel.LYRICS) SideLyrics(now, side) else DockedPlayer(now, side)
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
splitScreen,
|
||||
Modifier.align(if (playerLeft) Alignment.CenterStart else Alignment.CenterEnd),
|
||||
enter = androidx.compose.animation.slideInHorizontally(tween(220)) { if (playerLeft) -it / 3 else it / 3 } +
|
||||
androidx.compose.animation.fadeIn(tween(180)),
|
||||
exit = androidx.compose.animation.slideOutHorizontally(tween(180)) { if (playerLeft) -it / 3 else it / 3 } +
|
||||
androidx.compose.animation.fadeOut(tween(140)),
|
||||
) {
|
||||
val card = Modifier
|
||||
.width(column)
|
||||
.fillMaxHeight()
|
||||
.padding(
|
||||
start = if (playerLeft) CARD_GAP else 0.dp,
|
||||
end = if (playerLeft) 0.dp else CARD_GAP,
|
||||
top = CARD_GAP,
|
||||
bottom = LocalNavBarInset.current + CARD_GAP,
|
||||
)
|
||||
if (shown.value == SidePanel.LYRICS) SideLyrics(now, card) else DockedPlayer(now, card)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The player takes one part in this many of the screen's width. */
|
||||
private const val PLAYER_SHARE = 3.6f
|
||||
|
||||
/** Narrow enough to leave the map most of a small screen; on a wide one the map gets the extra room. */
|
||||
private val PLAYER_MIN_WIDTH = 240.dp
|
||||
private val PLAYER_MAX_WIDTH = 340.dp
|
||||
|
||||
/** Between the player card and the screen's edges, and the map's own cards beside it. */
|
||||
private val CARD_GAP = 12.dp
|
||||
|
||||
/** The card's corners: rounder than a button's, as the map app's own cards are. */
|
||||
private val CARD_SHAPE = RoundedCornerShape(24.dp)
|
||||
|
||||
@Composable
|
||||
private fun MapPane(status: CarAppConnection.Status, frame: CarEnvironment.Frame) {
|
||||
val navigator = LocalCarNavigator.current
|
||||
@@ -123,8 +158,11 @@ private fun MapPane(status: CarAppConnection.Status, frame: CarEnvironment.Frame
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier.fillMaxSize().padding(24.dp),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
Modifier.fillMaxSize().padding(
|
||||
start = if (LocalCardsAtEnd.current) LocalSidePanelInset.current else 0.dp,
|
||||
end = if (LocalCardsAtEnd.current) 0.dp else LocalSidePanelInset.current,
|
||||
).padding(24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp, Alignment.CenterVertically),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val hasLast = CarSettings.lastNav.value != null
|
||||
@@ -132,18 +170,19 @@ private fun MapPane(status: CarAppConnection.Status, frame: CarEnvironment.Frame
|
||||
val scope = androidx.compose.runtime.rememberCoroutineScope()
|
||||
when (status.phase) {
|
||||
CarAppConnection.Phase.BINDING, CarAppConnection.Phase.HANDSHAKE ->
|
||||
Text("Starting ${status.app ?: "the map"}…", style = MaterialTheme.typography.titleMedium)
|
||||
Text(status.app?.let { stringResource(R.string.home_starting, it) } ?: stringResource(R.string.home_starting_map), style = MaterialTheme.typography.titleMedium)
|
||||
CarAppConnection.Phase.REJECTED, CarAppConnection.Phase.FAILED -> {
|
||||
Text("${status.app ?: "The map app"} would not start.", style = MaterialTheme.typography.titleMedium)
|
||||
Text(status.app?.let { stringResource(R.string.home_failed, it) } ?: stringResource(R.string.home_failed_map), style = MaterialTheme.typography.titleMedium)
|
||||
status.detail?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant) }
|
||||
GsButton(onClick = navigator::apps, tone = GsTone.Tonal) { Text("Choose another app") }
|
||||
Spacer(Modifier.height(8.dp))
|
||||
StatusButton(stringResource(R.string.home_choose_app), GsTone.Primary, navigator::apps)
|
||||
}
|
||||
else -> if (!hasNavApps) {
|
||||
// Nothing to auto-select and nothing to pick from Car apps either - this is the
|
||||
// only case worth a distinct screen, since every other IDLE case resolves itself.
|
||||
Text("No mapping apps found.", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.home_no_maps), style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
"Install a navigation app that supports Android Auto and it will start here on its own.",
|
||||
stringResource(R.string.home_no_maps_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -151,9 +190,18 @@ private fun MapPane(status: CarAppConnection.Status, frame: CarEnvironment.Frame
|
||||
// IDLE with a last app on record means the driver stopped it themselves - it will
|
||||
// not come back on its own, so the screen must offer a way to bring it back rather
|
||||
// than sit on a "starting" message that never resolves.
|
||||
Text("Map stopped.", style = MaterialTheme.typography.titleMedium)
|
||||
GsButton(onClick = { scope.launch { CarServices.reconnectNav(frame) } }, tone = GsTone.Tonal) { Text("Reopen") }
|
||||
GsButton(onClick = { navigator.apps() }, tone = GsTone.Tonal) { Text("Car apps") }
|
||||
Text(stringResource(R.string.home_map_stopped), style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
||||
Text(
|
||||
stringResource(R.string.home_map_stopped_body),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
// Side by side, the same size, the likely one lit: one clear choice, not a stack.
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||
StatusButton(stringResource(R.string.home_reopen_map), GsTone.Primary) { scope.launch { CarServices.reconnectNav(frame) } }
|
||||
StatusButton(stringResource(R.string.home_car_apps), GsTone.Tonal) { navigator.apps() }
|
||||
}
|
||||
} else {
|
||||
// Autostart is still picking one - a navigation app is installed, so this resolves
|
||||
// to a "Starting..." state itself, in the next frame or two.
|
||||
@@ -162,9 +210,17 @@ private fun MapPane(status: CarAppConnection.Status, frame: CarEnvironment.Frame
|
||||
}
|
||||
}
|
||||
|
||||
/** A big, even button for the map pane's few choices. */
|
||||
@Composable
|
||||
private fun StatusButton(label: String, tone: GsTone, onClick: () -> Unit) {
|
||||
GsButton(onClick = onClick, tone = tone, modifier = Modifier.width(220.dp).height(64.dp)) {
|
||||
Text(label, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The player, docked beside the map: art, title, progress, transport, then like, shuffle, repeat
|
||||
* and lyrics. While it's here the nav bar's pill steps aside on Home, so the controls never show
|
||||
* The player, docked beside the map: the art as large as it fits, then title, progress, transport,
|
||||
* and like, shuffle, repeat and lyrics along the bottom. While it's here the nav bar's pill steps aside on Home, so the controls never show
|
||||
* twice. The X sends it back to the nav bar.
|
||||
*/
|
||||
@Composable
|
||||
@@ -175,28 +231,31 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val art by produceState(now.art, now.art, now.artUri) { value = now.art ?: MediaArt.load(context, now.artUri) }
|
||||
|
||||
Surface(modifier = modifier, color = rememberArtColor(art, MaterialTheme.colorScheme.surfaceVariant)) {
|
||||
BoxWithConstraints(Modifier.fillMaxSize().padding(12.dp)) {
|
||||
// Whatever height the controls below leave over, so the art grows on a taller
|
||||
// screen and shrinks rather than pushing the buttons off a short one.
|
||||
val artSize = minOf(maxWidth - 44.dp, maxHeight - 250.dp).coerceIn(56.dp, 200.dp)
|
||||
// The theme's surface, with a faint wash of the cover's colours behind the top of it. A solid
|
||||
// slab of album colour beside the map fought it for attention; this only hints at the song.
|
||||
Surface(modifier = modifier.gsPanelShadow(CARD_SHAPE), shape = CARD_SHAPE, color = scheme.surfaceContainer) {
|
||||
AlbumGlow(art, Modifier.fillMaxSize())
|
||||
Box(Modifier.fillMaxSize().padding(12.dp)) {
|
||||
Column(Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(Modifier.fillMaxWidth()) {
|
||||
// The cover gets everything the controls leave, as big a square as fits, sitting
|
||||
// right on top of the title; the controls stay pinned to the bottom.
|
||||
BoxWithConstraints(Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.BottomCenter) {
|
||||
val artSize = minOf(maxWidth, maxHeight).coerceAtLeast(0.dp)
|
||||
// Art opens the full media screen, same as on the pill.
|
||||
val coverShape = RoundedCornerShape(16.dp)
|
||||
Box(
|
||||
Modifier
|
||||
.align(Alignment.TopCenter)
|
||||
.size(artSize)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.gsPanelShadow(coverShape)
|
||||
.clip(coverShape)
|
||||
.background(scheme.surface)
|
||||
.clickable { navigator.media() },
|
||||
) {
|
||||
art?.let { Image(it.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop) }
|
||||
// A new song's cover fades in over the old one instead of cutting to it.
|
||||
androidx.compose.animation.Crossfade(art, animationSpec = tween(350), label = "cover") { shown ->
|
||||
shown?.let { Image(it.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop) }
|
||||
}
|
||||
}
|
||||
GsIconButton(
|
||||
Icons.Filled.Close, "Back to the nav bar", navigator::closeSidePanel,
|
||||
Modifier.align(Alignment.TopEnd), size = 36.dp, iconSize = 20.dp,
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Text(
|
||||
@@ -212,46 +271,53 @@ private fun DockedPlayer(now: NowPlaying, modifier: Modifier) {
|
||||
} else {
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
TransportRow(now, media, skip = 48.dp, modifier = Modifier.fillMaxWidth())
|
||||
TransportRow(now, media, skip = 56.dp, modifier = Modifier.fillMaxWidth())
|
||||
Spacer(Modifier.height(10.dp))
|
||||
PlayModeRow(now, media, size = 42.dp, modifier = Modifier.fillMaxWidth()) {
|
||||
PlayModeRow(now, media, size = 50.dp, modifier = Modifier.fillMaxWidth()) {
|
||||
GsIconButton(
|
||||
ImageVector.vectorResource(R.drawable.lyrics_24), "Lyrics", navigator::showSideLyrics,
|
||||
size = 42.dp, iconSize = 22.dp,
|
||||
ImageVector.vectorResource(R.drawable.lyrics_24), stringResource(R.string.storage_lyrics), navigator::showSideLyrics,
|
||||
size = 50.dp, iconSize = 26.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
GsIconButton(
|
||||
Icons.Filled.Close, stringResource(R.string.home_back_nav), navigator::closeSidePanel,
|
||||
Modifier.align(Alignment.TopEnd), size = 36.dp, iconSize = 20.dp,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lyrics alone beside the map, big enough to read at a glance. No art: that space goes to a
|
||||
* larger title and artist instead. The player goes back to the nav bar meanwhile, whose dock
|
||||
* button swaps these back out for the controls.
|
||||
* Lyrics alone beside the map, big enough to read at a glance. The header is slim, since the
|
||||
* player goes back to the nav bar meanwhile and names the song there. Its note button swaps the
|
||||
* lyrics back for the player; the X closes the panel.
|
||||
*/
|
||||
@Composable
|
||||
private fun SideLyrics(now: NowPlaying, modifier: Modifier) {
|
||||
val navigator = LocalCarNavigator.current
|
||||
val context = LocalContext.current
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val art by produceState(now.art, now.art, now.artUri) { value = now.art ?: MediaArt.load(context, now.artUri) }
|
||||
Surface(modifier = modifier, color = rememberArtColor(art, MaterialTheme.colorScheme.surfaceVariant)) {
|
||||
Column(Modifier.fillMaxSize().padding(16.dp)) {
|
||||
Row(verticalAlignment = Alignment.Top) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(
|
||||
now.title, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold,
|
||||
maxLines = 2, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
now.artist, style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Surface(modifier = modifier.gsPanelShadow(CARD_SHAPE), shape = CARD_SHAPE, color = scheme.surfaceContainer) {
|
||||
Column(Modifier.fillMaxSize().padding(start = 18.dp, end = 12.dp, top = 12.dp, bottom = 8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(Modifier.size(36.dp).clip(RoundedCornerShape(8.dp)).background(scheme.surface)) {
|
||||
art?.let { Image(it.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop) }
|
||||
}
|
||||
GsIconButton(Icons.Filled.Close, "Hide lyrics", navigator::closeSidePanel, size = 36.dp, iconSize = 20.dp)
|
||||
Spacer(Modifier.size(10.dp))
|
||||
Text(
|
||||
listOf(now.title, now.artist).filter { it.isNotEmpty() }.joinToString(" · "),
|
||||
Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = scheme.onSurfaceVariant,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
// The way back to the player, as the player's own Lyrics button is the way here.
|
||||
GsIconButton(MediaIcons.MusicNote, stringResource(R.string.home_show_player), navigator::showSidePlayer, size = 36.dp, iconSize = 20.dp)
|
||||
Spacer(Modifier.size(6.dp))
|
||||
GsIconButton(Icons.Filled.Close, stringResource(R.string.home_hide_lyrics), navigator::closeSidePanel, size = 36.dp, iconSize = 20.dp)
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
LyricsPanel(now, Modifier.weight(1f).fillMaxWidth(), lineStyle = MaterialTheme.typography.headlineSmall)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -153,7 +155,7 @@ fun CarKeyboard(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Key(if (symbols) "abc" else "?123", Modifier.weight(1.4f)) { symbols = !symbols }
|
||||
Key("space", Modifier.weight(if (onDismiss != null) 3.2f else 4f)) { onTextChange("$text ") }
|
||||
Key(stringResource(R.string.keyboard_space), Modifier.weight(if (onDismiss != null) 3.2f else 4f)) { onTextChange("$text ") }
|
||||
if (onDismiss != null) {
|
||||
DismissKey(onDismiss, Modifier.weight(1.4f))
|
||||
}
|
||||
@@ -200,7 +202,7 @@ private fun DismissKey(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(Icons.Filled.Close, contentDescription = "Hide keyboard")
|
||||
Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.keyboard_hide))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +222,7 @@ private fun SubmitKey(modifier: Modifier, search: Boolean, image: ImageVector?,
|
||||
) {
|
||||
Icon(
|
||||
image ?: if (search) Icons.Filled.Search else Icons.Filled.ArrowBack,
|
||||
contentDescription = "Submit",
|
||||
contentDescription = stringResource(R.string.keyboard_submit),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
@@ -127,7 +129,7 @@ internal object LauncherCache {
|
||||
// notifications - rather than a filter per kind that has to know about the others.
|
||||
val tiles = (carApps.keys + mediaApps.keys + messagingApps.keys).flatMap { pkg ->
|
||||
val icon by lazy { iconOf(pkg) }
|
||||
tilesFor(carApps[pkg], mediaApps[pkg], messagingApps[pkg]).map { tile ->
|
||||
tilesFor(carApps[pkg], mediaApps[pkg], messagingApps[pkg], context.getString(R.string.media_browse)).map { tile ->
|
||||
when (tile) {
|
||||
is AppTile.Screen -> Entry(tile.label, icon, template = tile.app)
|
||||
is AppTile.Player -> Entry(tile.app.label, icon, media = tile.app)
|
||||
@@ -163,6 +165,7 @@ private fun tilesFor(
|
||||
car: TemplateApp?,
|
||||
media: app.seb3thehacker.gearslip.media.MediaApp?,
|
||||
messaging: app.seb3thehacker.gearslip.notify.MessagingApp?,
|
||||
browse: String,
|
||||
): List<AppTile> {
|
||||
if (car != null && !car.isMediaScreen && car.kind != "messaging") return listOf(AppTile.Screen(car, car.label))
|
||||
val pkg = car?.component?.packageName ?: media?.component?.packageName ?: messaging?.packageName ?: return emptyList()
|
||||
@@ -170,7 +173,7 @@ private fun tilesFor(
|
||||
val screenRefused = KnownApps.screenBroken(pkg) && KnownApps.playerWorks(pkg)
|
||||
val messengerScreen = messaging != null && (car?.kind == "messaging" || car?.kind == "app")
|
||||
if (car != null && !messengerScreen && !(media != null && screenRefused)) {
|
||||
add(AppTile.Screen(car, if (media != null) "${car.label} · Browse" else car.label))
|
||||
add(AppTile.Screen(car, if (media != null) "${car.label} · $browse" else car.label))
|
||||
}
|
||||
if (media != null) add(AppTile.Player(media))
|
||||
else if (messaging != null) add(AppTile.Messages(messaging))
|
||||
@@ -280,7 +283,7 @@ fun CarLauncher() {
|
||||
val pkg = entry.packageName?.takeIf { entry.messaging == null }
|
||||
val id = entry.componentId
|
||||
return Tile(
|
||||
entry.label, entry.icon, entry.builtIn?.glyph,
|
||||
entry.builtIn?.let { context.getString(it.labelRes) } ?: entry.label, entry.icon, entry.builtIn?.glyph,
|
||||
broken = pkg != null && (KnownApps.isBroken(context, pkg) || (entry.template != null && KnownApps.screenBroken(pkg))),
|
||||
onLongClick = id?.let { { navigator.showAppMenu(it) } },
|
||||
) { openApp(entry, id?.let { running[it] }, navigator, frame) }
|
||||
@@ -307,7 +310,7 @@ private fun BrokenBadge(size: androidx.compose.ui.unit.Dp, modifier: Modifier =
|
||||
modifier.size(size).background(Color(0xFFD32F2F), CircleShape),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(Icons.Filled.Close, contentDescription = "Doesn't work with Gearslip", tint = Color.White, modifier = Modifier.size(size * 0.66f))
|
||||
Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.launcher_broken), tint = Color.White, modifier = Modifier.size(size * 0.66f))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.material.icons.filled.Call
|
||||
import app.seb3thehacker.gearslip.call.CarCalls
|
||||
import app.seb3thehacker.gearslip.call.CallUiState
|
||||
@@ -115,12 +116,12 @@ private fun BreadcrumbBar(navigator: CarNavigator) {
|
||||
Modifier.fillMaxWidth().height(40.dp).padding(start = 2.dp, end = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, "Back", navigator::back, size = 32.dp, iconSize = 20.dp)
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.back), navigator::back, size = 32.dp, iconSize = 20.dp)
|
||||
Spacer(Modifier.width(2.dp))
|
||||
trail.forEachIndexed { index, crumb ->
|
||||
val isLast = index == trail.lastIndex
|
||||
Text(
|
||||
crumb.label,
|
||||
crumb.shown(),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
fontWeight = if (isLast) FontWeight.SemiBold else FontWeight.Normal,
|
||||
color = if (isLast) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -164,7 +165,11 @@ internal fun CarNavBar(navigator: CarNavigator) {
|
||||
val frame by CarEnvironment.frame.collectAsState()
|
||||
val running = rememberRunningApps()
|
||||
|
||||
Surface(color = MaterialTheme.colorScheme.surfaceContainer) {
|
||||
// A full-width strip along the bottom in the screens' own background, so it reads as part of
|
||||
// the screen above rather than a panel on it. Over the player it's see-through, so the
|
||||
// cover's glow carries on behind it.
|
||||
val overPlayer = navigator.current == CarScreen.Media
|
||||
Surface(color = if (overPlayer) androidx.compose.ui.graphics.Color.Transparent else MaterialTheme.colorScheme.background) {
|
||||
BoxWithConstraints(Modifier.fillMaxWidth().height(NAV_BAR_HEIGHT).padding(horizontal = 10.dp)) {
|
||||
Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
|
||||
// Home and Apps are two different places now, not a toggle on one button - the
|
||||
@@ -280,7 +285,7 @@ private fun NavHomeButton(navigator: CarNavigator, entries: List<Entry>, running
|
||||
}
|
||||
} else {
|
||||
Icon(
|
||||
ImageVector.vectorResource(R.drawable.navigation_24), contentDescription = "Home",
|
||||
ImageVector.vectorResource(R.drawable.navigation_24), contentDescription = stringResource(R.string.crumb_home),
|
||||
modifier = Modifier.padding(NAV_ITEM_PADDING).size(28.dp),
|
||||
)
|
||||
}
|
||||
@@ -341,7 +346,7 @@ private fun NavAppIcon(
|
||||
onLongClick = onLongClick,
|
||||
) {
|
||||
entry.icon?.let { Image(it, entry.label, Modifier.size(30.dp).clip(RoundedCornerShape(8.dp))) }
|
||||
entry.builtIn?.let { Icon(it.glyph, entry.label, Modifier.size(26.dp)) }
|
||||
entry.builtIn?.let { Icon(it.glyph, entry.shownLabel(), Modifier.size(26.dp)) }
|
||||
// Only running apps get the line along the bottom edge, the usual taskbar mark for it.
|
||||
if (open) {
|
||||
Box(
|
||||
@@ -492,7 +497,7 @@ private fun NavCallPill(call: CallUiState.Active, modifier: Modifier = Modifier)
|
||||
) {
|
||||
Icon(Icons.Filled.Call, contentDescription = null, modifier = Modifier.size(20.dp))
|
||||
Text(
|
||||
call.caller?.label ?: "On a call",
|
||||
call.caller?.label ?: stringResource(R.string.navbar_on_call),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
@@ -507,12 +512,12 @@ private fun NavCallPill(call: CallUiState.Active, modifier: Modifier = Modifier)
|
||||
Spacer(Modifier.width(2.dp))
|
||||
GsIconButton(
|
||||
if (muted) MediaIcons.MicOff else MediaIcons.Mic,
|
||||
if (muted) "Unmute" else "Mute",
|
||||
stringResource(if (muted) R.string.navbar_unmute else R.string.navbar_mute),
|
||||
CarCalls::toggleMute,
|
||||
tone = if (muted) GsTone.Primary else GsTone.Neutral, size = 36.dp, iconSize = 22.dp,
|
||||
)
|
||||
GsIconButton(
|
||||
MediaIcons.CallEnd, "End call", { CarCalls.end(context) },
|
||||
MediaIcons.CallEnd, stringResource(R.string.navbar_end_call), { CarCalls.end(context) },
|
||||
colors = GsColors(scheme.error, scheme.onError), size = 36.dp, iconSize = 22.dp,
|
||||
)
|
||||
}
|
||||
@@ -520,15 +525,16 @@ private fun NavCallPill(call: CallUiState.Active, modifier: Modifier = Modifier)
|
||||
}
|
||||
|
||||
/** "Title - Artist" on one line. The text itself cuts it off, and only where the pill runs out of room. */
|
||||
@Composable
|
||||
private fun nowPlayingLabel(title: String, artist: String): String =
|
||||
listOf(title.trim(), artist.trim()).filter { it.isNotEmpty() }.joinToString(" - ")
|
||||
.ifEmpty { "Nothing playing" }
|
||||
.ifEmpty { stringResource(R.string.media_nothing_playing) }
|
||||
|
||||
/**
|
||||
* The player in the nav bar, on every screen. Art and title open the full media screen;
|
||||
* play/pause and skip work right here. The dock button moves the player into the column beside
|
||||
* the map on Home (see [CarHome]), and the pill steps aside there while it's docked - unless the
|
||||
* column is showing lyrics instead, when the pill stays and its dock button swaps them back.
|
||||
* column is showing lyrics instead, when the pill stays and its dock button closes them.
|
||||
*/
|
||||
@Composable
|
||||
private fun NavNowPlaying(navigator: CarNavigator, selected: Boolean, modifier: Modifier = Modifier) {
|
||||
@@ -604,24 +610,34 @@ private fun NavNowPlaying(navigator: CarNavigator, selected: Boolean, modifier:
|
||||
}
|
||||
if (!showTitle) {
|
||||
GsIconButton(
|
||||
MediaIcons.Previous, "Previous", media::previous,
|
||||
MediaIcons.Previous, stringResource(R.string.player_previous), media::previous,
|
||||
enabled = now.canDo(PlaybackState.ACTION_SKIP_TO_PREVIOUS), size = 36.dp, iconSize = 22.dp,
|
||||
)
|
||||
}
|
||||
GsIconButton(
|
||||
if (now.playing) MediaIcons.Pause else Icons.Filled.PlayArrow,
|
||||
if (now.playing) "Pause" else "Play",
|
||||
stringResource(if (now.playing) R.string.player_pause else R.string.media_play),
|
||||
media::togglePlay,
|
||||
tone = GsTone.Primary, size = 36.dp, iconSize = 22.dp,
|
||||
)
|
||||
if (!showTitle) {
|
||||
GsIconButton(
|
||||
MediaIcons.Next, "Next", media::next,
|
||||
MediaIcons.Next, stringResource(R.string.player_next), media::next,
|
||||
enabled = now.canDo(PlaybackState.ACTION_SKIP_TO_NEXT), size = 36.dp, iconSize = 22.dp,
|
||||
)
|
||||
}
|
||||
// Drawn on the side the panel opens on, and filled in while it's open.
|
||||
val playerLeft by CarSettings.playerLeft.collectAsState()
|
||||
val docked = navigator.current == CarScreen.Home && navigator.sidePanel != SidePanel.NONE
|
||||
GsIconButton(
|
||||
MediaIcons.DockRight, "Move the player beside the map", navigator::togglePlayerDock,
|
||||
when {
|
||||
playerLeft && docked -> MediaIcons.DockLeftOpen
|
||||
playerLeft -> MediaIcons.DockLeft
|
||||
docked -> MediaIcons.DockRightOpen
|
||||
else -> MediaIcons.DockRight
|
||||
},
|
||||
stringResource(if (docked) R.string.navbar_close_panel else R.string.navbar_dock_player),
|
||||
navigator::togglePlayerDock,
|
||||
size = 36.dp, iconSize = 22.dp,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -41,7 +45,11 @@ sealed interface CarScreen {
|
||||
enum class SidePanel { NONE, CONTROLS, LYRICS }
|
||||
|
||||
/** One step on the breadcrumb trail: the screen it shows and the word for it in the trail. */
|
||||
data class Crumb(val screen: CarScreen, val label: String)
|
||||
data class Crumb(val screen: CarScreen, val label: String, @param:StringRes val labelRes: Int = 0) {
|
||||
/** [labelRes] when the step is one of Gearslip's own, translated; [label] for an app's own name. */
|
||||
@Composable
|
||||
fun shown(): String = if (labelRes != 0) stringResource(labelRes) else label
|
||||
}
|
||||
|
||||
/**
|
||||
* Where the car UI is, and how it got there. A car touchscreen has no hardware back button, so
|
||||
@@ -57,6 +65,7 @@ class CarNavigator(startId: String) {
|
||||
Crumb(
|
||||
if (CarApps.find(startId) != null) CarScreen.App(startId) else CarScreen.Home,
|
||||
"Home",
|
||||
R.string.crumb_home,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -69,15 +78,15 @@ class CarNavigator(startId: String) {
|
||||
|
||||
/** Pushes a new step, unless it's the same screen already on top - tapping the button for
|
||||
* where you already are shouldn't grow the trail. */
|
||||
private fun push(screen: CarScreen, label: String) {
|
||||
private fun push(screen: CarScreen, label: String, @StringRes labelRes: Int = 0) {
|
||||
if (stack.last().screen == screen) return
|
||||
stack.add(Crumb(screen, label))
|
||||
stack.add(Crumb(screen, label, labelRes))
|
||||
}
|
||||
|
||||
/** The one way back to a clean slate: resets the whole trail to just Home. */
|
||||
fun home() {
|
||||
stack.clear()
|
||||
stack.add(Crumb(CarScreen.Home, "Home"))
|
||||
stack.add(Crumb(CarScreen.Home, "Home", R.string.crumb_home))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,8 +99,9 @@ class CarNavigator(startId: String) {
|
||||
private set
|
||||
|
||||
/**
|
||||
* The pill's dock button: moves the player beside the map, going Home first if need be. With
|
||||
* lyrics already there it swaps them for the controls; with the controls there it closes.
|
||||
* The pill's dock button, an open/close switch for the panel: moves the player beside the map,
|
||||
* going Home first if need be, or closes the panel when something is already there. Switching
|
||||
* between the player and lyrics is done inside the panel itself.
|
||||
*/
|
||||
fun togglePlayerDock() {
|
||||
if (current != CarScreen.Home) {
|
||||
@@ -99,9 +109,11 @@ class CarNavigator(startId: String) {
|
||||
home()
|
||||
return
|
||||
}
|
||||
sidePanel = if (sidePanel == SidePanel.CONTROLS) SidePanel.NONE else SidePanel.CONTROLS
|
||||
sidePanel = if (sidePanel == SidePanel.NONE) SidePanel.CONTROLS else SidePanel.NONE
|
||||
}
|
||||
|
||||
fun showSidePlayer() { sidePanel = SidePanel.CONTROLS }
|
||||
|
||||
fun showSideLyrics() { sidePanel = SidePanel.LYRICS }
|
||||
|
||||
fun closeSidePanel() { sidePanel = SidePanel.NONE }
|
||||
@@ -114,17 +126,17 @@ class CarNavigator(startId: String) {
|
||||
|
||||
fun dismissAppMenu() { appMenu = null }
|
||||
|
||||
fun media() { push(CarScreen.Media, "Media") }
|
||||
fun media() { push(CarScreen.Media, "Media", R.string.crumb_media) }
|
||||
|
||||
fun browse(label: String) { push(CarScreen.Browse, label) }
|
||||
|
||||
fun apps() { push(CarScreen.Apps, "Apps") }
|
||||
fun apps() { push(CarScreen.Apps, "Apps", R.string.crumb_apps) }
|
||||
|
||||
fun settings() { push(CarScreen.Settings, "Settings") }
|
||||
fun settings() { push(CarScreen.Settings, "Settings", R.string.settings_title) }
|
||||
|
||||
fun dashboard() { push(CarScreen.Dashboard, "Dashboard") }
|
||||
fun dashboard() { push(CarScreen.Dashboard, "Dashboard", R.string.crumb_dashboard) }
|
||||
|
||||
fun weather() { push(CarScreen.Weather, "Weather") }
|
||||
fun weather() { push(CarScreen.Weather, "Weather", R.string.crumb_weather) }
|
||||
|
||||
fun vehicleData() { push(CarScreen.VehicleData, "Vehicle data") }
|
||||
|
||||
@@ -132,10 +144,10 @@ class CarNavigator(startId: String) {
|
||||
|
||||
/** The list lives on the dashboard; only a reply gets a screen of its own. */
|
||||
fun notifications(replyTo: String? = null) {
|
||||
if (replyTo == null) dashboard() else push(CarScreen.Notifications(replyTo), "Reply")
|
||||
if (replyTo == null) dashboard() else push(CarScreen.Notifications(replyTo), "Reply", R.string.crumb_reply)
|
||||
}
|
||||
|
||||
fun open(id: String) { push(CarScreen.App(id), CarApps.find(id)?.label ?: id) }
|
||||
fun open(id: String) { push(CarScreen.App(id), CarApps.find(id)?.label ?: id, CarApps.find(id)?.labelRes ?: 0) }
|
||||
|
||||
/** Jumps straight to a step already on the trail, dropping everything after it - what
|
||||
* tapping a breadcrumb does. Index 0 (Home) is always on the trail, so this always succeeds. */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import app.seb3thehacker.gearslip.car.theme.DarkStyle
|
||||
import android.content.Context
|
||||
import app.seb3thehacker.gearslip.AppSettings
|
||||
import app.seb3thehacker.gearslip.audio.CarAudio
|
||||
@@ -64,14 +65,17 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
private const val KEY_AUTOPLAY = "car_autoplay"
|
||||
private const val KEY_PLAYER_TITLE = "car_player_title"
|
||||
private const val KEY_PLAYER_LEFT = "car_player_left"
|
||||
private const val KEY_MORE_LYRICS = "car_more_lyrics"
|
||||
private const val KEY_THEME = "car_app_theme"
|
||||
private const val KEY_UI_THEME = "car_ui_theme"
|
||||
private const val KEY_DARK_STYLE = "car_dark_style"
|
||||
private const val KEY_PINNED = "car_pinned_apps"
|
||||
private const val KEY_APP_ORDER = "car_app_order"
|
||||
private const val KEY_EXPERIMENTAL_FEATURES = "car_experimental_features"
|
||||
private const val KEY_SYSTEM_SPEECH = "car_system_speech"
|
||||
private const val KEY_CAR_SENSORS = "car_sensors_enabled"
|
||||
private const val KEY_ADAPTIVE_VIDEO = "adaptive_video_enabled"
|
||||
private const val KEY_CLUSTER_VIDEO = "cluster_video_enabled"
|
||||
private const val KEY_DEBUG_BADGE = "debug_badge"
|
||||
private const val KEY_SEEN_SAFETY_WARNING = "car_seen_safety_warning"
|
||||
private const val KEY_UNITS = "car_units"
|
||||
@@ -104,8 +108,13 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
private val autoplayFlow = MutableStateFlow(false)
|
||||
private val playerTitleFlow = MutableStateFlow(false)
|
||||
private val playerLeftFlow = MutableStateFlow(false)
|
||||
private val moreLyricsFlow = MutableStateFlow(true)
|
||||
private val themeFlow = MutableStateFlow(AppTheme.PHONE)
|
||||
val appTheme: StateFlow<AppTheme> = themeFlow
|
||||
private val darkStyleFlow = MutableStateFlow(DarkStyle.BLACK)
|
||||
|
||||
/** How dark the car's dark mode goes; black until the driver picks another. */
|
||||
val darkStyle: StateFlow<DarkStyle> = darkStyleFlow
|
||||
private val uiThemeFlow = MutableStateFlow(GsThemes.default.id)
|
||||
|
||||
/** Id of the [GsTheme] controls are drawn in; an unknown id falls back to the default. */
|
||||
@@ -116,6 +125,7 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
private val systemSpeechFlow = MutableStateFlow(false)
|
||||
private val carSensorsFlow = MutableStateFlow(false)
|
||||
private val adaptiveVideoFlow = MutableStateFlow(false)
|
||||
private val clusterVideoFlow = MutableStateFlow(false)
|
||||
private val debugBadgeFlow = MutableStateFlow(true)
|
||||
private val seenSafetyWarningFlow = MutableStateFlow(false)
|
||||
private val unitsFlow = MutableStateFlow(Units.AUTO)
|
||||
@@ -224,6 +234,9 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
/** The home screen's player sits left of the map instead of right of it. */
|
||||
val playerLeft: StateFlow<Boolean> = playerLeftFlow
|
||||
|
||||
/** Lyrics from BetterLyrics and KuGou as well as LRCLIB; off sends song details to LRCLIB only. */
|
||||
val moreLyrics: StateFlow<Boolean> = moreLyricsFlow
|
||||
|
||||
val scale: StateFlow<Float> = scaleFlow
|
||||
val openOnConnect: StateFlow<String> = openFlow
|
||||
val nightMode: StateFlow<NightMode> = nightFlow
|
||||
@@ -282,6 +295,13 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
*/
|
||||
val adaptiveVideoEnabled: StateFlow<Boolean> = adaptiveVideoFlow
|
||||
|
||||
/**
|
||||
* Gearslip's turn card on a car's instrument-cluster screen, for a car that offers one as a
|
||||
* second video sink. Applies only while [experimentalFeaturesEnabled] is on too, and from the
|
||||
* next connection; untested in a car so far.
|
||||
*/
|
||||
val clusterVideoEnabled: StateFlow<Boolean> = clusterVideoFlow
|
||||
|
||||
/** Debug builds only: the car screen's DEBUG BUILD badge, which screenshots for the README leave out. */
|
||||
val debugBadge: StateFlow<Boolean> = debugBadgeFlow
|
||||
|
||||
@@ -312,10 +332,14 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
themeFlow.value = runCatching {
|
||||
AppTheme.valueOf(AppSettings.getString(app, KEY_THEME, AppTheme.PHONE.name))
|
||||
}.getOrDefault(AppTheme.PHONE)
|
||||
darkStyleFlow.value = runCatching {
|
||||
DarkStyle.valueOf(AppSettings.getString(app, KEY_DARK_STYLE, DarkStyle.BLACK.name))
|
||||
}.getOrDefault(DarkStyle.BLACK)
|
||||
uiThemeFlow.value = AppSettings.getString(app, KEY_UI_THEME, GsThemes.default.id)
|
||||
autoplayFlow.value = AppSettings.getString(app, KEY_AUTOPLAY, "false") == "true"
|
||||
playerTitleFlow.value = AppSettings.getString(app, KEY_PLAYER_TITLE, "false") == "true"
|
||||
playerLeftFlow.value = AppSettings.getString(app, KEY_PLAYER_LEFT, "false") == "true"
|
||||
moreLyricsFlow.value = AppSettings.getString(app, KEY_MORE_LYRICS, "true") == "true"
|
||||
pipeAudioFlow.value = AppSettings.getString(app, KEY_PIPE_AUDIO, "true") == "true"
|
||||
// Until the driver sets it, on for USB audio and off for Bluetooth, where the map app's
|
||||
// own voice already reaches the car.
|
||||
@@ -333,6 +357,7 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
systemSpeechFlow.value = AppSettings.getString(app, KEY_SYSTEM_SPEECH, "false") == "true"
|
||||
carSensorsFlow.value = AppSettings.getString(app, KEY_CAR_SENSORS, "false") == "true"
|
||||
adaptiveVideoFlow.value = AppSettings.getString(app, KEY_ADAPTIVE_VIDEO, "false") == "true"
|
||||
clusterVideoFlow.value = AppSettings.getString(app, KEY_CLUSTER_VIDEO, "false") == "true"
|
||||
debugBadgeFlow.value = AppSettings.getString(app, KEY_DEBUG_BADGE, "true") == "true"
|
||||
seenSafetyWarningFlow.value = AppSettings.getString(app, KEY_SEEN_SAFETY_WARNING, "false") == "true"
|
||||
unitsFlow.value = runCatching {
|
||||
@@ -378,6 +403,11 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
AppSettings.putString(app, KEY_THEME, theme.name)
|
||||
}
|
||||
|
||||
fun setDarkStyle(style: DarkStyle) {
|
||||
darkStyleFlow.value = style
|
||||
AppSettings.putString(app, KEY_DARK_STYLE, style.name)
|
||||
}
|
||||
|
||||
fun setUiTheme(id: String) {
|
||||
uiThemeFlow.value = id
|
||||
AppSettings.putString(app, KEY_UI_THEME, id)
|
||||
@@ -393,6 +423,11 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
AppSettings.putString(app, KEY_PLAYER_LEFT, on.toString())
|
||||
}
|
||||
|
||||
fun setMoreLyrics(on: Boolean) {
|
||||
moreLyricsFlow.value = on
|
||||
AppSettings.putString(app, KEY_MORE_LYRICS, on.toString())
|
||||
}
|
||||
|
||||
fun setAutoplay(on: Boolean) {
|
||||
autoplayFlow.value = on
|
||||
AppSettings.putString(app, KEY_AUTOPLAY, on.toString())
|
||||
@@ -441,6 +476,11 @@ private const val KEY_LOCATION_WARNING = "car_location_warning"
|
||||
AppSettings.putString(app, KEY_ADAPTIVE_VIDEO, on.toString())
|
||||
}
|
||||
|
||||
fun setClusterVideoEnabled(on: Boolean) {
|
||||
clusterVideoFlow.value = on
|
||||
AppSettings.putString(app, KEY_CLUSTER_VIDEO, on.toString())
|
||||
}
|
||||
|
||||
fun setCarSensorsEnabled(on: Boolean) {
|
||||
carSensorsFlow.value = on
|
||||
AppSettings.putString(app, KEY_CAR_SENSORS, on.toString())
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
|
||||
import app.seb3thehacker.gearslip.car.theme.DarkStyle
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -8,6 +11,9 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -65,11 +71,14 @@ fun CarSettingsScreen() {
|
||||
val autoplay by CarSettings.autoplay.collectAsState()
|
||||
val playerTitle by CarSettings.playerTitle.collectAsState()
|
||||
val playerLeft by CarSettings.playerLeft.collectAsState()
|
||||
val moreLyrics by CarSettings.moreLyrics.collectAsState()
|
||||
val theme by CarSettings.appTheme.collectAsState()
|
||||
val uiTheme by CarSettings.uiTheme.collectAsState()
|
||||
val darkStyle by CarSettings.darkStyle.collectAsState()
|
||||
val experimentalFeatures by CarSettings.experimentalFeaturesEnabled.collectAsState()
|
||||
val carSensors by CarSettings.carSensorsEnabled.collectAsState()
|
||||
val adaptiveVideo by CarSettings.adaptiveVideoEnabled.collectAsState()
|
||||
val clusterVideo by CarSettings.clusterVideoEnabled.collectAsState()
|
||||
val systemSpeech by CarSettings.systemSpeech.collectAsState()
|
||||
val display by CarEnvironment.display.collectAsState()
|
||||
val vehicle by CarEnvironment.vehicle.collectAsState()
|
||||
@@ -78,232 +87,259 @@ fun CarSettingsScreen() {
|
||||
value = withContext(Dispatchers.Default) { CertSummary.read(context) }
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize(),
|
||||
// Built as it scrolls into view rather than all at once: every row here is a set of
|
||||
// buttons, and building the whole screen made opening Settings visibly slow.
|
||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||
) {
|
||||
item {
|
||||
ScreenHeader("Settings")
|
||||
}
|
||||
// A rail of the groups down the left, and the chosen group's settings beside it: five short
|
||||
// lists instead of one long scroll.
|
||||
var chosen by androidx.compose.runtime.saveable.rememberSaveable { androidx.compose.runtime.mutableIntStateOf(0) }
|
||||
val listState = androidx.compose.foundation.lazy.rememberLazyListState()
|
||||
androidx.compose.runtime.LaunchedEffect(chosen) { listState.scrollToItem(0) }
|
||||
Row(Modifier.fillMaxSize().padding(start = 16.dp, top = 12.dp)) {
|
||||
SettingsRail(listOf(stringResource(R.string.car_group_screen), stringResource(R.string.car_group_nav_bar), stringResource(R.string.car_group_sound), stringResource(R.string.car_group_warnings), stringResource(R.string.car_group_more)), chosen) { chosen = it }
|
||||
LazyColumn(
|
||||
Modifier.weight(1f).fillMaxHeight(),
|
||||
state = listState,
|
||||
// Built as it scrolls into view rather than all at once: every row here is a set of
|
||||
// buttons, and building the whole screen made opening Settings visibly slow.
|
||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||
) {
|
||||
var section: Int
|
||||
section = 0
|
||||
if (section == chosen) item { Group(stringResource(R.string.car_group_screen)) }
|
||||
|
||||
item { Group("Screen") }
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Open on connect",
|
||||
listOf(CarSettings.HOME to "Home") + CarApps.all.filter { it.id != "settings" }.map { it.id to it.label },
|
||||
open,
|
||||
) { CarSettings.setOpenOnConnect(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Size",
|
||||
listOf(1f to "Normal", 1.25f to "Large", 1.5f to "Extra large"),
|
||||
scale,
|
||||
) { CarSettings.setScale(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"App theme",
|
||||
listOf(AppTheme.PHONE to "Follow phone", AppTheme.LIGHT to "Light", AppTheme.DARK to "Dark"),
|
||||
theme,
|
||||
) { CarSettings.setAppTheme(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Theme",
|
||||
GsThemes.builtIn.map { it.id to it.name },
|
||||
uiTheme,
|
||||
) { CarSettings.setUiTheme(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Map day/night",
|
||||
listOf(NightMode.AUTO to "Auto", NightMode.DAY to "Day", NightMode.NIGHT to "Night"),
|
||||
night,
|
||||
) { CarSettings.setNightMode(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Units",
|
||||
listOf(Units.AUTO to "Auto", Units.IMPERIAL to "Imperial (mi, ft, °F)", Units.METRIC to "Metric (km, m, °C)"),
|
||||
units,
|
||||
) { CarSettings.setUnits(it) }
|
||||
}
|
||||
|
||||
// For a head unit that reports the wrong size and stretches the picture (issue #12).
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Screen size (next connection)",
|
||||
listOf<ScreenSize?>(null).plus(SCREEN_PRESETS).plus(screenSize).distinct()
|
||||
.map { it to (it?.toString() ?: "From the car") },
|
||||
screenSize,
|
||||
) { CarSettings.setScreenSize(it) }
|
||||
}
|
||||
|
||||
if (screenSize != null) item {
|
||||
ChoiceRow(
|
||||
"Fit to the screen",
|
||||
listOf(ScreenFit.CROP to "Crop the edges", ScreenFit.SQUASH to "Squash to fit"),
|
||||
screenFit,
|
||||
) { CarSettings.setScreenFit(it) }
|
||||
}
|
||||
|
||||
item { Group("Nav bar and player") }
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Nav bar",
|
||||
listOf(
|
||||
NavBarMode.SHOW to "Always show",
|
||||
NavBarMode.HIDE_NAVIGATING to "Hide while navigating",
|
||||
NavBarMode.HIDE to "Hide until tapped",
|
||||
),
|
||||
navBar,
|
||||
) { CarSettings.setNavBarMode(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Player in the nav bar",
|
||||
listOf(false to "Skip buttons", true to "Song title"),
|
||||
playerTitle,
|
||||
) { CarSettings.setPlayerTitle(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Player beside the map",
|
||||
listOf(false to "Right", true to "Left"),
|
||||
playerLeft,
|
||||
) { CarSettings.setPlayerLeft(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Autoplay on connect",
|
||||
listOf(false to "Off", true to "On"),
|
||||
autoplay,
|
||||
) { CarSettings.setAutoplay(it) }
|
||||
}
|
||||
|
||||
item { Group("Sound and voice") }
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Car audio",
|
||||
listOf(false to "Bluetooth", true to "USB"),
|
||||
pipeAudio,
|
||||
) { CarSettings.setPipeAudio(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
Text(
|
||||
"USB sends the phone's sound through Gearslip. Android asks to share your screen each " +
|
||||
"drive, and a few apps block it. Bluetooth plays as the phone normally would, and " +
|
||||
"Android never asks.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
item { Group("Warnings") }
|
||||
|
||||
if (!pipeAudio) item {
|
||||
ChoiceRow(
|
||||
"Warn when Bluetooth isn't connected",
|
||||
listOf(false to "Off", true to "On"),
|
||||
bluetoothWarning,
|
||||
) { CarSettings.setBluetoothWarning(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Warn when location is off",
|
||||
listOf(false to "Off", true to "On"),
|
||||
locationWarning,
|
||||
) { CarSettings.setLocationWarning(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Spoken directions",
|
||||
listOf(false to "Off", true to "On"),
|
||||
speakDirections,
|
||||
) { CarSettings.setSpeakDirections(it) }
|
||||
}
|
||||
|
||||
item {
|
||||
Text(
|
||||
"Gearslip reads each turn aloud through the car's speakers, and the music dips while it " +
|
||||
"speaks. Turn it off if your map app's own voice already plays in the car.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
ExperimentalFeatureRow(
|
||||
title = "Use the phone's speech recognition",
|
||||
description = "Listens through the phone's own speech service instead of Gearslip's. On " +
|
||||
"most phones that's Google's, which may send your voice to Google. GrapheneOS has " +
|
||||
"none, so Gearslip keeps using its own.",
|
||||
checked = systemSpeech,
|
||||
) { CarSettings.setSystemSpeech(it) }
|
||||
}
|
||||
|
||||
item { Group("More") }
|
||||
|
||||
item {
|
||||
WebPageSetting()
|
||||
}
|
||||
|
||||
item {
|
||||
ScreenFit(vehicle)
|
||||
}
|
||||
|
||||
item {
|
||||
ChoiceRow(
|
||||
"Experimental features",
|
||||
listOf(false to "Hide", true to "Show"),
|
||||
experimentalFeatures,
|
||||
) { CarSettings.setExperimentalFeaturesEnabled(it) }
|
||||
}
|
||||
|
||||
if (experimentalFeatures) item {
|
||||
ExperimentalFeaturesList(carSensors, adaptiveVideo)
|
||||
}
|
||||
|
||||
item {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Connection", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Info("Phone", "${Build.MANUFACTURER} ${Build.MODEL}")
|
||||
Info("Vehicle", vehicle.ifEmpty { "-" })
|
||||
Info("Video", display.ifEmpty { "-" })
|
||||
Info("Certificate", cert?.headline ?: "Checking…")
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_open_on_connect),
|
||||
listOf(CarSettings.HOME to stringResource(R.string.choice_open_home)) + CarApps.all.filter { it.id != "settings" }.map { it.id to stringResource(it.labelRes) },
|
||||
open,
|
||||
) { CarSettings.setOpenOnConnect(it) }
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("About", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Info("Version", app.seb3thehacker.gearslip.BuildConfig.VERSION_NAME)
|
||||
Info("Build", app.seb3thehacker.gearslip.BuildConfig.VERSION_CODE.toString())
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_size),
|
||||
listOf(1f to stringResource(R.string.choice_size_normal), 1.25f to stringResource(R.string.choice_size_large), 1.5f to stringResource(R.string.choice_size_xlarge)),
|
||||
scale,
|
||||
) { CarSettings.setScale(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_app_theme),
|
||||
listOf(AppTheme.PHONE to stringResource(R.string.choice_theme_phone), AppTheme.LIGHT to stringResource(R.string.choice_theme_light), AppTheme.DARK to stringResource(R.string.choice_theme_dark)),
|
||||
theme,
|
||||
) { CarSettings.setAppTheme(it) }
|
||||
}
|
||||
|
||||
if (section == chosen && theme != AppTheme.LIGHT) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_dark_mode),
|
||||
listOf(
|
||||
DarkStyle.BLACK to stringResource(R.string.choice_dark_black),
|
||||
DarkStyle.BLACK_COLOUR to stringResource(R.string.choice_dark_black_colour),
|
||||
DarkStyle.DEEP_COLOUR to stringResource(R.string.choice_dark_deep_colour),
|
||||
DarkStyle.GREY to stringResource(R.string.choice_dark_grey),
|
||||
),
|
||||
darkStyle,
|
||||
) { CarSettings.setDarkStyle(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.carset_theme),
|
||||
GsThemes.builtIn.map { it.id to it.name },
|
||||
uiTheme,
|
||||
) { CarSettings.setUiTheme(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_map_day_night),
|
||||
listOf(NightMode.AUTO to stringResource(R.string.choice_auto), NightMode.DAY to stringResource(R.string.choice_night_day), NightMode.NIGHT to stringResource(R.string.choice_night_night)),
|
||||
night,
|
||||
) { CarSettings.setNightMode(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_units),
|
||||
listOf(Units.AUTO to stringResource(R.string.choice_auto), Units.IMPERIAL to stringResource(R.string.carset_imperial), Units.METRIC to stringResource(R.string.carset_metric)),
|
||||
units,
|
||||
) { CarSettings.setUnits(it) }
|
||||
}
|
||||
|
||||
// For a head unit that reports the wrong size and stretches the picture (issue #12).
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.carset_screen_size),
|
||||
listOf<ScreenSize?>(null).plus(SCREEN_PRESETS).plus(screenSize).distinct()
|
||||
.map { it to (it?.toString() ?: stringResource(R.string.screen_from_car)) },
|
||||
screenSize,
|
||||
) { CarSettings.setScreenSize(it) }
|
||||
}
|
||||
|
||||
if (section == chosen && screenSize != null) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.carset_fit),
|
||||
listOf(ScreenFit.CROP to stringResource(R.string.screen_crop), ScreenFit.SQUASH to stringResource(R.string.screen_squash)),
|
||||
screenFit,
|
||||
) { CarSettings.setScreenFit(it) }
|
||||
}
|
||||
|
||||
section = 1
|
||||
if (section == chosen) item { Group(stringResource(R.string.car_group_nav_bar)) }
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_nav_bar),
|
||||
listOf(
|
||||
NavBarMode.SHOW to stringResource(R.string.choice_nav_show),
|
||||
NavBarMode.HIDE_NAVIGATING to stringResource(R.string.choice_nav_hide_navigating),
|
||||
NavBarMode.HIDE to stringResource(R.string.choice_nav_hide),
|
||||
),
|
||||
navBar,
|
||||
) { CarSettings.setNavBarMode(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_player_nav_bar),
|
||||
listOf(false to stringResource(R.string.car_skip_buttons), true to stringResource(R.string.car_song_title)),
|
||||
playerTitle,
|
||||
) { CarSettings.setPlayerTitle(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_player_beside_map),
|
||||
listOf(false to stringResource(R.string.car_right), true to stringResource(R.string.car_left)),
|
||||
playerLeft,
|
||||
) { CarSettings.setPlayerLeft(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_autoplay),
|
||||
listOf(false to stringResource(R.string.off), true to stringResource(R.string.on)),
|
||||
autoplay,
|
||||
) { CarSettings.setAutoplay(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.carset_lyrics_from),
|
||||
listOf(false to "LRCLIB", true to stringResource(R.string.carset_more_sources)),
|
||||
moreLyrics,
|
||||
) { CarSettings.setMoreLyrics(it) }
|
||||
}
|
||||
|
||||
section = 2
|
||||
if (section == chosen) item { Group(stringResource(R.string.car_group_sound)) }
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_audio),
|
||||
listOf(false to stringResource(R.string.guide_audio_bluetooth), true to stringResource(R.string.guide_audio_usb)),
|
||||
pipeAudio,
|
||||
) { CarSettings.setPipeAudio(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
Text(
|
||||
stringResource(R.string.carset_audio_note),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
section = 3
|
||||
if (section == chosen) item { Group(stringResource(R.string.car_group_warnings)) }
|
||||
|
||||
if (section == chosen && !pipeAudio) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_warn_bluetooth),
|
||||
listOf(false to stringResource(R.string.off), true to stringResource(R.string.on)),
|
||||
bluetoothWarning,
|
||||
) { CarSettings.setBluetoothWarning(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_warn_location),
|
||||
listOf(false to stringResource(R.string.off), true to stringResource(R.string.on)),
|
||||
locationWarning,
|
||||
) { CarSettings.setLocationWarning(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_spoken_directions),
|
||||
listOf(false to stringResource(R.string.off), true to stringResource(R.string.on)),
|
||||
speakDirections,
|
||||
) { CarSettings.setSpeakDirections(it) }
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
Text(
|
||||
stringResource(R.string.carset_directions_note),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ExperimentalFeatureRow(
|
||||
"What's new after updates",
|
||||
"Show the list of changes once, the first time the car connects after an update.",
|
||||
whatsNew,
|
||||
) { CarSettings.setWhatsNewAfterUpdates(it) }
|
||||
GsButton(onClick = CarSettings::openWhatsNew, tone = GsTone.Tonal, modifier = Modifier.height(56.dp)) {
|
||||
Text("What's new", style = MaterialTheme.typography.titleMedium)
|
||||
title = stringResource(R.string.car_phone_speech),
|
||||
description = stringResource(R.string.carset_phone_speech_note),
|
||||
checked = systemSpeech,
|
||||
) { CarSettings.setSystemSpeech(it) }
|
||||
}
|
||||
|
||||
section = 4
|
||||
if (section == chosen) item { Group(stringResource(R.string.car_group_more)) }
|
||||
|
||||
if (section == chosen) item {
|
||||
WebPageSetting()
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ScreenFit(vehicle)
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
ChoiceRow(
|
||||
stringResource(R.string.car_experimental),
|
||||
listOf(false to stringResource(R.string.carset_hide), true to stringResource(R.string.carset_show)),
|
||||
experimentalFeatures,
|
||||
) { CarSettings.setExperimentalFeaturesEnabled(it) }
|
||||
}
|
||||
|
||||
if (section == chosen && experimentalFeatures) item {
|
||||
ExperimentalFeaturesList(carSensors, adaptiveVideo, clusterVideo)
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(stringResource(R.string.settings_connection), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Info(stringResource(R.string.carset_phone), "${Build.MANUFACTURER} ${Build.MODEL}")
|
||||
Info(stringResource(R.string.carset_vehicle), vehicle.ifEmpty { "-" })
|
||||
Info(stringResource(R.string.carset_video), display.ifEmpty { "-" })
|
||||
Info(stringResource(R.string.settings_certificate), cert?.headline ?: stringResource(R.string.cert_checking))
|
||||
}
|
||||
}
|
||||
|
||||
if (section == chosen) item {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(stringResource(R.string.settings_about), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Info(stringResource(R.string.carset_version), app.seb3thehacker.gearslip.BuildConfig.VERSION_NAME)
|
||||
Info(stringResource(R.string.carset_build), app.seb3thehacker.gearslip.BuildConfig.VERSION_CODE.toString())
|
||||
ExperimentalFeatureRow(
|
||||
stringResource(R.string.carset_whats_new_after),
|
||||
stringResource(R.string.carset_whats_new_after_sub),
|
||||
whatsNew,
|
||||
) { CarSettings.setWhatsNewAfterUpdates(it) }
|
||||
GsButton(onClick = CarSettings::openWhatsNew, tone = GsTone.Tonal, modifier = Modifier.height(56.dp)) {
|
||||
Text(stringResource(R.string.settings_whats_new), style = MaterialTheme.typography.titleMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -328,6 +364,47 @@ private fun <T> ChoiceRow(title: String, options: List<Pair<T, String>>, selecte
|
||||
}
|
||||
}
|
||||
|
||||
/** The groups, with the chosen one lit: a card down the left of Settings. */
|
||||
@Composable
|
||||
private fun SettingsRail(groups: List<String>, chosen: Int, onChoose: (Int) -> Unit) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val shape = RoundedCornerShape(24.dp)
|
||||
Surface(
|
||||
Modifier.width(250.dp).padding(bottom = 12.dp).fillMaxHeight().gsPanelShadow(shape),
|
||||
shape = shape,
|
||||
color = scheme.surfaceContainer,
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(10.dp).verticalScroll(androidx.compose.foundation.rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.settings_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(start = 14.dp, top = 6.dp, bottom = 10.dp),
|
||||
)
|
||||
groups.forEachIndexed { i, name ->
|
||||
val on = i == chosen
|
||||
GsIconBox(
|
||||
onClick = { onChoose(i) },
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
colors = if (on) GsColors(scheme.primaryContainer, scheme.onPrimaryContainer)
|
||||
else GsColors(Color.Transparent, scheme.onSurface),
|
||||
latched = on,
|
||||
) {
|
||||
Text(
|
||||
name,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = if (on) FontWeight.Bold else FontWeight.Normal,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A group of settings: a heading, so a long list reads as a few short ones. */
|
||||
@Composable
|
||||
private fun Group(title: String) {
|
||||
@@ -369,25 +446,26 @@ private fun <T> ChoiceButtons(options: List<Pair<T, String>>, selected: T, onSel
|
||||
* so the list reads fine whether there's one entry or several.
|
||||
*/
|
||||
@Composable
|
||||
private fun ExperimentalFeaturesList(carSensors: Boolean, adaptiveVideo: Boolean) {
|
||||
private fun ExperimentalFeaturesList(carSensors: Boolean, adaptiveVideo: Boolean, clusterVideo: Boolean) {
|
||||
Column(
|
||||
Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
ExperimentalFeatureRow(
|
||||
title = "Vehicle data",
|
||||
description = "Adds a Vehicle data tile: a live, raw dump of the head unit's sensor " +
|
||||
"channel. Field labels (speed, RPM, fuel...) are carried over from other " +
|
||||
"reverse-engineering projects and unverified against this car - treat them as a " +
|
||||
"guess, not a fact, until you've watched one change for yourself.",
|
||||
title = stringResource(R.string.car_vehicle_data),
|
||||
description = stringResource(R.string.carset_vehicle_data_note),
|
||||
checked = carSensors,
|
||||
) { CarSettings.setCarSensorsEnabled(it) }
|
||||
ExperimentalFeatureRow(
|
||||
title = "Adaptive video quality",
|
||||
description = "Lowers the picture's quality when the car, or its Wi-Fi, can't keep " +
|
||||
"up, and raises it again once it can. Takes effect on the next connection.",
|
||||
title = stringResource(R.string.car_adaptive_video),
|
||||
description = stringResource(R.string.carset_adaptive_note),
|
||||
checked = adaptiveVideo,
|
||||
) { CarSettings.setAdaptiveVideoEnabled(it) }
|
||||
ExperimentalFeatureRow(
|
||||
title = stringResource(R.string.car_cluster_video),
|
||||
description = stringResource(R.string.car_cluster_video_note),
|
||||
checked = clusterVideo,
|
||||
) { CarSettings.setClusterVideoEnabled(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +511,7 @@ private fun WebPageSetting() {
|
||||
var url by remember { mutableStateOf(AppSettings.startupUrl(context)) }
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Web page", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Text(stringResource(R.string.web_page), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Surface(
|
||||
onClick = { editing = !editing },
|
||||
shape = MaterialTheme.shapes.large,
|
||||
@@ -441,7 +519,7 @@ private fun WebPageSetting() {
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
url.ifEmpty { "DuckDuckGo (default) - tap to set a different page" },
|
||||
url.ifEmpty { stringResource(R.string.carset_web_default) },
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = if (url.isEmpty()) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
@@ -450,7 +528,7 @@ private fun WebPageSetting() {
|
||||
)
|
||||
}
|
||||
Text(
|
||||
"Shown by the Browser app on the launcher. Applies on the next connection.",
|
||||
stringResource(R.string.carset_web_note),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -493,22 +571,22 @@ private fun ScreenFit(vehicle: String) {
|
||||
}
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Screen fit", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Stepper("Top", insets.top) { change(insets.copy(top = (insets.top + it).coerceAtLeast(0))) }
|
||||
Stepper("Bottom", insets.bottom) { change(insets.copy(bottom = (insets.bottom + it).coerceAtLeast(0))) }
|
||||
Text(stringResource(R.string.carset_screen_fit), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Stepper(stringResource(R.string.carset_top), insets.top) { change(insets.copy(top = (insets.top + it).coerceAtLeast(0))) }
|
||||
Stepper(stringResource(R.string.carset_bottom), insets.bottom) { change(insets.copy(bottom = (insets.bottom + it).coerceAtLeast(0))) }
|
||||
Text(
|
||||
"Usable height ${480 - insets.top - insets.bottom}px of 480",
|
||||
stringResource(R.string.carset_usable_height, 480 - insets.top - insets.bottom),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
GsButton(
|
||||
onClick = {
|
||||
val ok = app.seb3thehacker.gearslip.VehicleProfiles.saveInsets(context, vehicle, insets)
|
||||
saved = if (ok) "Saved to $vehicle" else "Could not save"
|
||||
saved = if (ok) context.getString(R.string.carset_saved_to, vehicle) else context.getString(R.string.carset_save_failed)
|
||||
},
|
||||
enabled = known,
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
) { Text(if (known) "Save for this vehicle" else "No vehicle profile matched") }
|
||||
) { Text(if (known) stringResource(R.string.carset_save_vehicle) else stringResource(R.string.carset_no_profile)) }
|
||||
saved?.let { Text(it, style = MaterialTheme.typography.bodyMedium) }
|
||||
}
|
||||
}
|
||||
@@ -520,7 +598,7 @@ private fun Stepper(label: String, value: Int, onStep: (Int) -> Unit) {
|
||||
StepButton("−5", Modifier.weight(1f)) { onStep(-5) }
|
||||
StepButton("−1", Modifier.weight(1f)) { onStep(-1) }
|
||||
Text(
|
||||
"${value}px",
|
||||
stringResource(R.string.carset_px, value),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
modifier = Modifier.weight(1.3f),
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.material.icons.filled.Call
|
||||
import app.seb3thehacker.gearslip.call.CarCalls
|
||||
import app.seb3thehacker.gearslip.call.CallUiState
|
||||
@@ -62,6 +63,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import android.media.session.PlaybackState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -110,6 +113,7 @@ fun CarUi() {
|
||||
val phoneDark by CarEnvironment.phoneDark.collectAsState()
|
||||
val appTheme by CarSettings.appTheme.collectAsState()
|
||||
val uiThemeId by CarSettings.uiTheme.collectAsState()
|
||||
val darkStyle by CarSettings.darkStyle.collectAsState()
|
||||
val mediaApp by CarServices.mediaApp.collectAsState()
|
||||
val darkOutside by CarEnvironment.darkOutside.collectAsState()
|
||||
val appContext = LocalContext.current.applicationContext
|
||||
@@ -118,7 +122,7 @@ fun CarUi() {
|
||||
AppTheme.LIGHT -> false
|
||||
AppTheme.DARK -> true
|
||||
}
|
||||
val colors = (if (dark) dynamicDarkColorScheme(appContext) else dynamicLightColorScheme(appContext)).withTintedSurfaces(dark)
|
||||
val colors = (if (dark) dynamicDarkColorScheme(appContext) else dynamicLightColorScheme(appContext)).withTintedSurfaces(dark, darkStyle)
|
||||
val base = LocalDensity.current
|
||||
val insets by CarEnvironment.insets.collectAsState()
|
||||
val navigator = remember { CarNavigator(CarSettings.openOnConnect.value) }
|
||||
@@ -222,8 +226,10 @@ fun CarUi() {
|
||||
},
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
) {
|
||||
// Insets are in video-frame pixels, so convert with the un-scaled density.
|
||||
val pad = with(base) {
|
||||
// Insets are video-frame pixels. Resolve them with the live (size-scaled) density,
|
||||
// which padding() also uses, so the safe-area margin stays one fixed physical size
|
||||
// at every size setting instead of growing with it and eating the screen's sides.
|
||||
val pad = with(LocalDensity.current) {
|
||||
PaddingValues(
|
||||
start = insets.left.toDp(), top = insets.top.toDp(),
|
||||
end = insets.right.toDp(), bottom = insets.bottom.toDp(),
|
||||
@@ -232,12 +238,11 @@ fun CarUi() {
|
||||
val screen = navigator.current
|
||||
val safetyWarningSeen by CarSettings.hasSeenSafetyWarning.collectAsState()
|
||||
val keyboardVisible by CarKeyboardVisibility.state
|
||||
// Off "Always show", the bar floats over the map rather than taking a row of its
|
||||
// own: hiding it then leaves the map's surface the size it was. Resizing it would
|
||||
// hand the map app a new surface each time, and some go black on that.
|
||||
// The bar always lies over the map and every other screen rather than taking a row
|
||||
// of its own: hiding it then leaves the map's surface the size it was.
|
||||
// Resizing it would hand the map app a new surface each time, and some go black on that.
|
||||
val navBarMode by CarSettings.navBarMode.collectAsState()
|
||||
val appTemplate by CarServices.nav.template.collectAsState()
|
||||
val floatingBar = navBarMode != NavBarMode.SHOW
|
||||
val barMayHide = screen == CarScreen.Home && when (navBarMode) {
|
||||
NavBarMode.SHOW -> false
|
||||
NavBarMode.HIDE_NAVIGATING -> appTemplate is androidx.car.app.navigation.model.NavigationTemplate
|
||||
@@ -248,7 +253,8 @@ fun CarUi() {
|
||||
// A call keeps the bar up whatever the setting: End and Mute live on it.
|
||||
val onCall = CarCalls.state.collectAsState().value is CallUiState.Active
|
||||
val barShown = !keyboardVisible && (!barMayHide || controlsWake.navBarAwake || onCall)
|
||||
val homeInset = if (floatingBar && barShown) NAV_BAR_HEIGHT else 0.dp
|
||||
// Up to the bar's top edge, so the map's cards and the player end above it.
|
||||
val homeInset = if (barShown) NAV_BAR_HEIGHT else 0.dp
|
||||
// Only the screen that asked: going Home or anywhere else ends it.
|
||||
val fullscreen = CarFullscreen.active && screen != CarScreen.Home
|
||||
Box(Modifier.fillMaxSize().padding(pad)) {
|
||||
@@ -268,39 +274,53 @@ fun CarUi() {
|
||||
// The keyboard takes the nav bar's row instead of squeezing in above it -
|
||||
// nothing on the nav bar is useful mid-type, and the screen is too small to
|
||||
// show both.
|
||||
if (!floatingBar && !keyboardVisible) CarNavBar(navigator)
|
||||
}
|
||||
// Every other screen, and everything that pops up, in a layer over Home rather
|
||||
// than inside its area: full screen grows this layer over the nav bar's row while
|
||||
// Home, and the map's surface in it, keeps its size.
|
||||
// Each new screen fades in while rising a little, once, over a fifth of a second.
|
||||
// Coming from Home the backdrop fades in with it, over the map; between two other
|
||||
// screens it stays put, so the map never flashes through.
|
||||
val lastScreen = remember { arrayOf<CarScreen>(CarScreen.Home) }
|
||||
val fromHome = remember(screen) { lastScreen[0] == CarScreen.Home }
|
||||
SideEffect { lastScreen[0] = screen }
|
||||
val enter = remember(screen) { androidx.compose.animation.core.Animatable(0f) }
|
||||
LaunchedEffect(screen) { enter.animateTo(1f, androidx.compose.animation.core.tween(180)) }
|
||||
if (safetyWarningSeen) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(bottom = if (!floatingBar && !keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp),
|
||||
) {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
if (screen != CarScreen.Home) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer { if (fromHome) alpha = enter.value }
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
// The floating bar never hides here, so keep the screen clear of it.
|
||||
.padding(bottom = if (floatingBar && !keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp)
|
||||
// The bar never hides here, so keep the screen clear of it. The
|
||||
// player runs on under the see-through bar and keeps clear itself.
|
||||
.padding(bottom = if (!keyboardVisible && !fullscreen && screen != CarScreen.Media) NAV_BAR_HEIGHT else 0.dp)
|
||||
// Keeps touches that miss this screen's controls off the map.
|
||||
.pointerInput(Unit) { detectTapGestures { } },
|
||||
) {
|
||||
when (screen) {
|
||||
CarScreen.Home -> Unit
|
||||
CarScreen.Apps -> CarLauncher()
|
||||
CarScreen.Media -> mediaApp?.let { MediaScreen(it) { navigator.back() } }
|
||||
CarScreen.Browse -> BrowseAppScreen(navigator)
|
||||
CarScreen.Settings -> CarSettingsScreen()
|
||||
CarScreen.Dashboard -> CarDashboardScreen()
|
||||
CarScreen.Weather -> WeatherScreen()
|
||||
CarScreen.VehicleData -> VehicleDataScreen()
|
||||
is CarScreen.Notifications -> NotificationsScreen(screen.replyTo)
|
||||
is CarScreen.Messages -> MessagesScreen(screen.packageName)
|
||||
is CarScreen.App -> CarApps.find(screen.id)?.content?.invoke()
|
||||
Box(
|
||||
Modifier.fillMaxSize().graphicsLayer {
|
||||
if (!fromHome) alpha = enter.value
|
||||
translationY = (1f - enter.value) * 16.dp.toPx()
|
||||
},
|
||||
) {
|
||||
when (screen) {
|
||||
CarScreen.Home -> Unit
|
||||
CarScreen.Apps -> CarLauncher()
|
||||
CarScreen.Media -> CompositionLocalProvider(
|
||||
LocalNavBarInset provides if (!keyboardVisible && !fullscreen) NAV_BAR_HEIGHT else 0.dp,
|
||||
) { mediaApp?.let { MediaScreen(it) { navigator.back() } } }
|
||||
CarScreen.Browse -> BrowseAppScreen(navigator)
|
||||
CarScreen.Settings -> CarSettingsScreen()
|
||||
CarScreen.Dashboard -> CarDashboardScreen()
|
||||
CarScreen.Weather -> WeatherScreen()
|
||||
CarScreen.VehicleData -> VehicleDataScreen()
|
||||
is CarScreen.Notifications -> NotificationsScreen(screen.replyTo)
|
||||
is CarScreen.Messages -> MessagesScreen(screen.packageName)
|
||||
is CarScreen.App -> CarApps.find(screen.id)?.content?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +345,7 @@ fun CarUi() {
|
||||
// whatever the screen drew there.
|
||||
DebugBuildBadge(Modifier.align(Alignment.BottomEnd).padding(bottom = homeInset))
|
||||
}
|
||||
if (floatingBar || fullscreen) {
|
||||
run {
|
||||
androidx.compose.animation.AnimatedVisibility(
|
||||
if (fullscreen) CarFullscreen.revealed || onCall else barShown,
|
||||
Modifier.align(Alignment.BottomCenter),
|
||||
@@ -343,7 +363,7 @@ fun CarUi() {
|
||||
GsButton(onClick = CarFullscreen::exit, modifier = Modifier.height(48.dp)) {
|
||||
Icon(MediaIcons.FullscreenExit, contentDescription = null, modifier = Modifier.size(22.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text("Exit full screen", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.carui_exit_fullscreen), style = MaterialTheme.typography.titleMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,22 +408,20 @@ private fun CarSafetyWizard(onDone: () -> Unit) {
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Drive safely", style = ChromeType.headline, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.carui_drive_safely), style = ChromeType.headline, fontWeight = FontWeight.Bold)
|
||||
Text(
|
||||
"Gearslip is unofficial software: it is not made or reviewed by any " +
|
||||
"carmaker, and it can fail without warning.",
|
||||
stringResource(R.string.carui_unofficial),
|
||||
style = ChromeType.body,
|
||||
)
|
||||
Text(
|
||||
"Never interact with this screen while driving. Set the route or the " +
|
||||
"song before you go, then keep your attention on the road.",
|
||||
stringResource(R.string.carui_never_interact),
|
||||
style = ChromeType.body,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(24.dp))
|
||||
GsButton(onClick = onDone, modifier = Modifier.fillMaxWidth().height(56.dp)) {
|
||||
Text("I understand", style = ChromeType.body)
|
||||
Text(stringResource(R.string.carui_understand), style = ChromeType.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +452,7 @@ private fun BrowseAppScreen(navigator: CarNavigator) {
|
||||
}
|
||||
val failed = status.phase == CarAppConnection.Phase.REJECTED || status.phase == CarAppConnection.Phase.FAILED
|
||||
if (failed || timedOut) {
|
||||
AppWontStart(status.app ?: "This app", leave)
|
||||
AppWontStart(status.app ?: stringResource(R.string.carui_this_app), leave)
|
||||
return
|
||||
}
|
||||
CarAppStage(CarServices.browse, status, frame, onDisconnect = leave)
|
||||
@@ -450,13 +468,13 @@ private fun AppWontStart(app: String, onBack: () -> Unit) {
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text("$app doesn't work with Gearslip yet.", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(R.string.carui_app_unsupported, app), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Text(
|
||||
"It didn't start. Some apps only run on Google's Android Auto.",
|
||||
stringResource(R.string.carui_app_unsupported_body),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
GsButton(onClick = onBack, tone = GsTone.Tonal) { Text("Back") }
|
||||
GsButton(onClick = onBack, tone = GsTone.Tonal) { Text(stringResource(R.string.back)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
@@ -76,7 +78,7 @@ fun CarWhatsNew() {
|
||||
) {
|
||||
Column(Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||
Text(
|
||||
"What's new in ${release.versionName}",
|
||||
stringResource(R.string.whatsnew_title, release.versionName),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
@@ -99,12 +101,12 @@ fun CarWhatsNew() {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(14.dp)) {
|
||||
GsSwitch(afterUpdates, CarSettings::setWhatsNewAfterUpdates)
|
||||
Text(
|
||||
"Show after updates",
|
||||
stringResource(R.string.whatsnew_show_after),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
GsButton(onClick = close, modifier = Modifier.height(56.dp)) {
|
||||
Text("Got it", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.whatsnew_got_it), style = MaterialTheme.typography.titleMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.seb3thehacker.gearslip.host.clockTime
|
||||
import app.seb3thehacker.gearslip.host.display
|
||||
import app.seb3thehacker.gearslip.host.remainingTime
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* The instrument-cluster screen: the next turn, big enough to read in a glance past the wheel.
|
||||
* The arrow and the distance to it, what to do there, and the time, distance and arrival left
|
||||
* along the bottom. With no route it shows what's playing, quietly, or nothing.
|
||||
*
|
||||
* Always dark, like the dials around it. [marginX] and [marginY] are the pixels the car crops off
|
||||
* the frame, kept clear.
|
||||
*/
|
||||
@Composable
|
||||
internal fun ClusterScreen(marginX: Int, marginY: Int) {
|
||||
// The map app's turn changes a few times a minute: read it once a second.
|
||||
val turn by produceState<ClusterTurn?>(null) {
|
||||
while (true) {
|
||||
value = ClusterTurn.now()
|
||||
delay(1_000)
|
||||
}
|
||||
}
|
||||
val density = LocalDensity.current
|
||||
MaterialTheme(colorScheme = darkColorScheme()) {
|
||||
CompositionLocalProvider(LocalContentColor provides Color.White) {
|
||||
BoxWithConstraints(
|
||||
Modifier.fillMaxSize().background(Color.Black).padding(
|
||||
horizontal = with(density) { (marginX / 2).toDp() } + 24.dp,
|
||||
vertical = with(density) { (marginY / 2).toDp() } + 16.dp,
|
||||
),
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
) {
|
||||
// Sized off the height: clusters run from short strips to near squares.
|
||||
val unit = maxHeight / 12
|
||||
val t = turn
|
||||
if (t == null) {
|
||||
NowPlayingLine(Modifier.align(Alignment.Center))
|
||||
return@BoxWithConstraints
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(unit * 0.4f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CarGlyph(t.icon, Modifier.size(unit * 3.4f))
|
||||
Spacer(Modifier.width(unit))
|
||||
Text(
|
||||
t.distance,
|
||||
fontSize = with(density) { (unit * 2.2f).toSp() },
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
if (t.what.isNotEmpty()) {
|
||||
Text(
|
||||
t.what,
|
||||
fontSize = with(density) { (unit * 1.1f).toSp() },
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
if (t.trip.isNotEmpty()) {
|
||||
Text(
|
||||
t.trip,
|
||||
fontSize = with(density) { (unit * 0.85f).toSp() },
|
||||
color = Color.White.copy(alpha = 0.7f),
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NowPlayingLine(modifier: Modifier) {
|
||||
val now = CarServices.mediaOrNull()?.now?.value
|
||||
if (now == null || now.title.isEmpty()) return
|
||||
Text(
|
||||
listOf(now.title, now.artist).filter { it.isNotEmpty() }.joinToString(" · "),
|
||||
modifier,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = Color.White.copy(alpha = 0.7f),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
/** What the cluster shows of a route, read from [NavTurn] and the trip's estimate to the destination. */
|
||||
private class ClusterTurn(
|
||||
val icon: androidx.car.app.model.CarIcon?,
|
||||
val distance: String,
|
||||
val what: String,
|
||||
val trip: String,
|
||||
) {
|
||||
companion object {
|
||||
fun now(): ClusterTurn? {
|
||||
val turn = NavTurn.current() ?: return null
|
||||
if (turn.rerouting) return null
|
||||
val step = turn.step
|
||||
val cue = step.cue?.toString().orEmpty()
|
||||
val what = cue.ifEmpty { step.road?.toString().orEmpty() }
|
||||
val estimate = CarServices.navOrNull()?.trip?.value?.destinationTravelEstimates?.firstOrNull()
|
||||
val trip = estimate?.let { e ->
|
||||
listOf(
|
||||
e.remainingTimeSeconds.takeIf { it >= 0 }?.let { remainingTime(it) }.orEmpty(),
|
||||
e.remainingDistance.display(),
|
||||
e.arrivalTimeAtDestination.clockTime(),
|
||||
).filter { it.isNotEmpty() }.joinToString(" · ")
|
||||
}.orEmpty()
|
||||
return ClusterTurn(step.maneuver?.icon, turn.distance.display(), what, trip)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.speech.tts.TextToSpeech
|
||||
@@ -121,7 +122,7 @@ object DirectionsVoice {
|
||||
say(instruction)
|
||||
stage = Stage.DONE
|
||||
} else {
|
||||
say("In ${spoken(turn.distance)}, $instruction")
|
||||
say(ahead(turn.distance, instruction))
|
||||
stage = if (meters != null && meters > PREPARE_FROM_METERS) Stage.FAR else Stage.PREPARED
|
||||
}
|
||||
return
|
||||
@@ -132,7 +133,7 @@ object DirectionsVoice {
|
||||
stage = Stage.DONE
|
||||
}
|
||||
stage == Stage.FAR && meters != null && meters <= PREPARE_AT_METERS -> {
|
||||
say("In ${spoken(turn.distance)}, $instruction")
|
||||
say(ahead(turn.distance, instruction))
|
||||
stage = Stage.PREPARED
|
||||
}
|
||||
}
|
||||
@@ -140,67 +141,79 @@ object DirectionsVoice {
|
||||
|
||||
/** What to do, in words: built from the manoeuvre when the app names one, else the app's own text. */
|
||||
private fun instruction(step: Step): String? {
|
||||
val res = appContext?.resources ?: return null
|
||||
val road = step.road.text()
|
||||
val onto = if (road.isBlank()) "" else " onto $road"
|
||||
val toward = if (road.isBlank()) "" else " toward $road"
|
||||
// Each manoeuvre is a whole sentence, with and without the road, so a translation can
|
||||
// put the road wherever its language wants it.
|
||||
fun say(plain: Int, withRoad: Int) =
|
||||
if (road.isBlank() || withRoad == 0) res.getString(plain) else res.getString(withRoad, road)
|
||||
val maneuver = step.maneuver
|
||||
val built = when (maneuver?.type) {
|
||||
Maneuver.TYPE_TURN_SLIGHT_LEFT -> "Turn slightly left$onto"
|
||||
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> "Turn slightly right$onto"
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> "Turn left$onto"
|
||||
Maneuver.TYPE_TURN_NORMAL_RIGHT -> "Turn right$onto"
|
||||
Maneuver.TYPE_TURN_SHARP_LEFT -> "Turn sharply left$onto"
|
||||
Maneuver.TYPE_TURN_SHARP_RIGHT -> "Turn sharply right$onto"
|
||||
Maneuver.TYPE_U_TURN_LEFT, Maneuver.TYPE_U_TURN_RIGHT -> "Make a U-turn"
|
||||
Maneuver.TYPE_KEEP_LEFT -> "Keep left$toward"
|
||||
Maneuver.TYPE_KEEP_RIGHT -> "Keep right$toward"
|
||||
Maneuver.TYPE_FORK_LEFT -> "Keep left at the fork$toward"
|
||||
Maneuver.TYPE_FORK_RIGHT -> "Keep right at the fork$toward"
|
||||
Maneuver.TYPE_TURN_SLIGHT_LEFT -> say(R.string.dir_slight_left, R.string.dir_slight_left_onto)
|
||||
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> say(R.string.dir_slight_right, R.string.dir_slight_right_onto)
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> say(R.string.dir_left, R.string.dir_left_onto)
|
||||
Maneuver.TYPE_TURN_NORMAL_RIGHT -> say(R.string.dir_right, R.string.dir_right_onto)
|
||||
Maneuver.TYPE_TURN_SHARP_LEFT -> say(R.string.dir_sharp_left, R.string.dir_sharp_left_onto)
|
||||
Maneuver.TYPE_TURN_SHARP_RIGHT -> say(R.string.dir_sharp_right, R.string.dir_sharp_right_onto)
|
||||
Maneuver.TYPE_U_TURN_LEFT, Maneuver.TYPE_U_TURN_RIGHT -> say(R.string.dir_u_turn, 0)
|
||||
Maneuver.TYPE_KEEP_LEFT -> say(R.string.dir_keep_left, R.string.dir_keep_left_toward)
|
||||
Maneuver.TYPE_KEEP_RIGHT -> say(R.string.dir_keep_right, R.string.dir_keep_right_toward)
|
||||
Maneuver.TYPE_FORK_LEFT -> say(R.string.dir_fork_left, R.string.dir_fork_left_toward)
|
||||
Maneuver.TYPE_FORK_RIGHT -> say(R.string.dir_fork_right, R.string.dir_fork_right_toward)
|
||||
Maneuver.TYPE_ON_RAMP_SLIGHT_LEFT, Maneuver.TYPE_ON_RAMP_NORMAL_LEFT, Maneuver.TYPE_ON_RAMP_SHARP_LEFT,
|
||||
Maneuver.TYPE_ON_RAMP_U_TURN_LEFT -> "Take the ramp on the left$onto"
|
||||
Maneuver.TYPE_ON_RAMP_U_TURN_LEFT -> say(R.string.dir_ramp_left, R.string.dir_ramp_left_onto)
|
||||
Maneuver.TYPE_ON_RAMP_SLIGHT_RIGHT, Maneuver.TYPE_ON_RAMP_NORMAL_RIGHT, Maneuver.TYPE_ON_RAMP_SHARP_RIGHT,
|
||||
Maneuver.TYPE_ON_RAMP_U_TURN_RIGHT -> "Take the ramp on the right$onto"
|
||||
Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT, Maneuver.TYPE_OFF_RAMP_NORMAL_LEFT -> "Take the exit on the left$toward"
|
||||
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT, Maneuver.TYPE_OFF_RAMP_NORMAL_RIGHT -> "Take the exit on the right$toward"
|
||||
Maneuver.TYPE_MERGE_LEFT -> "Merge left$onto"
|
||||
Maneuver.TYPE_MERGE_RIGHT -> "Merge right$onto"
|
||||
Maneuver.TYPE_MERGE_SIDE_UNSPECIFIED -> "Merge$onto"
|
||||
Maneuver.TYPE_ON_RAMP_U_TURN_RIGHT -> say(R.string.dir_ramp_right, R.string.dir_ramp_right_onto)
|
||||
Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT, Maneuver.TYPE_OFF_RAMP_NORMAL_LEFT -> say(R.string.dir_exit_left, R.string.dir_exit_left_toward)
|
||||
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT, Maneuver.TYPE_OFF_RAMP_NORMAL_RIGHT -> say(R.string.dir_exit_right, R.string.dir_exit_right_toward)
|
||||
Maneuver.TYPE_MERGE_LEFT -> say(R.string.dir_merge_left, R.string.dir_merge_left_onto)
|
||||
Maneuver.TYPE_MERGE_RIGHT -> say(R.string.dir_merge_right, R.string.dir_merge_right_onto)
|
||||
Maneuver.TYPE_MERGE_SIDE_UNSPECIFIED -> say(R.string.dir_merge, R.string.dir_merge_onto)
|
||||
Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW, Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE,
|
||||
Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW, Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE ->
|
||||
maneuver.roundaboutExitNumber.takeIf { it in 1..ORDINALS.size }
|
||||
?.let { "At the roundabout, take the ${ORDINALS[it - 1]} exit$onto" }
|
||||
?: "Go through the roundabout$onto"
|
||||
Maneuver.TYPE_ROUNDABOUT_ENTER_CW, Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> "Enter the roundabout"
|
||||
Maneuver.TYPE_ROUNDABOUT_EXIT_CW, Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> "Exit the roundabout$onto"
|
||||
Maneuver.TYPE_STRAIGHT -> "Continue straight$onto"
|
||||
Maneuver.TYPE_NAME_CHANGE -> "Continue$onto"
|
||||
Maneuver.TYPE_FERRY_BOAT, Maneuver.TYPE_FERRY_BOAT_LEFT, Maneuver.TYPE_FERRY_BOAT_RIGHT -> "Take the ferry"
|
||||
Maneuver.TYPE_FERRY_TRAIN, Maneuver.TYPE_FERRY_TRAIN_LEFT, Maneuver.TYPE_FERRY_TRAIN_RIGHT -> "Take the train"
|
||||
Maneuver.TYPE_DESTINATION, Maneuver.TYPE_DESTINATION_STRAIGHT -> "Your destination is ahead"
|
||||
Maneuver.TYPE_DESTINATION_LEFT -> "Your destination is on the left"
|
||||
Maneuver.TYPE_DESTINATION_RIGHT -> "Your destination is on the right"
|
||||
?.let { res.getString(ORDINALS[it - 1]) }
|
||||
?.let { exit ->
|
||||
if (road.isBlank()) res.getString(R.string.dir_roundabout_take, exit)
|
||||
else res.getString(R.string.dir_roundabout_take_onto, exit, road)
|
||||
}
|
||||
?: say(R.string.dir_roundabout_through, R.string.dir_roundabout_through_onto)
|
||||
Maneuver.TYPE_ROUNDABOUT_ENTER_CW, Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> say(R.string.dir_roundabout_enter, 0)
|
||||
Maneuver.TYPE_ROUNDABOUT_EXIT_CW, Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> say(R.string.dir_roundabout_exit, R.string.dir_roundabout_exit_onto)
|
||||
Maneuver.TYPE_STRAIGHT -> say(R.string.dir_straight, R.string.dir_straight_onto)
|
||||
Maneuver.TYPE_NAME_CHANGE -> say(R.string.dir_continue, R.string.dir_continue_onto)
|
||||
Maneuver.TYPE_FERRY_BOAT, Maneuver.TYPE_FERRY_BOAT_LEFT, Maneuver.TYPE_FERRY_BOAT_RIGHT -> say(R.string.dir_ferry, 0)
|
||||
Maneuver.TYPE_FERRY_TRAIN, Maneuver.TYPE_FERRY_TRAIN_LEFT, Maneuver.TYPE_FERRY_TRAIN_RIGHT -> say(R.string.dir_train, 0)
|
||||
Maneuver.TYPE_DESTINATION, Maneuver.TYPE_DESTINATION_STRAIGHT -> say(R.string.dir_destination, 0)
|
||||
Maneuver.TYPE_DESTINATION_LEFT -> say(R.string.dir_destination_left, 0)
|
||||
Maneuver.TYPE_DESTINATION_RIGHT -> say(R.string.dir_destination_right, 0)
|
||||
else -> null
|
||||
}
|
||||
return built ?: step.cue.text().ifBlank { null }
|
||||
}
|
||||
|
||||
/** "In 300 meters, turn left": the distance ahead of a turn that isn't close yet. */
|
||||
private fun ahead(d: Distance, instruction: String): String =
|
||||
appContext?.getString(R.string.dir_in_distance, spoken(d), instruction) ?: instruction
|
||||
|
||||
/** The app's own rounded distance, read the way a person would say it. */
|
||||
private fun spoken(d: Distance): String {
|
||||
val res = appContext?.resources ?: return ""
|
||||
val n = d.displayDistance
|
||||
val number = if (n % 1.0 == 0.0) n.toInt().toString() else "%.1f".format(n)
|
||||
val one = n == 1.0
|
||||
val whole = n % 1.0 == 0.0
|
||||
val number = if (whole) n.toInt().toString() else "%.1f".format(n)
|
||||
return when (d.displayUnit) {
|
||||
Distance.UNIT_KILOMETERS, Distance.UNIT_KILOMETERS_P1 -> if (one) "1 kilometer" else "$number kilometers"
|
||||
Distance.UNIT_KILOMETERS, Distance.UNIT_KILOMETERS_P1 ->
|
||||
if (whole) res.getQuantityString(R.plurals.dir_km, n.toInt(), number) else res.getString(R.string.dir_km_decimal, number)
|
||||
Distance.UNIT_MILES, Distance.UNIT_MILES_P1 -> when (n) {
|
||||
0.25 -> "a quarter mile"
|
||||
0.5 -> "half a mile"
|
||||
0.75 -> "three quarters of a mile"
|
||||
1.0 -> "1 mile"
|
||||
else -> "$number miles"
|
||||
0.25 -> res.getString(R.string.dir_quarter_mile)
|
||||
0.5 -> res.getString(R.string.dir_half_mile)
|
||||
0.75 -> res.getString(R.string.dir_three_quarter_mile)
|
||||
else -> if (whole) res.getQuantityString(R.plurals.dir_miles, n.toInt(), number) else res.getString(R.string.dir_miles_decimal, number)
|
||||
}
|
||||
Distance.UNIT_FEET -> "$number feet"
|
||||
Distance.UNIT_YARDS -> "$number yards"
|
||||
else -> "${n.roundToInt()} meters"
|
||||
Distance.UNIT_FEET -> res.getQuantityString(R.plurals.dir_feet, n.toInt(), number)
|
||||
Distance.UNIT_YARDS -> res.getQuantityString(R.plurals.dir_yards, n.toInt(), number)
|
||||
else -> res.getQuantityString(R.plurals.dir_meters, n.roundToInt(), n.roundToInt())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,5 +360,8 @@ object DirectionsVoice {
|
||||
private const val LEAD_MS = 100L
|
||||
private const val HOLD_MS = 2_000L
|
||||
private const val SYNTH_TIMEOUT_S = 10L
|
||||
private val ORDINALS = listOf("first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth")
|
||||
private val ORDINALS = listOf(
|
||||
R.string.dir_ordinal_1, R.string.dir_ordinal_2, R.string.dir_ordinal_3, R.string.dir_ordinal_4,
|
||||
R.string.dir_ordinal_5, R.string.dir_ordinal_6, R.string.dir_ordinal_7, R.string.dir_ordinal_8,
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
@@ -11,6 +13,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import android.provider.Settings
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -83,34 +86,138 @@ internal fun BrowsePanel(browse: app.seb3thehacker.gearslip.media.BrowseState, m
|
||||
// row could say that the app's name in the top-left corner hasn't already - showing it
|
||||
// again here (as "Browse", or as the app's own name) was just the same label twice.
|
||||
if (media.canGoUp || browse.trail.isNotEmpty()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(Modifier.padding(bottom = 12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
if (media.canGoUp) {
|
||||
GsIconButton(Icons.Filled.ArrowBack, "Up", media::up)
|
||||
GsIconButton(Icons.Filled.ArrowBack, stringResource(R.string.media_up), media::up, size = 44.dp, shape = CircleShape)
|
||||
Spacer(Modifier.width(14.dp))
|
||||
}
|
||||
browse.trail.lastOrNull()?.let {
|
||||
Text(
|
||||
it,
|
||||
style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold,
|
||||
style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
when {
|
||||
browse.loading -> Notice("Loading…", null)
|
||||
browse.loading -> Notice(stringResource(R.string.media_loading), null)
|
||||
browse.unavailable -> Notice(
|
||||
"$appLabel keeps its library to itself.",
|
||||
"Start music in the app on your phone. Play, pause, skip and seek work from here.",
|
||||
stringResource(R.string.media_keeps_library, appLabel),
|
||||
stringResource(R.string.media_browse_start),
|
||||
)
|
||||
browse.failed -> Notice("Could not load this list.", null)
|
||||
browse.entries.isEmpty() -> Notice("Nothing here.", null)
|
||||
else -> LazyColumn(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(browse.entries, key = { it.id }) { entry -> EntryRow(entry, media) }
|
||||
browse.failed -> Notice(stringResource(R.string.media_load_failed), null)
|
||||
browse.entries.isEmpty() -> Notice(stringResource(R.string.media_nothing_here), null)
|
||||
else -> BrowseList(browse.entries, media)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A list laid out the way the app asked: under its section headings, covers where it asked for a
|
||||
* grid and rows everywhere else. A grid section among others is a shelf that scrolls sideways; a
|
||||
* list that is one grid and nothing else wraps into rows of covers instead.
|
||||
*/
|
||||
@Composable
|
||||
private fun BrowseList(entries: List<MediaEntry>, media: CarMedia) {
|
||||
val sections = remember(entries) { browseSections(entries) }
|
||||
BoxWithConstraints(Modifier.fillMaxSize()) {
|
||||
val onlyGrid = sections.size == 1 && sections[0].grid && sections[0].heading == null
|
||||
val columns = ((maxWidth + SHELF_GAP) / (SHELF_COVER + SHELF_GAP)).toInt().coerceAtLeast(2)
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
sections.forEachIndexed { i, section ->
|
||||
val heading = section.heading
|
||||
if (heading != null && sections.getOrNull(i - 1)?.heading != heading) {
|
||||
item(key = "heading $i") {
|
||||
Text(
|
||||
heading,
|
||||
style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = if (i == 0) 0.dp else 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
when {
|
||||
!section.grid -> items(section.entries, key = { it.id }) { entry -> EntryRow(entry, media) }
|
||||
onlyGrid -> items(section.entries.chunked(columns)) { row ->
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(SHELF_GAP)) {
|
||||
row.forEach { CoverTile(it, media, Modifier.weight(1f)) }
|
||||
repeat(columns - row.size) { Spacer(Modifier.weight(1f)) }
|
||||
}
|
||||
}
|
||||
else -> item(key = "shelf $i") {
|
||||
androidx.compose.foundation.lazy.LazyRow(horizontalArrangement = Arrangement.spacedBy(SHELF_GAP)) {
|
||||
items(section.entries, key = { it.id }) { CoverTile(it, media, Modifier.width(SHELF_COVER)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val SHELF_COVER = 132.dp
|
||||
private val SHELF_GAP = 14.dp
|
||||
|
||||
/** A run of entries under one heading that are all covers or all rows. */
|
||||
private class BrowseSection(val heading: String?, val grid: Boolean, val entries: List<MediaEntry>)
|
||||
|
||||
private fun browseSections(entries: List<MediaEntry>): List<BrowseSection> {
|
||||
val out = mutableListOf<BrowseSection>()
|
||||
var run = mutableListOf<MediaEntry>()
|
||||
for (entry in entries) {
|
||||
val last = run.lastOrNull()
|
||||
if (last != null && (last.group != entry.group || last.grid != entry.grid)) {
|
||||
out += BrowseSection(last.group, last.grid, run)
|
||||
run = mutableListOf()
|
||||
}
|
||||
run += entry
|
||||
}
|
||||
run.lastOrNull()?.let { out += BrowseSection(it.group, it.grid, run) }
|
||||
return out
|
||||
}
|
||||
|
||||
/** One entry as a cover with its name under it: what the app's grids are made of. */
|
||||
@Composable
|
||||
private fun CoverTile(entry: MediaEntry, media: CarMedia, modifier: Modifier) {
|
||||
val context = LocalContext.current
|
||||
val icon by produceState(entry.iconBitmap, entry.iconUri) {
|
||||
value = entry.iconBitmap ?: MediaArt.load(context, entry.iconUri)
|
||||
}
|
||||
val shape = RoundedCornerShape(16.dp)
|
||||
Column(modifier.clip(shape).clickable { media.select(entry) }) {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.gsPanelShadow(shape)
|
||||
.clip(shape)
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
icon?.let { Image(it.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop) }
|
||||
?: Icon(
|
||||
if (entry.browsable) MediaIcons.Folder else MediaIcons.MusicNote, null,
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.fillMaxSize(0.35f),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
entry.title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(top = 8.dp, start = 2.dp),
|
||||
)
|
||||
if (entry.subtitle.isNotEmpty()) {
|
||||
Text(
|
||||
entry.subtitle, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(start = 2.dp, bottom = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- up next -----------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -127,12 +234,15 @@ internal fun QueuePanel(queue: List<QueueTrack>, now: NowPlaying, media: CarMedi
|
||||
Column(modifier) {
|
||||
if (upNext.isEmpty()) {
|
||||
Notice(
|
||||
"Nothing queued.",
|
||||
"${if (now.title.isEmpty()) "Nothing playing" else "Only what's already playing"} - " +
|
||||
"not every app tells Gearslip what comes next.",
|
||||
stringResource(R.string.media_nothing_queued),
|
||||
stringResource(if (now.title.isEmpty()) R.string.media_queue_empty_idle else R.string.media_queue_empty_playing),
|
||||
)
|
||||
} else {
|
||||
LazyColumn(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
items(upNext, key = { it.queueId }) { track -> QueueRow(track, media) }
|
||||
}
|
||||
}
|
||||
@@ -148,18 +258,18 @@ private fun QueueRow(track: QueueTrack, media: CarMedia) {
|
||||
Surface(
|
||||
onClick = { media.playQueueItem(track) },
|
||||
shape = MaterialTheme.shapes.large,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(Modifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RowArt(icon, MediaIcons.MusicNote)
|
||||
Column(Modifier.weight(1f).padding(horizontal = 12.dp)) {
|
||||
Text(track.title, style = MaterialTheme.typography.titleSmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Column(Modifier.weight(1f).padding(horizontal = 14.dp)) {
|
||||
Text(track.title, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
if (track.subtitle.isNotEmpty()) {
|
||||
Text(track.subtitle, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(track.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
Icon(Icons.Filled.PlayArrow, "Play", tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Icon(Icons.Filled.PlayArrow, stringResource(R.string.media_play), tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,19 +283,19 @@ internal fun EntryRow(entry: MediaEntry, media: CarMedia, onClick: () -> Unit =
|
||||
Surface(
|
||||
onClick = onClick,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(Modifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RowArt(icon, if (entry.browsable) MediaIcons.Folder else MediaIcons.MusicNote)
|
||||
Column(Modifier.weight(1f).padding(horizontal = 12.dp)) {
|
||||
Text(entry.title, style = MaterialTheme.typography.titleSmall, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Column(Modifier.weight(1f).padding(horizontal = 14.dp)) {
|
||||
Text(entry.title, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
if (entry.subtitle.isNotEmpty()) {
|
||||
Text(entry.subtitle, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(entry.subtitle, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
}
|
||||
if (entry.browsable && entry.playable) {
|
||||
GsIconButton(Icons.Filled.PlayArrow, "Play", { media.playAll(entry) })
|
||||
GsIconButton(Icons.Filled.PlayArrow, stringResource(R.string.media_play), { media.playAll(entry) })
|
||||
}
|
||||
if (entry.browsable) Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, null)
|
||||
}
|
||||
@@ -196,7 +306,7 @@ internal fun EntryRow(entry: MediaEntry, media: CarMedia, onClick: () -> Unit =
|
||||
@Composable
|
||||
private fun RowArt(icon: android.graphics.Bitmap?, fallback: ImageVector) {
|
||||
Box(
|
||||
Modifier.size(62.dp).clip(MaterialTheme.shapes.medium).background(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||
Modifier.size(56.dp).clip(RoundedCornerShape(12.dp)).background(MaterialTheme.colorScheme.surfaceContainerHigh),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (icon != null) Image(icon.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
|
||||
@@ -217,8 +327,14 @@ internal object MediaIcons {
|
||||
val Shuffle = icon("Shuffle", "M10.59,9.17L5.41,4 4,5.41l5.17,5.17 1.42,-1.41zM14.5,4l2.04,2.04L4,18.59 5.41,20 17.96,7.46 20,9.5L20,4h-5.5zM14.83,13.41l-1.41,1.41 3.13,3.13L14.5,20L20,20v-5.5l-2.04,2.04 -3.13,-3.13z")
|
||||
val Repeat = icon("Repeat", "M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z")
|
||||
val RepeatOne = icon("RepeatOne", "M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4zM13,15L13,9h-1l-2,1v1h1.5v4L13,15z")
|
||||
/** A screen with a filled right-hand panel: "put the player beside the map". */
|
||||
val DockRight = icon("DockRight", "M2,4h20v16H2zM4,6h10v12H4z", PathFillType.EvenOdd)
|
||||
/**
|
||||
* A screen split into the map and a side panel: outlined while the panel is closed ("put the
|
||||
* player beside the map"), filled in while it's open ("close it"), on the side it opens on.
|
||||
*/
|
||||
val DockRight = icon("DockRight", "M2,4h20v16H2zM4,6h10v12H4zM16,6h4v12h-4z", PathFillType.EvenOdd)
|
||||
val DockRightOpen = icon("DockRightOpen", "M2,4h20v16H2zM4,6h10v12H4z", PathFillType.EvenOdd)
|
||||
val DockLeft = icon("DockLeft", "M2,4h20v16H2zM4,6h4v12H4zM10,6h10v12H10z", PathFillType.EvenOdd)
|
||||
val DockLeftOpen = icon("DockLeftOpen", "M2,4h20v16H2zM10,6h10v12H10z", PathFillType.EvenOdd)
|
||||
val Fullscreen = icon("Fullscreen", "M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z")
|
||||
val FullscreenExit = icon("FullscreenExit", "M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z")
|
||||
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -19,6 +24,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
@@ -49,6 +55,9 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.graphics.PathFillType
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
@@ -97,18 +106,18 @@ fun MediaScreen(app: MediaApp, onExit: () -> Unit) {
|
||||
|
||||
val art = rememberArt(now)
|
||||
|
||||
// The art's colour behind everything, header included; the buttons keep their own.
|
||||
Surface(Modifier.fillMaxSize(), color = rememberArtColor(art, MaterialTheme.colorScheme.background, darkLightness = 0.15f, lightLightness = 0.9f)) {
|
||||
// The theme's background, with the cover's colours glowing faintly behind the cover on the left.
|
||||
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
|
||||
if (phase == CarMedia.Phase.READY && !searching) AlbumGlow(art, Modifier.fillMaxSize(), wide = true)
|
||||
if (phase != CarMedia.Phase.READY) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
Column(Modifier.fillMaxSize().padding(bottom = LocalNavBarInset.current)) {
|
||||
AppIdentity(app, onExit, Modifier.padding(horizontal = 16.dp, vertical = 10.dp))
|
||||
when (phase) {
|
||||
CarMedia.Phase.REJECTED -> when (rejection) {
|
||||
CarMedia.Rejection.NEEDS_NOTIFICATION_ACCESS -> Notice(
|
||||
"${app.label} keeps its library to itself.",
|
||||
"Gearslip can still control it once it has Notification access on the phone. " +
|
||||
"Allow it under Settings > Notifications > Notification access.",
|
||||
action = "Open on phone" to {
|
||||
stringResource(R.string.media_keeps_library, app.label),
|
||||
stringResource(R.string.media_needs_access),
|
||||
action = stringResource(R.string.media_open_on_phone) to {
|
||||
val app = context.applicationContext
|
||||
runCatching {
|
||||
app.startActivity(
|
||||
@@ -118,24 +127,23 @@ fun MediaScreen(app: MediaApp, onExit: () -> Unit) {
|
||||
},
|
||||
)
|
||||
CarMedia.Rejection.NEEDS_APP_RUNNING -> Notice(
|
||||
"${app.label} isn't running.",
|
||||
"${app.label} plays through Gearslip's own player, but only while it's " +
|
||||
"open on the phone. Open it once and it keeps playing here in the car.",
|
||||
action = "Open ${app.label}" to { media.launchAndConnect(app) },
|
||||
stringResource(R.string.media_not_running, app.label),
|
||||
stringResource(R.string.media_not_running_body, app.label),
|
||||
action = stringResource(R.string.media_open_app, app.label) to { media.launchAndConnect(app) },
|
||||
)
|
||||
CarMedia.Rejection.REFUSED -> Notice(
|
||||
"${app.label} would not let Gearslip browse it.",
|
||||
"Some media apps only accept Google's own host.",
|
||||
stringResource(R.string.media_refused, app.label),
|
||||
stringResource(R.string.media_refused_body),
|
||||
)
|
||||
}
|
||||
else -> Notice("Connecting to ${app.label}…", null)
|
||||
else -> Notice(stringResource(R.string.media_connecting, app.label), null)
|
||||
}
|
||||
}
|
||||
return@Surface
|
||||
}
|
||||
|
||||
if (searching) {
|
||||
SearchPanel(
|
||||
Box(Modifier.padding(bottom = LocalNavBarInset.current)) { SearchPanel(
|
||||
appLabel = app.label,
|
||||
media = media,
|
||||
canPickForYou = now.canSearch,
|
||||
@@ -153,52 +161,91 @@ fun MediaScreen(app: MediaApp, onExit: () -> Unit) {
|
||||
tab = Tab.NOW_PLAYING
|
||||
},
|
||||
onClose = { searching = false },
|
||||
)
|
||||
) }
|
||||
return@Surface
|
||||
}
|
||||
|
||||
// Two columns, split the same way on every tab so nothing jumps when switching: the app
|
||||
// and the art (or, beside a list, the compact player) on the left; the tabs across the
|
||||
// top of the right column, over whatever the tab shows.
|
||||
Row(
|
||||
Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, top = 10.dp, bottom = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
Column(Modifier.weight(0.8f).fillMaxHeight()) {
|
||||
AppIdentity(app, onExit)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
if (tab == Tab.NOW_PLAYING) {
|
||||
CoverArt(art, Modifier.weight(1f).fillMaxWidth())
|
||||
} else {
|
||||
CompactPlayer(now, media, Modifier.weight(1f).fillMaxWidth())
|
||||
// The page runs down under the see-through nav bar, so the cover's glow carries on behind
|
||||
// it; the content stops at the bar's top edge. Lists run right to it; the rest keeps a gap.
|
||||
val barTop = LocalNavBarInset.current
|
||||
val barInset = barTop + 8.dp
|
||||
val tabs: @Composable (Modifier) -> Unit = { tabModifier ->
|
||||
MediaTab(stringResource(R.string.media_now_playing), tab == Tab.NOW_PLAYING, tabModifier) { tab = Tab.NOW_PLAYING }
|
||||
MediaTab(stringResource(R.string.media_up_next), tab == Tab.QUEUE, tabModifier) { tab = Tab.QUEUE }
|
||||
MediaTab(stringResource(R.string.media_browse), tab == Tab.BROWSE, tabModifier) { tab = Tab.BROWSE }
|
||||
MediaTab(stringResource(R.string.storage_lyrics), tab == Tab.LYRICS, tabModifier) { tab = Tab.LYRICS }
|
||||
}
|
||||
val searchButton: @Composable () -> Unit = {
|
||||
if (now.canSearch || media.canSearchLibrary) {
|
||||
GsIconButton(
|
||||
Icons.Filled.Search,
|
||||
stringResource(R.string.media_search, app.label),
|
||||
{ searching = true },
|
||||
colors = GsColors(MaterialTheme.colorScheme.surfaceContainerHigh, MaterialTheme.colorScheme.onSurface),
|
||||
size = 44.dp,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
)
|
||||
}
|
||||
}
|
||||
val tabContent: @Composable (Modifier) -> Unit = { contentModifier ->
|
||||
Box(contentModifier) {
|
||||
when (tab) {
|
||||
Tab.NOW_PLAYING -> StageControls(now, media, Modifier.fillMaxSize().padding(bottom = barInset))
|
||||
Tab.BROWSE -> BrowsePanel(browse, media, app.label, Modifier.fillMaxSize().padding(bottom = barTop))
|
||||
Tab.QUEUE -> QueuePanel(queue, now, media, Modifier.fillMaxSize().padding(bottom = barTop))
|
||||
Tab.LYRICS -> LyricsPanel(now, Modifier.fillMaxSize().padding(bottom = barInset))
|
||||
}
|
||||
}
|
||||
Column(Modifier.weight(1.2f).fillMaxHeight()) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
MediaTab("Now playing", tab == Tab.NOW_PLAYING, Modifier.weight(1f)) { tab = Tab.NOW_PLAYING }
|
||||
MediaTab("Browse", tab == Tab.BROWSE, Modifier.weight(1f)) { tab = Tab.BROWSE }
|
||||
MediaTab("Up next", tab == Tab.QUEUE, Modifier.weight(1f)) { tab = Tab.QUEUE }
|
||||
MediaTab("Lyrics", tab == Tab.LYRICS, Modifier.weight(1f)) { tab = Tab.LYRICS }
|
||||
if (now.canSearch || media.canSearchLibrary) {
|
||||
GsIconButton(
|
||||
Icons.Filled.Search,
|
||||
"Search ${app.label}",
|
||||
{ searching = true },
|
||||
colors = GsColors(MaterialTheme.colorScheme.surfaceContainerHigh, MaterialTheme.colorScheme.onSurface),
|
||||
size = 44.dp,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
)
|
||||
}
|
||||
|
||||
BoxWithConstraints(Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, top = 10.dp)) {
|
||||
// A small screen, or a big one at Extra large size, has no room for two columns: the
|
||||
// app and the tabs share one bar, and a list takes the whole width. The nav bar keeps
|
||||
// the controls, so nothing is lost.
|
||||
if (maxWidth < NARROW_WIDTH || maxHeight - barInset < NARROW_HEIGHT) {
|
||||
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AppIdentity(app, onExit, Modifier.widthIn(max = 180.dp))
|
||||
Row(
|
||||
Modifier.weight(1f).horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End),
|
||||
) { tabs(Modifier) }
|
||||
searchButton()
|
||||
}
|
||||
search?.let { SearchStatus(it, app.label, media::dismissSearch) }
|
||||
if (tab == Tab.NOW_PLAYING) NowPlayingPage(now, media, Modifier.weight(1f).fillMaxWidth().padding(bottom = barInset), padded = false)
|
||||
else tabContent(Modifier.weight(1f).fillMaxWidth())
|
||||
}
|
||||
return@BoxWithConstraints
|
||||
}
|
||||
|
||||
// Two columns, split the same way on every tab so nothing jumps when switching: the app
|
||||
// and the art (or, beside a list, the compact player) on the left; the tabs across the
|
||||
// top of the right column, over whatever the tab shows.
|
||||
Row(Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(24.dp)) {
|
||||
Column(Modifier.weight(0.8f).fillMaxHeight().padding(bottom = barInset)) {
|
||||
AppIdentity(app, onExit)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
if (tab == Tab.NOW_PLAYING) {
|
||||
CoverArt(art, Modifier.weight(1f).fillMaxWidth())
|
||||
} else {
|
||||
CompactPlayer(now, media, art, Modifier.weight(1f).fillMaxWidth())
|
||||
}
|
||||
}
|
||||
search?.let { SearchStatus(it, app.label, media::dismissSearch) }
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Box(Modifier.weight(1f).fillMaxWidth()) {
|
||||
when (tab) {
|
||||
Tab.NOW_PLAYING -> NowPlayingControls(now, media, Modifier.fillMaxSize())
|
||||
Tab.BROWSE -> BrowsePanel(browse, media, app.label, Modifier.fillMaxSize())
|
||||
Tab.QUEUE -> QueuePanel(queue, now, media, Modifier.fillMaxSize())
|
||||
Tab.LYRICS -> LyricsPanel(now, Modifier.fillMaxSize())
|
||||
Column(Modifier.weight(1.2f).fillMaxHeight()) {
|
||||
// The tabs share the row evenly; a long word (a translation, a big text size)
|
||||
// shrinks to fit its tab instead.
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
tabs(Modifier.weight(1f))
|
||||
searchButton()
|
||||
}
|
||||
search?.let { SearchStatus(it, app.label, media::dismissSearch) }
|
||||
Spacer(Modifier.height(12.dp))
|
||||
tabContent(Modifier.weight(1f).fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +260,7 @@ fun MediaScreen(app: MediaApp, onExit: () -> Unit) {
|
||||
private fun AppIdentity(app: MediaApp, onExit: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val icon = remember(app) { findEntry(app.component.flattenToString())?.icon }
|
||||
Row(modifier, verticalAlignment = Alignment.CenterVertically) {
|
||||
GsIconButton(Icons.Filled.ArrowBack, "Back", onExit, size = 44.dp)
|
||||
GsIconButton(Icons.Filled.ArrowBack, stringResource(R.string.back), onExit, size = 44.dp)
|
||||
Spacer(Modifier.width(14.dp))
|
||||
icon?.let {
|
||||
Image(it, null, Modifier.size(26.dp).clip(RoundedCornerShape(7.dp)))
|
||||
@@ -273,10 +320,10 @@ private fun SearchPanel(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
GsIconButton(Icons.Filled.ArrowBack, "Back", onClose, size = ICON_BUTTON_SIZE)
|
||||
GsIconButton(Icons.Filled.ArrowBack, stringResource(R.string.back), onClose, size = ICON_BUTTON_SIZE)
|
||||
SearchField(
|
||||
query,
|
||||
"Search $appLabel",
|
||||
stringResource(R.string.media_search, appLabel),
|
||||
Modifier.weight(1f).clickable { typing = true },
|
||||
onClear = {
|
||||
query = ""
|
||||
@@ -287,12 +334,12 @@ private fun SearchPanel(
|
||||
Box(Modifier.weight(1f).fillMaxWidth().padding(horizontal = 16.dp)) {
|
||||
val entries = current?.entries
|
||||
when {
|
||||
trimmed.isEmpty() -> SearchNote("Type a song, artist, album, or playlist.")
|
||||
trimmed.isEmpty() -> SearchNote(stringResource(R.string.media_search_hint))
|
||||
unsupported && canPickForYou ->
|
||||
SearchNote("$appLabel can't list results. Press search, and $appLabel picks what to play.")
|
||||
unsupported -> SearchNote("$appLabel can't search from the car.")
|
||||
entries == null -> SearchNote("Searching $appLabel…")
|
||||
entries.isEmpty() -> SearchNote("$appLabel found nothing for \"$trimmed\".")
|
||||
SearchNote(stringResource(R.string.media_search_no_list, appLabel))
|
||||
unsupported -> SearchNote(stringResource(R.string.media_search_unsupported, appLabel))
|
||||
entries == null -> SearchNote(stringResource(R.string.media_searching, appLabel))
|
||||
entries.isEmpty() -> SearchNote(stringResource(R.string.media_search_nothing, appLabel, trimmed))
|
||||
else -> LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(bottom = 8.dp)) {
|
||||
items(entries) { entry ->
|
||||
EntryRow(entry, media) {
|
||||
@@ -350,8 +397,8 @@ private fun SearchStatus(search: CarMedia.Search, appLabel: String, onDismiss: (
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
) {
|
||||
Text(
|
||||
if (search.ignored) "$appLabel didn't play anything for \"${search.query}\"."
|
||||
else "Searching $appLabel for \"${search.query}\"…",
|
||||
if (search.ignored) stringResource(R.string.media_search_ignored, appLabel, search.query)
|
||||
else stringResource(R.string.media_searching_for, appLabel, search.query),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
@@ -376,14 +423,17 @@ private fun MediaTab(label: String, selected: Boolean, modifier: Modifier, onCli
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(minFontSize = 11.sp, maxFontSize = MaterialTheme.typography.titleSmall.fontSize),
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The song's lyrics, fetched when this tab opens. Synced lyrics follow the playback position with
|
||||
* the current line in the accent colour; unsynced ones are plain scrolling text.
|
||||
* The song's lyrics, fetched when this tab opens. Synced lyrics follow playback: the line being
|
||||
* sung sits a little above the middle in bold, lines already sung dim, and the list fades out at
|
||||
* both edges instead of cutting a line in half. Word-timed lyrics also light up each word as it's
|
||||
* sung. Unsynced ones are plain scrolling text.
|
||||
*/
|
||||
@Composable
|
||||
internal fun LyricsPanel(
|
||||
@@ -394,43 +444,89 @@ internal fun LyricsPanel(
|
||||
val context = LocalContext.current
|
||||
val lyrics by produceState<LyricsState>(LyricsState.Loading, now.title, now.artist, now.album, now.durationMs) {
|
||||
value = LyricsState.Loading
|
||||
value = Lyrics.find(context, now.title, now.artist, now.album, now.durationMs)
|
||||
?.let { LyricsState.Found(it) } ?: LyricsState.Missing
|
||||
}
|
||||
val list = rememberLazyListState()
|
||||
|
||||
// Ticks with playback so the highlighted line moves; only synced lyrics need it.
|
||||
var position by remember { mutableLongStateOf(0L) }
|
||||
LaunchedEffect(now) {
|
||||
while (true) {
|
||||
position = now.currentPosition()
|
||||
kotlinx.coroutines.delay(300)
|
||||
val found = Lyrics.find(context, now.title, now.artist, now.album, now.durationMs)
|
||||
value = found?.let { LyricsState.Found(it) } ?: LyricsState.Missing
|
||||
// Cached lyrics show at once; if a source now has better ones, they replace them.
|
||||
if (found != null) {
|
||||
Lyrics.upgrade(context, now.title, now.artist, now.album, now.durationMs, found)
|
||||
?.let { value = LyricsState.Found(it) }
|
||||
}
|
||||
}
|
||||
|
||||
when (val state = lyrics) {
|
||||
LyricsState.Loading -> Notice("Looking for lyrics…", null)
|
||||
LyricsState.Loading -> Notice(stringResource(R.string.media_looking_lyrics), null)
|
||||
LyricsState.Missing -> Notice(
|
||||
if (now.title.isEmpty()) "Nothing playing." else "No lyrics found for this track.",
|
||||
stringResource(if (now.title.isEmpty()) R.string.media_nothing_playing_dot else R.string.media_no_lyrics),
|
||||
null,
|
||||
)
|
||||
is LyricsState.Found -> {
|
||||
val lines = state.result.lines
|
||||
val current = if (state.result.synced) lines.indexOfLast { it.timeMs <= position } else -1
|
||||
LaunchedEffect(current) {
|
||||
if (current >= 0) list.animateScrollToItem((current - 1).coerceAtLeast(0))
|
||||
is LyricsState.Found -> Column(modifier) {
|
||||
if (state.result.synced) SyncedLyrics(now, state.result, lineStyle, Modifier.weight(1f).fillMaxWidth())
|
||||
else PlainLyrics(state.result, lineStyle, Modifier.weight(1f).fillMaxWidth())
|
||||
// Debug builds name the service the lyrics came from, to see which one answers.
|
||||
if (app.seb3thehacker.gearslip.BuildConfig.DEBUG) {
|
||||
val from = state.result.source.ifEmpty { "an older cache" }
|
||||
val kind = when {
|
||||
state.result.lines.any { it.words.isNotEmpty() } -> "word-timed"
|
||||
state.result.synced -> "synced"
|
||||
else -> "plain"
|
||||
}
|
||||
Text(
|
||||
"From $from · $kind",
|
||||
Modifier.padding(top = 4.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
}
|
||||
LazyColumn(modifier, state = list, verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
itemsIndexed(lines) { index, line ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SyncedLyrics(
|
||||
now: NowPlaying,
|
||||
result: LyricsResult,
|
||||
lineStyle: androidx.compose.ui.text.TextStyle,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val lines = result.lines
|
||||
val wordTimed = lines.any { it.words.isNotEmpty() }
|
||||
// Every frame while words are filling in, so a word doesn't light up in steps; otherwise a
|
||||
// few times a second is enough to move the line along.
|
||||
var position by remember { mutableLongStateOf(now.currentPosition()) }
|
||||
LaunchedEffect(now, wordTimed) {
|
||||
while (true) {
|
||||
position = now.currentPosition()
|
||||
if (wordTimed && now.state == PlaybackState.STATE_PLAYING) androidx.compose.runtime.withFrameMillis { }
|
||||
else delay(300)
|
||||
}
|
||||
}
|
||||
// Only the line changing recomposes the list; the moving position is read by the one line.
|
||||
val current by remember(lines) {
|
||||
androidx.compose.runtime.derivedStateOf { lines.indexOfLast { it.timeMs <= position } }
|
||||
}
|
||||
val list = rememberLazyListState()
|
||||
LaunchedEffect(current) {
|
||||
if (current >= 0) list.animateScrollToItem(current)
|
||||
}
|
||||
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val sung = scheme.onSurfaceVariant.copy(alpha = 0.55f)
|
||||
BoxWithConstraints(modifier.fadingEdges()) {
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize(),
|
||||
state = list,
|
||||
// The padding lets the first line start, and the last line end, near the middle.
|
||||
contentPadding = PaddingValues(top = maxHeight * 0.32f, bottom = maxHeight * 0.6f),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
itemsIndexed(lines) { index, line ->
|
||||
if (index == current) {
|
||||
CurrentLine(line, { position }, lineStyle)
|
||||
} else {
|
||||
Text(
|
||||
line.text.ifEmpty { " " },
|
||||
style = lineStyle,
|
||||
fontWeight = if (index == current) FontWeight.Bold else FontWeight.Normal,
|
||||
color = when {
|
||||
!state.result.synced -> MaterialTheme.colorScheme.onSurface
|
||||
index == current -> MaterialTheme.colorScheme.primary
|
||||
else -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||
},
|
||||
color = if (index < current) sung else scheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -438,6 +534,72 @@ internal fun LyricsPanel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The line being sung, in bold and a size up. With word timings, words already sung are in the
|
||||
* accent colour, the one being sung fills in letter by letter, and the rest wait dimmed.
|
||||
*/
|
||||
@Composable
|
||||
private fun CurrentLine(line: app.seb3thehacker.gearslip.media.LyricLine, position: () -> Long, lineStyle: androidx.compose.ui.text.TextStyle) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val style = lineStyle.copy(fontWeight = FontWeight.Bold, fontSize = lineStyle.fontSize * 1.15f)
|
||||
if (line.words.isEmpty()) {
|
||||
Text(line.text.ifEmpty { " " }, style = style, color = scheme.primary)
|
||||
return
|
||||
}
|
||||
val lit = scheme.primary
|
||||
val waiting = scheme.onSurfaceVariant.copy(alpha = 0.7f)
|
||||
val now = position()
|
||||
val text = androidx.compose.ui.text.buildAnnotatedString {
|
||||
line.words.forEachIndexed { i, word ->
|
||||
// The line's first word has no leading space to trim, the last no trailing one.
|
||||
val shown = if (i == line.words.lastIndex) word.text.trimEnd() else word.text
|
||||
val progress = when {
|
||||
now >= word.endMs -> 1f
|
||||
now <= word.startMs -> 0f
|
||||
else -> (now - word.startMs).toFloat() / (word.endMs - word.startMs).coerceAtLeast(1)
|
||||
}
|
||||
val letters = shown.trimEnd().length
|
||||
val filled = progress * letters
|
||||
shown.forEachIndexed { c, ch ->
|
||||
val amount = (filled - c).coerceIn(0f, 1f)
|
||||
withStyle(androidx.compose.ui.text.SpanStyle(color = androidx.compose.ui.graphics.lerp(waiting, lit, amount))) {
|
||||
append(ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(text, style = style)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlainLyrics(result: LyricsResult, lineStyle: androidx.compose.ui.text.TextStyle, modifier: Modifier) {
|
||||
LazyColumn(
|
||||
modifier.fadingEdges(),
|
||||
contentPadding = PaddingValues(vertical = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
items(result.lines) { line ->
|
||||
Text(line.text.ifEmpty { " " }, style = lineStyle, color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Fades the top and bottom edges out, so a line scrolling past doesn't end in a hard cut. */
|
||||
private fun Modifier.fadingEdges(): Modifier = this
|
||||
.graphicsLayer { compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen }
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
androidx.compose.ui.graphics.Brush.verticalGradient(
|
||||
0f to androidx.compose.ui.graphics.Color.Transparent,
|
||||
0.14f to androidx.compose.ui.graphics.Color.Black,
|
||||
0.86f to androidx.compose.ui.graphics.Color.Black,
|
||||
1f to androidx.compose.ui.graphics.Color.Transparent,
|
||||
),
|
||||
blendMode = androidx.compose.ui.graphics.BlendMode.DstIn,
|
||||
)
|
||||
}
|
||||
|
||||
private sealed interface LyricsState {
|
||||
data object Loading : LyricsState
|
||||
data object Missing : LyricsState
|
||||
@@ -468,29 +630,39 @@ internal fun Notice(title: String, detail: String?, action: Pair<String, () -> U
|
||||
// --- now playing -----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The now-playing view on its own, for places with no tabs around it (an app's own
|
||||
* now-playing template): the cover on the left, [NowPlayingControls] on the right.
|
||||
* The now-playing view on its own: an app's own now-playing template, or the Now playing tab on a
|
||||
* small screen. The cover on the left and [NowPlayingControls] on the right where there's room;
|
||||
* narrower, the cover sits above the controls, and only when they still fit under it.
|
||||
*/
|
||||
@Composable
|
||||
internal fun NowPlayingPage(now: NowPlaying, media: CarMedia, modifier: Modifier) {
|
||||
Row(
|
||||
modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
CoverArt(rememberArt(now), Modifier.weight(0.8f).fillMaxHeight())
|
||||
NowPlayingControls(now, media, Modifier.weight(1.2f).fillMaxHeight())
|
||||
internal fun NowPlayingPage(now: NowPlaying, media: CarMedia, modifier: Modifier, padded: Boolean = true) {
|
||||
BoxWithConstraints(if (padded) modifier.padding(horizontal = 24.dp, vertical = 8.dp) else modifier) {
|
||||
val art = rememberArt(now)
|
||||
if (maxWidth >= 520.dp && maxHeight >= 260.dp) {
|
||||
val artWidth = minOf(maxHeight, (maxWidth - 24.dp) * 0.4f)
|
||||
Row(Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(24.dp)) {
|
||||
CoverArt(art, Modifier.width(artWidth).fillMaxHeight())
|
||||
NowPlayingControls(now, media, Modifier.weight(1f).fillMaxHeight())
|
||||
}
|
||||
} else {
|
||||
val artHeight = if (maxHeight >= 400.dp) minOf(maxHeight * 0.25f, 160.dp) else 0.dp
|
||||
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (artHeight > 0.dp) CoverArt(art, Modifier.fillMaxWidth().height(artHeight))
|
||||
NowPlayingControls(now, media, Modifier.weight(1f).fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The cover, as big a square as its space allows, centred in it. */
|
||||
@Composable
|
||||
private fun CoverArt(art: android.graphics.Bitmap?, modifier: Modifier) {
|
||||
BoxWithConstraints(modifier, contentAlignment = Alignment.Center) {
|
||||
private fun CoverArt(art: android.graphics.Bitmap?, modifier: Modifier, alignment: Alignment = Alignment.Center) {
|
||||
BoxWithConstraints(modifier, contentAlignment = alignment) {
|
||||
val shape = RoundedCornerShape(22.dp)
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.large,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shadowElevation = 6.dp,
|
||||
modifier = Modifier.size(minOf(maxWidth, maxHeight)),
|
||||
shape = shape,
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
modifier = Modifier.size(minOf(maxWidth, maxHeight)).gsPanelShadow(shape),
|
||||
) {
|
||||
if (art != null) {
|
||||
Image(art.asImageBitmap(), null, Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
|
||||
@@ -504,65 +676,138 @@ private fun CoverArt(art: android.graphics.Bitmap?, modifier: Modifier) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Now playing beside the big cover: the title, artist and seek bar along the top, and the
|
||||
* controls in two rows under them, as big as the height left over allows - the transport, then
|
||||
* like, shuffle, repeat and the app's own buttons.
|
||||
*/
|
||||
@Composable
|
||||
private fun StageControls(now: NowPlaying, media: CarMedia, modifier: Modifier) {
|
||||
BoxWithConstraints(modifier) {
|
||||
// Short of height (a small screen, or Extra large size), the title steps down a size.
|
||||
val compact = maxHeight < 360.dp
|
||||
val showTimes = maxHeight >= 260.dp
|
||||
val titleRoom = maxHeight * 0.4f
|
||||
Column(Modifier.fillMaxSize().padding(start = 4.dp, end = 4.dp, top = if (compact) 6.dp else 16.dp, bottom = 4.dp)) {
|
||||
// The title gives up its height first and scrolls, so the controls keep theirs.
|
||||
Column(Modifier.heightIn(max = titleRoom).verticalScroll(rememberScrollState())) {
|
||||
Text(
|
||||
now.title.ifEmpty { stringResource(R.string.media_nothing_playing) },
|
||||
style = MaterialTheme.typography.displaySmall, fontWeight = FontWeight.Bold,
|
||||
maxLines = 2, overflow = TextOverflow.Ellipsis,
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = if (compact) 20.sp else 22.sp,
|
||||
maxFontSize = MaterialTheme.typography.displaySmall.fontSize,
|
||||
stepSize = 2.sp,
|
||||
),
|
||||
)
|
||||
Text(
|
||||
listOf(now.artist, now.album).filter { it.isNotEmpty() }.joinToString(" · "),
|
||||
style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
now.error?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error, maxLines = 2) }
|
||||
if (now.title.isEmpty() && now.error == null) {
|
||||
Text(
|
||||
stringResource(R.string.media_press_play),
|
||||
style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
PlayerProgress(now, media, Modifier.fillMaxWidth().padding(top = if (compact) 4.dp else 12.dp), showTimes = showTimes)
|
||||
// What's left goes to the buttons: play takes about half of it, the row below a third,
|
||||
// and neither grows past what a row of them fits across.
|
||||
BoxWithConstraints(Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
val gap = (maxHeight * 0.08f).coerceIn(6.dp, 20.dp)
|
||||
val play = minOf(maxHeight * 0.5f, maxWidth / 3.2f, 128.dp).coerceAtLeast(56.dp)
|
||||
val mode = minOf(maxHeight * 0.32f, maxWidth / 6.5f, 80.dp).coerceAtLeast(44.dp)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(gap), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
TransportRow(now, media, skip = play / 1.5f, playScale = 1.5f, modifier = Modifier.fillMaxWidth())
|
||||
PlayModeRow(now, media, size = mode, modifier = Modifier.fillMaxWidth(), maxOther = 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything about the track and every control for it, read top to bottom: title and artist,
|
||||
* the seek bar, the transport, then like, shuffle and repeat as big icon-only buttons.
|
||||
*/
|
||||
@Composable
|
||||
private fun NowPlayingControls(now: NowPlaying, media: CarMedia, modifier: Modifier) {
|
||||
Column(
|
||||
modifier.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp, Alignment.CenterVertically),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
now.title.ifEmpty { "Nothing playing" },
|
||||
style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold,
|
||||
maxLines = 2, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
listOf(now.artist, now.album).filter { it.isNotEmpty() }.joinToString(" - "),
|
||||
style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
now.error?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error, maxLines = 2) }
|
||||
if (now.title.isEmpty() && now.error == null) {
|
||||
BoxWithConstraints(modifier) {
|
||||
val compact = maxHeight < 360.dp || maxWidth < 420.dp
|
||||
val showTimes = maxHeight >= 260.dp
|
||||
Column(
|
||||
Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(if (compact) 8.dp else 14.dp, Alignment.CenterVertically),
|
||||
) {
|
||||
// The title gives up its height first and scrolls, so the controls never leave the screen.
|
||||
Column(Modifier.weight(1f, fill = false).verticalScroll(rememberScrollState())) {
|
||||
Text(
|
||||
"Press play to pick up where you left off, or find something in Browse.",
|
||||
style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
now.title.ifEmpty { stringResource(R.string.media_nothing_playing) },
|
||||
style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold,
|
||||
maxLines = 2, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
listOf(now.artist, now.album).filter { it.isNotEmpty() }.joinToString(" - "),
|
||||
style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
now.error?.let { Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error, maxLines = 2) }
|
||||
if (now.title.isEmpty() && now.error == null) {
|
||||
Text(
|
||||
stringResource(R.string.media_press_play),
|
||||
style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
PlayerProgress(now, media, Modifier.fillMaxWidth(), showTimes = showTimes)
|
||||
TransportRow(now, media, skip = if (compact) 48.dp else 60.dp, modifier = Modifier.fillMaxWidth())
|
||||
PlayModeRow(now, media, size = if (compact) 46.dp else 56.dp, modifier = Modifier.fillMaxWidth(), maxOther = 2)
|
||||
}
|
||||
PlayerProgress(now, media, Modifier.fillMaxWidth())
|
||||
TransportRow(now, media, skip = 60.dp, modifier = Modifier.fillMaxWidth())
|
||||
PlayModeRow(now, media, size = 56.dp, modifier = Modifier.fillMaxWidth(), maxOther = 2)
|
||||
}
|
||||
}
|
||||
|
||||
/** Below these, the media app drops to one column: see [MediaScreen]. */
|
||||
private val NARROW_WIDTH = 600.dp
|
||||
private val NARROW_HEIGHT = 300.dp
|
||||
|
||||
/**
|
||||
* The player beside the browse list, the queue and the lyrics: no art, which the list needs the
|
||||
* room for, just what's playing, the seek bar and the controls.
|
||||
* The player beside the browse list, the queue and the lyrics: the cover as big as the controls
|
||||
* leave room for, with the title and artist beside it rather than under it, so the cover gets the
|
||||
* height; then the seek bar and the controls along the bottom.
|
||||
*/
|
||||
@Composable
|
||||
private fun CompactPlayer(now: NowPlaying, media: CarMedia, modifier: Modifier) {
|
||||
Column(
|
||||
modifier.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
now.title.ifEmpty { "Nothing playing" },
|
||||
style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold,
|
||||
maxLines = 2, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
now.artist,
|
||||
style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
private fun CompactPlayer(now: NowPlaying, media: CarMedia, art: android.graphics.Bitmap?, modifier: Modifier) {
|
||||
Column(modifier) {
|
||||
BoxWithConstraints(Modifier.weight(1f).fillMaxWidth()) {
|
||||
val cover = minOf(maxHeight, maxWidth * 0.62f).coerceAtLeast(0.dp)
|
||||
Row(Modifier.fillMaxSize(), verticalAlignment = Alignment.Bottom) {
|
||||
CoverArt(art, Modifier.size(cover))
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Column(Modifier.weight(1f).padding(bottom = 4.dp)) {
|
||||
Text(
|
||||
now.title.ifEmpty { stringResource(R.string.media_nothing_playing) },
|
||||
style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold,
|
||||
maxLines = 3, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
now.artist,
|
||||
style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 2, overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
PlayerProgress(now, media, Modifier.fillMaxWidth())
|
||||
TransportRow(now, media, skip = 52.dp, modifier = Modifier.fillMaxWidth())
|
||||
Spacer(Modifier.height(6.dp))
|
||||
TransportRow(now, media, skip = 54.dp, modifier = Modifier.fillMaxWidth())
|
||||
Spacer(Modifier.height(10.dp))
|
||||
PlayModeRow(now, media, size = 48.dp, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import android.os.SystemClock
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
@@ -83,9 +85,10 @@ internal fun ReplyGlyph(modifier: Modifier = Modifier) =
|
||||
Icon(ImageVector.vectorResource(R.drawable.voice_selection_24), null, modifier)
|
||||
|
||||
/** "Alex", or "Alex · 3 new" when a burst of messages from one chat has been merged. */
|
||||
@Composable
|
||||
private fun CarNotification.heading(): String {
|
||||
val who = title.ifBlank { appLabel }
|
||||
return if (burst > 1) "$who · $burst new" else who
|
||||
return if (burst > 1) pluralStringResource(R.plurals.msg_burst, burst, who, burst) else who
|
||||
}
|
||||
|
||||
/** The card. It shows the voice reply too, wherever that was started from. */
|
||||
@@ -106,17 +109,17 @@ fun NotificationPopup(showNew: Boolean, modifier: Modifier = Modifier) {
|
||||
Text(n.heading(), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(n.text, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
GsIconButton(Icons.Filled.Close, "Dismiss", { CarNotifications.dismissPopup(n.id) }, size = 40.dp, iconSize = 20.dp)
|
||||
GsIconButton(Icons.Filled.Close, stringResource(R.string.msg_dismiss), { CarNotifications.dismissPopup(n.id) }, size = 40.dp, iconSize = 20.dp)
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
GsButton(onClick = { VoiceReply.read(n) }, tone = GsTone.Neutral, modifier = Modifier.weight(1f)) {
|
||||
ReadGlyph(Modifier.size(20.dp))
|
||||
Text("Read", Modifier.padding(start = 8.dp))
|
||||
Text(stringResource(R.string.msg_read), Modifier.padding(start = 8.dp))
|
||||
}
|
||||
if (n.reply != null) {
|
||||
GsButton(onClick = { VoiceReply.reply(n) }, modifier = Modifier.weight(1f)) {
|
||||
ReplyGlyph(Modifier.size(20.dp))
|
||||
Text("Reply", Modifier.padding(start = 8.dp))
|
||||
Text(stringResource(R.string.crumb_reply), Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,36 +194,36 @@ private fun VoiceReplyBody(s: VoiceReply.State, n: CarNotification) {
|
||||
when (s.phase) {
|
||||
VoiceReply.Phase.READING, VoiceReply.Phase.READ -> {
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) {
|
||||
Text(if (s.phase == VoiceReply.Phase.READING) "Stop" else "Close")
|
||||
Text(if (s.phase == VoiceReply.Phase.READING) stringResource(R.string.msg_stop) else stringResource(R.string.close))
|
||||
}
|
||||
if (n.reply != null) {
|
||||
GsButton(onClick = { VoiceReply.reply(n, readFirst = false) }, modifier = wide) {
|
||||
ReplyGlyph(Modifier.size(20.dp))
|
||||
Text("Reply", Modifier.padding(start = 8.dp))
|
||||
Text(stringResource(R.string.crumb_reply), Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
VoiceReply.Phase.OFFERING -> {
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text("No") }
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text(stringResource(R.string.msg_no)) }
|
||||
GsButton(onClick = { VoiceReply.reply(n, readFirst = false) }, modifier = wide) {
|
||||
ReplyGlyph(Modifier.size(20.dp))
|
||||
Text("Reply", Modifier.padding(start = 8.dp))
|
||||
Text(stringResource(R.string.crumb_reply), Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
VoiceReply.Phase.ASKING, VoiceReply.Phase.LISTENING ->
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text("Cancel") }
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text(stringResource(R.string.cancel)) }
|
||||
VoiceReply.Phase.CONFIRMING -> {
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text("Cancel") }
|
||||
GsButton(onClick = VoiceReply::sendNow, modifier = wide) { Text("Send now") }
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text(stringResource(R.string.cancel)) }
|
||||
GsButton(onClick = VoiceReply::sendNow, modifier = wide) { Text(stringResource(R.string.msg_send_now)) }
|
||||
}
|
||||
VoiceReply.Phase.ERROR -> {
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text("Close") }
|
||||
GsButton(onClick = VoiceReply::cancel, tone = GsTone.Neutral, modifier = wide) { Text(stringResource(R.string.close)) }
|
||||
if (n.reply != null) {
|
||||
GsButton(onClick = { VoiceReply.reply(n, readFirst = false) }, tone = GsTone.Tonal, modifier = wide) { Text("Try again") }
|
||||
GsButton(onClick = { VoiceReply.reply(n, readFirst = false) }, tone = GsTone.Tonal, modifier = wide) { Text(stringResource(R.string.update_try_again)) }
|
||||
GsButton(
|
||||
onClick = { VoiceReply.cancel(); navigator.notifications(replyTo = n.key) },
|
||||
tone = GsTone.Neutral, modifier = wide,
|
||||
) { Text("Type") }
|
||||
) { Text(stringResource(R.string.msg_type)) }
|
||||
}
|
||||
}
|
||||
VoiceReply.Phase.SENT, VoiceReply.Phase.IDLE -> Unit
|
||||
@@ -228,13 +231,14 @@ private fun VoiceReplyBody(s: VoiceReply.State, n: CarNotification) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun voiceHeading(s: VoiceReply.State, who: String): String = when (s.phase) {
|
||||
VoiceReply.Phase.READING, VoiceReply.Phase.READ -> who
|
||||
VoiceReply.Phase.OFFERING, VoiceReply.Phase.ASKING -> "Reply to $who"
|
||||
VoiceReply.Phase.LISTENING -> "Listening"
|
||||
VoiceReply.Phase.CONFIRMING -> "Sending to $who"
|
||||
VoiceReply.Phase.SENT -> "Sent to $who"
|
||||
VoiceReply.Phase.ERROR -> "Couldn't reply"
|
||||
VoiceReply.Phase.OFFERING, VoiceReply.Phase.ASKING -> stringResource(R.string.msg_reply_to, who)
|
||||
VoiceReply.Phase.LISTENING -> stringResource(R.string.msg_listening)
|
||||
VoiceReply.Phase.CONFIRMING -> stringResource(R.string.msg_sending_to, who)
|
||||
VoiceReply.Phase.SENT -> stringResource(R.string.msg_sent_to, who)
|
||||
VoiceReply.Phase.ERROR -> stringResource(R.string.msg_reply_failed)
|
||||
VoiceReply.Phase.IDLE -> ""
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
@@ -8,6 +11,8 @@ import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -60,14 +65,14 @@ fun MessagesScreen(packageName: String) {
|
||||
val navigator = LocalCarNavigator.current
|
||||
val context = LocalContext.current.applicationContext
|
||||
val entry = remember(packageName) { findEntry(MESSAGING_PREFIX + packageName) }
|
||||
val label = entry?.label ?: history.firstOrNull { it.packageName == packageName }?.appLabel ?: "Messages"
|
||||
val label = entry?.label ?: history.firstOrNull { it.packageName == packageName }?.appLabel ?: stringResource(R.string.msg_messages)
|
||||
val threads = history.filter { it.packageName == packageName }
|
||||
|
||||
LaunchedEffect(history) { CarNotifications.dismissPopupFrom(packageName) }
|
||||
|
||||
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Column(Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, top = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, "Back", navigator::back)
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.back), navigator::back)
|
||||
entry?.icon?.let { Image(it, contentDescription = null, modifier = Modifier.size(36.dp).clip(RoundedCornerShape(8.dp))) }
|
||||
Text(label, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
@@ -82,7 +87,7 @@ fun MessagesScreen(packageName: String) {
|
||||
|
||||
if (threads.isEmpty()) {
|
||||
Text(
|
||||
"No recent messages. New $label messages appear here as they arrive on the phone.",
|
||||
stringResource(R.string.msg_none_recent, label),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -104,6 +109,7 @@ fun MessagesScreen(packageName: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun ConversationCard(n: CarNotification, onRead: () -> Unit, onReply: () -> Unit, onMarkRead: () -> Unit) {
|
||||
var expanded by rememberSaveable(n.key) { mutableStateOf(false) }
|
||||
@@ -143,40 +149,48 @@ private fun ConversationCard(n: CarNotification, onRead: () -> Unit, onReply: ()
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
)
|
||||
if (n.replied) {
|
||||
Icon(
|
||||
Icons.Filled.Check,
|
||||
contentDescription = stringResource(R.string.notif_replied),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 8.dp).size(20.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (!expanded && lines.size > COLLAPSED_LINES) {
|
||||
Text(
|
||||
"${lines.size - COLLAPSED_LINES} earlier",
|
||||
pluralStringResource(R.plurals.msg_earlier, lines.size - COLLAPSED_LINES, lines.size - COLLAPSED_LINES),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
shown.forEach { MessageLine(it, showSender = n.isGroup) }
|
||||
// Below the messages and side by side, so the text keeps the card's whole width.
|
||||
FlowRow(
|
||||
Modifier.padding(top = 6.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
GsButton(onClick = onRead, tone = GsTone.Neutral) {
|
||||
ReadGlyph(Modifier.size(20.dp))
|
||||
Text(stringResource(R.string.msg_read), Modifier.padding(start = 8.dp))
|
||||
}
|
||||
if (n.reply != null) {
|
||||
GsButton(onClick = onReply, tone = GsTone.Tonal) {
|
||||
ReplyGlyph(Modifier.size(20.dp))
|
||||
Text(stringResource(R.string.crumb_reply), Modifier.padding(start = 8.dp))
|
||||
}
|
||||
GsButton(onClick = { quick = !quick }, tone = GsTone.Neutral) {
|
||||
Text(stringResource(if (quick) R.string.msg_hide_quick else R.string.msg_quick))
|
||||
}
|
||||
}
|
||||
if (n.markRead != null) {
|
||||
GsButton(onClick = onMarkRead, tone = GsTone.Neutral) { Text(stringResource(R.string.msg_mark_read)) }
|
||||
}
|
||||
}
|
||||
if (quick) QuickReplies(n, Modifier.padding(top = 6.dp))
|
||||
}
|
||||
Column(horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
if (n.replied) {
|
||||
Icon(Icons.Filled.Check, contentDescription = "Replied", tint = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
GsButton(onClick = onRead, tone = GsTone.Neutral) {
|
||||
ReadGlyph(Modifier.size(20.dp))
|
||||
Text("Read", Modifier.padding(start = 8.dp))
|
||||
}
|
||||
if (n.reply != null) {
|
||||
GsButton(onClick = onReply, tone = GsTone.Tonal) {
|
||||
ReplyGlyph(Modifier.size(20.dp))
|
||||
Text("Reply", Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
if (n.reply != null) {
|
||||
GsButton(onClick = { quick = !quick }, tone = GsTone.Neutral) {
|
||||
Text(if (quick) "Hide quick replies" else "Quick reply")
|
||||
}
|
||||
}
|
||||
if (n.markRead != null) {
|
||||
GsButton(onClick = onMarkRead, tone = GsTone.Neutral) { Text("Mark read") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,8 +198,8 @@ private fun ConversationCard(n: CarNotification, onRead: () -> Unit, onReply: ()
|
||||
@Composable
|
||||
private fun MessageLine(line: ChatLine, showSender: Boolean) {
|
||||
val who = when {
|
||||
line.sender == null -> "You: "
|
||||
showSender -> "${line.sender}: "
|
||||
line.sender == null -> stringResource(R.string.msg_you)
|
||||
showSender -> stringResource(R.string.msg_sender, line.sender)
|
||||
else -> ""
|
||||
}
|
||||
Text(
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -65,26 +68,32 @@ private val CarGlyph: ImageVector = ImageVector.Builder("Car", 24.dp, 24.dp, 24f
|
||||
*/
|
||||
internal class BuiltInApp(
|
||||
val id: String,
|
||||
/** English, for matching what a driver says; [labelRes] is what the screen shows. */
|
||||
val label: String,
|
||||
@param:StringRes val labelRes: Int,
|
||||
val glyph: ImageVector,
|
||||
val screen: CarScreen?,
|
||||
val open: (CarNavigator) -> Unit,
|
||||
)
|
||||
|
||||
internal object BuiltInApps {
|
||||
val Web = BuiltInApp("web", "Browser", Icons.Filled.Search, CarScreen.App("web")) { it.open("web") }
|
||||
val ScreenSharing = BuiltInApp("phone", "Screen sharing", Icons.Filled.Share, CarScreen.App("phone")) { it.open("phone") }
|
||||
val Phone = BuiltInApp("dialer", "Phone", Icons.Filled.Call, CarScreen.App("dialer")) { it.open("dialer") }
|
||||
val VehicleData = BuiltInApp("vehicle", "Vehicle data", Icons.Filled.Info, CarScreen.VehicleData) { it.vehicleData() }
|
||||
val Settings = BuiltInApp("settings", "Settings", Icons.Filled.Settings, CarScreen.Settings) { it.settings() }
|
||||
val Web = BuiltInApp("web", "Browser", R.string.app_browser, Icons.Filled.Search, CarScreen.App("web")) { it.open("web") }
|
||||
val ScreenSharing = BuiltInApp("phone", "Screen sharing", R.string.app_screen_sharing, Icons.Filled.Share, CarScreen.App("phone")) { it.open("phone") }
|
||||
val Phone = BuiltInApp("dialer", "Phone", R.string.carset_phone, Icons.Filled.Call, CarScreen.App("dialer")) { it.open("dialer") }
|
||||
val VehicleData = BuiltInApp("vehicle", "Vehicle data", R.string.car_vehicle_data, Icons.Filled.Info, CarScreen.VehicleData) { it.vehicleData() }
|
||||
val Settings = BuiltInApp("settings", "Settings", R.string.settings_title, Icons.Filled.Settings, CarScreen.Settings) { it.settings() }
|
||||
|
||||
/** Hands the screen back to the car's own interface, like Android Auto's Exit. */
|
||||
val Exit = BuiltInApp("exit", "Exit", CarGlyph, screen = null) { CarFocus.exitToCar() }
|
||||
val Exit = BuiltInApp("exit", "Exit", R.string.app_exit, CarGlyph, screen = null) { CarFocus.exitToCar() }
|
||||
|
||||
val all = listOf(Web, ScreenSharing, Phone, VehicleData, Settings, Exit)
|
||||
val entries: List<Entry> = all.map { Entry(it.label, icon = null, builtIn = it) }
|
||||
}
|
||||
|
||||
/** The name a driver sees: translated for Gearslip's own apps, the app's own name otherwise. */
|
||||
@Composable
|
||||
internal fun Entry.shownLabel(): String = builtIn?.let { stringResource(it.labelRes) } ?: label
|
||||
|
||||
/** Vehicle data is experimental: raw, unverified sensor labels (see [CarSensors]). */
|
||||
@Composable
|
||||
internal fun rememberVehicleDataShown(): Boolean {
|
||||
@@ -215,7 +224,7 @@ internal fun AppMenu(navigator: CarNavigator, running: Map<String, RunningSlot>)
|
||||
entry.icon?.let { Image(it, null, Modifier.size(44.dp).clip(RoundedCornerShape(10.dp))) }
|
||||
entry.builtIn?.let { Icon(it.glyph, null, Modifier.size(36.dp)) }
|
||||
Text(
|
||||
entry.label,
|
||||
entry.shownLabel(),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.padding(start = 14.dp),
|
||||
@@ -232,9 +241,9 @@ internal fun AppMenu(navigator: CarNavigator, running: Map<String, RunningSlot>)
|
||||
) {
|
||||
Text(
|
||||
when {
|
||||
isPinned -> "Unpin from nav bar"
|
||||
barFull -> "Nav bar is full (${CarSettings.MAX_PINNED} pinned)"
|
||||
else -> "Pin to nav bar"
|
||||
isPinned -> stringResource(R.string.navapps_unpin)
|
||||
barFull -> stringResource(R.string.navapps_full, CarSettings.MAX_PINNED)
|
||||
else -> stringResource(R.string.navapps_pin)
|
||||
},
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
@@ -248,13 +257,13 @@ internal fun AppMenu(navigator: CarNavigator, running: Map<String, RunningSlot>)
|
||||
},
|
||||
tone = GsTone.Danger,
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
) { Text("Close app", style = MaterialTheme.typography.titleMedium) }
|
||||
) { Text(stringResource(R.string.navapps_close), style = MaterialTheme.typography.titleMedium) }
|
||||
}
|
||||
GsButton(
|
||||
onClick = navigator::dismissAppMenu,
|
||||
tone = GsTone.Neutral,
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
) { Text("Cancel", style = MaterialTheme.typography.titleMedium) }
|
||||
) { Text(stringResource(R.string.cancel), style = MaterialTheme.typography.titleMedium) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,19 +290,19 @@ private fun MoveButtons(id: String) {
|
||||
tone = GsTone.Tonal,
|
||||
enabled = index > 0,
|
||||
modifier = Modifier.weight(1f).height(56.dp),
|
||||
) { Text("Move left", style = MaterialTheme.typography.titleMedium) }
|
||||
) { Text(stringResource(R.string.navapps_move_left), style = MaterialTheme.typography.titleMedium) }
|
||||
GsButton(
|
||||
onClick = { move(1) },
|
||||
tone = GsTone.Tonal,
|
||||
enabled = index < order.lastIndex,
|
||||
modifier = Modifier.weight(1f).height(56.dp),
|
||||
) { Text("Move right", style = MaterialTheme.typography.titleMedium) }
|
||||
) { Text(stringResource(R.string.navapps_move_right), style = MaterialTheme.typography.titleMedium) }
|
||||
}
|
||||
if (custom.isNotEmpty()) {
|
||||
GsButton(
|
||||
onClick = { CarSettings.setAppOrder(emptyList()) },
|
||||
tone = GsTone.Neutral,
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
) { Text("Reset app order", style = MaterialTheme.typography.titleMedium) }
|
||||
) { Text(stringResource(R.string.navapps_reset), style = MaterialTheme.typography.titleMedium) }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
@@ -79,12 +81,12 @@ fun NotificationsSection() {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
"Notifications",
|
||||
stringResource(R.string.notif_title),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (history.isNotEmpty()) TextButton(onClick = CarNotifications::clear) { Text("Clear all") }
|
||||
if (history.isNotEmpty()) TextButton(onClick = CarNotifications::clear) { Text(stringResource(R.string.notif_clear_all)) }
|
||||
}
|
||||
|
||||
if (!listening) {
|
||||
@@ -97,7 +99,7 @@ fun NotificationsSection() {
|
||||
|
||||
if (history.isEmpty()) {
|
||||
Text(
|
||||
"Nothing yet. New notifications from the phone will show up here.",
|
||||
stringResource(R.string.notif_empty),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -124,11 +126,11 @@ internal fun AccessBanner(onOpen: () -> Unit) {
|
||||
) {
|
||||
Icon(Icons.Filled.Warning, contentDescription = null)
|
||||
Text(
|
||||
"Notifications are off. Allow Gearslip under Settings > Notifications > Notification access on the phone.",
|
||||
stringResource(R.string.notif_off),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
GsButton(onClick = onOpen) { Text("Open") }
|
||||
GsButton(onClick = onOpen) { Text(stringResource(R.string.guide_open)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,10 +162,10 @@ private fun NotificationRow(n: CarNotification, onReply: (CarNotification) -> Un
|
||||
}
|
||||
}
|
||||
when {
|
||||
n.replied -> Icon(Icons.Filled.Check, contentDescription = "Replied", tint = MaterialTheme.colorScheme.primary)
|
||||
n.replied -> Icon(Icons.Filled.Check, contentDescription = stringResource(R.string.notif_replied), tint = MaterialTheme.colorScheme.primary)
|
||||
n.reply != null -> GsButton(onClick = { VoiceReply.reply(n) }, tone = GsTone.Tonal) {
|
||||
ReplyGlyph(Modifier.size(20.dp))
|
||||
Text("Reply", Modifier.padding(start = 8.dp))
|
||||
Text(stringResource(R.string.crumb_reply), Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,14 +200,14 @@ private fun ReplyPane(n: CarNotification, onDone: () -> Unit) {
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, "Back", onDone)
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.back), onDone)
|
||||
Surface(
|
||||
shape = RoundedCornerShape(24.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
Text(
|
||||
if (text.isEmpty()) "Reply to ${n.title.ifBlank { n.appLabel }}" else "$text|",
|
||||
if (text.isEmpty()) stringResource(R.string.msg_reply_to, n.title.ifBlank { n.appLabel }) else "$text|",
|
||||
color = if (text.isEmpty()) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -52,8 +54,8 @@ fun PhoneAppsScreen() {
|
||||
value = PhoneApps.cached() ?: withContext(Dispatchers.IO) { PhoneApps.installed(context).also { PhoneApps.warm(context) } }
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxSize().padding(horizontal = 20.dp, vertical = 12.dp)) {
|
||||
ScreenHeader("Screen sharing")
|
||||
Column(Modifier.fillMaxSize().padding(start = 20.dp, end = 20.dp, top = 12.dp)) {
|
||||
ScreenHeader(stringResource(R.string.app_screen_sharing))
|
||||
Spacer(Modifier.height(12.dp))
|
||||
if (!TouchRelayService.enabled) {
|
||||
TouchRelayWarning()
|
||||
@@ -63,7 +65,7 @@ fun PhoneAppsScreen() {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = 108.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 8.dp),
|
||||
contentPadding = PaddingValues(bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
@@ -94,11 +96,11 @@ private fun TouchRelayWarning() {
|
||||
) {
|
||||
Icon(Icons.Filled.Warning, contentDescription = null)
|
||||
Text(
|
||||
"Touch is off. Enable Gearslip under Settings > Accessibility on the phone.",
|
||||
stringResource(R.string.phoneapps_touch_off),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
GsButton(onClick = { PhoneApps.openAccessibilitySettings(context) }) { Text("Open") }
|
||||
GsButton(onClick = { PhoneApps.openAccessibilitySettings(context) }) { Text(stringResource(R.string.guide_open)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
@@ -12,6 +14,7 @@ import app.seb3thehacker.gearslip.GearslipLog
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -136,13 +139,13 @@ fun PhoneDialerScreen() {
|
||||
var hasPermissions by remember { mutableStateOf(hasCallPermissions(context)) }
|
||||
var tab by remember { mutableStateOf(TAB_CONTACTS) }
|
||||
|
||||
Column(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Column(Modifier.fillMaxSize().padding(start = 16.dp, end = 16.dp, top = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
// The tabs share the title's row, so the list and keypad get the height back.
|
||||
ScreenHeader("Phone") {
|
||||
ScreenHeader(stringResource(R.string.carset_phone)) {
|
||||
if (hasPermissions) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
DialerTab("Contacts", tab == TAB_CONTACTS, Modifier.width(140.dp)) { tab = TAB_CONTACTS }
|
||||
DialerTab("Keypad", tab == TAB_KEYPAD, Modifier.width(140.dp)) { tab = TAB_KEYPAD }
|
||||
DialerTab(stringResource(R.string.dialer_contacts), tab == TAB_CONTACTS, Modifier.width(140.dp)) { tab = TAB_CONTACTS }
|
||||
DialerTab(stringResource(R.string.dialer_keypad), tab == TAB_KEYPAD, Modifier.width(140.dp)) { tab = TAB_KEYPAD }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +156,7 @@ fun PhoneDialerScreen() {
|
||||
}
|
||||
|
||||
Box(Modifier.weight(1f).fillMaxWidth()) {
|
||||
if (tab == TAB_CONTACTS) ContactsList(context) else Keypad(context)
|
||||
if (tab == TAB_CONTACTS) ContactsList(context) else Box(Modifier.padding(bottom = 16.dp)) { Keypad(context) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,14 +171,13 @@ private fun MissingPermission(context: Context, onGranted: () -> Unit) {
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(
|
||||
"Gearslip needs the Phone and Contacts permissions to dial from here. Allow them on " +
|
||||
"the phone when you're parked.",
|
||||
stringResource(R.string.dialer_needs_permission),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
GsButton(tone = GsTone.Tonal, onClick = {
|
||||
app.seb3thehacker.gearslip.PermissionActivity.ask(context, CALL_PERMISSIONS)
|
||||
}) { Text("Ask on the phone") }
|
||||
}) { Text(stringResource(R.string.dialer_ask)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +213,7 @@ private fun Keypad(context: Context) {
|
||||
|
||||
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(
|
||||
number.ifEmpty { "Enter a number" },
|
||||
number.ifEmpty { stringResource(R.string.dialer_enter_number) },
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = if (number.isEmpty()) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
@@ -240,7 +242,7 @@ private fun Keypad(context: Context) {
|
||||
colors = gsColors(GsTone.Primary),
|
||||
enabled = number.isNotEmpty(),
|
||||
modifier = Modifier.weight(1f).fillMaxWidth(),
|
||||
) { Icon(Icons.Filled.Call, "Call") }
|
||||
) { Icon(Icons.Filled.Call, stringResource(R.string.dialer_call)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,16 +284,16 @@ private fun ContactsList(context: Context) {
|
||||
Box(Modifier.weight(1f).fillMaxWidth()) {
|
||||
when {
|
||||
contacts.isEmpty() -> Text(
|
||||
"No contacts with a phone number found.",
|
||||
stringResource(R.string.dialer_no_contacts),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
filtered.isEmpty() -> Text(
|
||||
"No contacts match \"$query\".",
|
||||
stringResource(R.string.dialer_no_match, query),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
else -> LazyColumn(Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
else -> LazyColumn(Modifier.fillMaxSize(), contentPadding = PaddingValues(bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(filtered) { contact ->
|
||||
ContactRow(contact) { pendingCall = contact }
|
||||
}
|
||||
@@ -344,14 +346,14 @@ private fun ConfirmCallOverlay(contact: Contact, onConfirm: () -> Unit, onDismis
|
||||
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text("Call ${contact.name}?", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(R.string.dialer_call_who, contact.name), style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.Bold)
|
||||
Text(contact.number, style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
GsButton(onClick = onDismiss, tone = GsTone.Outline, modifier = Modifier.weight(1f).height(56.dp)) {
|
||||
Text("Cancel", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.cancel), style = MaterialTheme.typography.titleMedium)
|
||||
}
|
||||
GsButton(onClick = onConfirm, modifier = Modifier.weight(1f).height(56.dp)) {
|
||||
Text("Call", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.dialer_call), style = MaterialTheme.typography.titleMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,7 +370,7 @@ private fun ContactSearchField(query: String, active: Boolean, onToggle: () -> U
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
query.ifEmpty { "Search contacts" },
|
||||
query.ifEmpty { stringResource(R.string.dialer_search) },
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = if (query.isEmpty()) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
@@ -406,7 +408,7 @@ private fun ContactRow(contact: Contact, onClick: () -> Unit) {
|
||||
Text(contact.name, style = MaterialTheme.typography.titleMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(contact.number, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
Icon(Icons.Filled.Call, contentDescription = "Call", tint = MaterialTheme.colorScheme.primary)
|
||||
Icon(Icons.Filled.Call, contentDescription = stringResource(R.string.dialer_call), tint = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.media.session.PlaybackState
|
||||
import android.support.v4.media.session.PlaybackStateCompat
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -94,23 +96,23 @@ internal fun formatDuration(ms: Long): String {
|
||||
return if (hours > 0) "%d:%02d:%02d".format(hours, minutes, seconds) else "%d:%02d".format(minutes, seconds)
|
||||
}
|
||||
|
||||
/** Previous, play/pause, next. [skip] sizes the outer two; play is a step bigger. */
|
||||
/** Previous, play/pause, next. [skip] sizes the outer two; play is [playScale] times bigger. */
|
||||
@Composable
|
||||
internal fun TransportRow(now: NowPlaying, media: CarMedia, skip: Dp, modifier: Modifier = Modifier) {
|
||||
val play = skip * 1.25f
|
||||
internal fun TransportRow(now: NowPlaying, media: CarMedia, skip: Dp, modifier: Modifier = Modifier, playScale: Float = 1.25f) {
|
||||
val play = skip * playScale
|
||||
Row(modifier, horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically) {
|
||||
GsIconButton(
|
||||
MediaIcons.Previous, "Previous", media::previous,
|
||||
MediaIcons.Previous, stringResource(R.string.player_previous), media::previous,
|
||||
enabled = now.canDo(PlaybackState.ACTION_SKIP_TO_PREVIOUS), size = skip, iconSize = skip * 0.58f, shape = CircleShape,
|
||||
)
|
||||
GsIconButton(
|
||||
if (now.playing) MediaIcons.Pause else Icons.Filled.PlayArrow,
|
||||
if (now.playing) "Pause" else "Play",
|
||||
stringResource(if (now.playing) R.string.player_pause else R.string.media_play),
|
||||
media::togglePlay,
|
||||
tone = GsTone.Primary, size = play, iconSize = play * 0.58f, shape = CircleShape,
|
||||
)
|
||||
GsIconButton(
|
||||
MediaIcons.Next, "Next", media::next,
|
||||
MediaIcons.Next, stringResource(R.string.player_next), media::next,
|
||||
enabled = now.canDo(PlaybackState.ACTION_SKIP_TO_NEXT), size = skip, iconSize = skip * 0.58f, shape = CircleShape,
|
||||
)
|
||||
}
|
||||
@@ -147,7 +149,7 @@ internal fun PlayModeRow(
|
||||
} else {
|
||||
GsIconButton(
|
||||
if (liked == true) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
|
||||
if (liked == true) "Unlike" else "Like",
|
||||
stringResource(if (liked == true) R.string.player_unlike else R.string.player_like),
|
||||
media::toggleLike,
|
||||
enabled = liked != null,
|
||||
tone = if (liked == true) GsTone.Primary else GsTone.Neutral,
|
||||
@@ -159,7 +161,7 @@ internal fun PlayModeRow(
|
||||
CustomActionButton(shuffleAction, media, size, toggle = true)
|
||||
} else {
|
||||
GsIconButton(
|
||||
MediaIcons.Shuffle, if (shuffleOn) "Shuffle off" else "Shuffle on",
|
||||
MediaIcons.Shuffle, stringResource(if (shuffleOn) R.string.player_shuffle_off else R.string.player_shuffle_on),
|
||||
media::toggleShuffle,
|
||||
enabled = shuffleKnown,
|
||||
tone = if (shuffleOn) GsTone.Primary else GsTone.Neutral,
|
||||
@@ -172,7 +174,7 @@ internal fun PlayModeRow(
|
||||
} else {
|
||||
GsIconButton(
|
||||
if (modes.repeat == PlaybackStateCompat.REPEAT_MODE_ONE) MediaIcons.RepeatOne else MediaIcons.Repeat,
|
||||
"Repeat",
|
||||
stringResource(R.string.player_repeat),
|
||||
media::cycleRepeat,
|
||||
enabled = repeatKnown,
|
||||
tone = if (repeatOn) GsTone.Primary else GsTone.Neutral,
|
||||
@@ -229,26 +231,48 @@ internal fun rememberArt(now: NowPlaying): android.graphics.Bitmap? {
|
||||
}
|
||||
|
||||
/**
|
||||
* A background taken from the album art's main colour, in place of [fallback]. Only the hue and a
|
||||
* capped amount of its saturation come from the art; the lightness stays close to the theme's
|
||||
* own ([darkLightness] or [lightLightness]), so text drawn in the theme's colours reads the same
|
||||
* on any cover. Art with no real colour in it keeps [fallback]. Fades between songs rather than
|
||||
* jumping.
|
||||
* Three soft pools of the cover's colours: top left, top right and the middle, read off a 3x3
|
||||
* shrink of it. Still, and redrawn only while a new song's colours fade in, so it costs the video
|
||||
* stream nothing between songs. Fainter on a light card, where colour shows more. [wide] is for
|
||||
* a whole screen: the pools gather on its left, behind the cover, and reach less far across it.
|
||||
*/
|
||||
@Composable
|
||||
internal fun rememberArtColor(
|
||||
art: android.graphics.Bitmap?,
|
||||
fallback: Color,
|
||||
darkLightness: Float = 0.2f,
|
||||
lightLightness: Float = 0.86f,
|
||||
): Color {
|
||||
val dark = fallback.luminance() < 0.5f
|
||||
val tint by produceState<ArtColor.Tint?>(null, art) {
|
||||
value = art?.let { withContext(Dispatchers.Default) { runCatching { ArtColor.of(it) }.getOrNull() } }
|
||||
internal fun AlbumGlow(art: android.graphics.Bitmap?, modifier: Modifier, wide: Boolean = false) {
|
||||
val spots by produceState<List<Color>>(emptyList(), art) {
|
||||
value = art?.let { kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.Default) { glowColours(it) } }.orEmpty()
|
||||
}
|
||||
val strength = if (MaterialTheme.colorScheme.surface.luminance() < 0.5f) 0.30f else 0.18f
|
||||
val fade = tween<Color>(600)
|
||||
val first by androidx.compose.animation.animateColorAsState(spots.getOrNull(0)?.copy(alpha = strength) ?: Color.Transparent, fade, label = "glow")
|
||||
val second by androidx.compose.animation.animateColorAsState(spots.getOrNull(1)?.copy(alpha = strength) ?: Color.Transparent, fade, label = "glow")
|
||||
val third by androidx.compose.animation.animateColorAsState(spots.getOrNull(2)?.copy(alpha = strength * 0.8f) ?: Color.Transparent, fade, label = "glow")
|
||||
androidx.compose.foundation.Canvas(modifier) {
|
||||
fun pool(colour: Color, x: Float, y: Float, reach: Float) {
|
||||
if (colour.alpha <= 0f) return
|
||||
drawRect(
|
||||
androidx.compose.ui.graphics.Brush.radialGradient(
|
||||
listOf(colour, Color.Transparent),
|
||||
center = androidx.compose.ui.geometry.Offset(size.width * x, size.height * y),
|
||||
radius = size.width * reach,
|
||||
),
|
||||
)
|
||||
}
|
||||
if (wide) {
|
||||
pool(third, 0.22f, 0.8f, 0.35f)
|
||||
pool(first, 0.1f, 0.25f, 0.42f)
|
||||
pool(second, 0.3f, 0.45f, 0.38f)
|
||||
} else {
|
||||
pool(third, 0.5f, 0.5f, 0.85f)
|
||||
pool(first, 0.2f, 0.1f, 0.9f)
|
||||
pool(second, 0.85f, 0.22f, 0.8f)
|
||||
}
|
||||
}
|
||||
val target = tint?.let {
|
||||
if (dark) Color.hsl(it.hue, it.saturation.coerceIn(0.3f, 0.5f), darkLightness)
|
||||
else Color.hsl(it.hue, it.saturation.coerceIn(0.35f, 0.55f), lightLightness)
|
||||
} ?: fallback
|
||||
return animateColorAsState(target, tween(600), label = "art colour").value
|
||||
}
|
||||
|
||||
/** The cover's colours at its top left, top right and middle. */
|
||||
private fun glowColours(art: android.graphics.Bitmap): List<Color> {
|
||||
// A hardware bitmap can't be scaled or read on the CPU, so copy it out first.
|
||||
val readable = if (art.config == android.graphics.Bitmap.Config.HARDWARE) art.copy(android.graphics.Bitmap.Config.ARGB_8888, false) else art
|
||||
val small = android.graphics.Bitmap.createScaledBitmap(readable ?: return emptyList(), 3, 3, true)
|
||||
return listOf(small.getPixel(0, 0), small.getPixel(2, 0), small.getPixel(1, 1)).map { Color(it).copy(alpha = 1f) }
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import app.seb3thehacker.gearslip.AppSettings
|
||||
import app.seb3thehacker.gearslip.GearslipLog
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
/** One saved place. Home and Work keep a blank [label]; their names come from strings.xml. */
|
||||
data class SavedPlace(val label: String = "", val address: String = "")
|
||||
|
||||
/**
|
||||
* Up to [COUNT] places a driver can name by voice in any map app: slot [HOME], slot [WORK],
|
||||
* then three they name themselves. "Navigate home" sends the saved address as the search.
|
||||
*/
|
||||
object SavedPlaces {
|
||||
|
||||
const val COUNT = 5
|
||||
const val HOME = 0
|
||||
const val WORK = 1
|
||||
|
||||
private const val KEY = "saved_places"
|
||||
|
||||
private val HOME_WORDS = setOf("home", "house", "place", "home address", "my home", "my house", "my place")
|
||||
private val WORK_WORDS = setOf("work", "office", "job", "work address", "my work", "my office", "my job", "the office")
|
||||
|
||||
fun all(context: Context): List<SavedPlace> {
|
||||
val saved = runCatching {
|
||||
val array = JSONArray(AppSettings.getString(context, KEY, "[]"))
|
||||
List(array.length()) { i ->
|
||||
array.getJSONObject(i).let { SavedPlace(it.optString("label"), it.optString("address")) }
|
||||
}
|
||||
}.getOrDefault(emptyList())
|
||||
return List(COUNT) { saved.getOrNull(it) ?: SavedPlace() }
|
||||
}
|
||||
|
||||
fun set(context: Context, index: Int, place: SavedPlace) {
|
||||
val next = all(context).toMutableList().also { it[index] = place }
|
||||
val array = JSONArray()
|
||||
next.forEach { array.put(JSONObject().put("label", it.label).put("address", it.address)) }
|
||||
AppSettings.putString(context, KEY, array.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* The slot [spoken] names, if any: "home", "my house", "work", or a custom label. Returns the
|
||||
* slot even when its address is blank, so the assistant can say it isn't set yet.
|
||||
*/
|
||||
fun match(context: Context, spoken: String): Pair<Int, SavedPlace>? {
|
||||
val said = spoken.lowercase().trim()
|
||||
val places = all(context)
|
||||
if (said in HOME_WORDS) return HOME to places[HOME]
|
||||
if (said in WORK_WORDS) return WORK to places[WORK]
|
||||
val squashed = squash(said.removePrefix("my ").removePrefix("the "))
|
||||
if (squashed.isEmpty()) return null
|
||||
places.forEachIndexed { i, place ->
|
||||
if (i > WORK && place.label.isNotBlank() && squash(place.label) == squashed) return i to place
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun squash(s: String) = s.lowercase().filter { it.isLetterOrDigit() }
|
||||
|
||||
fun hasLocationPermission(context: Context) =
|
||||
context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
|
||||
context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
/** Where the phone is now, as a street address, or as coordinates when there's no geocoder. Null if unknown. */
|
||||
suspend fun here(context: Context): String? = withContext(Dispatchers.IO) {
|
||||
val location = locate(context) ?: return@withContext null
|
||||
val address = runCatching {
|
||||
if (!Geocoder.isPresent()) return@runCatching null
|
||||
@Suppress("DEPRECATION")
|
||||
Geocoder(context, Locale.getDefault()).getFromLocation(location.latitude, location.longitude, 1)
|
||||
?.firstOrNull()?.getAddressLine(0)
|
||||
}.getOrNull()
|
||||
address ?: "%.6f,%.6f".format(Locale.US, location.latitude, location.longitude)
|
||||
}
|
||||
|
||||
private suspend fun locate(context: Context): Location? {
|
||||
if (!hasLocationPermission(context)) return null
|
||||
val manager = context.getSystemService(LocationManager::class.java) ?: return null
|
||||
val provider = listOf(LocationManager.FUSED_PROVIDER, LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER)
|
||||
.firstOrNull { it in manager.allProviders && manager.isProviderEnabled(it) }
|
||||
// A fresh fix: saving the spot you're standing on wants better than an hour-old one.
|
||||
val fresh = provider?.let {
|
||||
withTimeoutOrNull(FIX_TIMEOUT_MS) {
|
||||
suspendCancellableCoroutine { done ->
|
||||
runCatching {
|
||||
manager.getCurrentLocation(it, null, context.mainExecutor) { fix -> done.resume(fix) }
|
||||
}.onFailure { e ->
|
||||
GearslipLog.w("places: fix failed: ${e.message}")
|
||||
done.resume(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fresh ?: manager.allProviders
|
||||
.mapNotNull { runCatching { manager.getLastKnownLocation(it) }.getOrNull() }
|
||||
.maxByOrNull { it.time }
|
||||
}
|
||||
|
||||
private const val FIX_TIMEOUT_MS = 15_000L
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
@@ -29,7 +31,7 @@ fun ScreenHeader(title: String, modifier: Modifier = Modifier, trailing: @Compos
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, "Back", navigator::back)
|
||||
GsIconButton(Icons.AutoMirrored.Filled.ArrowBack, stringResource(R.string.back), navigator::back)
|
||||
Text(
|
||||
title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import android.graphics.Rect
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
@@ -99,11 +101,12 @@ fun TemplateChrome(template: Template?, modifier: Modifier = Modifier) {
|
||||
* A template that isn't map-backed covers the surface completely, and reporting an empty
|
||||
* rectangle is how the app is told to stop bothering.
|
||||
*/
|
||||
fun visibleAreaFor(template: Template?, width: Int, height: Int, measured: Rect? = null): Rect = when (template) {
|
||||
fun visibleAreaFor(template: Template?, width: Int, height: Int, measured: Rect? = null, cardsAtEnd: Boolean = false): Rect = when (template) {
|
||||
is NavigationTemplate -> measured ?: Rect(0, 0, width, height)
|
||||
is MapWithContentTemplate, is MapTemplate, is PlaceListNavigationTemplate,
|
||||
is RoutePreviewNavigationTemplate, is PlaceListMapTemplate,
|
||||
-> measured ?: Rect((width * CARD_COLUMN_FRACTION).toInt(), 0, width, height)
|
||||
-> measured ?: if (cardsAtEnd) Rect(0, 0, (width * (1 - CARD_COLUMN_FRACTION)).toInt(), height)
|
||||
else Rect((width * CARD_COLUMN_FRACTION).toInt(), 0, width, height)
|
||||
null -> Rect(0, 0, width, height)
|
||||
else -> Rect(0, 0, 0, 0)
|
||||
}
|
||||
@@ -152,6 +155,24 @@ internal class ChromeCover {
|
||||
|
||||
internal val LocalChromeCover = compositionLocalOf<ChromeCover?> { null }
|
||||
|
||||
/**
|
||||
* The app's cards go to the far edge of the map and its buttons to the near one, set while the
|
||||
* player is docked on the left: the player, the map and the cards each keep their own space
|
||||
* instead of the cards crowding up against the player.
|
||||
*/
|
||||
internal val LocalCardsAtEnd = compositionLocalOf { false }
|
||||
|
||||
/**
|
||||
* How much of the map's width the docked player floats over, on the side opposite the cards. The
|
||||
* map itself runs underneath at full size; the app is told this part is covered, and the chrome
|
||||
* keeps clear of it.
|
||||
*/
|
||||
internal val LocalSidePanelInset = compositionLocalOf { 0.dp }
|
||||
|
||||
/** The edge the app's cards cover, and the one its map buttons do. */
|
||||
internal val cardEdge @Composable get() = if (LocalCardsAtEnd.current) CoverEdge.END else CoverEdge.START
|
||||
internal val buttonEdge @Composable get() = if (LocalCardsAtEnd.current) CoverEdge.START else CoverEdge.END
|
||||
|
||||
/**
|
||||
* The floating nav bar, as far as the app's map is concerned. Its full [height] at once rather
|
||||
* than following the slide, so the app is told once instead of every frame.
|
||||
@@ -196,12 +217,15 @@ private fun NavigationBars(
|
||||
) {
|
||||
val connection = LocalCarAppConnection.current
|
||||
val pan by connection.panMode.collectAsState()
|
||||
val atEnd = LocalCardsAtEnd.current
|
||||
val cardEdge = cardEdge
|
||||
val buttonEdge = buttonEdge
|
||||
androidx.compose.foundation.layout.BoxWithConstraints(modifier.fillMaxSize()) {
|
||||
val column = cardColumnWidth(maxWidth)
|
||||
Row(Modifier.fillMaxSize()) {
|
||||
val cards = @Composable {
|
||||
if (routing != null || estimate != null) {
|
||||
Column(
|
||||
Modifier.width(column).fillMaxHeight().covers("nav-cards", CoverEdge.START).padding(12.dp),
|
||||
Modifier.width(column).fillMaxHeight().covers("nav-cards", cardEdge).padding(12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
routing?.let { RoutingCard(it, Modifier.fillMaxWidth()) }
|
||||
@@ -209,14 +233,21 @@ private fun NavigationBars(
|
||||
estimate?.let { TravelEstimateCard(it, Modifier.fillMaxWidth()) }
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
}
|
||||
val buttons = @Composable {
|
||||
RightEdgeControls(
|
||||
if (pan) null else template.actionStrip,
|
||||
template.mapActionStrip,
|
||||
Modifier.padding(12.dp),
|
||||
mapActionsModifier = Modifier.covers("nav-buttons", CoverEdge.END),
|
||||
mapActionsModifier = Modifier.covers("nav-buttons", buttonEdge),
|
||||
alignment = if (atEnd) Alignment.Start else Alignment.End,
|
||||
)
|
||||
}
|
||||
Row(Modifier.fillMaxSize()) {
|
||||
if (atEnd) buttons() else cards()
|
||||
Spacer(Modifier.weight(1f))
|
||||
if (atEnd) cards() else buttons()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +259,7 @@ private fun NavigationBars(
|
||||
private fun RoutingCard(info: RoutingInfo, modifier: Modifier = Modifier) {
|
||||
if (info.isLoading) {
|
||||
ChromeSurface(modifier) {
|
||||
Text("Finding a route…", Modifier.padding(horizontal = 18.dp, vertical = 16.dp), style = ChromeType.title)
|
||||
Text(stringResource(R.string.nav_finding_route), Modifier.padding(horizontal = 18.dp, vertical = 16.dp), style = ChromeType.title)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -283,7 +314,7 @@ private fun RoutingCard(info: RoutingInfo, modifier: Modifier = Modifier) {
|
||||
Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text("Then", style = ChromeType.label, color = scheme.onSurfaceVariant)
|
||||
Text(stringResource(R.string.nav_then), style = ChromeType.label, color = scheme.onSurfaceVariant)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
CarGlyph(next.maneuver?.icon, Modifier.size(28.dp))
|
||||
val nextText = next.cue.text().ifEmpty { next.road.text() }
|
||||
@@ -345,7 +376,7 @@ private fun LaneTile(lane: Lane) {
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.KeyboardArrowUp,
|
||||
contentDescription = if (recommended) "Recommended lane" else "Lane",
|
||||
contentDescription = stringResource(if (recommended) R.string.nav_lane_recommended else R.string.nav_lane),
|
||||
tint = if (recommended) MaterialTheme.colorScheme.onPrimaryContainer
|
||||
else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(24.dp).rotate(shown.turnDegrees()),
|
||||
@@ -398,7 +429,7 @@ private fun TravelEstimateCard(estimate: TravelEstimate, modifier: Modifier = Mo
|
||||
)
|
||||
}
|
||||
val detail = listOfNotNull(
|
||||
arrival.takeIf { it.isNotEmpty() }?.let { "Arrive $it" },
|
||||
arrival.takeIf { it.isNotEmpty() }?.let { stringResource(R.string.nav_arrive, it) },
|
||||
distance.takeIf { it.isNotEmpty() },
|
||||
).joinToString(" • ")
|
||||
if (detail.isNotEmpty()) {
|
||||
@@ -413,7 +444,7 @@ private fun TravelEstimateCard(estimate: TravelEstimate, modifier: Modifier = Mo
|
||||
// pops the on-screen card, leaving the app still believing it is navigating.
|
||||
GsIconButton(
|
||||
Icons.Filled.Close,
|
||||
"Stop navigating",
|
||||
stringResource(R.string.nav_stop),
|
||||
{ connection.stopNavigating() },
|
||||
colors = GsColors(MaterialTheme.colorScheme.surfaceContainerHighest, MaterialTheme.colorScheme.onSurface),
|
||||
size = 52.dp,
|
||||
@@ -445,12 +476,14 @@ private fun MapPaneLayout(
|
||||
// Pan mode hands the whole screen to the map, leaving only its own controls.
|
||||
val pan by connection.panMode.collectAsState()
|
||||
val edgeActions = (actionStrip?.actions.orEmpty() + mapActionStrip?.actions.orEmpty()).map { it.identity() }.toSet()
|
||||
val atEnd = LocalCardsAtEnd.current
|
||||
val cardEdge = cardEdge
|
||||
androidx.compose.foundation.layout.BoxWithConstraints(modifier.fillMaxWidth()) {
|
||||
if (!pan) {
|
||||
ChromeSurface(
|
||||
Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.covers("map-pane", CoverEdge.START)
|
||||
.align(if (atEnd) Alignment.TopEnd else Alignment.TopStart)
|
||||
.covers("map-pane", cardEdge)
|
||||
.padding(12.dp)
|
||||
.width(cardColumnWidth(maxWidth) - 24.dp)
|
||||
.let { if (fullHeight) it.height(maxHeight - 24.dp) else it.heightIn(max = maxHeight - 24.dp) },
|
||||
@@ -463,7 +496,8 @@ private fun MapPaneLayout(
|
||||
RightEdgeControls(
|
||||
if (pan) null else actionStrip,
|
||||
mapActionStrip,
|
||||
Modifier.align(Alignment.TopEnd).padding(12.dp),
|
||||
Modifier.align(if (atEnd) Alignment.TopStart else Alignment.TopEnd).padding(12.dp),
|
||||
alignment = if (atEnd) Alignment.Start else Alignment.End,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -543,7 +577,7 @@ private fun RoutePreviewChrome(template: RoutePreviewNavigationTemplate, modifie
|
||||
)
|
||||
Box(Modifier.weight(1f, fill = false)) {
|
||||
if (template.isLoading) {
|
||||
Text("Finding routes…", Modifier.padding(14.dp), style = ChromeType.title)
|
||||
Text(stringResource(R.string.nav_finding_routes), Modifier.padding(14.dp), style = ChromeType.title)
|
||||
} else {
|
||||
ItemListColumn(template.itemList)
|
||||
}
|
||||
@@ -567,7 +601,7 @@ private fun ListPane(
|
||||
ChromeSurface(modifier, shape = androidx.compose.foundation.shape.RoundedCornerShape(0.dp)) {
|
||||
Column {
|
||||
HeaderBar(title, startAction)
|
||||
if (loading) Text("Loading…", Modifier.padding(14.dp), style = ChromeType.title)
|
||||
if (loading) Text(stringResource(R.string.media_loading), Modifier.padding(14.dp), style = ChromeType.title)
|
||||
else ItemListColumn(itemList)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
@@ -317,7 +319,7 @@ internal fun SearchField(
|
||||
Spacer(Modifier.width(12.dp))
|
||||
}
|
||||
Text(
|
||||
query.ifEmpty { hint.ifEmpty { "Search" } },
|
||||
query.ifEmpty { hint.ifEmpty { stringResource(R.string.template_search) } },
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = if (query.isEmpty()) muted else MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
@@ -327,7 +329,7 @@ internal fun SearchField(
|
||||
if (onClear != null && query.isNotEmpty()) {
|
||||
GsIconButton(
|
||||
androidx.compose.material.icons.Icons.Filled.Close,
|
||||
"Clear",
|
||||
stringResource(R.string.storage_clear),
|
||||
onClear,
|
||||
colors = GsColors(androidx.compose.ui.graphics.Color.Transparent, muted),
|
||||
size = 42.dp,
|
||||
@@ -623,7 +625,7 @@ private fun MediaPlaybackContent(template: MediaPlaybackTemplate, modifier: Modi
|
||||
} else {
|
||||
Box(Modifier.weight(1f).fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
"Nothing playing yet.",
|
||||
stringResource(R.string.template_nothing_playing),
|
||||
style = ChromeType.body,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
|
||||
@@ -647,6 +649,6 @@ private typealias ColumnScopeAlias = androidx.compose.foundation.layout.ColumnSc
|
||||
@Composable
|
||||
private fun Loading() {
|
||||
Box(Modifier.fillMaxWidth().height(80.dp), contentAlignment = Alignment.Center) {
|
||||
Text("Loading…", style = ChromeType.title)
|
||||
Text(stringResource(R.string.media_loading), style = ChromeType.title)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.input.pointer.changedToUp
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -34,6 +37,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Call
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -228,6 +232,8 @@ internal fun RightEdgeControls(
|
||||
modifier: Modifier = Modifier,
|
||||
/** For the stack of map buttons alone, without the row of the app's own above it. */
|
||||
mapActionsModifier: Modifier = Modifier,
|
||||
/** End normally; Start when the app's cards have moved to the far edge and these to the near one. */
|
||||
alignment: Alignment.Horizontal = Alignment.End,
|
||||
) {
|
||||
val connection = LocalCarAppConnection.current
|
||||
val actions = actionStrip?.actions.orEmpty().filter { it.isDrawable() }
|
||||
@@ -244,6 +250,11 @@ internal fun RightEdgeControls(
|
||||
// Kept up while the knob is on one of them, or the ring would fade out along with the button.
|
||||
var holdsFocus by remember { mutableStateOf(false) }
|
||||
val visible = awake || pan || holdsFocus
|
||||
// With the player beside the map there is no room for the app's row and the map controls
|
||||
// both, so the app's own buttons fold behind one; opening it swaps them in for the map's.
|
||||
val folded = LocalSidePanelInset.current > 0.dp && actions.size > 1
|
||||
var open by remember { mutableStateOf(false) }
|
||||
androidx.compose.runtime.LaunchedEffect(visible, folded) { if (!visible || !folded) open = false }
|
||||
val alpha by androidx.compose.animation.core.animateFloatAsState(
|
||||
targetValue = if (visible) 1f else RESTING_ALPHA,
|
||||
animationSpec = androidx.compose.animation.core.tween(if (visible) 150 else 500),
|
||||
@@ -262,18 +273,45 @@ internal fun RightEdgeControls(
|
||||
}
|
||||
}
|
||||
},
|
||||
horizontalAlignment = Alignment.End,
|
||||
horizontalAlignment = alignment,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
if (actions.isNotEmpty()) {
|
||||
if (folded) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
GsIconButton(
|
||||
Icons.Filled.MoreVert,
|
||||
stringResource(R.string.car_group_more),
|
||||
onClick = { open = !open },
|
||||
colors = GsColors(if (open) scheme.primary else scheme.surfaceContainerHighest, if (open) scheme.onPrimary else scheme.onSurface),
|
||||
size = ICON_BUTTON_SIZE,
|
||||
iconSize = ICON_BUTTON_GLYPH,
|
||||
shape = androidx.compose.foundation.shape.CircleShape,
|
||||
)
|
||||
if (open) {
|
||||
// A tap on any of them closes the stack again; the tap still reaches the button.
|
||||
Column(
|
||||
Modifier.pointerInput(Unit) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
if (awaitPointerEvent(PointerEventPass.Initial).changes.any { it.changedToUp() }) open = false
|
||||
}
|
||||
}
|
||||
},
|
||||
horizontalAlignment = alignment,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
actions.forEach { ActionButton(it) }
|
||||
}
|
||||
}
|
||||
} else if (actions.isNotEmpty()) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
actions.forEach { ActionButton(it) }
|
||||
}
|
||||
}
|
||||
if (mapActions.isNotEmpty()) {
|
||||
if (mapActions.isNotEmpty() && !(folded && open)) {
|
||||
Column(
|
||||
mapActionsModifier,
|
||||
horizontalAlignment = Alignment.End,
|
||||
horizontalAlignment = alignment,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
mapActions.forEach { ActionButton(it) }
|
||||
@@ -423,7 +461,7 @@ internal fun AutoStartButton(action: Action, modifier: Modifier = Modifier) {
|
||||
) {
|
||||
if (hasGlyph) CarGlyph(action.icon, Modifier.size(24.dp).align(Alignment.CenterStart))
|
||||
Text(
|
||||
action.title.text().ifEmpty { "Start" },
|
||||
action.title.text().ifEmpty { stringResource(R.string.template_start) },
|
||||
style = ChromeType.title,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.*
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -300,7 +302,7 @@ private fun DetailRow(row: CarRow, placeName: String) {
|
||||
Row(Modifier.padding(horizontal = 16.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
androidx.compose.material3.Icon(Icons.Filled.Call, null, Modifier.size(20.dp))
|
||||
Spacer(Modifier.width(6.dp))
|
||||
Text("Call", style = ChromeType.body)
|
||||
Text(stringResource(R.string.dialer_call), style = ChromeType.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -27,7 +28,7 @@ import androidx.compose.ui.unit.dp
|
||||
fun VehicleDataScreen() {
|
||||
val readings by CarSensors.readings.collectAsState()
|
||||
|
||||
Column(Modifier.fillMaxSize().padding(horizontal = 24.dp, vertical = 16.dp)) {
|
||||
Column(Modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 16.dp)) {
|
||||
ScreenHeader("Vehicle data")
|
||||
Text(
|
||||
"Raw sensor channel from the connected head unit. Field meaning is a best guess - " +
|
||||
@@ -46,7 +47,7 @@ fun VehicleDataScreen() {
|
||||
return@Column
|
||||
}
|
||||
|
||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
LazyColumn(contentPadding = PaddingValues(bottom = 16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
items(readings, key = { it.typeId }) { reading -> ReadingRow(reading) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.annotation.StringRes
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
@@ -38,7 +42,7 @@ class WeatherData(
|
||||
|
||||
sealed interface WeatherState {
|
||||
data object Loading : WeatherState
|
||||
data class Unavailable(val reason: String) : WeatherState
|
||||
data class Unavailable(@param:StringRes val reason: Int) : WeatherState
|
||||
data class Ready(val data: WeatherData) : WeatherState
|
||||
}
|
||||
|
||||
@@ -78,7 +82,7 @@ object Weather {
|
||||
if (flow.value !is WeatherState.Ready) {
|
||||
val allowed = hasLocationPermission(context)
|
||||
flow.value = WeatherState.Unavailable(
|
||||
if (allowed) "Waiting for a location fix." else "Allow location for Gearslip on the phone to see the weather.",
|
||||
if (allowed) R.string.weather_waiting_fix else R.string.weather_allow_location,
|
||||
)
|
||||
}
|
||||
return
|
||||
@@ -92,7 +96,7 @@ object Weather {
|
||||
// No place name: logs get shared in bug reports.
|
||||
GearslipLog.i("weather: loaded")
|
||||
}
|
||||
else if (flow.value !is WeatherState.Ready) flow.value = WeatherState.Unavailable("Could not reach the weather service.")
|
||||
else if (flow.value !is WeatherState.Ready) flow.value = WeatherState.Unavailable(R.string.weather_unreachable)
|
||||
}
|
||||
|
||||
// --- location -------------------------------------------------------------------------
|
||||
@@ -197,6 +201,26 @@ object Weather {
|
||||
)
|
||||
}
|
||||
|
||||
/** Open-Meteo's WMO weather codes, in words on the screen. [describe] is the English the assistant says. */
|
||||
@StringRes
|
||||
fun conditionRes(code: Int): Int = when (code) {
|
||||
0 -> R.string.weather_clear
|
||||
1 -> R.string.weather_mostly_clear
|
||||
2 -> R.string.weather_partly_cloudy
|
||||
3 -> R.string.weather_overcast
|
||||
45, 48 -> R.string.weather_fog
|
||||
in 51..57 -> R.string.weather_drizzle
|
||||
in 61..67 -> R.string.weather_rain
|
||||
in 71..77 -> R.string.weather_snow
|
||||
in 80..82 -> R.string.weather_rain_showers
|
||||
in 85..86 -> R.string.weather_snow_showers
|
||||
in 95..99 -> R.string.weather_thunderstorm
|
||||
else -> 0
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun condition(code: Int): String = conditionRes(code).let { if (it == 0) "-" else stringResource(it) }
|
||||
|
||||
/** Open-Meteo's WMO weather codes, in words. */
|
||||
fun describe(code: Int): String = when (code) {
|
||||
0 -> "Clear"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -41,11 +43,11 @@ fun WeatherScreen() {
|
||||
Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||
) {
|
||||
ScreenHeader("Weather")
|
||||
ScreenHeader(stringResource(R.string.crumb_weather))
|
||||
when (val s = state) {
|
||||
WeatherState.Loading -> Text("Getting the weather…", style = MaterialTheme.typography.titleMedium)
|
||||
WeatherState.Loading -> Text(stringResource(R.string.weather_getting), style = MaterialTheme.typography.titleMedium)
|
||||
is WeatherState.Unavailable -> Text(
|
||||
s.reason, style = MaterialTheme.typography.titleMedium,
|
||||
stringResource(s.reason), style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
is WeatherState.Ready -> Forecast(s.data)
|
||||
@@ -66,18 +68,18 @@ private fun Forecast(w: WeatherData) {
|
||||
WeatherIcon(w.code, w.isDay, size = 88.dp)
|
||||
Column {
|
||||
Text("${w.temp}${w.tempUnit}", fontSize = 56.sp, fontWeight = FontWeight.Bold)
|
||||
Text(Weather.describe(w.code), style = MaterialTheme.typography.titleLarge)
|
||||
Text(Weather.condition(w.code), style = MaterialTheme.typography.titleLarge)
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
Column(horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text("Feels like ${w.feelsLike}${w.tempUnit}", style = MaterialTheme.typography.bodyLarge)
|
||||
Text("Humidity ${w.humidity}%", style = MaterialTheme.typography.bodyLarge)
|
||||
Text("Wind ${w.wind} ${w.windUnit}", style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.weather_feels_like, "${w.feelsLike}${w.tempUnit}"), style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.weather_humidity, w.humidity), style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.weather_wind, "${w.wind} ${w.windUnit}"), style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text("Next hours", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Text(stringResource(R.string.weather_next_hours), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
LazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
items(w.hours) { h ->
|
||||
Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.large) {
|
||||
@@ -101,7 +103,7 @@ private fun Forecast(w: WeatherData) {
|
||||
}
|
||||
}
|
||||
|
||||
Text("This week", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Text(stringResource(R.string.weather_this_week), style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
w.days.forEachIndexed { i, d ->
|
||||
Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, shape = MaterialTheme.shapes.large) {
|
||||
@@ -111,11 +113,11 @@ private fun Forecast(w: WeatherData) {
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Text(
|
||||
if (i == 0) "Today" else SimpleDateFormat("EEEE", Locale.getDefault()).format(Date(d.date)),
|
||||
if (i == 0) stringResource(R.string.weather_today) else SimpleDateFormat("EEEE", Locale.getDefault()).format(Date(d.date)),
|
||||
Modifier.width(120.dp), style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
WeatherIcon(d.code, true, size = 28.dp)
|
||||
Text(Weather.describe(d.code), Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
|
||||
Text(Weather.condition(d.code), Modifier.weight(1f), style = MaterialTheme.typography.bodyMedium)
|
||||
if (d.rainChance > 0) Text("${d.rainChance}%", color = MaterialTheme.colorScheme.primary)
|
||||
Text("${d.low}° / ${d.high}°", style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
@@ -123,7 +125,7 @@ private fun Forecast(w: WeatherData) {
|
||||
}
|
||||
}
|
||||
Text(
|
||||
"Weather data by Open-Meteo.com (CC BY 4.0)",
|
||||
stringResource(R.string.weather_credit),
|
||||
style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.car
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
@@ -95,19 +97,19 @@ fun WebApp(content: String) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
// Back steps through the page's history first (the interceptor above), then leaves.
|
||||
if (!fullscreen) {
|
||||
ScreenHeader(pageTitle.ifEmpty { "Browser" }, Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
GsIconButton(androidx.compose.material.icons.Icons.Filled.Search, "Search or type an address", {
|
||||
ScreenHeader(pageTitle.ifEmpty { stringResource(R.string.app_browser) }, Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
GsIconButton(androidx.compose.material.icons.Icons.Filled.Search, stringResource(R.string.web_search), {
|
||||
fieldText = null
|
||||
address = ""
|
||||
})
|
||||
if (canGoForward) {
|
||||
GsIconButton(androidx.compose.material.icons.Icons.AutoMirrored.Filled.ArrowForward, "Forward", { web?.goForward() })
|
||||
GsIconButton(androidx.compose.material.icons.Icons.AutoMirrored.Filled.ArrowForward, stringResource(R.string.web_forward), { web?.goForward() })
|
||||
}
|
||||
GsIconButton(androidx.compose.material.icons.Icons.Filled.Refresh, "Reload", { web?.reload() })
|
||||
GsIconButton(androidx.compose.material.icons.Icons.Filled.Home, "Start page", {
|
||||
GsIconButton(androidx.compose.material.icons.Icons.Filled.Refresh, stringResource(R.string.web_reload), { web?.reload() })
|
||||
GsIconButton(androidx.compose.material.icons.Icons.Filled.Home, stringResource(R.string.web_start_page), {
|
||||
web?.let { view -> loadContent(view, content) }
|
||||
})
|
||||
GsIconButton(MediaIcons.Fullscreen, "Full screen", CarFullscreen::enter)
|
||||
GsIconButton(MediaIcons.Fullscreen, stringResource(R.string.web_fullscreen), CarFullscreen::enter)
|
||||
}
|
||||
}
|
||||
Box(Modifier.weight(1f).fillMaxSize()) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package app.seb3thehacker.gearslip.car.theme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
|
||||
/**
|
||||
* The phone's Material You colours, with its accent washed over the surfaces. Material You's own
|
||||
@@ -11,7 +12,8 @@ import androidx.compose.ui.graphics.compositeOver
|
||||
* Material 3's surface tint does, and it changes with the wallpaper like everything else.
|
||||
* Higher containers take more of the tint, so cards still stand off the background.
|
||||
*/
|
||||
fun ColorScheme.withTintedSurfaces(dark: Boolean): ColorScheme {
|
||||
fun ColorScheme.withTintedSurfaces(dark: Boolean, style: DarkStyle = DarkStyle.GREY): ColorScheme {
|
||||
if (dark && style != DarkStyle.GREY) return withDarkStyle(style)
|
||||
val base = if (dark) 0.03f else 0.02f
|
||||
fun tint(own: Color, step: Int) = primary.copy(alpha = base + step * (if (dark) 0.015f else 0.01f)).compositeOver(own)
|
||||
return copy(
|
||||
@@ -27,3 +29,56 @@ fun ColorScheme.withTintedSurfaces(dark: Boolean): ColorScheme {
|
||||
surfaceVariant = tint(surfaceVariant, 3),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* How dark the car's dark mode goes. [GREY] is Material You's own dark, with the tint above;
|
||||
* the others are darker, and the two colour ones take their hue from the phone's accent.
|
||||
*/
|
||||
enum class DarkStyle { GREY, BLACK, DEEP_COLOUR, BLACK_COLOUR }
|
||||
|
||||
/**
|
||||
* The surfaces rebuilt at [style]'s brightness, from the background up to the highest container.
|
||||
* The levels are 0-255 greys; each surface keeps Material You's own slight hue at that level.
|
||||
*/
|
||||
private fun ColorScheme.withDarkStyle(style: DarkStyle): ColorScheme {
|
||||
val levels = when (style) {
|
||||
DarkStyle.BLACK -> listOf(0f, 12f, 17f, 25f, 34f)
|
||||
DarkStyle.DEEP_COLOUR -> listOf(11f, 19f, 24f, 32f, 41f)
|
||||
else -> listOf(0f, 17f, 22f, 30f, 39f)
|
||||
}
|
||||
val colour = style != DarkStyle.BLACK
|
||||
// The accent's saturated container colour, not the pastel accent itself: tinting with the
|
||||
// pastel only makes a surface lighter, where this one gives it colour at the same brightness.
|
||||
fun at(own: Color, step: Int): Color {
|
||||
val level = levels[step] / 255f
|
||||
if (level == 0f) return Color.Black
|
||||
val neutral = own.scaledTo(level)
|
||||
return if (colour) {
|
||||
lerp(neutral, primaryContainer.scaledTo(level), (0.6f + 0.08f * step).coerceAtMost(0.95f))
|
||||
} else {
|
||||
primary.copy(alpha = 0.02f + 0.015f * step).compositeOver(neutral)
|
||||
}
|
||||
}
|
||||
return copy(
|
||||
// Grey text that read fine on grey looks dim on black, so it moves a little towards white.
|
||||
onSurfaceVariant = lerp(onSurfaceVariant, onSurface, 0.3f),
|
||||
background = at(background, 0),
|
||||
surface = at(surface, 0),
|
||||
surfaceDim = at(surfaceDim, 0),
|
||||
surfaceContainerLowest = at(surfaceContainerLowest, 0),
|
||||
surfaceContainerLow = at(surfaceContainerLow, 1),
|
||||
surfaceContainer = at(surfaceContainer, 2),
|
||||
surfaceContainerHigh = at(surfaceContainerHigh, 3),
|
||||
surfaceContainerHighest = at(surfaceContainerHighest, 4),
|
||||
surfaceBright = at(surfaceBright, 4),
|
||||
surfaceVariant = at(surfaceVariant, 3),
|
||||
)
|
||||
}
|
||||
|
||||
/** This colour's hue at an average brightness of [level], 0 to 1. */
|
||||
private fun Color.scaledTo(level: Float): Color {
|
||||
val mean = (red + green + blue) / 3f
|
||||
if (mean <= 0f) return Color(level, level, level)
|
||||
val k = level / mean
|
||||
return Color((red * k).coerceAtMost(1f), (green * k).coerceAtMost(1f), (blue * k).coerceAtMost(1f))
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.dropShadow
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.drawOutline
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
@@ -172,6 +173,46 @@ fun Modifier.gsSurface(
|
||||
* outlines: cheap, works on every API level, and already soft enough that the car's video
|
||||
* compression has nothing crisp to smear.
|
||||
*/
|
||||
/**
|
||||
* The current theme's shadow under a panel floating over the map, such as the docked player.
|
||||
* Draws nothing in a look without one.
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.gsPanelShadow(shape: Shape): Modifier {
|
||||
val look = gsLook()
|
||||
if (look.panelShadow <= 0.dp || look.panelShadowAlpha <= 0f) return this
|
||||
// A real blur, as soft as the nav bar's gradient and as strong: stacked outlines showed as
|
||||
// rings around a card this big.
|
||||
return dropShadow(
|
||||
shape,
|
||||
androidx.compose.ui.graphics.shadow.Shadow(
|
||||
radius = look.panelShadow * 3,
|
||||
color = Color.Black.copy(alpha = look.panelShadowAlpha * 0.6f),
|
||||
offset = androidx.compose.ui.unit.DpOffset(0.dp, look.panelShadow / 2),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/** The same shadow cast upwards from the top edge of a bar along the bottom of the screen. */
|
||||
@Composable
|
||||
fun Modifier.gsBarShadow(): Modifier {
|
||||
val look = gsLook()
|
||||
if (look.panelShadow <= 0.dp || look.panelShadowAlpha <= 0f) return this
|
||||
return drawBehind {
|
||||
val reach = look.panelShadow.toPx() * 1.5f
|
||||
drawRect(
|
||||
Brush.verticalGradient(
|
||||
0f to Color.Transparent,
|
||||
1f to Color.Black.copy(alpha = look.panelShadowAlpha * 0.6f),
|
||||
startY = -reach,
|
||||
endY = 0f,
|
||||
),
|
||||
topLeft = androidx.compose.ui.geometry.Offset(0f, -reach),
|
||||
size = Size(size.width, reach),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DrawScope.drawSoftShadow(shape: Shape, elevation: Float, alpha: Float, lift: Float) {
|
||||
if (alpha <= 0f) return
|
||||
val blur = elevation * 2f
|
||||
|
||||
@@ -64,6 +64,12 @@ data class ControlLook(
|
||||
* from; a flat look takes the dot.
|
||||
*/
|
||||
val holdMarker: HoldMarkerStyle = HoldMarkerStyle.PEEL,
|
||||
/**
|
||||
* The shadow a panel floating over the map casts onto it (the docked player, the lyrics), and
|
||||
* the nav bar casts up. 0 for none, so a flat look stays flat.
|
||||
*/
|
||||
val panelShadow: Dp = 0.dp,
|
||||
val panelShadowAlpha: Float = 0f,
|
||||
/** Replaces this look when the UI is light, for effects that only work on one background. */
|
||||
val light: ControlLook? = null,
|
||||
)
|
||||
@@ -72,7 +78,11 @@ object GsThemes {
|
||||
val Keycap = GsTheme(
|
||||
id = "keycap",
|
||||
name = "Keycap",
|
||||
control = ControlLook(lip = 4.dp, pressedScale = 1f, holdMarker = HoldMarkerStyle.PEEL),
|
||||
// Short and dark, like the keys' own solid base.
|
||||
control = ControlLook(
|
||||
lip = 4.dp, pressedScale = 1f, holdMarker = HoldMarkerStyle.PEEL,
|
||||
panelShadow = 4.dp, panelShadowAlpha = 0.45f,
|
||||
),
|
||||
)
|
||||
|
||||
val Tactile = GsTheme(
|
||||
@@ -89,6 +99,8 @@ object GsThemes {
|
||||
pressedDim = 0.12f,
|
||||
pressedScale = 1f,
|
||||
holdMarker = HoldMarkerStyle.PEEL,
|
||||
panelShadow = 12.dp,
|
||||
panelShadowAlpha = 0.42f,
|
||||
// A wide soft shadow bands once the car's video stream compresses it on a light
|
||||
// background, so light mode gets a tight one and leans on the bevel instead.
|
||||
light = ControlLook(
|
||||
@@ -102,6 +114,8 @@ object GsThemes {
|
||||
pressedDim = 0.12f,
|
||||
pressedScale = 1f,
|
||||
holdMarker = HoldMarkerStyle.PEEL,
|
||||
panelShadow = 4.dp,
|
||||
panelShadowAlpha = 0.22f,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -227,6 +227,7 @@ class CarAppConnection(private val context: Context) {
|
||||
|
||||
private fun disconnect(keepLocation: Boolean) {
|
||||
main.removeCallbacks(handshakeTimeout)
|
||||
hardware.stop()
|
||||
clearToasts()
|
||||
lent?.let { sendSurfaceDestroyed(it) }
|
||||
lent = null
|
||||
@@ -599,6 +600,7 @@ class CarAppConnection(private val context: Context) {
|
||||
fun reportAreas(visible: Rect, stable: Rect) {
|
||||
areas = visible to stable
|
||||
val callback = surfaceCallback ?: return
|
||||
GearslipLog.i("host: areas for $appPackage: visible=${visible.toShortString()} stable=${stable.toShortString()}")
|
||||
runCatching { callback.onStableAreaChanged(stable, noop("onStableAreaChanged")) }
|
||||
runCatching { callback.onVisibleAreaChanged(visible, noop("onVisibleAreaChanged")) }
|
||||
}
|
||||
@@ -684,38 +686,25 @@ class CarAppConnection(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Answers car-hardware questions with "this car doesn't have that".
|
||||
* Answers car-hardware questions from what the car itself sends: see [CarHardwareFeed].
|
||||
*
|
||||
* A phone knows nothing about a car's sensors, so every answer here is "unsupported" - but
|
||||
* it has to be an actual answer. Returning no binder for this service at all is what made
|
||||
* Organic Maps crash in onAppStart: its CarSensorsManager assumes a host that offers the
|
||||
* service, and the library's own "unsupported" path only runs once a reply arrives.
|
||||
*
|
||||
* The reply's Bundleable is ignored by the library whenever isSupported is false, but the
|
||||
* IBinder must be a real IOnDoneCallback - the app dispatches the reply through it.
|
||||
* Every question has to get an actual answer, even "unsupported". Returning no binder for
|
||||
* this service at all is what made Organic Maps crash in onAppStart: its CarSensorsManager
|
||||
* assumes a host that offers the service, and the library's own "unsupported" path only runs
|
||||
* once a reply arrives. The reply's IBinder must be a real IOnDoneCallback: the app
|
||||
* dispatches the reply through it.
|
||||
*/
|
||||
private val hardware = CarHardwareFeed { name -> noop(name).asBinder() }
|
||||
|
||||
private val hardwareHost = object : ICarHardwareHost.Stub() {
|
||||
override fun getInterfaceVersion(): Int = ICarHardwareHost.VERSION
|
||||
override fun getCarHardwareResult(
|
||||
type: Int,
|
||||
params: Bundleable?,
|
||||
result: ICarHardwareResult?,
|
||||
) = unsupported(type, result)
|
||||
override fun getCarHardwareResult(type: Int, params: Bundleable?, result: ICarHardwareResult?) =
|
||||
hardware.get(type, result)
|
||||
|
||||
override fun subscribeCarHardwareResult(
|
||||
type: Int,
|
||||
params: Bundleable?,
|
||||
result: ICarHardwareResult?,
|
||||
) = unsupported(type, result)
|
||||
override fun subscribeCarHardwareResult(type: Int, params: Bundleable?, result: ICarHardwareResult?) =
|
||||
hardware.subscribe(type, result)
|
||||
|
||||
override fun unsubscribeCarHardwareResult(type: Int, params: Bundleable?) = Unit
|
||||
|
||||
private fun unsupported(type: Int, result: ICarHardwareResult?) {
|
||||
val target = result ?: return
|
||||
runCatching {
|
||||
target.onCarHardwareResult(type, false, null, noop("carHardwareResult").asBinder())
|
||||
}.onFailure { GearslipLog.w("host: could not answer hardware result $type") }
|
||||
}
|
||||
override fun unsubscribeCarHardwareResult(type: Int, params: Bundleable?) = hardware.unsubscribe(type)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
package app.seb3thehacker.gearslip.host
|
||||
|
||||
import android.os.IBinder
|
||||
import androidx.car.app.hardware.ICarHardwareResult
|
||||
import androidx.car.app.hardware.common.CarUnit
|
||||
import androidx.car.app.hardware.common.CarValue
|
||||
import androidx.car.app.hardware.info.CarHardwareLocation
|
||||
import androidx.car.app.hardware.info.Compass
|
||||
import androidx.car.app.hardware.info.EnergyLevel
|
||||
import androidx.car.app.hardware.info.Mileage
|
||||
import androidx.car.app.hardware.info.Model
|
||||
import androidx.car.app.hardware.info.Speed
|
||||
import androidx.car.app.serialization.Bundleable
|
||||
import app.seb3thehacker.gearslip.GearslipLog
|
||||
import app.seb3thehacker.gearslip.car.CarData
|
||||
import app.seb3thehacker.gearslip.car.CarSettings
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* What a car app gets when it asks the car hardware API (CarInfo, CarSensors) about the car:
|
||||
* the model, speed, odometer, fuel and range, compass and the car's own GPS, read from [CarData].
|
||||
* Anything else, or anything the car never sends, is answered "this car doesn't have that".
|
||||
*
|
||||
* A one-off question ([get]) is answered at once. A subscription gets the current value, if
|
||||
* there is one, and then each new one as the car sends it, until the app unsubscribes or the
|
||||
* connection ends ([stop]).
|
||||
*/
|
||||
internal class CarHardwareFeed(private val callback: (name: String) -> IBinder) {
|
||||
|
||||
private val subscribers = ConcurrentHashMap<Int, CopyOnWriteArrayList<ICarHardwareResult>>()
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
@Volatile private var watching: Job? = null
|
||||
|
||||
fun get(type: Int, result: ICarHardwareResult?) {
|
||||
val target = result ?: return
|
||||
send(target, type, valueOf(type, CarData.state.value) ?: return unsupported(target, type))
|
||||
}
|
||||
|
||||
fun subscribe(type: Int, result: ICarHardwareResult?) {
|
||||
val target = result ?: return
|
||||
if (type !in SENSOR_OF) return unsupported(target, type)
|
||||
subscribers.getOrPut(type) { CopyOnWriteArrayList() }.add(target)
|
||||
GearslipLog.i("host: an app subscribed to car hardware ${NAMES[type] ?: type}")
|
||||
valueOf(type, CarData.state.value)?.let { send(target, type, it) }
|
||||
watch()
|
||||
}
|
||||
|
||||
fun unsubscribe(type: Int) {
|
||||
subscribers.remove(type)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
subscribers.clear()
|
||||
watching?.cancel()
|
||||
watching = null
|
||||
}
|
||||
|
||||
fun close() {
|
||||
stop()
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
/** Sends each subscribed kind again when the car's reading of it changes. */
|
||||
private fun watch() {
|
||||
if (watching?.isActive == true) return
|
||||
watching = scope.launch {
|
||||
var seen = CarData.state.value.updatedAt
|
||||
CarData.state.collect { s ->
|
||||
for ((type, targets) in subscribers) {
|
||||
val sensor = SENSOR_OF[type] ?: continue
|
||||
if (s.updatedAt[sensor] == seen[sensor]) continue
|
||||
val value = valueOf(type, s) ?: continue
|
||||
targets.forEach { send(it, type, value) }
|
||||
}
|
||||
seen = s.updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun valueOf(type: Int, s: CarData.Snapshot): Any? {
|
||||
val imperial = CarSettings.imperial()
|
||||
val distanceUnit = known(if (imperial) CarUnit.MILE else CarUnit.KILOMETER)
|
||||
val at = s.updatedAt[SENSOR_OF[type]] ?: System.currentTimeMillis()
|
||||
fun <T : Any> v(value: T) = CarValue(value, at, CarValue.STATUS_SUCCESS)
|
||||
return when (type) {
|
||||
TYPE_MODEL -> CarData.model?.let { m ->
|
||||
Model.Builder().setName(v(m.name)).apply { m.year?.let { setYear(v(it)) } }.build()
|
||||
}
|
||||
TYPE_SPEED -> s.speedMps?.let {
|
||||
Speed.Builder()
|
||||
.setRawSpeedMetersPerSecond(v(it))
|
||||
.setDisplaySpeedMetersPerSecond(v(it))
|
||||
.setSpeedDisplayUnit(known(if (imperial) CarUnit.MILES_PER_HOUR else CarUnit.KILOMETERS_PER_HOUR))
|
||||
.build()
|
||||
}
|
||||
TYPE_MILEAGE -> s.odometerKm?.let {
|
||||
Mileage.Builder().setOdometerMeters(v(it * 1000f)).setDistanceDisplayUnit(distanceUnit).build()
|
||||
}
|
||||
TYPE_ENERGY_LEVEL -> if (s.fuelPercent == null && s.rangeKm == null && s.fuelLow == null) null else {
|
||||
EnergyLevel.Builder().apply {
|
||||
s.fuelPercent?.let { setFuelPercent(v(it.toFloat())) }
|
||||
s.rangeKm?.let { setRangeRemainingMeters(v(it * 1000f)) }
|
||||
s.fuelLow?.let { setEnergyIsLow(v(it)) }
|
||||
setDistanceDisplayUnit(distanceUnit)
|
||||
}.build()
|
||||
}
|
||||
TYPE_COMPASS -> s.compassDegrees?.let { Compass(v(listOf(it))) }
|
||||
TYPE_CAR_LOCATION -> s.location?.let { l ->
|
||||
val location = android.location.Location("car").apply {
|
||||
latitude = l.latitude
|
||||
longitude = l.longitude
|
||||
l.accuracyMeters?.let { accuracy = it }
|
||||
l.altitudeMeters?.let { altitude = it }
|
||||
l.speedMps?.let { speed = it }
|
||||
l.bearingDegrees?.let { bearing = it }
|
||||
time = at
|
||||
}
|
||||
CarHardwareLocation(v(location))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun known(value: Int) = CarValue(value, System.currentTimeMillis(), CarValue.STATUS_SUCCESS)
|
||||
|
||||
private fun send(target: ICarHardwareResult, type: Int, value: Any) {
|
||||
runCatching {
|
||||
target.onCarHardwareResult(type, true, Bundleable.create(value), callback("carHardwareResult"))
|
||||
}.onFailure {
|
||||
// The app went away: stop telling it.
|
||||
subscribers.values.forEach { list -> list.remove(target) }
|
||||
GearslipLog.w("host: could not send car hardware ${NAMES[type] ?: type}: ${it.javaClass.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun unsupported(target: ICarHardwareResult, type: Int) {
|
||||
runCatching { target.onCarHardwareResult(type, false, null, callback("carHardwareResult")) }
|
||||
.onFailure { GearslipLog.w("host: could not answer hardware result $type") }
|
||||
}
|
||||
|
||||
companion object {
|
||||
// ICarHardwareResultTypes in the car library.
|
||||
private const val TYPE_MODEL = 1
|
||||
private const val TYPE_ENERGY_LEVEL = 4
|
||||
private const val TYPE_SPEED = 5
|
||||
private const val TYPE_MILEAGE = 6
|
||||
private const val TYPE_COMPASS = 21
|
||||
private const val TYPE_CAR_LOCATION = 23
|
||||
|
||||
/** Which of the car's sensors each kind comes from, for knowing when it changed. */
|
||||
private val SENSOR_OF = mapOf(
|
||||
TYPE_ENERGY_LEVEL to CarData.FUEL,
|
||||
TYPE_SPEED to CarData.SPEED,
|
||||
TYPE_MILEAGE to CarData.ODOMETER,
|
||||
TYPE_COMPASS to CarData.COMPASS,
|
||||
TYPE_CAR_LOCATION to CarData.LOCATION,
|
||||
)
|
||||
|
||||
private val NAMES = mapOf(
|
||||
TYPE_MODEL to "model", TYPE_ENERGY_LEVEL to "energy level", TYPE_SPEED to "speed",
|
||||
TYPE_MILEAGE to "mileage", TYPE_COMPASS to "compass", TYPE_CAR_LOCATION to "car location",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,10 @@ class MediaEntry(
|
||||
val iconBitmap: Bitmap?,
|
||||
val browsable: Boolean,
|
||||
val playable: Boolean,
|
||||
/** Shown as a cover in a grid rather than a row, as the app asked. */
|
||||
val grid: Boolean = false,
|
||||
/** The heading the app files this under, when it splits a list into sections. */
|
||||
val group: String? = null,
|
||||
)
|
||||
|
||||
/** [icon] is a drawable id inside the media app's own package, not Gearslip's; 0 for none. */
|
||||
@@ -271,7 +275,10 @@ class CarMedia(private val context: Context) {
|
||||
GearslipLog.w("media: ${app.label} went away")
|
||||
_phase.value = Phase.REJECTED
|
||||
}
|
||||
}, null)
|
||||
}, Bundle().apply {
|
||||
// Tells the app it may ask for grids and section headings, as it would of Android Auto.
|
||||
putBoolean(STYLE_SUPPORTED, true)
|
||||
})
|
||||
browser = mb
|
||||
GearslipLog.i("media: connecting to ${app.label}'s library")
|
||||
runCatching { mb.connect() }.onFailure {
|
||||
@@ -342,6 +349,8 @@ class CarMedia(private val context: Context) {
|
||||
GearslipLog.i("media: connected to ${app.label}, root=${mb.root}")
|
||||
attach(MediaController(context, mb.sessionToken))
|
||||
rootId = mb.root
|
||||
childStyles.clear()
|
||||
mb.extras?.let { childStyles[mb.root] = it.getInt(STYLE_BROWSABLE, 0) to it.getInt(STYLE_PLAYABLE, 0) }
|
||||
path.clear()
|
||||
_phase.value = Phase.READY
|
||||
open(mb.root)
|
||||
@@ -427,7 +436,7 @@ class CarMedia(private val context: Context) {
|
||||
if (parentId != subscribed) return
|
||||
_browse.value = BrowseState(
|
||||
trail = path.map { it.second },
|
||||
entries = children.map(::entryOf),
|
||||
entries = children.map { entryOf(it, childStyles[parentId]) },
|
||||
loading = false,
|
||||
// An empty top level is an app keeping its library to itself, not an empty one.
|
||||
unavailable = path.isEmpty() && children.isEmpty(),
|
||||
@@ -441,8 +450,21 @@ class CarMedia(private val context: Context) {
|
||||
})
|
||||
}
|
||||
|
||||
private fun entryOf(item: MediaBrowser.MediaItem): MediaEntry {
|
||||
/**
|
||||
* How a folder asked for its children to be shown, browsable ones and playable ones: by
|
||||
* folder id, read off the folder itself (or the root's extras) before it's opened.
|
||||
*/
|
||||
private val childStyles = HashMap<String, Pair<Int, Int>>()
|
||||
|
||||
private fun entryOf(item: MediaBrowser.MediaItem, parentStyle: Pair<Int, Int>?): MediaEntry {
|
||||
val d = item.description
|
||||
val extras = d.extras
|
||||
if (extras != null && item.isBrowsable) {
|
||||
childStyles[item.mediaId.orEmpty()] = extras.getInt(STYLE_BROWSABLE, 0) to extras.getInt(STYLE_PLAYABLE, 0)
|
||||
}
|
||||
// The item's own hint first, then what its folder asked for its kind of child.
|
||||
val style = extras?.getInt(STYLE_SINGLE_ITEM, 0)?.takeIf { it != 0 }
|
||||
?: (if (item.isBrowsable) parentStyle?.first else parentStyle?.second) ?: 0
|
||||
return MediaEntry(
|
||||
id = item.mediaId.orEmpty(),
|
||||
title = d.title?.toString().orEmpty(),
|
||||
@@ -451,6 +473,8 @@ class CarMedia(private val context: Context) {
|
||||
iconBitmap = d.iconBitmap,
|
||||
browsable = item.isBrowsable,
|
||||
playable = item.isPlayable,
|
||||
grid = style == STYLE_GRID || style == STYLE_CATEGORY_GRID,
|
||||
group = extras?.getString(STYLE_GROUP_TITLE)?.takeIf { it.isNotBlank() },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -772,3 +796,12 @@ class CarMedia(private val context: Context) {
|
||||
const val SEARCH_NOTICE_MS = 6_000L
|
||||
}
|
||||
}
|
||||
|
||||
/** Android Auto's browse layout hints, which any app built for it already sends. */
|
||||
private const val STYLE_SUPPORTED = "android.media.browse.CONTENT_STYLE_SUPPORTED"
|
||||
private const val STYLE_BROWSABLE = "android.media.browse.CONTENT_STYLE_BROWSABLE_HINT"
|
||||
private const val STYLE_PLAYABLE = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT"
|
||||
private const val STYLE_SINGLE_ITEM = "android.media.browse.CONTENT_STYLE_SINGLE_ITEM_HINT"
|
||||
private const val STYLE_GROUP_TITLE = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT"
|
||||
private const val STYLE_GRID = 2
|
||||
private const val STYLE_CATEGORY_GRID = 4
|
||||
@@ -85,7 +85,9 @@ class CarMediaStatus(
|
||||
}
|
||||
|
||||
private fun sendTrack(now: NowPlaying) {
|
||||
val key = "${now.title}\u0000${now.artist}\u0000${now.album}\u0000${now.durationMs}\u0000${now.art?.generationId}"
|
||||
// Whether there's a cover, not which bitmap: some apps (Metrolist) hand over a fresh copy
|
||||
// of the same cover with every update, and resending it each second wastes the link.
|
||||
val key = "${now.title}\u0000${now.artist}\u0000${now.album}\u0000${now.durationMs}\u0000${now.art != null}"
|
||||
if (key == sentTrack) return
|
||||
sentTrack = key
|
||||
var body = Protobuf.stringField(1, now.title) +
|
||||
|
||||
@@ -1,28 +1,55 @@
|
||||
package app.seb3thehacker.gearslip.media
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import android.util.Xml
|
||||
import app.seb3thehacker.gearslip.GearslipLog
|
||||
import app.seb3thehacker.gearslip.car.CarSettings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.File
|
||||
import java.io.StringReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.net.URLEncoder
|
||||
import java.security.MessageDigest
|
||||
|
||||
class LyricLine(val timeMs: Long, val text: String)
|
||||
|
||||
/** [synced] means every line carries a time, so the current one can be followed along. */
|
||||
class LyricsResult(val lines: List<LyricLine>, val synced: Boolean)
|
||||
import kotlin.math.abs
|
||||
|
||||
/**
|
||||
* Looks lyrics up on LRCLIB (lrclib.net), a free community database that needs no key.
|
||||
* One line. [words] is filled only for word-timed lyrics, and joined back together it reads as
|
||||
* [text]; each word carries the space after it, if any, so a word split into syllables stays whole.
|
||||
*/
|
||||
class LyricLine(val timeMs: Long, val text: String, val words: List<LyricWord> = emptyList())
|
||||
|
||||
/** A word, or a syllable of one, sung from [startMs] to [endMs]. */
|
||||
class LyricWord(val startMs: Long, val endMs: Long, val text: String)
|
||||
|
||||
/**
|
||||
* [synced] means every line carries a time, so the current one can be followed along. [source]
|
||||
* names the service that had them, shown on debug builds.
|
||||
*/
|
||||
class LyricsResult(val lines: List<LyricLine>, val synced: Boolean, val source: String = "")
|
||||
|
||||
/**
|
||||
* Looks lyrics up online, in the order Metrolist uses: BetterLyrics, then LRCLIB, then KuGou.
|
||||
* Metrolist's other sources match songs by YouTube video id, which a song from another app
|
||||
* doesn't have, and Paxsenix calls Apple Music with a borrowed token, so they're left out.
|
||||
*
|
||||
* Media apps almost never publish lyrics in their session metadata, so this is the only general
|
||||
* source. It sends the track's title, artist, album and length to lrclib.net, which is why the
|
||||
* lookup happens only when the driver opens the lyrics view and never in the background.
|
||||
* - BetterLyrics (lyrics-api.boidu.dev): word-timed lyrics, but only for songs its server has
|
||||
* already fetched; anything else needs an API key and falls through.
|
||||
* - LRCLIB (lrclib.net): a free community database that needs no key.
|
||||
* - KuGou (kugou.com): a large Chinese music service, strong on Asian music.
|
||||
*
|
||||
* Media apps almost never publish lyrics in their session metadata, so these are the only general
|
||||
* source. Each lookup sends the track's title, artist, album and length to these services, which
|
||||
* is why it happens only when the driver opens the lyrics view, never in the background. With
|
||||
* [CarSettings.moreLyrics] off, only LRCLIB is asked.
|
||||
*
|
||||
* Results are cached twice over: in memory for the life of the process, and on disk under the
|
||||
* cache directory so a song looked up on a past drive doesn't need the network again on this one.
|
||||
@@ -33,33 +60,104 @@ object Lyrics {
|
||||
|
||||
private val cache = HashMap<String, LyricsResult?>()
|
||||
|
||||
/** Longest any one source may take, DNS included: a server that never answers can't hold the rest up. */
|
||||
private const val SOURCE_TIMEOUT_MS = 12_000L
|
||||
/** How long a cached song waits before its sources are asked again for better lyrics. */
|
||||
private const val RECHECK_MS = 24 * 60 * 60 * 1000L
|
||||
private val io = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
/** Word-timed beats line-timed, which beats plain text. */
|
||||
private const val BEST = 3
|
||||
private fun quality(result: LyricsResult?): Int = when {
|
||||
result == null -> 0
|
||||
!result.synced -> 1
|
||||
result.lines.any { it.words.isNotEmpty() } -> BEST
|
||||
else -> 2
|
||||
}
|
||||
|
||||
/** One place to look, with the best [quality] it can give. */
|
||||
private class Source(val name: String, val best: Int, val lookup: () -> LyricsResult?)
|
||||
|
||||
private fun sources(title: String, artist: String, album: String, durationMs: Long): List<Source> {
|
||||
val more = CarSettings.moreLyrics.value
|
||||
val seconds = (durationMs / 1000).toInt()
|
||||
return buildList {
|
||||
if (more) add(Source("BetterLyrics", BEST) { betterLyrics(title, artist, album, seconds) })
|
||||
add(Source("LRCLIB", 2) { exact(title, artist, album, durationMs) ?: search(title, artist) })
|
||||
if (more) add(Source("KuGou", 2) { kuGou(title, artist, album, seconds) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun key(title: String, artist: String, album: String, durationMs: Long) = "$title|$artist|$album|$durationMs"
|
||||
|
||||
suspend fun find(context: Context, title: String, artist: String, album: String, durationMs: Long): LyricsResult? {
|
||||
if (title.isBlank()) return null
|
||||
val key = "$title|$artist|$album|$durationMs"
|
||||
synchronized(cache) { if (cache.containsKey(key)) return cache[key] }
|
||||
val key = key(title, artist, album, durationMs)
|
||||
// A miss with only LRCLIB isn't a miss once more sources are on, so the mode is part of it.
|
||||
val memoryKey = "${CarSettings.moreLyrics.value}|$key"
|
||||
synchronized(cache) { if (cache.containsKey(memoryKey)) return cache[memoryKey] }
|
||||
|
||||
val fromDisk = withContext(Dispatchers.IO) { readDisk(context, key) }
|
||||
if (fromDisk != null) {
|
||||
synchronized(cache) { cache[key] = fromDisk }
|
||||
synchronized(cache) { cache[memoryKey] = fromDisk }
|
||||
return fromDisk
|
||||
}
|
||||
|
||||
val result = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
exact(title, artist, album, durationMs) ?: search(title, artist)
|
||||
}.onFailure { GearslipLog.w("lyrics: lookup failed: ${it.message}") }.getOrNull()
|
||||
}
|
||||
synchronized(cache) { cache[key] = result }
|
||||
if (result != null) withContext(Dispatchers.IO) { writeDisk(context, key, result) }
|
||||
val result = firstBetter(sources(title, artist, album, durationMs), than = 0)
|
||||
synchronized(cache) { cache[memoryKey] = result }
|
||||
if (result != null) withContext(Dispatchers.IO) { writeDisk(context, key, result, System.currentTimeMillis()) }
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* After [find] answered with [current], looks for better lyrics than it: word-timed where it has
|
||||
* line-timed, timed where it has plain. Only the sources that could beat it are asked, and only
|
||||
* once a day per song, so an old cache entry improves when a source gains the song without every
|
||||
* play sending the song out again. Returns the better lyrics, or null to keep [current].
|
||||
*/
|
||||
suspend fun upgrade(
|
||||
context: Context, title: String, artist: String, album: String, durationMs: Long, current: LyricsResult,
|
||||
): LyricsResult? {
|
||||
val have = quality(current)
|
||||
if (have >= BEST || title.isBlank()) return null
|
||||
val key = key(title, artist, album, durationMs)
|
||||
val checked = withContext(Dispatchers.IO) { checkedAt(context, key) }
|
||||
if (System.currentTimeMillis() - checked < RECHECK_MS) return null
|
||||
|
||||
val candidates = sources(title, artist, album, durationMs).filter { it.best > have && it.name != current.source }
|
||||
val better = firstBetter(candidates, than = have)
|
||||
withContext(Dispatchers.IO) { writeDisk(context, key, better ?: current, System.currentTimeMillis()) }
|
||||
if (better == null) return null
|
||||
GearslipLog.i("lyrics: replaced ${current.source.ifEmpty { "cached" }} lyrics with ${better.source}'s")
|
||||
synchronized(cache) { cache["${CarSettings.moreLyrics.value}|$key"] = better }
|
||||
return better
|
||||
}
|
||||
|
||||
/** Asks [sources] in order and returns the first answer of a higher [quality] than [than]. */
|
||||
private suspend fun firstBetter(sources: List<Source>, than: Int): LyricsResult? {
|
||||
for (source in sources) {
|
||||
val job = io.async {
|
||||
runCatching(source.lookup).onFailure { GearslipLog.w("lyrics: ${source.name} failed: ${it.message}") }.getOrNull()
|
||||
}
|
||||
val result = withTimeoutOrNull(SOURCE_TIMEOUT_MS) { job.await() }
|
||||
?.let { LyricsResult(it.lines, it.synced, source.name) }
|
||||
if (quality(result) > than) {
|
||||
GearslipLog.i("lyrics: found on ${source.name} (${if (result!!.synced) "synced" else "plain"})")
|
||||
return result
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun cacheFile(context: Context, key: String): File {
|
||||
val digest = MessageDigest.getInstance("SHA-1").digest(key.toByteArray())
|
||||
val name = digest.joinToString("") { "%02x".format(it) }
|
||||
return File(context.cacheDir, "lyrics").apply { mkdirs() }.resolve("$name.json")
|
||||
return File(context.cacheDir, LYRICS_DIR).apply { mkdirs() }.resolve("$name.json")
|
||||
}
|
||||
|
||||
/** Where the lyrics cache lives under the app's cache directory, for the storage screen too. */
|
||||
const val LYRICS_DIR = "lyrics"
|
||||
|
||||
private fun readDisk(context: Context, key: String): LyricsResult? = runCatching {
|
||||
val file = cacheFile(context, key)
|
||||
if (!file.exists()) return null
|
||||
@@ -67,23 +165,153 @@ object Lyrics {
|
||||
val lines = json.getJSONArray("lines").let { arr ->
|
||||
(0 until arr.length()).map { i ->
|
||||
val line = arr.getJSONObject(i)
|
||||
LyricLine(line.getLong("t"), line.getString("x"))
|
||||
val words = line.optJSONArray("w")?.let { w ->
|
||||
(0 until w.length()).map { j ->
|
||||
val word = w.getJSONArray(j)
|
||||
LyricWord(word.getLong(0), word.getLong(1), word.getString(2))
|
||||
}
|
||||
}.orEmpty()
|
||||
LyricLine(line.getLong("t"), line.getString("x"), words)
|
||||
}
|
||||
}
|
||||
LyricsResult(lines, json.getBoolean("synced"))
|
||||
val source = json.optString("source")
|
||||
// Saved before word timings were kept: fetch again rather than wait a day for an upgrade
|
||||
// that skips the source it came from.
|
||||
if (source == "BetterLyrics" && lines.none { it.words.isNotEmpty() }) return null
|
||||
LyricsResult(lines, json.getBoolean("synced"), source)
|
||||
}.getOrNull()
|
||||
|
||||
private fun writeDisk(context: Context, key: String, result: LyricsResult) {
|
||||
/** When the song's sources were last asked; 0 for entries from before the date was kept. */
|
||||
private fun checkedAt(context: Context, key: String): Long =
|
||||
runCatching { JSONObject(cacheFile(context, key).readText()).optLong("checked") }.getOrDefault(0L)
|
||||
|
||||
private fun writeDisk(context: Context, key: String, result: LyricsResult, checked: Long) {
|
||||
runCatching {
|
||||
val lines = JSONArray()
|
||||
result.lines.forEach { line ->
|
||||
lines.put(JSONObject().put("t", line.timeMs).put("x", line.text))
|
||||
val entry = JSONObject().put("t", line.timeMs).put("x", line.text)
|
||||
if (line.words.isNotEmpty()) {
|
||||
entry.put("w", JSONArray(line.words.map { JSONArray().put(it.startMs).put(it.endMs).put(it.text) }))
|
||||
}
|
||||
lines.put(entry)
|
||||
}
|
||||
val json = JSONObject().put("synced", result.synced).put("lines", lines)
|
||||
val json = JSONObject().put("synced", result.synced).put("source", result.source)
|
||||
.put("checked", checked).put("lines", lines)
|
||||
cacheFile(context, key).writeText(json.toString())
|
||||
}.onFailure { GearslipLog.w("lyrics: could not write disk cache: ${it.message}") }
|
||||
}
|
||||
|
||||
/** Forgets every cached song, on disk and in memory. */
|
||||
fun clearCache(context: Context) {
|
||||
synchronized(cache) { cache.clear() }
|
||||
File(context.cacheDir, LYRICS_DIR).deleteRecursively()
|
||||
}
|
||||
|
||||
// --- BetterLyrics ---
|
||||
|
||||
private fun betterLyrics(title: String, artist: String, album: String, seconds: Int): LyricsResult? {
|
||||
if (artist.isBlank()) return null
|
||||
val query = buildString {
|
||||
append("s=").append(enc(title)).append("&a=").append(enc(artist))
|
||||
if (seconds > 0) append("&d=").append(seconds)
|
||||
if (album.isNotBlank()) append("&al=").append(enc(album))
|
||||
}
|
||||
val body = get("https://lyrics-api.boidu.dev/getLyrics?$query") ?: return null
|
||||
val ttml = JSONObject(body).optString("ttml").takeIf { it.isNotBlank() && it != "null" } ?: return null
|
||||
return parseTtml(ttml)
|
||||
}
|
||||
|
||||
/**
|
||||
* Apple-style TTML: one `<p begin=…>` per line, its words in `<span begin=… end=…>`s with
|
||||
* spaces between; a word sung in syllables is several spans with no space. Background vocals,
|
||||
* translations and romanisations are left out, so a line reads as sung.
|
||||
*/
|
||||
private fun parseTtml(ttml: String): LyricsResult? {
|
||||
val parser = Xml.newPullParser().apply {
|
||||
setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
|
||||
setInput(StringReader(ttml))
|
||||
}
|
||||
val lines = mutableListOf<LyricLine>()
|
||||
var inP = false
|
||||
var lineBegin: Long? = null
|
||||
var firstSpanBegin: Long? = null
|
||||
var skipDepth = 0
|
||||
val text = StringBuilder()
|
||||
// The span being read, and the words of the line so far, each with the space after it.
|
||||
var spanBegin: Long? = null
|
||||
var spanEnd: Long? = null
|
||||
val words = mutableListOf<Triple<Long, Long, StringBuilder>>()
|
||||
var event = parser.eventType
|
||||
while (event != XmlPullParser.END_DOCUMENT) {
|
||||
when (event) {
|
||||
XmlPullParser.START_TAG -> when {
|
||||
parser.name == "p" -> {
|
||||
inP = true; text.clear(); firstSpanBegin = null; words.clear()
|
||||
lineBegin = attr(parser, "begin")?.let(::ttmlMs)
|
||||
}
|
||||
inP && skipDepth > 0 -> skipDepth++
|
||||
inP && parser.name == "span" -> {
|
||||
val role = attr(parser, "role")
|
||||
if (role == "x-bg" || role == "x-translation" || role == "x-roman") skipDepth = 1
|
||||
else {
|
||||
spanBegin = attr(parser, "begin")?.let(::ttmlMs)
|
||||
spanEnd = attr(parser, "end")?.let(::ttmlMs)
|
||||
if (firstSpanBegin == null) firstSpanBegin = spanBegin
|
||||
}
|
||||
}
|
||||
}
|
||||
XmlPullParser.TEXT -> if (inP && skipDepth == 0) {
|
||||
val raw = parser.text
|
||||
text.append(raw)
|
||||
val begin = spanBegin
|
||||
val end = spanEnd
|
||||
if (begin != null && end != null && raw.isNotBlank()) {
|
||||
val word = raw.replace(Regex("\\s+"), " ").trimStart()
|
||||
words.add(Triple(begin, end, StringBuilder(word)))
|
||||
} else if (raw.isNotEmpty() && raw.isBlank() && words.isNotEmpty() && !words.last().third.endsWith(" ")) {
|
||||
words.last().third.append(' ')
|
||||
}
|
||||
}
|
||||
XmlPullParser.END_TAG -> when {
|
||||
inP && skipDepth > 0 -> skipDepth--
|
||||
inP && parser.name == "span" -> { spanBegin = null; spanEnd = null }
|
||||
parser.name == "p" -> {
|
||||
inP = false
|
||||
val timed = words.map { (b, e, w) -> LyricWord(b, e, w.toString()) }
|
||||
val line = if (timed.isNotEmpty()) timed.joinToString("") { it.text }.trim()
|
||||
else text.toString().replace(Regex("\\s+"), " ").trim()
|
||||
if (line.isNotEmpty()) lines.add(LyricLine(lineBegin ?: firstSpanBegin ?: -1, line, timed))
|
||||
}
|
||||
}
|
||||
}
|
||||
event = parser.next()
|
||||
}
|
||||
if (lines.isEmpty()) return null
|
||||
val synced = lines.all { it.timeMs >= 0 } && lines.any { it.timeMs > 0 }
|
||||
return if (synced) LyricsResult(lines.sortedBy { it.timeMs }, synced = true)
|
||||
else LyricsResult(lines.map { LyricLine(0, it.text) }, synced = false)
|
||||
}
|
||||
|
||||
private fun attr(parser: XmlPullParser, localName: String): String? =
|
||||
(0 until parser.attributeCount).firstOrNull { parser.getAttributeName(it) == localName }
|
||||
?.let { parser.getAttributeValue(it) }
|
||||
|
||||
/** TTML times: `1:23.456`, `1:02:03.4`, `83.456s`, `250ms` or a bare number of seconds. */
|
||||
private fun ttmlMs(raw: String): Long? {
|
||||
val t = raw.trim()
|
||||
val seconds = when {
|
||||
':' in t -> t.split(':').fold(0.0) { acc, part -> acc * 60 + (part.toDoubleOrNull() ?: return null) }
|
||||
t.endsWith("ms") -> (t.dropLast(2).toDoubleOrNull() ?: return null) / 1000
|
||||
t.endsWith("s") -> t.dropLast(1).toDoubleOrNull() ?: return null
|
||||
t.endsWith("m") -> (t.dropLast(1).toDoubleOrNull() ?: return null) * 60
|
||||
t.endsWith("h") -> (t.dropLast(1).toDoubleOrNull() ?: return null) * 3600
|
||||
else -> t.toDoubleOrNull() ?: return null
|
||||
}
|
||||
return (seconds * 1000).toLong()
|
||||
}
|
||||
|
||||
// --- LRCLIB ---
|
||||
|
||||
private fun exact(title: String, artist: String, album: String, durationMs: Long): LyricsResult? {
|
||||
if (artist.isBlank()) return null
|
||||
val query = buildString {
|
||||
@@ -111,6 +339,73 @@ object Lyrics {
|
||||
return LyricsResult(plain.lines().map { LyricLine(0, it) }, synced = false)
|
||||
}
|
||||
|
||||
// --- KuGou ---
|
||||
|
||||
/** Songs within this many seconds of the playing one count as the same recording. */
|
||||
private const val KUGOU_DURATION_SLACK = 8
|
||||
private val KUGOU_TIMED = Regex("""\[(\d\d):(\d\d)\.(\d{2,3})].*""")
|
||||
/** A credit line such as `[00:01.00]作词:…` or `[00:02.00]Composer: …`. */
|
||||
private val KUGOU_CREDIT = Regex(""".+].+[::].+""")
|
||||
private const val KUGOU_CREDIT_LINES = 30
|
||||
|
||||
private fun kuGou(title: String, artist: String, album: String, seconds: Int): LyricsResult? {
|
||||
val keyword = buildString {
|
||||
append(kuGouTitle(title)).append(" - ").append(kuGouArtist(artist))
|
||||
if (album.isNotBlank()) append(' ').append(album)
|
||||
}
|
||||
val encoded = URLEncoder.encode(keyword, "UTF-8").replace("+", "%20")
|
||||
// First the song itself, matched by length, then its lyrics by the song's hash.
|
||||
val songs = get(
|
||||
"https://mobileservice.kugou.com/api/v3/search/song?version=9108&plat=0&pagesize=8&showtype=0&keyword=$encoded",
|
||||
)?.let { JSONObject(it).optJSONObject("data")?.optJSONArray("info") }
|
||||
if (songs != null) for (i in 0 until songs.length()) {
|
||||
val song = songs.getJSONObject(i)
|
||||
if (seconds > 0 && abs(song.optInt("duration") - seconds) > KUGOU_DURATION_SLACK) continue
|
||||
val hash = song.optString("hash").takeIf { it.isNotBlank() } ?: continue
|
||||
kuGouCandidate("hash=" + enc(hash))?.let { return it }
|
||||
}
|
||||
// Then a plain lyrics search by name.
|
||||
val byName = "keyword=$encoded" + if (seconds > 0) "&duration=${seconds * 1000}" else ""
|
||||
return kuGouCandidate(byName)
|
||||
}
|
||||
|
||||
private fun kuGouCandidate(query: String): LyricsResult? {
|
||||
val body = get("https://lyrics.kugou.com/search?ver=1&man=yes&client=pc&$query") ?: return null
|
||||
val candidate = JSONObject(body).optJSONArray("candidates")?.optJSONObject(0) ?: return null
|
||||
val id = candidate.optLong("id").takeIf { it != 0L } ?: return null
|
||||
val accessKey = candidate.optString("accesskey").takeIf { it.isNotBlank() } ?: return null
|
||||
val download = get(
|
||||
"https://lyrics.kugou.com/download?fmt=lrc&charset=utf8&client=pc&ver=1&id=$id&accesskey=${enc(accessKey)}",
|
||||
) ?: return null
|
||||
val content = JSONObject(download).optString("content").takeIf { it.isNotBlank() } ?: return null
|
||||
val lrc = String(Base64.decode(content, Base64.DEFAULT), Charsets.UTF_8)
|
||||
val lines = parseLrc(kuGouTrim(lrc).joinToString("\n")).filter { it.text.isNotEmpty() }
|
||||
return if (lines.isEmpty()) null else LyricsResult(lines, synced = true)
|
||||
}
|
||||
|
||||
/** Keeps the timed lines and drops the songwriter and producer credits KuGou puts at either end. */
|
||||
private fun kuGouTrim(lrc: String): List<String> {
|
||||
val timed = lrc.lines().filter { it.matches(KUGOU_TIMED) }
|
||||
if (timed.isEmpty()) return timed
|
||||
val headLimit = minOf(KUGOU_CREDIT_LINES, timed.lastIndex)
|
||||
val head = (headLimit downTo 0).firstOrNull { timed[it].matches(KUGOU_CREDIT) }?.plus(1) ?: 0
|
||||
val rest = timed.drop(head)
|
||||
if (rest.isEmpty()) return rest
|
||||
val tailLimit = minOf(KUGOU_CREDIT_LINES, rest.lastIndex)
|
||||
val tail = (tailLimit downTo 0).firstOrNull { rest[rest.lastIndex - it].matches(KUGOU_CREDIT) }?.plus(1) ?: 0
|
||||
return rest.dropLast(tail)
|
||||
}
|
||||
|
||||
/** KuGou's titles leave out bracketed extras such as "(feat. …)" or "(Live)". */
|
||||
private fun kuGouTitle(title: String) = listOf("\\(.*\\)", "(.*)", "「.*」", "『.*』", "<.*>", "《.*》", "〈.*〉", "<.*>")
|
||||
.fold(title) { acc, pattern -> acc.replace(Regex(pattern), "") }.trim()
|
||||
|
||||
/** KuGou joins several artists with "、". */
|
||||
private fun kuGouArtist(artist: String) = artist.replace(", ", "、").replace(" & ", "、").replace(".", "")
|
||||
.replace(Regex("\\(.*\\)"), "").replace(Regex("(.*)"), "").trim()
|
||||
|
||||
// --- shared ---
|
||||
|
||||
/** `[01:23.45] words` - a line may carry several stamps when a chorus repeats. */
|
||||
private fun parseLrc(text: String): List<LyricLine> {
|
||||
val stamp = Regex("""\[(\d+):(\d+(?:\.\d+)?)]""")
|
||||
@@ -129,6 +424,7 @@ object Lyrics {
|
||||
connectTimeout = 5_000
|
||||
readTimeout = 8_000
|
||||
setRequestProperty("User-Agent", "Gearslip (car media host)")
|
||||
setRequestProperty("Accept", "application/json")
|
||||
}
|
||||
return try {
|
||||
if (connection.responseCode != 200) null else connection.inputStream.bufferedReader().readText()
|
||||
|
||||
@@ -26,7 +26,7 @@ object WorkedPrompt {
|
||||
private var loaded = false
|
||||
private val flow = MutableStateFlow<String?>(null)
|
||||
|
||||
/** The car's name while the ask is waiting for an answer, otherwise null. */
|
||||
/** The car's name while the ask is waiting for an answer (empty when the car didn't say), otherwise null. */
|
||||
fun pending(context: Context): StateFlow<String?> {
|
||||
if (!loaded) {
|
||||
loaded = true
|
||||
@@ -99,17 +99,14 @@ object WorkedPrompt {
|
||||
private fun saved(context: Context): JSONObject? =
|
||||
prefs(context).getString(KEY_CAR, null)?.let { runCatching { JSONObject(it) }.getOrNull() }
|
||||
|
||||
/** "your 2019 Clio", "your Renault Media Nav", or "your car" when it didn't say. */
|
||||
/** "2019 Clio", "Renault Media Nav", or empty when the car didn't say. */
|
||||
private fun carName(car: JSONObject): String {
|
||||
val model = car.optString("car").trim()
|
||||
val year = car.optString("year").trim().takeIf { it.isNotEmpty() && it != "0" }
|
||||
val unit = "${car.optString("make")} ${car.optString("model")}".trim()
|
||||
return when {
|
||||
model.isNotEmpty() -> "your " + listOfNotNull(year, model).joinToString(" ")
|
||||
unit.isNotEmpty() -> "your $unit"
|
||||
else -> "your car"
|
||||
model.isNotEmpty() -> listOfNotNull(year, model).joinToString(" ")
|
||||
else -> unit
|
||||
}
|
||||
}
|
||||
|
||||
const val TITLE_START = "Gearslip worked with "
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
@@ -11,6 +13,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.Build
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -44,6 +47,7 @@ internal object GearslipLinks {
|
||||
const val MATRIX = "https://matrix.to/#/!mf0_mgBd5KqKpc9FfX_wHp1jFCcwAtPrndIn_oGIyMk?via=matrix.org"
|
||||
const val SOURCE = "https://github.com/twinksoftware/gearslip"
|
||||
const val CARS = "$SOURCE/blob/main/CARS.md"
|
||||
const val TRANSLATE = "https://hosted.weblate.org/engage/gearslip/"
|
||||
|
||||
/** This version's release notes on GitHub; every release is tagged v plus its version name. */
|
||||
val RELEASE_NOTES = "$SOURCE/releases/tag/v${app.seb3thehacker.gearslip.BuildConfig.VERSION_NAME}"
|
||||
@@ -59,14 +63,14 @@ internal object GearslipLinks {
|
||||
runCatching {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("monero:$MONERO")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}.onFailure {
|
||||
Toast.makeText(context, "No Monero wallet app found. Copy the address instead.", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(context, context.getString(R.string.about_no_wallet), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun copyMonero(context: Context) {
|
||||
context.getSystemService(ClipboardManager::class.java)
|
||||
?.setPrimaryClip(ClipData.newPlainText("Monero address", MONERO))
|
||||
Toast.makeText(context, "Monero address copied", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.about_monero_copied), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +88,7 @@ internal fun DonateRows() {
|
||||
SettingsRow(
|
||||
"Liberapay",
|
||||
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.LIBERAPAY) },
|
||||
subtitle = "Give weekly or monthly",
|
||||
subtitle = stringResource(R.string.about_give_regularly),
|
||||
icon = Icons.Filled.Favorite,
|
||||
trailing = arrow,
|
||||
)
|
||||
@@ -92,16 +96,16 @@ internal fun DonateRows() {
|
||||
SettingsRow(
|
||||
"Buy Me a Coffee",
|
||||
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.BUY_ME_A_COFFEE) },
|
||||
subtitle = "Give once or monthly, by card. Buy Me a Coffee keeps 5%",
|
||||
subtitle = stringResource(R.string.about_bmac_sub),
|
||||
icon = Icons.Filled.Favorite,
|
||||
trailing = arrow,
|
||||
)
|
||||
if (GearslipLinks.PAYMENT_LINK.isNotEmpty()) {
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"Give once",
|
||||
stringResource(R.string.about_give_once),
|
||||
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.PAYMENT_LINK) },
|
||||
subtitle = "Any amount, by card",
|
||||
subtitle = stringResource(R.string.about_any_amount),
|
||||
icon = Icons.Filled.Favorite,
|
||||
trailing = arrow,
|
||||
)
|
||||
@@ -112,7 +116,7 @@ internal fun DonateRows() {
|
||||
SettingsRow(
|
||||
"Monero",
|
||||
Modifier.clickable { moneroOpen = true },
|
||||
subtitle = "Open your wallet or copy the address",
|
||||
subtitle = stringResource(R.string.about_monero_sub),
|
||||
icon = Icons.Filled.Favorite,
|
||||
trailing = arrow,
|
||||
)
|
||||
@@ -127,10 +131,10 @@ internal fun MoneroDialog(onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
|
||||
title = { Text("Support with Monero") },
|
||||
title = { Text(stringResource(R.string.about_monero_title)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text("Send any amount to this address from your Monero wallet.")
|
||||
Text(stringResource(R.string.about_monero_body))
|
||||
SelectionContainer {
|
||||
Text(
|
||||
GearslipLinks.MONERO,
|
||||
@@ -143,20 +147,20 @@ internal fun MoneroDialog(onDismiss: () -> Unit) {
|
||||
confirmButton = {
|
||||
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = { GearslipLinks.openMoneroWallet(context) }, Modifier.fillMaxWidth()) {
|
||||
Text("Open in wallet")
|
||||
Text(stringResource(R.string.about_open_wallet))
|
||||
}
|
||||
OutlinedButton(onClick = { GearslipLinks.copyMonero(context) }, Modifier.fillMaxWidth()) {
|
||||
Text("Copy address")
|
||||
Text(stringResource(R.string.about_copy_address))
|
||||
}
|
||||
TextButton(onClick = onDismiss, Modifier.fillMaxWidth()) {
|
||||
Text("Close")
|
||||
Text(stringResource(R.string.close))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** The About section's links: the chat room, the source code, and credits. */
|
||||
/** The About section's links: the chat room, translations, the source code, and credits. */
|
||||
@Composable
|
||||
internal fun AboutLinkRows() {
|
||||
val context = LocalContext.current
|
||||
@@ -168,25 +172,32 @@ internal fun AboutLinkRows() {
|
||||
)
|
||||
}
|
||||
SettingsRow(
|
||||
"Join the chat on Matrix",
|
||||
stringResource(R.string.about_matrix),
|
||||
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.MATRIX) },
|
||||
subtitle = "Talk with other drivers about cars, apps, and Gearslip",
|
||||
subtitle = stringResource(R.string.about_matrix_sub),
|
||||
icon = Icons.AutoMirrored.Filled.Send,
|
||||
trailing = arrow,
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"Source code",
|
||||
stringResource(R.string.about_translate),
|
||||
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.TRANSLATE) },
|
||||
subtitle = stringResource(R.string.about_translate_sub),
|
||||
icon = Icons.Filled.Edit,
|
||||
trailing = arrow,
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
stringResource(R.string.about_source),
|
||||
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.SOURCE) },
|
||||
subtitle = "Read or change Gearslip's code under the AGPL",
|
||||
subtitle = stringResource(R.string.about_source_sub),
|
||||
icon = Icons.Filled.Build,
|
||||
trailing = arrow,
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"Voice credits",
|
||||
subtitle = "Speech recognition by sherpa-onnx (Apache 2.0), with Kroko ASR by Banafo " +
|
||||
"(CC BY-SA 4.0), and Parakeet and Canary by NVIDIA (CC BY 4.0)",
|
||||
stringResource(R.string.about_voice_credits),
|
||||
subtitle = stringResource(R.string.about_voice_credits_sub),
|
||||
icon = Icons.Filled.Favorite,
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,16 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -24,27 +33,30 @@ fun AudioChoiceDialog(onUseBluetooth: () -> Unit, onKeepAsking: () -> Unit) {
|
||||
var showCommand by remember { mutableStateOf(false) }
|
||||
AlertDialog(
|
||||
onDismissRequest = onKeepAsking,
|
||||
title = { Text("Play car audio over Bluetooth?") },
|
||||
title = { Text(stringResource(R.string.audio_bt_title)) },
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(
|
||||
"Android treats music sent over USB like sharing your screen, so it asks every " +
|
||||
"drive. Over Bluetooth it never asks. You can change this later in Settings.",
|
||||
)
|
||||
Column(Modifier.verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(stringResource(R.string.audio_bt_body))
|
||||
if (showCommand) {
|
||||
Text(
|
||||
"Or keep USB and stop the question for good: plug the phone into a " +
|
||||
"computer with USB debugging on, and run this once.",
|
||||
stringResource(R.string.audio_keep_usb),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
CopyableCommand("adb shell appops set ${BuildConfig.APPLICATION_ID} PROJECT_MEDIA allow")
|
||||
} else {
|
||||
TextButton(onClick = { showCommand = true }) { Text("Never ask, using a computer") }
|
||||
}
|
||||
// Stacked full width: three labels this long never fit the dialog's one button row.
|
||||
Column(Modifier.fillMaxWidth().padding(top = 4.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = onUseBluetooth, Modifier.fillMaxWidth()) { Text(stringResource(R.string.audio_use_bt)) }
|
||||
OutlinedButton(onClick = onKeepAsking, Modifier.fillMaxWidth()) { Text(stringResource(R.string.audio_keep_asking)) }
|
||||
if (!showCommand) {
|
||||
TextButton(onClick = { showCommand = true }, Modifier.fillMaxWidth()) {
|
||||
Text(stringResource(R.string.audio_never_ask))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = onUseBluetooth) { Text("Use Bluetooth") } },
|
||||
dismissButton = { TextButton(onClick = onKeepAsking) { Text("Keep asking") } },
|
||||
confirmButton = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,15 +69,13 @@ fun AudioChoiceDialog(onUseBluetooth: () -> Unit, onKeepAsking: () -> Unit) {
|
||||
fun MicBlockedDialog(onOpenSettings: () -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Car audio needs the microphone permission") },
|
||||
title = { Text(stringResource(R.string.audio_mic_title)) },
|
||||
text = {
|
||||
Text(
|
||||
"Android lets an app send other apps' sound to the car only if it has the " +
|
||||
"microphone permission. It's turned off for Gearslip, so music plays on the " +
|
||||
"phone. Turn it on under Permissions, then Microphone.",
|
||||
stringResource(R.string.audio_mic_body),
|
||||
)
|
||||
},
|
||||
confirmButton = { TextButton(onClick = onOpenSettings) { Text("Open app settings") } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Not now") } },
|
||||
confirmButton = { TextButton(onClick = onOpenSettings) { Text(stringResource(R.string.audio_open_settings)) } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.worked_not_now)) } },
|
||||
)
|
||||
}
|
||||
@@ -71,6 +71,14 @@ fun CarPreviewScreen(onBack: () -> Unit) {
|
||||
|
||||
val projector = remember { ScreenProjector(context) }
|
||||
|
||||
// The preview is something to watch, like a map on a dash mount, so the phone stays awake on
|
||||
// it. Only here: the flag goes with this screen, and every other screen sleeps as usual.
|
||||
val view = androidx.compose.ui.platform.LocalView.current
|
||||
DisposableEffect(view) {
|
||||
view.keepScreenOn = true
|
||||
onDispose { view.keepScreenOn = false }
|
||||
}
|
||||
|
||||
DisposableEffect(width, height, density) {
|
||||
CarEnvironment.start(context)
|
||||
// No surface yet: the SurfaceView below hands its own over once it exists, and the
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -41,19 +43,18 @@ internal fun CarScreenSection() {
|
||||
|
||||
if (editingCar) {
|
||||
SizeDialog(
|
||||
title = "Car screen size",
|
||||
note = "Use this if the car's picture looks stretched. Enter the screen's real size, " +
|
||||
"such as 1280x480. It takes effect the next time you connect.",
|
||||
title = stringResource(R.string.screen_car_size),
|
||||
note = stringResource(R.string.screen_car_size_note),
|
||||
initial = size,
|
||||
clearLabel = "Use the car's",
|
||||
clearLabel = stringResource(R.string.screen_use_cars),
|
||||
onDismiss = { editingCar = false },
|
||||
onSave = { CarSettings.setScreenSize(it); editingCar = false },
|
||||
)
|
||||
}
|
||||
if (editingPreview) {
|
||||
SizeDialog(
|
||||
title = "Preview size",
|
||||
note = "The size the car preview draws at, such as 1920x720.",
|
||||
title = stringResource(R.string.screen_preview_size),
|
||||
note = stringResource(R.string.screen_preview_note),
|
||||
initial = previewCustom,
|
||||
clearLabel = null,
|
||||
onDismiss = { editingPreview = false },
|
||||
@@ -65,62 +66,62 @@ internal fun CarScreenSection() {
|
||||
}
|
||||
|
||||
SettingsSection(
|
||||
"Car screen",
|
||||
footer = "Gearslip uses the size the car reports unless you set one.",
|
||||
stringResource(R.string.screen_car_screen),
|
||||
footer = stringResource(R.string.screen_footer),
|
||||
) {
|
||||
SettingsRow(
|
||||
"Screen size",
|
||||
stringResource(R.string.screen_size),
|
||||
Modifier.clickable { editingCar = true },
|
||||
subtitle = size?.let { "$it, set by you" } ?: "From the car",
|
||||
subtitle = size?.let { stringResource(R.string.screen_set_by_you, it.toString()) } ?: stringResource(R.string.screen_from_car),
|
||||
)
|
||||
if (size != null) {
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"Crop the edges",
|
||||
stringResource(R.string.screen_crop),
|
||||
Modifier.selectable(fit == ScreenFit.CROP, role = Role.RadioButton) { CarSettings.setScreenFit(ScreenFit.CROP) },
|
||||
subtitle = "For a car that cuts off the top and bottom, or the sides",
|
||||
subtitle = stringResource(R.string.screen_crop_sub),
|
||||
trailing = { RadioButton(selected = fit == ScreenFit.CROP, onClick = null) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"Squash to fit",
|
||||
stringResource(R.string.screen_squash),
|
||||
Modifier.selectable(fit == ScreenFit.SQUASH, role = Role.RadioButton) { CarSettings.setScreenFit(ScreenFit.SQUASH) },
|
||||
subtitle = "For a car that squeezes the whole picture, so circles look like ovals",
|
||||
subtitle = stringResource(R.string.screen_squash_sub),
|
||||
trailing = { RadioButton(selected = fit == ScreenFit.SQUASH, onClick = null) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection("Car preview size") {
|
||||
SettingsSection(stringResource(R.string.screen_preview_title)) {
|
||||
SettingsRow(
|
||||
"Last car",
|
||||
stringResource(R.string.screen_last_car),
|
||||
Modifier.selectable(preview == PreviewSize.LAST_CAR, role = Role.RadioButton) {
|
||||
CarSettings.setPreviewSize(PreviewSize.LAST_CAR)
|
||||
},
|
||||
subtitle = "The size of the last car you connected to",
|
||||
subtitle = stringResource(R.string.screen_last_car_sub),
|
||||
trailing = { RadioButton(selected = preview == PreviewSize.LAST_CAR, onClick = null) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"This screen",
|
||||
stringResource(R.string.screen_this_screen),
|
||||
Modifier.selectable(preview == PreviewSize.FILL, role = Role.RadioButton) {
|
||||
CarSettings.setPreviewSize(PreviewSize.FILL)
|
||||
},
|
||||
subtitle = "Fills this device's screen, for a head unit running Gearslip itself",
|
||||
subtitle = stringResource(R.string.screen_this_screen_sub),
|
||||
trailing = { RadioButton(selected = preview == PreviewSize.FILL, onClick = null) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"Custom",
|
||||
stringResource(R.string.screen_custom),
|
||||
Modifier.selectable(preview == PreviewSize.CUSTOM, role = Role.RadioButton) {
|
||||
if (previewCustom == null) editingPreview = true else CarSettings.setPreviewSize(PreviewSize.CUSTOM)
|
||||
},
|
||||
subtitle = previewCustom?.toString() ?: "Enter a size",
|
||||
subtitle = previewCustom?.toString() ?: stringResource(R.string.screen_enter_size),
|
||||
trailing = { RadioButton(selected = preview == PreviewSize.CUSTOM, onClick = null) },
|
||||
)
|
||||
if (preview == PreviewSize.CUSTOM) {
|
||||
SettingsDivider()
|
||||
SettingsRow("Change the custom size", Modifier.clickable { editingPreview = true })
|
||||
SettingsRow(stringResource(R.string.screen_change_custom), Modifier.clickable { editingPreview = true })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,14 +146,14 @@ private fun SizeDialog(
|
||||
text = {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(note)
|
||||
OutlinedTextField(width, { width = it.filter(Char::isDigit) }, label = { Text("Width") }, singleLine = true, keyboardOptions = number)
|
||||
OutlinedTextField(height, { height = it.filter(Char::isDigit) }, label = { Text("Height") }, singleLine = true, keyboardOptions = number)
|
||||
OutlinedTextField(width, { width = it.filter(Char::isDigit) }, label = { Text(stringResource(R.string.screen_width)) }, singleLine = true, keyboardOptions = number)
|
||||
OutlinedTextField(height, { height = it.filter(Char::isDigit) }, label = { Text(stringResource(R.string.screen_height)) }, singleLine = true, keyboardOptions = number)
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = { onSave(parsed) }, enabled = parsed != null) { Text("Save") } },
|
||||
confirmButton = { TextButton(onClick = { onSave(parsed) }, enabled = parsed != null) { Text(stringResource(R.string.save)) } },
|
||||
dismissButton = {
|
||||
if (clearLabel != null) TextButton(onClick = { onSave(null) }) { Text(clearLabel) }
|
||||
else TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
else TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) }
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -80,7 +82,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
"Certificate Setup",
|
||||
stringResource(R.string.certsetup_title),
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
@@ -88,9 +90,9 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
if (installed) {
|
||||
"Cars accept only Google-issued certificates, and you have one loaded."
|
||||
stringResource(R.string.certsetup_loaded)
|
||||
} else {
|
||||
"Cars accept only Google-issued certificates. Gearslip doesn't include one, so choose where yours comes from."
|
||||
stringResource(R.string.certsetup_choose)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
@@ -100,14 +102,24 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
|
||||
if (installed) {
|
||||
Text(
|
||||
if (manualProgress) "Certificate imported." else "Certificate installed.",
|
||||
if (manualProgress) stringResource(R.string.cert_imported) else stringResource(R.string.certsetup_installed),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
// A downloaded certificate works in most cars, not all: say where to go when it doesn't.
|
||||
if (!manualProgress) {
|
||||
Text(
|
||||
stringResource(R.string.certsetup_installed_hint),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Button(onClick = onDone, modifier = Modifier.fillMaxWidth().height(56.dp)) {
|
||||
Text("Continue")
|
||||
Text(stringResource(R.string.certsetup_continue))
|
||||
}
|
||||
Spacer(Modifier.height(40.dp))
|
||||
return@Column
|
||||
@@ -130,11 +142,10 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp),
|
||||
) {
|
||||
Text("Download from aasdk")
|
||||
Text(stringResource(R.string.cert_download))
|
||||
}
|
||||
Text(
|
||||
"The open-source aasdk project publishes this certificate at github.com/opencardev/aasdk. " +
|
||||
"Most cars accept it; some from around 2020 on don't.",
|
||||
stringResource(R.string.certsetup_aasdk_sub),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -146,7 +157,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
Box(Modifier.fillMaxWidth().height(56.dp), contentAlignment = Alignment.Center) {
|
||||
DownloadingRow {
|
||||
CircularProgressIndicator(Modifier.size(20.dp), strokeWidth = 2.dp)
|
||||
Text("Downloading…", style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.cert_downloading), style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +168,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
Surface(color = MaterialTheme.colorScheme.errorContainer, shape = MaterialTheme.shapes.medium) {
|
||||
Column(Modifier.fillMaxWidth().padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
"Download failed",
|
||||
stringResource(R.string.certsetup_failed),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
@@ -168,7 +179,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
)
|
||||
Button(onClick = { downloadState = DownloadState.Idle }, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("Retry")
|
||||
Text(stringResource(R.string.certsetup_retry))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,9 +191,9 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
OutlinedButton(
|
||||
onClick = { importLauncher.launch(arrayOf("*/*")) },
|
||||
modifier = Modifier.fillMaxWidth().height(52.dp),
|
||||
) { Text("Import my own file") }
|
||||
) { Text(stringResource(R.string.cert_import)) }
|
||||
Text(
|
||||
"A .p12 file with its private key, from wherever you choose.",
|
||||
stringResource(R.string.certsetup_import_sub),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
@@ -197,7 +208,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().height(52.dp),
|
||||
) {
|
||||
Text("Skip car connection setup")
|
||||
Text(stringResource(R.string.certsetup_skip))
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(40.dp))
|
||||
@@ -216,7 +227,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
pendingUri = null
|
||||
manualProgress = true
|
||||
}.onFailure {
|
||||
reportError("Wrong password or not a valid PKCS#12 file with a private key.")
|
||||
reportError(context.getString(R.string.cert_import_failed))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -224,7 +235,7 @@ fun CertSetupScreen(onDone: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
/** A tight horizontal row for the inline "Downloading…" spinner state. */
|
||||
/** A tight horizontal row for the inline stringResource(R.string.cert_downloading) spinner state. */
|
||||
@Composable
|
||||
private fun DownloadingRow(content: @Composable RowScope.() -> Unit) {
|
||||
Row(
|
||||
@@ -248,20 +259,20 @@ private fun PasswordDialog(onDismiss: () -> Unit, onConfirm: (String, (String) -
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Certificate password") },
|
||||
title = { Text(stringResource(R.string.cert_password_title)) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = { password = it; error = null },
|
||||
singleLine = true,
|
||||
label = { Text("Password") },
|
||||
label = { Text(stringResource(R.string.cert_password)) },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
isError = error != null,
|
||||
supportingText = { Text(error ?: "Leave empty if the file has none.") },
|
||||
supportingText = { Text(error ?: stringResource(R.string.cert_password_hint)) },
|
||||
)
|
||||
},
|
||||
confirmButton = { TextButton(onClick = { onConfirm(password) { error = it } }) { Text("Import") } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } },
|
||||
confirmButton = { TextButton(onClick = { onConfirm(password) { error = it } }) { Text(stringResource(R.string.cert_import_button)) } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.content.Context
|
||||
import app.seb3thehacker.gearslip.CertProvider
|
||||
import java.text.DateFormat
|
||||
@@ -28,20 +29,20 @@ data class CertSummary(
|
||||
val identity = CertProvider.loadSupplied(context)
|
||||
?: return CertSummary(
|
||||
CertProvider.Kind.SELF_SIGNED,
|
||||
"None loaded (self-signed; cars reject it)",
|
||||
context.getString(R.string.cert_none),
|
||||
null, null, null,
|
||||
)
|
||||
val cert = identity.certificate
|
||||
val name = friendlyName(cert.subjectX500Principal.name)
|
||||
val prefix = when (identity.kind) {
|
||||
CertProvider.Kind.IMPORTED -> "Imported"
|
||||
CertProvider.Kind.DOWNLOADED -> "Downloaded"
|
||||
CertProvider.Kind.ADB_STAGED -> "Staged over adb"
|
||||
CertProvider.Kind.SELF_SIGNED -> "Self-signed"
|
||||
val headline = when (identity.kind) {
|
||||
CertProvider.Kind.IMPORTED -> R.string.cert_kind_imported
|
||||
CertProvider.Kind.DOWNLOADED -> R.string.cert_kind_downloaded
|
||||
CertProvider.Kind.ADB_STAGED -> R.string.cert_kind_adb
|
||||
CertProvider.Kind.SELF_SIGNED -> R.string.cert_kind_self
|
||||
}
|
||||
return CertSummary(
|
||||
identity.kind,
|
||||
"$prefix: $name",
|
||||
context.getString(headline, name),
|
||||
cert.subjectX500Principal.name,
|
||||
cert.issuerX500Principal.name,
|
||||
DateFormat.getDateInstance(DateFormat.MEDIUM).format(Date(cert.notAfter.time)),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -29,7 +31,7 @@ fun CopyableCommand(command: String) {
|
||||
shape = MaterialTheme.shapes.small,
|
||||
onClick = {
|
||||
clipboard.setText(AnnotatedString(command))
|
||||
Toast.makeText(context, "Command copied", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, context.getString(R.string.command_copied), Toast.LENGTH_SHORT).show()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
|
||||
) {
|
||||
@@ -41,7 +43,7 @@ fun CopyableCommand(command: String) {
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Text(
|
||||
"Tap to copy",
|
||||
stringResource(R.string.command_tap_copy),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -100,31 +102,30 @@ internal fun DonateDialog(live: Boolean) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { DonatePrompt.answer(context, donated = false) },
|
||||
icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
|
||||
title = { Text("Glad that drive worked") },
|
||||
title = { Text(stringResource(R.string.donate_title)) },
|
||||
text = {
|
||||
Text(
|
||||
"Gearslip is free, with no ads and no tracking. If it gets you on the road, your " +
|
||||
"support keeps it going.",
|
||||
stringResource(R.string.donate_body),
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = { give(GearslipLinks.LIBERAPAY) }, Modifier.fillMaxWidth()) {
|
||||
Text("Support on Liberapay")
|
||||
Text(stringResource(R.string.donate_liberapay))
|
||||
}
|
||||
OutlinedButton(onClick = { give(GearslipLinks.BUY_ME_A_COFFEE) }, Modifier.fillMaxWidth()) {
|
||||
Text("Buy me a coffee")
|
||||
Text(stringResource(R.string.donate_bmac))
|
||||
}
|
||||
if (GearslipLinks.PAYMENT_LINK.isNotEmpty()) {
|
||||
OutlinedButton(onClick = { give(GearslipLinks.PAYMENT_LINK) }, Modifier.fillMaxWidth()) {
|
||||
Text("Give once by card")
|
||||
Text(stringResource(R.string.donate_card))
|
||||
}
|
||||
}
|
||||
OutlinedButton(onClick = { monero = true }, Modifier.fillMaxWidth()) {
|
||||
Text("Monero")
|
||||
}
|
||||
TextButton(onClick = { DonatePrompt.answer(context, donated = false) }, Modifier.fillMaxWidth()) {
|
||||
Text("Not now")
|
||||
Text(stringResource(R.string.worked_not_now))
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.foundation.layout.windowInsetsTopHeight
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.background
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
@@ -37,7 +43,7 @@ import app.seb3thehacker.gearslip.BuildConfig
|
||||
import app.seb3thehacker.gearslip.CertProvider
|
||||
import app.seb3thehacker.gearslip.stats.UsageStats
|
||||
|
||||
private enum class Screen { HOME, LOGS, SETTINGS, CAR_PREVIEW, CERT_SETUP, SETUP_GUIDE, USAGE_NOTES, HELP, WHATS_NEW, RECOMMENDED_APPS, CAR_GROUP }
|
||||
private enum class Screen { HOME, LOGS, SETTINGS, CAR_PREVIEW, CERT_SETUP, SETUP_GUIDE, USAGE_NOTES, HELP, WHATS_NEW, RECOMMENDED_APPS, CAR_GROUP, SAVED_PLACES }
|
||||
|
||||
/** Three screens and a back stack of depth one: no navigation library needed. */
|
||||
@Composable
|
||||
@@ -95,15 +101,24 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.windowInsetsPadding(WindowInsets.statusBars)
|
||||
.consumeWindowInsets(WindowInsets.statusBars),
|
||||
) {
|
||||
if (BuildConfig.DEBUG && screen != Screen.CAR_PREVIEW) DebugBuildBanner()
|
||||
// On a debug build, Home's yellow header runs up behind the status bar, whose icons then go dark.
|
||||
val debugHeader = BuildConfig.DEBUG && screen == Screen.HOME
|
||||
val view = LocalView.current
|
||||
val darkTheme = gearslipDark()
|
||||
SideEffect {
|
||||
val window = (view.context as? Activity)?.window ?: return@SideEffect
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = debugHeader || !darkTheme
|
||||
}
|
||||
|
||||
Column(Modifier.weight(1f)) {
|
||||
Column(Modifier.fillMaxSize()) {
|
||||
Spacer(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.windowInsetsTopHeight(WindowInsets.statusBars)
|
||||
.background(if (debugHeader) DebugYellow else Color.Transparent),
|
||||
)
|
||||
|
||||
Column(Modifier.weight(1f).consumeWindowInsets(WindowInsets.statusBars)) {
|
||||
when (screen) {
|
||||
Screen.HOME -> HomeScreen(
|
||||
onOpenSettings = { settingsAtUsageNotes = false; screen = Screen.SETTINGS },
|
||||
@@ -112,7 +127,13 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
onOpenCarPreview = { backTo = Screen.HOME; screen = Screen.CAR_PREVIEW },
|
||||
onOpenLogs = { backTo = Screen.HOME; screen = Screen.LOGS },
|
||||
onDisconnect = onDisconnect,
|
||||
onOpenCarGroup = { carGroup = it; screen = Screen.CAR_GROUP },
|
||||
onOpenCarGroup = {
|
||||
if (it == CarGroup.PLACES) {
|
||||
screen = Screen.SAVED_PLACES
|
||||
} else {
|
||||
carGroup = it; screen = Screen.CAR_GROUP
|
||||
}
|
||||
},
|
||||
)
|
||||
Screen.CAR_GROUP -> CarGroupScreen(carGroup, onBack = { screen = Screen.HOME })
|
||||
Screen.WHATS_NEW -> WhatsNewScreen(onBack = { screen = backTo })
|
||||
@@ -124,6 +145,7 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
atUsageNotes = settingsAtUsageNotes,
|
||||
)
|
||||
Screen.RECOMMENDED_APPS -> RecommendedAppsScreen(onBack = { screen = Screen.SETTINGS })
|
||||
Screen.SAVED_PLACES -> SavedPlacesScreen(onBack = { screen = Screen.HOME })
|
||||
Screen.CAR_PREVIEW -> CarPreviewScreen(onBack = { screen = backTo })
|
||||
Screen.HELP -> HelpScreen(
|
||||
onBack = { screen = backTo },
|
||||
@@ -150,26 +172,3 @@ fun GearslipApp(onDisconnect: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
PhoneTypingBar()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug builds only ([BuildConfig.DEBUG]); a release build never composes this. Sits above every
|
||||
* phone screen, below the status bar - not the car screen, which is a separate render target
|
||||
* ([app.seb3thehacker.gearslip.car.CarUi] carries its own corner badge) - so a debug install is
|
||||
* never mistaken for a release one.
|
||||
*/
|
||||
@Composable
|
||||
private fun DebugBuildBanner() {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth().height(28.dp),
|
||||
color = Color(0xFFF5C518),
|
||||
contentColor = Color.Black,
|
||||
) {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
"DEBUG BUILD",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,32 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import app.seb3thehacker.gearslip.car.theme.withTintedSurfaces
|
||||
|
||||
/**
|
||||
* Material You: the palette comes from the wallpaper (minSdk 35, so dynamic color always exists),
|
||||
* with the same accent tint over the surfaces as the car screen, so the two look like one app.
|
||||
* with the same surfaces as the car screen, so the two look like one app.
|
||||
*/
|
||||
@Composable
|
||||
fun GearslipTheme(content: @Composable () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dark = isSystemInDarkTheme()
|
||||
val scheme = (if (dark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)).withTintedSurfaces(dark)
|
||||
val dark = gearslipDark()
|
||||
// The car's Dark mode setting, so the phone and the car screen stay one look.
|
||||
val darkStyle by app.seb3thehacker.gearslip.car.CarSettings.darkStyle.collectAsState()
|
||||
val scheme = (if (dark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)).withTintedSurfaces(dark, darkStyle)
|
||||
MaterialTheme(colorScheme = scheme, content = content)
|
||||
}
|
||||
|
||||
/** Whether the phone app is dark: the car's App theme setting, so Light means light everywhere. */
|
||||
@Composable
|
||||
fun gearslipDark(): Boolean {
|
||||
val theme by app.seb3thehacker.gearslip.car.CarSettings.appTheme.collectAsState()
|
||||
return when (theme) {
|
||||
app.seb3thehacker.gearslip.car.AppTheme.PHONE -> isSystemInDarkTheme()
|
||||
app.seb3thehacker.gearslip.car.AppTheme.LIGHT -> false
|
||||
app.seb3thehacker.gearslip.car.AppTheme.DARK -> true
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.BuildConfig
|
||||
import app.seb3thehacker.gearslip.car.CarSettings
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -40,54 +43,48 @@ import androidx.compose.ui.unit.dp
|
||||
* that can, in one tap, in which case it is shown as a button instead.
|
||||
*/
|
||||
private class Step(
|
||||
val action: String,
|
||||
val detail: String? = null,
|
||||
@param:StringRes val action: Int,
|
||||
@param:StringRes val detail: Int? = null,
|
||||
val command: String? = null,
|
||||
val buttonLabel: String? = null,
|
||||
@param:StringRes val buttonLabel: Int? = null,
|
||||
val onClick: (() -> Unit)? = null,
|
||||
)
|
||||
|
||||
private val FIRST_DRIVE = listOf(
|
||||
Step("Turn off Android Auto.", "Disable or uninstall the Android Auto app, so the car talks to Gearslip instead."),
|
||||
Step("Load a certificate.", "In Settings, download one or import your own."),
|
||||
Step(R.string.help_aa_off, R.string.help_aa_off_detail),
|
||||
Step(R.string.help_cert, R.string.help_cert_detail),
|
||||
Step(
|
||||
"Allow the microphone.",
|
||||
"Android won't let an app pass music to the car without it. Gearslip listens only when " +
|
||||
"you tap the mic or press the talk button on your wheel.",
|
||||
R.string.help_mic,
|
||||
R.string.help_mic_detail,
|
||||
),
|
||||
)
|
||||
|
||||
private fun inTheCar(onRequestCallScreening: () -> Unit) = listOf(
|
||||
Step(
|
||||
"Plug in, or connect wirelessly.",
|
||||
"Gearslip opens by itself when the car connects. For wireless, pair the phone with the " +
|
||||
"car first, then tap Connect wirelessly on Home.",
|
||||
R.string.help_plug_in,
|
||||
R.string.help_plug_in_detail,
|
||||
),
|
||||
Step(
|
||||
"Pair over Bluetooth for calls.",
|
||||
"Calls play through the car once your phone and the car are paired. Gearslip offers to " +
|
||||
"pair the first time you plug in.",
|
||||
R.string.help_pair,
|
||||
R.string.help_pair_detail,
|
||||
),
|
||||
Step(
|
||||
"Answer calls from the car screen.",
|
||||
"Set Gearslip as your caller ID and spam app. Without it, calls still ring, but only " +
|
||||
"the phone can answer them.",
|
||||
buttonLabel = "Set up",
|
||||
R.string.help_answer,
|
||||
R.string.help_answer_detail,
|
||||
buttonLabel = R.string.guide_set_up,
|
||||
onClick = onRequestCallScreening,
|
||||
),
|
||||
)
|
||||
|
||||
private val USB_AUDIO = listOf(
|
||||
Step(
|
||||
"Stop the audio prompt each drive.",
|
||||
"Connect the phone to a computer with USB debugging on, and run this once. Skip it and " +
|
||||
"you tap Start each time you set off.",
|
||||
R.string.help_prompt,
|
||||
R.string.help_prompt_detail,
|
||||
command = "adb shell appops set ${BuildConfig.APPLICATION_ID} PROJECT_MEDIA allow",
|
||||
),
|
||||
Step(
|
||||
"Keep notifications readable.",
|
||||
"Android hides what notifications say while it shares audio. Run this too, and Gearslip " +
|
||||
"lifts that only while music goes to the car.",
|
||||
R.string.help_readable,
|
||||
R.string.help_readable_detail,
|
||||
command = "adb shell pm grant ${BuildConfig.APPLICATION_ID} android.permission.WRITE_SECURE_SETTINGS",
|
||||
),
|
||||
)
|
||||
@@ -104,10 +101,10 @@ fun HelpScreen(onBack: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("How to connect") },
|
||||
title = { Text(stringResource(R.string.help_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -122,21 +119,20 @@ fun HelpScreen(onBack: () -> Unit, onRequestCallScreening: () -> Unit) {
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
Spacer(Modifier.height(0.dp))
|
||||
SettingsSection("Which cars work") {
|
||||
SettingsSection(stringResource(R.string.help_which_cars)) {
|
||||
Text(
|
||||
"Most cars work. Starting around 2020, some cars added newer security " +
|
||||
"standards that reject Gearslip's certificate, and Gearslip can't run in those cars.",
|
||||
stringResource(R.string.help_which_cars_body),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
|
||||
)
|
||||
FilledTonalButton(
|
||||
onClick = { GearslipLinks.open(context, GearslipLinks.CARS) },
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) { Text("See the full list") }
|
||||
) { Text(stringResource(R.string.help_full_list)) }
|
||||
}
|
||||
Steps("Before your first drive", FIRST_DRIVE)
|
||||
Steps("In the car", car)
|
||||
if (usb) Steps("USB audio", USB_AUDIO)
|
||||
Steps(stringResource(R.string.help_first_drive), FIRST_DRIVE)
|
||||
Steps(stringResource(R.string.help_in_car), car)
|
||||
if (usb) Steps(stringResource(R.string.help_usb_audio), USB_AUDIO)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
@@ -160,14 +156,14 @@ private fun Steps(title: String, steps: List<Step>) {
|
||||
modifier = Modifier.width(16.dp),
|
||||
)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
Text(step.action, style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(step.action), style = MaterialTheme.typography.bodyLarge, fontWeight = FontWeight.SemiBold)
|
||||
step.detail?.let {
|
||||
Text(it, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(it), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
step.command?.let { CopyableCommand(it) }
|
||||
if (step.onClick != null) {
|
||||
FilledTonalButton(onClick = step.onClick, modifier = Modifier.padding(top = 4.dp)) {
|
||||
Text(step.buttonLabel ?: "Open")
|
||||
Text(stringResource(step.buttonLabel ?: R.string.guide_open))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.theme.DarkStyle
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -37,23 +42,38 @@ import app.seb3thehacker.gearslip.car.Units
|
||||
import app.seb3thehacker.gearslip.car.theme.GsThemes
|
||||
|
||||
/** The car's setting groups, each opening its own screen from the phone's home screen. */
|
||||
enum class CarGroup(val title: String) {
|
||||
SCREEN("Screen"),
|
||||
NAV_BAR("Nav bar and player"),
|
||||
SOUND("Sound and voice"),
|
||||
WARNINGS("Warnings"),
|
||||
enum class CarGroup(@param:StringRes val title: Int) {
|
||||
SCREEN(R.string.car_group_screen),
|
||||
NAV_BAR(R.string.car_group_nav_bar),
|
||||
SOUND(R.string.car_group_sound),
|
||||
PLACES(R.string.places_title),
|
||||
WARNINGS(R.string.car_group_warnings),
|
||||
MORE(R.string.car_group_more),
|
||||
}
|
||||
|
||||
private val sizeChoices = listOf(1f to "Normal", 1.25f to "Large", 1.5f to "Extra large")
|
||||
private val themeChoices = listOf(AppTheme.PHONE to "Follow phone", AppTheme.LIGHT to "Light", AppTheme.DARK to "Dark")
|
||||
private val nightChoices = listOf(NightMode.AUTO to "Auto", NightMode.DAY to "Day", NightMode.NIGHT to "Night")
|
||||
private val unitChoices = listOf(Units.AUTO to "Auto", Units.IMPERIAL to "Imperial", Units.METRIC to "Metric")
|
||||
private val navBarChoices = listOf(
|
||||
NavBarMode.SHOW to "Always show",
|
||||
NavBarMode.HIDE_NAVIGATING to "Hide while navigating",
|
||||
NavBarMode.HIDE to "Hide until tapped",
|
||||
@Composable
|
||||
private fun sizeChoices() = listOf(1f to stringResource(R.string.choice_size_normal), 1.25f to stringResource(R.string.choice_size_large), 1.5f to stringResource(R.string.choice_size_xlarge))
|
||||
@Composable
|
||||
private fun themeChoices() = listOf(AppTheme.PHONE to stringResource(R.string.choice_theme_phone), AppTheme.LIGHT to stringResource(R.string.choice_theme_light), AppTheme.DARK to stringResource(R.string.choice_theme_dark))
|
||||
@Composable
|
||||
private fun darkChoices() = listOf(
|
||||
DarkStyle.BLACK to stringResource(R.string.choice_dark_black),
|
||||
DarkStyle.BLACK_COLOUR to stringResource(R.string.choice_dark_black_colour),
|
||||
DarkStyle.DEEP_COLOUR to stringResource(R.string.choice_dark_deep_colour),
|
||||
DarkStyle.GREY to stringResource(R.string.choice_dark_grey),
|
||||
)
|
||||
private fun openChoices() = listOf(CarSettings.HOME to "Home") + CarApps.all.filter { it.id != "settings" }.map { it.id to it.label }
|
||||
@Composable
|
||||
private fun nightChoices() = listOf(NightMode.AUTO to stringResource(R.string.choice_auto), NightMode.DAY to stringResource(R.string.choice_night_day), NightMode.NIGHT to stringResource(R.string.choice_night_night))
|
||||
@Composable
|
||||
private fun unitChoices() = listOf(Units.AUTO to stringResource(R.string.choice_auto), Units.IMPERIAL to stringResource(R.string.choice_units_imperial), Units.METRIC to stringResource(R.string.choice_units_metric))
|
||||
@Composable
|
||||
private fun navBarChoices() = listOf(
|
||||
NavBarMode.SHOW to stringResource(R.string.choice_nav_show),
|
||||
NavBarMode.HIDE_NAVIGATING to stringResource(R.string.choice_nav_hide_navigating),
|
||||
NavBarMode.HIDE to stringResource(R.string.choice_nav_hide),
|
||||
)
|
||||
@Composable
|
||||
private fun openChoices() = listOf(CarSettings.HOME to stringResource(R.string.choice_open_home)) + CarApps.all.filter { it.id != "settings" }.map { it.id to stringResource(it.labelRes) }
|
||||
|
||||
/**
|
||||
* The car's settings on the phone's home screen: one card per group with a summary of where it
|
||||
@@ -71,20 +91,44 @@ internal fun HomeCarSettings(onOpen: (CarGroup) -> Unit, modifier: Modifier = Mo
|
||||
val locationWarning by CarSettings.locationWarning.collectAsState()
|
||||
val playerLeft by CarSettings.playerLeft.collectAsState()
|
||||
val theme by CarSettings.appTheme.collectAsState()
|
||||
val experimental by CarSettings.experimentalFeaturesEnabled.collectAsState()
|
||||
|
||||
val opens = openChoices()
|
||||
val sizes = sizeChoices()
|
||||
val themes = themeChoices()
|
||||
val navBars = navBarChoices()
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
fun str(id: Int) = context.getString(id)
|
||||
// Read again each time Home shows, so a place saved a moment ago is listed.
|
||||
val places = androidx.compose.runtime.remember { app.seb3thehacker.gearslip.car.SavedPlaces.all(context) }
|
||||
fun summary(group: CarGroup) = when (group) {
|
||||
CarGroup.SCREEN -> listOf(openChoices().label(open), sizeChoices.label(scale), themeChoices.label(theme))
|
||||
CarGroup.NAV_BAR -> listOf(navBarChoices.label(navBar), "Player on the ${if (playerLeft) "left" else "right"}")
|
||||
CarGroup.SOUND -> listOf(if (pipeAudio) "USB audio" else "Bluetooth audio", "Directions ${if (speakDirections) "on" else "off"}")
|
||||
CarGroup.SCREEN -> listOf(opens.label(open), sizes.label(scale), themes.label(theme))
|
||||
CarGroup.NAV_BAR -> listOf(navBars.label(navBar), str(if (playerLeft) R.string.summary_player_left else R.string.summary_player_right))
|
||||
CarGroup.SOUND -> listOf(
|
||||
str(if (pipeAudio) R.string.summary_usb_audio else R.string.summary_bluetooth_audio),
|
||||
str(if (speakDirections) R.string.summary_directions_on else R.string.summary_directions_off),
|
||||
)
|
||||
CarGroup.PLACES -> places.mapIndexedNotNull { i, place ->
|
||||
when {
|
||||
place.address.isBlank() -> null
|
||||
i == app.seb3thehacker.gearslip.car.SavedPlaces.HOME -> str(R.string.places_home)
|
||||
i == app.seb3thehacker.gearslip.car.SavedPlaces.WORK -> str(R.string.places_work)
|
||||
else -> place.label.ifBlank { context.getString(R.string.places_other, i - app.seb3thehacker.gearslip.car.SavedPlaces.WORK) }
|
||||
}
|
||||
}.ifEmpty { listOf(str(R.string.places_sub)) }
|
||||
CarGroup.WARNINGS -> listOf(
|
||||
"Bluetooth ${if (bluetoothWarning && !pipeAudio) "on" else "off"}",
|
||||
"Location ${if (locationWarning) "on" else "off"}",
|
||||
str(if (bluetoothWarning && !pipeAudio) R.string.summary_bluetooth_on else R.string.summary_bluetooth_off),
|
||||
str(if (locationWarning) R.string.summary_location_on else R.string.summary_location_off),
|
||||
)
|
||||
CarGroup.MORE -> listOf(
|
||||
str(R.string.web_page),
|
||||
str(if (experimental) R.string.summary_experimental_shown else R.string.summary_experimental_hidden),
|
||||
)
|
||||
}.filter { it.isNotEmpty() }.joinToString(" · ")
|
||||
|
||||
Column(modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(
|
||||
"Car",
|
||||
stringResource(R.string.home_car),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
@@ -94,7 +138,7 @@ internal fun HomeCarSettings(onOpen: (CarGroup) -> Unit, modifier: Modifier = Mo
|
||||
CarGroup.entries.forEachIndexed { i, group ->
|
||||
if (i > 0) SettingsDivider()
|
||||
SettingsRow(
|
||||
group.title,
|
||||
stringResource(group.title),
|
||||
Modifier.clickable { onOpen(group) },
|
||||
subtitle = summary(group),
|
||||
trailing = { Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = null) },
|
||||
@@ -112,10 +156,10 @@ internal fun CarGroupScreen(group: CarGroup, onBack: () -> Unit) {
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
androidx.compose.material3.TopAppBar(
|
||||
title = { Text(group.title) },
|
||||
title = { Text(stringResource(group.title)) },
|
||||
navigationIcon = {
|
||||
androidx.compose.material3.IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -127,8 +171,18 @@ internal fun CarGroupScreen(group: CarGroup, onBack: () -> Unit) {
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
SettingsCard { GroupRows(group) }
|
||||
// The bigger pieces each keep their own card: the car screen's size and the preview's,
|
||||
// and the two voice models with their downloads.
|
||||
when (group) {
|
||||
CarGroup.SCREEN -> CarScreenSection()
|
||||
CarGroup.SOUND -> {
|
||||
CarAudioSection()
|
||||
VoiceModelSection()
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
Text(
|
||||
"These are the same settings as Settings in the car. A change here shows there too.",
|
||||
stringResource(R.string.car_group_same),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
@@ -152,55 +206,99 @@ private fun GroupRows(group: CarGroup) {
|
||||
val autoplay by CarSettings.autoplay.collectAsState()
|
||||
val playerTitle by CarSettings.playerTitle.collectAsState()
|
||||
val playerLeft by CarSettings.playerLeft.collectAsState()
|
||||
val moreLyrics by CarSettings.moreLyrics.collectAsState()
|
||||
val theme by CarSettings.appTheme.collectAsState()
|
||||
val uiTheme by CarSettings.uiTheme.collectAsState()
|
||||
val darkStyle by CarSettings.darkStyle.collectAsState()
|
||||
val experimental by CarSettings.experimentalFeaturesEnabled.collectAsState()
|
||||
val carSensors by CarSettings.carSensorsEnabled.collectAsState()
|
||||
val adaptiveVideo by CarSettings.adaptiveVideoEnabled.collectAsState()
|
||||
val clusterVideo by CarSettings.clusterVideoEnabled.collectAsState()
|
||||
|
||||
when (group) {
|
||||
CarGroup.SCREEN -> {
|
||||
Choice("Open on connect", openChoices(), open, CarSettings::setOpenOnConnect)
|
||||
Choice(stringResource(R.string.car_open_on_connect), openChoices(), open, CarSettings::setOpenOnConnect)
|
||||
SettingsDivider()
|
||||
Choice("Size", sizeChoices, scale, CarSettings::setScale)
|
||||
Choice(stringResource(R.string.car_size), sizeChoices(), scale, CarSettings::setScale)
|
||||
SettingsDivider()
|
||||
Choice("App theme", themeChoices, theme, CarSettings::setAppTheme)
|
||||
Choice(stringResource(R.string.car_app_theme), themeChoices(), theme, CarSettings::setAppTheme)
|
||||
SettingsDivider()
|
||||
Choice("Buttons", GsThemes.builtIn.map { it.id to it.name }, uiTheme, CarSettings::setUiTheme)
|
||||
SettingsDivider()
|
||||
Choice("Map day/night", nightChoices, night, CarSettings::setNightMode)
|
||||
SettingsDivider()
|
||||
Choice("Units", unitChoices, units, CarSettings::setUnits)
|
||||
}
|
||||
CarGroup.NAV_BAR -> {
|
||||
Choice("Nav bar", navBarChoices, navBar, CarSettings::setNavBarMode)
|
||||
SettingsDivider()
|
||||
Choice("Player in the nav bar", listOf(false to "Skip buttons", true to "Song title"), playerTitle, CarSettings::setPlayerTitle)
|
||||
SettingsDivider()
|
||||
Choice("Player beside the map", listOf(false to "Right", true to "Left"), playerLeft, CarSettings::setPlayerLeft)
|
||||
SettingsDivider()
|
||||
Toggle("Autoplay on connect", autoplay, CarSettings::setAutoplay)
|
||||
}
|
||||
CarGroup.SOUND -> {
|
||||
Choice("Car audio", listOf(false to "Bluetooth", true to "USB"), pipeAudio, CarSettings::setPipeAudio)
|
||||
SettingsDivider()
|
||||
Toggle(
|
||||
"Spoken directions",
|
||||
speakDirections,
|
||||
CarSettings::setSpeakDirections,
|
||||
note = "Turn it off if your map app's own voice already plays in the car.",
|
||||
)
|
||||
SettingsDivider()
|
||||
Toggle(
|
||||
"Use the phone's speech recognition",
|
||||
systemSpeech,
|
||||
CarSettings::setSystemSpeech,
|
||||
note = "On most phones that's Google's, which may send your voice to Google.",
|
||||
)
|
||||
}
|
||||
CarGroup.WARNINGS -> {
|
||||
if (!pipeAudio) {
|
||||
Toggle("Warn when Bluetooth isn't connected", bluetoothWarning, CarSettings::setBluetoothWarning)
|
||||
if (theme != AppTheme.LIGHT) {
|
||||
Choice(stringResource(R.string.car_dark_mode), darkChoices(), darkStyle, CarSettings::setDarkStyle)
|
||||
SettingsDivider()
|
||||
}
|
||||
Toggle("Warn when location is off", locationWarning, CarSettings::setLocationWarning)
|
||||
Choice(stringResource(R.string.car_buttons), GsThemes.builtIn.map { it.id to it.name }, uiTheme, CarSettings::setUiTheme)
|
||||
SettingsDivider()
|
||||
Choice(stringResource(R.string.car_map_day_night), nightChoices(), night, CarSettings::setNightMode)
|
||||
SettingsDivider()
|
||||
Choice(stringResource(R.string.car_units), unitChoices(), units, CarSettings::setUnits)
|
||||
}
|
||||
CarGroup.NAV_BAR -> {
|
||||
Choice(stringResource(R.string.car_nav_bar), navBarChoices(), navBar, CarSettings::setNavBarMode)
|
||||
SettingsDivider()
|
||||
Choice(stringResource(R.string.car_player_nav_bar), listOf(false to stringResource(R.string.car_skip_buttons), true to stringResource(R.string.car_song_title)), playerTitle, CarSettings::setPlayerTitle)
|
||||
SettingsDivider()
|
||||
Choice(stringResource(R.string.car_player_beside_map), listOf(false to stringResource(R.string.car_right), true to stringResource(R.string.car_left)), playerLeft, CarSettings::setPlayerLeft)
|
||||
SettingsDivider()
|
||||
Toggle(stringResource(R.string.car_autoplay), autoplay, CarSettings::setAutoplay)
|
||||
SettingsDivider()
|
||||
Toggle(
|
||||
stringResource(R.string.car_more_lyrics),
|
||||
moreLyrics,
|
||||
CarSettings::setMoreLyrics,
|
||||
note = stringResource(R.string.car_more_lyrics_note),
|
||||
)
|
||||
}
|
||||
CarGroup.SOUND -> {
|
||||
Toggle(
|
||||
stringResource(R.string.car_spoken_directions),
|
||||
speakDirections,
|
||||
CarSettings::setSpeakDirections,
|
||||
note = stringResource(R.string.car_spoken_directions_note),
|
||||
)
|
||||
SettingsDivider()
|
||||
Toggle(
|
||||
stringResource(R.string.car_phone_speech),
|
||||
systemSpeech,
|
||||
CarSettings::setSystemSpeech,
|
||||
note = stringResource(R.string.car_phone_speech_note),
|
||||
)
|
||||
}
|
||||
CarGroup.PLACES -> Unit
|
||||
CarGroup.WARNINGS -> {
|
||||
if (!pipeAudio) {
|
||||
Toggle(stringResource(R.string.car_warn_bluetooth), bluetoothWarning, CarSettings::setBluetoothWarning)
|
||||
SettingsDivider()
|
||||
}
|
||||
Toggle(stringResource(R.string.car_warn_location), locationWarning, CarSettings::setLocationWarning)
|
||||
}
|
||||
CarGroup.MORE -> {
|
||||
WebPageRow()
|
||||
SettingsDivider()
|
||||
Toggle(stringResource(R.string.car_experimental), experimental, CarSettings::setExperimentalFeaturesEnabled)
|
||||
if (experimental) {
|
||||
SettingsDivider()
|
||||
Toggle(
|
||||
stringResource(R.string.car_vehicle_data),
|
||||
carSensors,
|
||||
CarSettings::setCarSensorsEnabled,
|
||||
note = stringResource(R.string.car_vehicle_data_note),
|
||||
)
|
||||
SettingsDivider()
|
||||
Toggle(
|
||||
stringResource(R.string.car_adaptive_video),
|
||||
adaptiveVideo,
|
||||
CarSettings::setAdaptiveVideoEnabled,
|
||||
note = stringResource(R.string.car_adaptive_video_note),
|
||||
)
|
||||
SettingsDivider()
|
||||
Toggle(
|
||||
stringResource(R.string.car_cluster_video),
|
||||
clusterVideo,
|
||||
CarSettings::setClusterVideoEnabled,
|
||||
note = stringResource(R.string.car_cluster_video_note),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -243,3 +341,54 @@ private fun Toggle(title: String, on: Boolean, onChange: (Boolean) -> Unit, note
|
||||
trailing = { Switch(checked = on, onCheckedChange = null) },
|
||||
)
|
||||
}
|
||||
|
||||
/** The page the car's Browser app opens, typed here instead of on the car's keyboard. */
|
||||
@Composable
|
||||
private fun WebPageRow() {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
var url by remember { mutableStateOf(app.seb3thehacker.gearslip.AppSettings.startupUrl(context)) }
|
||||
Column(Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 12.dp)) {
|
||||
androidx.compose.material3.OutlinedTextField(
|
||||
url,
|
||||
{
|
||||
url = it
|
||||
app.seb3thehacker.gearslip.AppSettings.setStartupUrl(context, it)
|
||||
},
|
||||
label = { Text(stringResource(R.string.web_page)) },
|
||||
placeholder = { Text("DuckDuckGo") },
|
||||
singleLine = true,
|
||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(keyboardType = androidx.compose.ui.text.input.KeyboardType.Uri),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.web_page_note),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 6.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** How music reaches the car: by Bluetooth as usual, or through Gearslip over USB. Same choice as setup. */
|
||||
@Composable
|
||||
internal fun CarAudioSection() {
|
||||
val usb by CarSettings.pipeAudio.collectAsState()
|
||||
SettingsSection(
|
||||
stringResource(R.string.car_audio),
|
||||
footer = stringResource(if (usb) R.string.car_audio_usb_footer else R.string.car_audio_bt_footer),
|
||||
) {
|
||||
SettingsRow(
|
||||
stringResource(R.string.car_audio_bt),
|
||||
Modifier.selectable(!usb, role = androidx.compose.ui.semantics.Role.RadioButton) { CarSettings.setPipeAudio(false) },
|
||||
subtitle = stringResource(R.string.car_audio_bt_sub),
|
||||
trailing = { androidx.compose.material3.RadioButton(selected = !usb, onClick = null) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
stringResource(R.string.guide_audio_usb),
|
||||
Modifier.selectable(usb, role = androidx.compose.ui.semantics.Role.RadioButton) { CarSettings.setPipeAudio(true) },
|
||||
subtitle = stringResource(R.string.car_audio_usb_sub),
|
||||
trailing = { androidx.compose.material3.RadioButton(selected = usb, onClick = null) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.speech.SpeechModels
|
||||
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
@@ -19,7 +21,6 @@ import androidx.compose.foundation.layout.size
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import app.seb3thehacker.gearslip.car.CarSettings
|
||||
import app.seb3thehacker.gearslip.car.BluetoothAudioCheck
|
||||
import android.provider.Settings
|
||||
@@ -82,7 +83,12 @@ fun HomeScreen(
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.padding(padding),
|
||||
) {
|
||||
HomeHeader(onOpenSettings)
|
||||
Column(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
Column(
|
||||
@@ -90,30 +96,6 @@ fun HomeScreen(
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Spacer(Modifier.height(24.dp))
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
"Gearslip",
|
||||
style = MaterialTheme.typography.displayLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
IconButton(
|
||||
onClick = onOpenSettings,
|
||||
modifier = Modifier.size(56.dp),
|
||||
) {
|
||||
Icon(Icons.Filled.Settings, contentDescription = "Settings", modifier = Modifier.size(40.dp))
|
||||
}
|
||||
}
|
||||
Text(
|
||||
"v${LocalContext.current.appVersionName()}",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
StatusCard(status)
|
||||
if (live) BluetoothMissingCard()
|
||||
@@ -134,19 +116,20 @@ fun HomeScreen(
|
||||
// Sits at the bottom, where a thumb reaches it.
|
||||
Column(Modifier.padding(vertical = 16.dp)) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
HomeShortcut(Icons.Filled.Info, "Help", onOpenHelp, Modifier.weight(1f))
|
||||
HomeShortcut(Icons.Filled.Info, stringResource(R.string.home_help), onOpenHelp, Modifier.weight(1f))
|
||||
// The preview draws the car screen on the phone, so it waits until no car is using it.
|
||||
if (!live) HomeShortcut(Icons.Filled.PlayArrow, "Preview", onOpenCarPreview, Modifier.weight(1f))
|
||||
HomeShortcut(Icons.AutoMirrored.Filled.List, "Logs", onOpenLogs, Modifier.weight(1f))
|
||||
if (!live) HomeShortcut(Icons.Filled.PlayArrow, stringResource(R.string.home_preview), onOpenCarPreview, Modifier.weight(1f))
|
||||
HomeShortcut(Icons.AutoMirrored.Filled.List, stringResource(R.string.home_logs), onOpenLogs, Modifier.weight(1f))
|
||||
}
|
||||
if (live) {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Button(onClick = onDisconnect, modifier = Modifier.fillMaxWidth().height(56.dp)) {
|
||||
Text("Disconnect")
|
||||
Text(stringResource(R.string.home_disconnect))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sharing just went on from the "it worked" ask: a little thanks.
|
||||
if (celebrate) ConfettiFall(onDone = WorkedPrompt::celebrated)
|
||||
@@ -155,6 +138,38 @@ fun HomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
/** Debug builds wear a yellow header, behind the status bar too, so they're never mistaken for a release install. */
|
||||
internal val DebugYellow = Color(0xFFF5C518)
|
||||
private val OnDebugYellow = Color(0xFF2A2200)
|
||||
|
||||
/** The app's name, version and Settings, fixed above Home's scrolling cards. */
|
||||
@Composable
|
||||
private fun HomeHeader(onOpenSettings: () -> Unit) {
|
||||
val debug = BuildConfig.DEBUG
|
||||
val content = if (debug) OnDebugYellow else MaterialTheme.colorScheme.onBackground
|
||||
Surface(color = if (debug) DebugYellow else Color.Transparent, contentColor = content) {
|
||||
Column(Modifier.fillMaxWidth().padding(start = 24.dp, end = 12.dp, top = 16.dp, bottom = 12.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
"Gearslip",
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
IconButton(onClick = onOpenSettings, modifier = Modifier.size(56.dp)) {
|
||||
Icon(Icons.Filled.Settings, contentDescription = stringResource(R.string.settings_title), modifier = Modifier.size(32.dp))
|
||||
}
|
||||
}
|
||||
val version = "v${LocalContext.current.appVersionName()}"
|
||||
Text(
|
||||
if (debug) stringResource(R.string.home_debug_build, version) else version,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = if (debug) content else MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** One of Home's shortcuts at the bottom: an icon over a one-word label, small enough that all fit in a row. */
|
||||
@Composable
|
||||
private fun HomeShortcut(icon: androidx.compose.ui.graphics.vector.ImageVector, label: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
@@ -186,16 +201,15 @@ private fun BluetoothMissingCard() {
|
||||
val context = LocalContext.current
|
||||
// Yellow, not the theme's error red: music is missing, but nothing is broken. Material has no
|
||||
// warning colour, so these are fixed amber tones for light and dark.
|
||||
val dark = isSystemInDarkTheme()
|
||||
val dark = gearslipDark()
|
||||
val container = if (dark) Color(0xFF4A3B00) else Color(0xFFFFE08A)
|
||||
val content = if (dark) Color(0xFFFFE08A) else Color(0xFF3A2E00)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Surface(color = container, contentColor = content, shape = MaterialTheme.shapes.extraLarge) {
|
||||
Column(Modifier.fillMaxWidth().padding(20.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text("Bluetooth isn't connected", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(R.string.home_bt_title), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Text(
|
||||
"Music plays over Bluetooth, but your phone isn't connected to the car. Connect it " +
|
||||
"here or from the car's Bluetooth menu.",
|
||||
stringResource(R.string.home_bt_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Button(
|
||||
@@ -206,7 +220,7 @@ private fun BluetoothMissingCard() {
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
) { Text("Bluetooth settings") }
|
||||
) { Text(stringResource(R.string.bluetooth_settings)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +233,7 @@ private fun VoiceDownloadCard() {
|
||||
Spacer(Modifier.height(16.dp))
|
||||
SettingsCard {
|
||||
Column(Modifier.padding(horizontal = 16.dp, vertical = 14.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Text("Downloading voice, $percent%", style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.home_voice_downloading, percent), style = MaterialTheme.typography.bodyLarge)
|
||||
LinearProgressIndicator(progress = { percent / 100f }, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
@@ -263,9 +277,23 @@ private fun StatusCard(status: SessionStatus.Snapshot) {
|
||||
) {}
|
||||
}
|
||||
Column {
|
||||
Text(status.headline, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.SemiBold)
|
||||
// The steady states read from strings.xml; a stage or a failure comes with its own text.
|
||||
val headline = when (status.phase) {
|
||||
SessionStatus.Phase.IDLE -> stringResource(R.string.status_idle)
|
||||
SessionStatus.Phase.CONNECTING -> stringResource(R.string.status_connecting)
|
||||
SessionStatus.Phase.PROJECTING -> stringResource(R.string.status_projecting)
|
||||
SessionStatus.Phase.DISCONNECTED -> stringResource(R.string.status_disconnected)
|
||||
SessionStatus.Phase.FAILED -> status.headline
|
||||
}
|
||||
val detail = when (status.phase) {
|
||||
SessionStatus.Phase.IDLE -> stringResource(R.string.status_idle_detail)
|
||||
SessionStatus.Phase.PROJECTING -> stringResource(R.string.status_projecting_detail)
|
||||
SessionStatus.Phase.DISCONNECTED -> stringResource(R.string.status_disconnected_detail)
|
||||
else -> status.detail
|
||||
}
|
||||
Text(headline, style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.SemiBold)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(status.detail, style = MaterialTheme.typography.bodyMedium)
|
||||
Text(detail, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
// The other way in: wireless, right under the cable's instructions.
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.text.format.Formatter
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.unit.dp
|
||||
import app.seb3thehacker.gearslip.media.Lyrics
|
||||
import app.seb3thehacker.gearslip.speech.SpeechModels
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The screen behind "Manage storage" on Gearslip's page in the phone's App info › Storage.
|
||||
* Clears each kind of stored data on its own: the lyrics cache, the voice models, old logs and
|
||||
* other temporary files. Some Android versions drop their own "Clear storage" button once an app
|
||||
* has this screen, so deleting everything is offered here too.
|
||||
*
|
||||
* The manifest names it in full: a relative name would be read against the installed package,
|
||||
* and the debug build's ".dev" package has no such class.
|
||||
*/
|
||||
class ManageSpaceActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent { GearslipTheme { StorageScreen(onBack = ::finish) } }
|
||||
}
|
||||
}
|
||||
|
||||
/** What each kind of stored data takes up, measured on a background thread. */
|
||||
private class Usage(val lyricsSongs: Int, val lyrics: Long, val voice: Long, val logs: Long, val other: Long) {
|
||||
val total get() = lyrics + voice + logs + other
|
||||
}
|
||||
|
||||
private fun File.size(): Long = walkBottomUp().filter { it.isFile }.sumOf { it.length() }
|
||||
|
||||
private fun lyricsDir(context: Context) = File(context.cacheDir, Lyrics.LYRICS_DIR)
|
||||
private fun voiceDir(context: Context) = File(context.filesDir, "speech")
|
||||
|
||||
/** Every launch's log but the newest, which is the one this launch is still writing. */
|
||||
private fun oldLogs(context: Context): List<File> {
|
||||
val dir = context.getExternalFilesDir(null) ?: return emptyList()
|
||||
val logs = dir.listFiles { f -> f.isFile && f.name.startsWith("gearslip-") && f.name.endsWith(".log") }
|
||||
?.sortedByDescending { it.name }.orEmpty()
|
||||
return logs.drop(1) + listOfNotNull(File(context.cacheDir, "shared").takeIf { it.exists() })
|
||||
}
|
||||
|
||||
/** The cache directory without the lyrics: downloaded updates, shared logs, spoken directions. */
|
||||
private fun otherCache(context: Context): List<File> =
|
||||
context.cacheDir.listFiles()?.filter { it.name != Lyrics.LYRICS_DIR && it.name != "shared" }.orEmpty()
|
||||
|
||||
private fun measure(context: Context) = Usage(
|
||||
lyricsSongs = lyricsDir(context).listFiles()?.size ?: 0,
|
||||
lyrics = lyricsDir(context).size(),
|
||||
voice = voiceDir(context).size(),
|
||||
logs = oldLogs(context).sumOf { it.size() },
|
||||
other = otherCache(context).sumOf { it.size() },
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun StorageScreen(onBack: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var refresh by remember { mutableIntStateOf(0) }
|
||||
var usage by remember { mutableStateOf<Usage?>(null) }
|
||||
LaunchedEffect(refresh) { usage = withContext(Dispatchers.IO) { measure(context) } }
|
||||
fun clear(work: () -> Unit) {
|
||||
scope.launch {
|
||||
withContext(Dispatchers.IO) { work() }
|
||||
refresh++
|
||||
}
|
||||
}
|
||||
fun size(bytes: Long) = Formatter.formatShortFileSize(context, bytes)
|
||||
|
||||
Scaffold(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.storage_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back)) }
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
Column(
|
||||
Modifier.fillMaxSize().padding(padding).padding(horizontal = 24.dp).verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
val u = usage
|
||||
Text(
|
||||
if (u == null) stringResource(R.string.storage_measuring) else stringResource(R.string.storage_total, size(u.total)),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
SettingsCard {
|
||||
StorageRow(
|
||||
stringResource(R.string.storage_lyrics),
|
||||
u?.let { pluralStringResource(R.plurals.storage_lyrics_detail, it.lyricsSongs, it.lyricsSongs, size(it.lyrics)) },
|
||||
enabled = (u?.lyrics ?: 0) > 0,
|
||||
action = stringResource(R.string.storage_clear),
|
||||
) { clear { Lyrics.clearCache(context) } }
|
||||
SettingsDivider()
|
||||
StorageRow(
|
||||
stringResource(R.string.storage_voice),
|
||||
u?.let { stringResource(R.string.storage_voice_detail, size(it.voice)) },
|
||||
enabled = (u?.voice ?: 0) > 0,
|
||||
action = stringResource(R.string.storage_remove),
|
||||
) {
|
||||
clear {
|
||||
voiceDir(context).deleteRecursively()
|
||||
SpeechModels.init(context)
|
||||
}
|
||||
}
|
||||
SettingsDivider()
|
||||
StorageRow(
|
||||
stringResource(R.string.storage_logs),
|
||||
u?.let { stringResource(R.string.storage_logs_detail, size(it.logs)) },
|
||||
enabled = (u?.logs ?: 0) > 0,
|
||||
action = stringResource(R.string.storage_clear),
|
||||
) { clear { oldLogs(context).forEach { it.deleteRecursively() } } }
|
||||
SettingsDivider()
|
||||
StorageRow(
|
||||
stringResource(R.string.storage_other),
|
||||
u?.let { stringResource(R.string.storage_other_detail, size(it.other)) },
|
||||
enabled = (u?.other ?: 0) > 0,
|
||||
action = stringResource(R.string.storage_clear),
|
||||
) { clear { otherCache(context).forEach { it.deleteRecursively() } } }
|
||||
}
|
||||
DeleteEverything()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StorageRow(title: String, detail: String?, enabled: Boolean, action: String, onClick: () -> Unit) {
|
||||
SettingsRow(
|
||||
title,
|
||||
subtitle = detail ?: stringResource(R.string.storage_measuring),
|
||||
trailing = { TextButton(onClick = onClick, enabled = enabled) { Text(action) } },
|
||||
)
|
||||
}
|
||||
|
||||
/** Android's "Clear storage", which this screen replaces: two taps, so it can't happen by accident. */
|
||||
@Composable
|
||||
private fun DeleteEverything() {
|
||||
val context = LocalContext.current
|
||||
var armed by remember { mutableStateOf(false) }
|
||||
SettingsCard {
|
||||
SettingsRow(
|
||||
stringResource(R.string.storage_delete_all),
|
||||
subtitle = if (armed) {
|
||||
stringResource(R.string.storage_delete_armed)
|
||||
} else {
|
||||
stringResource(R.string.storage_delete_sub)
|
||||
},
|
||||
titleColor = MaterialTheme.colorScheme.error,
|
||||
trailing = {
|
||||
TextButton(onClick = {
|
||||
if (armed) context.getSystemService(ActivityManager::class.java)?.clearApplicationUserData()
|
||||
else armed = true
|
||||
}) { Text(stringResource(if (armed) R.string.storage_delete else R.string.storage_delete_ellipsis), color = MaterialTheme.colorScheme.error) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
@@ -49,7 +51,7 @@ fun PhoneTypingBar(modifier: Modifier = Modifier) {
|
||||
Surface(color = MaterialTheme.colorScheme.surfaceContainerHigh, modifier = modifier.fillMaxWidth().imePadding()) {
|
||||
Row(Modifier.padding(horizontal = 12.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton(onClick = PhoneTyping::dismiss) {
|
||||
Icon(Icons.Filled.Close, contentDescription = "Close the car keyboard")
|
||||
Icon(Icons.Filled.Close, contentDescription = stringResource(R.string.typing_close))
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
@@ -57,14 +59,14 @@ fun PhoneTypingBar(modifier: Modifier = Modifier) {
|
||||
value = it
|
||||
PhoneTyping.type(it.text)
|
||||
},
|
||||
label = { Text("Type for the car screen") },
|
||||
label = { Text(stringResource(R.string.typing_label)) },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||
keyboardActions = KeyboardActions(onSearch = { PhoneTyping.submit() }),
|
||||
modifier = Modifier.weight(1f).focusRequester(focus),
|
||||
)
|
||||
IconButton(onClick = PhoneTyping::submit) {
|
||||
Icon(Icons.Filled.Search, contentDescription = "Search on the car screen")
|
||||
Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.typing_search))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Drawable
|
||||
@@ -43,22 +46,22 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
|
||||
/** A free app that runs on Gearslip's car screen, and where to get it. */
|
||||
private class RecommendedApp(val name: String, val packageName: String, val url: String, val about: String)
|
||||
private class RecommendedApp(val name: String, val packageName: String, val url: String, @param:StringRes val about: Int)
|
||||
|
||||
private val recommended = listOf(
|
||||
"Maps" to listOf(
|
||||
RecommendedApp("Organic Maps", "app.organicmaps", "https://github.com/organicmaps/organicmaps", "Offline maps and routing."),
|
||||
RecommendedApp("Vela Maps", "app.vela", "https://github.com/PimpinPumpkin/Vela", "Turn-by-turn navigation without Google."),
|
||||
R.string.recommended_maps to listOf(
|
||||
RecommendedApp("Organic Maps", "app.organicmaps", "https://github.com/organicmaps/organicmaps", R.string.recommended_about_organic),
|
||||
RecommendedApp("Vela Maps", "app.vela", "https://github.com/PimpinPumpkin/Vela", R.string.recommended_about_vela),
|
||||
),
|
||||
"Music" to listOf(
|
||||
RecommendedApp("ViVi Music", "com.vivi.vivimusic", "https://github.com/vivizzz007/vivi-music", "A YouTube Music player."),
|
||||
RecommendedApp("Metrolist", "com.metrolist.music", "https://github.com/MetrolistGroup/Metrolist", "A YouTube Music player."),
|
||||
R.string.recommended_music to listOf(
|
||||
RecommendedApp("ViVi Music", "com.vivi.vivimusic", "https://github.com/vivizzz007/vivi-music", R.string.recommended_about_ytm),
|
||||
RecommendedApp("Metrolist", "com.metrolist.music", "https://github.com/MetrolistGroup/Metrolist", R.string.recommended_about_ytm),
|
||||
),
|
||||
"Podcasts" to listOf(
|
||||
RecommendedApp("AntennaPod", "de.danoeh.antennapod", "https://github.com/AntennaPod/AntennaPod", "Podcasts, chapters and all."),
|
||||
R.string.recommended_podcasts to listOf(
|
||||
RecommendedApp("AntennaPod", "de.danoeh.antennapod", "https://github.com/AntennaPod/AntennaPod", R.string.recommended_about_antennapod),
|
||||
),
|
||||
"Other cool apps" to listOf(
|
||||
RecommendedApp("RF Sentinel", "com.rfsentinel.app", "https://github.com/CIS-C0/RFSentinel", "Spots trackers and cameras nearby."),
|
||||
R.string.recommended_other to listOf(
|
||||
RecommendedApp("RF Sentinel", "com.rfsentinel.app", "https://github.com/CIS-C0/RFSentinel", R.string.recommended_about_rfsentinel),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -70,10 +73,10 @@ fun RecommendedAppsScreen(onBack: () -> Unit) {
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Recommended apps") },
|
||||
title = { Text(stringResource(R.string.recommended_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -87,7 +90,7 @@ fun RecommendedAppsScreen(onBack: () -> Unit) {
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
Text(
|
||||
RECOMMENDED_INTRO,
|
||||
stringResource(R.string.recommended_intro),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -98,12 +101,9 @@ fun RecommendedAppsScreen(onBack: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Hidden for now: off, neither Settings nor the setup guide shows the list. The code stays. */
|
||||
/** The list's entry in Settings, hidden for now. The setup guide always ends on the list. */
|
||||
internal const val SHOW_RECOMMENDED_APPS = false
|
||||
|
||||
internal const val RECOMMENDED_INTRO =
|
||||
"These free apps run on Gearslip's car screen. Each button opens the app's page on GitHub."
|
||||
|
||||
/** The same green as the launcher's "works" check. */
|
||||
private val INSTALLED_GREEN = Color(0xFF43A047)
|
||||
|
||||
@@ -115,7 +115,7 @@ internal fun RecommendedAppsList(modifier: Modifier = Modifier) {
|
||||
for ((section, apps) in recommended) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(
|
||||
section,
|
||||
stringResource(section),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
@@ -145,12 +145,12 @@ private fun AppRow(app: RecommendedApp, onOpen: () -> Unit) {
|
||||
// Colours set outright: the setup guide draws this on its own background, with no
|
||||
// Surface to hand down a content colour, and unset text came out dark on dark.
|
||||
Text(app.name, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface)
|
||||
Text(app.about, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Text(stringResource(app.about), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
if (icon != null) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 4.dp)) {
|
||||
Icon(Icons.Filled.CheckCircle, contentDescription = null, tint = INSTALLED_GREEN, modifier = Modifier.size(16.dp))
|
||||
Text(
|
||||
"Installed",
|
||||
stringResource(R.string.recommended_installed),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = INSTALLED_GREEN,
|
||||
modifier = Modifier.padding(start = 6.dp),
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import android.Manifest
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Place
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.car.SavedPlace
|
||||
import app.seb3thehacker.gearslip.car.SavedPlaces
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** Home, Work and three named places, each saved the moment it's typed. */
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SavedPlacesScreen(onBack: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val places = remember { SavedPlaces.all(context).toMutableList() }
|
||||
Scaffold(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.places_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.imePadding()
|
||||
.padding(horizontal = 24.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.places_intro),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
for (i in 0 until SavedPlaces.COUNT) {
|
||||
PlaceCard(i, places[i]) { place ->
|
||||
places[i] = place
|
||||
SavedPlaces.set(context, i, place)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlaceCard(index: Int, initial: SavedPlace, onChange: (SavedPlace) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var label by remember { mutableStateOf(initial.label) }
|
||||
var address by remember { mutableStateOf(initial.address) }
|
||||
var finding by remember { mutableStateOf(false) }
|
||||
var notFound by remember { mutableStateOf(false) }
|
||||
val custom = index > SavedPlaces.WORK
|
||||
|
||||
fun save() = onChange(SavedPlace(if (custom) label.trim() else "", address.trim()))
|
||||
fun findHere() {
|
||||
finding = true
|
||||
notFound = false
|
||||
scope.launch {
|
||||
val here = SavedPlaces.here(context)
|
||||
finding = false
|
||||
if (here == null) {
|
||||
notFound = true
|
||||
} else {
|
||||
address = here
|
||||
save()
|
||||
}
|
||||
}
|
||||
}
|
||||
val askLocation = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
|
||||
if (SavedPlaces.hasLocationPermission(context)) findHere() else notFound = true
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Icon(
|
||||
when (index) {
|
||||
SavedPlaces.HOME -> Icons.Filled.Home
|
||||
SavedPlaces.WORK -> Briefcase
|
||||
else -> Icons.Filled.Place
|
||||
},
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
when (index) {
|
||||
SavedPlaces.HOME -> stringResource(R.string.places_home)
|
||||
SavedPlaces.WORK -> stringResource(R.string.places_work)
|
||||
else -> label.ifBlank { stringResource(R.string.places_other, index - SavedPlaces.WORK) }
|
||||
},
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
)
|
||||
}
|
||||
if (custom) {
|
||||
OutlinedTextField(
|
||||
value = label,
|
||||
onValueChange = { label = it; save() },
|
||||
label = { Text(stringResource(R.string.places_name)) },
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words, imeAction = ImeAction.Next),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
OutlinedTextField(
|
||||
value = address,
|
||||
onValueChange = { address = it; notFound = false; save() },
|
||||
label = { Text(stringResource(R.string.places_address)) },
|
||||
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Words, imeAction = ImeAction.Done),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
if (notFound) {
|
||||
Text(
|
||||
stringResource(R.string.places_not_found),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
if (SavedPlaces.hasLocationPermission(context)) {
|
||||
findHere()
|
||||
} else {
|
||||
askLocation.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
|
||||
}
|
||||
},
|
||||
enabled = !finding,
|
||||
) {
|
||||
if (finding) {
|
||||
CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp)
|
||||
} else {
|
||||
Icon(Icons.Filled.LocationOn, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
}
|
||||
Spacer(Modifier.size(8.dp))
|
||||
Text(stringResource(if (finding) R.string.places_finding else R.string.places_here))
|
||||
}
|
||||
if (address.isNotBlank() || label.isNotBlank()) {
|
||||
TextButton(onClick = { label = ""; address = ""; notFound = false; save() }) {
|
||||
Text(stringResource(R.string.places_clear))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Material's "work" briefcase, which only ships in the extended icon set. */
|
||||
private val Briefcase: ImageVector by lazy {
|
||||
materialIcon("Filled.Work") {
|
||||
materialPath {
|
||||
moveTo(20f, 6f); horizontalLineToRelative(-4f); verticalLineTo(4f)
|
||||
curveToRelative(0f, -1.11f, -0.89f, -2f, -2f, -2f); horizontalLineToRelative(-4f)
|
||||
curveTo(8.89f, 2f, 8f, 2.89f, 8f, 4f); verticalLineToRelative(2f); horizontalLineTo(4f)
|
||||
curveToRelative(-1.11f, 0f, -1.99f, 0.89f, -1.99f, 2f); lineTo(2f, 19f)
|
||||
curveToRelative(0f, 1.11f, 0.89f, 2f, 2f, 2f); horizontalLineToRelative(16f)
|
||||
curveToRelative(1.11f, 0f, 2f, -0.89f, 2f, -2f); verticalLineTo(8f)
|
||||
curveToRelative(0f, -1.11f, -0.89f, -2f, -2f, -2f); close()
|
||||
moveTo(14f, 6f); horizontalLineToRelative(-4f); verticalLineTo(4f); horizontalLineToRelative(4f)
|
||||
verticalLineToRelative(2f); close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.runtime.withFrameNanos
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -83,6 +86,16 @@ fun SettingsScreen(
|
||||
atUsageNotes: Boolean = false,
|
||||
) {
|
||||
val scroll = rememberScrollState()
|
||||
// Built a section per frame after the first two, so the screen shows at once instead of
|
||||
// freezing while all of it composes; once built, it scrolls without composing anything new.
|
||||
var built by remember { mutableIntStateOf(2) }
|
||||
LaunchedEffect(Unit) {
|
||||
while (built < SECTIONS) {
|
||||
withFrameNanos { }
|
||||
built++
|
||||
}
|
||||
}
|
||||
fun ready(n: Int) = built > n
|
||||
// Opened from Home's "it worked" card: start at usage notes.
|
||||
var usageNotesY by remember { mutableIntStateOf(-1) }
|
||||
if (atUsageNotes) {
|
||||
@@ -96,10 +109,10 @@ fun SettingsScreen(
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Settings") },
|
||||
title = { Text(stringResource(R.string.settings_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -117,36 +130,36 @@ fun SettingsScreen(
|
||||
SettingsSection {
|
||||
val context = LocalContext.current
|
||||
SettingsRow(
|
||||
"What's new",
|
||||
stringResource(R.string.settings_whats_new),
|
||||
Modifier.clickable { GearslipLinks.open(context, GearslipLinks.RELEASE_NOTES) },
|
||||
subtitle = "Read the ${BuildConfig.VERSION_NAME} release notes on GitHub",
|
||||
subtitle = stringResource(R.string.settings_whats_new_sub, BuildConfig.VERSION_NAME),
|
||||
icon = Icons.Filled.Star,
|
||||
trailing = { Arrow() },
|
||||
)
|
||||
}
|
||||
SettingsSection(
|
||||
"Support Gearslip",
|
||||
footer = "Gearslip is free, with no ads and no tracking. Your support keeps it going.",
|
||||
stringResource(R.string.settings_support),
|
||||
footer = stringResource(R.string.settings_support_footer),
|
||||
) { DonateRows() }
|
||||
ConnectionSection()
|
||||
CarAudioSection()
|
||||
CarScreenSection()
|
||||
VoiceModelSection()
|
||||
if (SHOW_RECOMMENDED_APPS) AppsSection(onOpenRecommendedApps)
|
||||
SettingsSection(
|
||||
"Usage notes",
|
||||
if (ready(2)) ConnectionSection()
|
||||
if (SHOW_RECOMMENDED_APPS && ready(6)) AppsSection(onOpenRecommendedApps)
|
||||
if (ready(7)) SettingsSection(
|
||||
stringResource(R.string.settings_usage_notes),
|
||||
Modifier.onGloballyPositioned { usageNotesY = it.positionInParent().y.toInt() },
|
||||
header = USAGE_STATS_INTRO,
|
||||
header = usageStatsIntro(LocalContext.current),
|
||||
) { UsageStatsRows() }
|
||||
SettingsSection("Updates") { UpdateRow() }
|
||||
CertificateSection()
|
||||
AboutSection(onReplayTutorial)
|
||||
if (BuildConfig.DEBUG) DebugSection()
|
||||
if (ready(8)) SettingsSection(stringResource(R.string.settings_updates)) { UpdateRow() }
|
||||
if (ready(9)) CertificateSection()
|
||||
if (ready(10)) AboutSection(onReplayTutorial)
|
||||
if (BuildConfig.DEBUG && ready(11)) DebugSection()
|
||||
Spacer(Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** How many sections Settings builds one frame at a time; see [ready]. */
|
||||
private const val SECTIONS = 12
|
||||
|
||||
@Composable
|
||||
private fun Arrow() = Icon(
|
||||
Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
@@ -157,11 +170,11 @@ private fun Arrow() = Icon(
|
||||
/** The setup guide replay, and where Gearslip lives outside the app. */
|
||||
@Composable
|
||||
private fun AboutSection(onReplayTutorial: () -> Unit) {
|
||||
SettingsSection("About") {
|
||||
SettingsSection(stringResource(R.string.settings_about)) {
|
||||
SettingsRow(
|
||||
"Replay the setup guide",
|
||||
stringResource(R.string.settings_replay),
|
||||
Modifier.clickable(onClick = onReplayTutorial),
|
||||
subtitle = "Go through the first-run steps again",
|
||||
subtitle = stringResource(R.string.settings_replay_sub),
|
||||
icon = Icons.Filled.Refresh,
|
||||
trailing = { Arrow() },
|
||||
)
|
||||
@@ -175,14 +188,14 @@ private fun AboutSection(onReplayTutorial: () -> Unit) {
|
||||
private fun ConnectionSection() {
|
||||
val context = LocalContext.current
|
||||
var enabled by remember { mutableStateOf(AppSettings.gearslipEnabled(context)) }
|
||||
SettingsSection("Connection") {
|
||||
SettingsSection(stringResource(R.string.settings_connection)) {
|
||||
SettingsRow(
|
||||
"Use Gearslip in the car",
|
||||
stringResource(R.string.settings_use_in_car),
|
||||
Modifier.toggleable(enabled, role = Role.Switch) {
|
||||
enabled = it
|
||||
AppSettings.setGearslipEnabled(context, it)
|
||||
},
|
||||
subtitle = if (enabled) "Gearslip answers when you plug into a car" else "Off: Android Auto can take the connection instead",
|
||||
subtitle = stringResource(if (enabled) R.string.settings_use_in_car_on else R.string.settings_use_in_car_off),
|
||||
icon = Icons.Filled.Settings,
|
||||
trailing = { Switch(checked = enabled, onCheckedChange = null) },
|
||||
)
|
||||
@@ -211,35 +224,11 @@ private fun DebugSection() {
|
||||
}
|
||||
}
|
||||
|
||||
/** How music reaches the car: by Bluetooth as usual, or through Gearslip over USB. Same choice as setup. */
|
||||
@Composable
|
||||
private fun CarAudioSection() {
|
||||
val usb by CarSettings.pipeAudio.collectAsState()
|
||||
SettingsSection(
|
||||
"Car audio",
|
||||
footer = if (usb) "Run the command in Help once, and USB stops asking." else "Calls go over Bluetooth either way.",
|
||||
) {
|
||||
SettingsRow(
|
||||
"Bluetooth (Recommended)",
|
||||
Modifier.selectable(!usb, role = Role.RadioButton) { CarSettings.setPipeAudio(false) },
|
||||
subtitle = "No prompt each drive, and your map app's voice reaches the car",
|
||||
trailing = { RadioButton(selected = !usb, onClick = null) },
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"USB",
|
||||
Modifier.selectable(usb, role = Role.RadioButton) { CarSettings.setPipeAudio(true) },
|
||||
subtitle = "Clearer sound with no delay. Each drive, Android asks you to share your screen, which lets Gearslip send the sound",
|
||||
trailing = { RadioButton(selected = usb, onClick = null) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppsSection(onOpenRecommendedApps: () -> Unit) {
|
||||
SettingsSection("Apps") {
|
||||
SettingsSection(stringResource(R.string.settings_apps)) {
|
||||
SettingsRow(
|
||||
"Recommended apps",
|
||||
stringResource(R.string.recommended_title),
|
||||
Modifier.clickable(onClick = onOpenRecommendedApps),
|
||||
icon = Icons.Filled.Star,
|
||||
trailing = { Arrow() },
|
||||
@@ -266,21 +255,25 @@ private fun CertificateSection() {
|
||||
if (uri != null) pendingUri = uri
|
||||
}
|
||||
|
||||
SettingsSection("Certificate", footer = notice ?: "Changes take effect on the next connection.") {
|
||||
// With the aasdk certificate loaded, the footer says what to do when a car rejects it.
|
||||
val certFooter = stringResource(
|
||||
if (summary?.kind == CertProvider.Kind.DOWNLOADED) R.string.cert_downloaded_footer else R.string.cert_next_connection,
|
||||
)
|
||||
SettingsSection(stringResource(R.string.settings_certificate), footer = notice ?: certFooter) {
|
||||
val s = summary
|
||||
val loaded = s != null && s.kind != CertProvider.Kind.SELF_SIGNED
|
||||
val hasDetails = s?.subject != null || s?.issuer != null
|
||||
SettingsRow(
|
||||
s?.headline ?: "Checking…",
|
||||
s?.headline ?: stringResource(R.string.cert_checking),
|
||||
if (hasDetails) Modifier.clickable { detailsOpen = !detailsOpen } else Modifier,
|
||||
subtitle = s?.validUntil?.let { "Valid until $it" },
|
||||
subtitle = s?.validUntil?.let { stringResource(R.string.cert_valid_until, it) },
|
||||
icon = if (s == null || loaded) Icons.Filled.Lock else Icons.Filled.Warning,
|
||||
tint = if (s == null || loaded) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error,
|
||||
trailing = if (hasDetails) {
|
||||
{
|
||||
Icon(
|
||||
if (detailsOpen) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = if (detailsOpen) "Hide details" else "Show details",
|
||||
contentDescription = stringResource(if (detailsOpen) R.string.cert_hide_details else R.string.cert_show_details),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
@@ -291,39 +284,39 @@ private fun CertificateSection() {
|
||||
Modifier.padding(start = 56.dp, end = 16.dp, bottom = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
s?.subject?.let { Detail("Subject", it) }
|
||||
s?.issuer?.let { Detail("Issuer", it) }
|
||||
s?.subject?.let { Detail(stringResource(R.string.cert_subject), it) }
|
||||
s?.issuer?.let { Detail(stringResource(R.string.cert_issuer), it) }
|
||||
}
|
||||
}
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
if (downloading) "Downloading…" else "Download from aasdk",
|
||||
stringResource(if (downloading) R.string.cert_downloading else R.string.cert_download),
|
||||
Modifier.clickable(enabled = !downloading) {
|
||||
downloading = true
|
||||
scope.launch {
|
||||
val result = withContext(Dispatchers.IO) { runCatching { CertProvider.downloadAndImport(context) } }
|
||||
downloading = false
|
||||
result.onSuccess {
|
||||
notice = "Certificate downloaded."
|
||||
notice = context.getString(R.string.cert_downloaded)
|
||||
refresh++
|
||||
}.onFailure {
|
||||
notice = "Download failed: ${it.message}"
|
||||
notice = context.getString(R.string.cert_download_failed, it.message.toString())
|
||||
}
|
||||
}
|
||||
},
|
||||
subtitle = "The certificate the open-source aasdk project publishes on GitHub. Gearslip doesn't include one",
|
||||
subtitle = stringResource(R.string.cert_download_sub),
|
||||
icon = Icons.Filled.Refresh,
|
||||
)
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"Import my own file",
|
||||
stringResource(R.string.cert_import),
|
||||
Modifier.clickable { picker.launch(arrayOf("*/*")) },
|
||||
subtitle = "A .p12 file with its private key, from wherever you choose",
|
||||
subtitle = stringResource(R.string.cert_import_sub),
|
||||
icon = Icons.Filled.Add,
|
||||
)
|
||||
val removable = when (s?.kind) {
|
||||
CertProvider.Kind.IMPORTED -> "Remove imported certificate"
|
||||
CertProvider.Kind.DOWNLOADED -> "Remove downloaded certificate"
|
||||
CertProvider.Kind.IMPORTED -> stringResource(R.string.cert_remove_imported)
|
||||
CertProvider.Kind.DOWNLOADED -> stringResource(R.string.cert_remove_downloaded)
|
||||
else -> null
|
||||
}
|
||||
if (removable != null) {
|
||||
@@ -332,7 +325,7 @@ private fun CertificateSection() {
|
||||
removable,
|
||||
Modifier.clickable {
|
||||
if (s?.kind == CertProvider.Kind.IMPORTED) CertProvider.removeImported(context) else CertProvider.removeDownloaded(context)
|
||||
notice = if (s?.kind == CertProvider.Kind.IMPORTED) "Removed the imported certificate." else "Removed the downloaded certificate."
|
||||
notice = context.getString(if (s?.kind == CertProvider.Kind.IMPORTED) R.string.cert_removed_imported else R.string.cert_removed_downloaded)
|
||||
refresh++
|
||||
},
|
||||
icon = Icons.Filled.Delete,
|
||||
@@ -352,10 +345,10 @@ private fun CertificateSection() {
|
||||
}
|
||||
result.onSuccess {
|
||||
pendingUri = null
|
||||
notice = "Certificate imported."
|
||||
notice = context.getString(R.string.cert_imported)
|
||||
refresh++
|
||||
}.onFailure {
|
||||
reportError("Could not import: wrong password, or not a PKCS#12 file with a private key.")
|
||||
reportError(context.getString(R.string.cert_import_failed))
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -378,20 +371,20 @@ private fun PasswordDialog(onDismiss: () -> Unit, onConfirm: (String, (String) -
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Certificate password") },
|
||||
title = { Text(stringResource(R.string.cert_password_title)) },
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = { password = it; error = null },
|
||||
singleLine = true,
|
||||
label = { Text("Password") },
|
||||
label = { Text(stringResource(R.string.cert_password)) },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
isError = error != null,
|
||||
supportingText = { Text(error ?: "Leave empty if the file has none.") },
|
||||
supportingText = { Text(error ?: stringResource(R.string.cert_password_hint)) },
|
||||
)
|
||||
},
|
||||
confirmButton = { TextButton(onClick = { onConfirm(password) { error = it } }) { Text("Import") } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } },
|
||||
confirmButton = { TextButton(onClick = { onConfirm(password) { error = it } }) { Text(stringResource(R.string.cert_import_button)) } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import app.seb3thehacker.gearslip.speech.SpeechModels
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -160,8 +162,8 @@ private fun hasNotificationAccess(context: Context): Boolean {
|
||||
* who set up before the notes existed see once. Leaving it without turning them on is a no.
|
||||
*/
|
||||
private fun usageNotesPage(context: Context) = GuidePage(
|
||||
title = "Help other drivers.",
|
||||
explanation = USAGE_STATS_INTRO,
|
||||
title = context.getString(R.string.guide_usage_title),
|
||||
explanation = usageStatsIntro(context),
|
||||
icon = { HeartIcon(it) },
|
||||
content = {
|
||||
UsageStatsChoices()
|
||||
@@ -175,9 +177,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
// --- Safety: always first, and never skipped by an already-granted check ---------
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Drive safely.",
|
||||
explanation = "Gearslip is unofficial. No carmaker made or checked it, and it can fail " +
|
||||
"without warning. Set it up before you go, then keep your eyes on the road.",
|
||||
title = context.getString(R.string.guide_safety_title),
|
||||
explanation = context.getString(R.string.guide_safety_body),
|
||||
icon = glyph(Icons.Filled.Warning),
|
||||
),
|
||||
)
|
||||
@@ -187,9 +188,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!granted(context, arrayOf(Manifest.permission.RECORD_AUDIO))) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Microphone.",
|
||||
explanation = "Gearslip uses it to send your music to the car and to hear you when you " +
|
||||
"ask for something or reply to a message. What you say stays on the phone.",
|
||||
title = context.getString(R.string.guide_mic_title),
|
||||
explanation = context.getString(R.string.guide_mic_body),
|
||||
icon = { MicIcon(it) },
|
||||
permissions = arrayOf(Manifest.permission.RECORD_AUDIO),
|
||||
),
|
||||
@@ -198,9 +198,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!granted(context, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION))) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Location.",
|
||||
explanation = "The car's map and nearby searches need it. Without it, the dashboard " +
|
||||
"shows no map.",
|
||||
title = context.getString(R.string.guide_location_title),
|
||||
explanation = context.getString(R.string.guide_location_body),
|
||||
icon = glyph(Icons.Filled.LocationOn),
|
||||
permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||
),
|
||||
@@ -209,8 +208,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!granted(context, arrayOf(Manifest.permission.READ_CALENDAR))) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Calendar.",
|
||||
explanation = "The car screen shows your next event. Gearslip only reads your calendar.",
|
||||
title = context.getString(R.string.guide_calendar_title),
|
||||
explanation = context.getString(R.string.guide_calendar_body),
|
||||
icon = glyph(Icons.Filled.DateRange),
|
||||
permissions = arrayOf(Manifest.permission.READ_CALENDAR),
|
||||
),
|
||||
@@ -226,9 +225,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!granted(context, callPermissions)) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Calls and contacts.",
|
||||
explanation = "The car shows who's calling by name, and its Phone app can dial a " +
|
||||
"number or call one of your contacts. Gearslip only reads them.",
|
||||
title = context.getString(R.string.guide_calls_title),
|
||||
explanation = context.getString(R.string.guide_calls_body),
|
||||
icon = glyph(Icons.Filled.Phone),
|
||||
permissions = callPermissions,
|
||||
),
|
||||
@@ -237,9 +235,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!granted(context, arrayOf(Manifest.permission.BLUETOOTH_CONNECT))) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Bluetooth.",
|
||||
explanation = "The first time you plug in, Gearslip offers to pair your phone with the " +
|
||||
"car, so calls play through its speakers.",
|
||||
title = context.getString(R.string.guide_bluetooth_title),
|
||||
explanation = context.getString(R.string.guide_bluetooth_body),
|
||||
icon = glyph(Icons.Filled.Call),
|
||||
permissions = arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
|
||||
),
|
||||
@@ -249,9 +246,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!granted(context, arrayOf(Manifest.permission.POST_NOTIFICATIONS))) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Notifications.",
|
||||
explanation = "While Gearslip is connected, it shows a notification with a button " +
|
||||
"to stop. Without this, Android hides it.",
|
||||
title = context.getString(R.string.guide_notifications_title),
|
||||
explanation = context.getString(R.string.guide_notifications_body),
|
||||
icon = glyph(Icons.Filled.Notifications),
|
||||
permissions = arrayOf(Manifest.permission.POST_NOTIFICATIONS),
|
||||
),
|
||||
@@ -260,11 +256,10 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!hasNotificationAccess(context)) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Messages.",
|
||||
explanation = "The car shows your messages, reads them aloud and lets you reply. " +
|
||||
"Turn Gearslip on in the list this opens.",
|
||||
title = context.getString(R.string.guide_messages_title),
|
||||
explanation = context.getString(R.string.guide_messages_body),
|
||||
icon = glyph(Icons.Filled.Email),
|
||||
actionLabel = "Open notification access",
|
||||
actionLabel = context.getString(R.string.guide_open_notification_access),
|
||||
onAction = {
|
||||
context.startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS))
|
||||
},
|
||||
@@ -278,11 +273,10 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (androidAutoPackage != null) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Turn off Android Auto.",
|
||||
explanation = "Android Auto is still installed, and it fights Gearslip for the car's " +
|
||||
"USB connection. Disable or uninstall it on the screen this opens.",
|
||||
title = context.getString(R.string.guide_android_auto_title),
|
||||
explanation = context.getString(R.string.guide_android_auto_body),
|
||||
icon = glyph(Icons.Filled.Close),
|
||||
actionLabel = "Open Android Auto's app info",
|
||||
actionLabel = context.getString(R.string.guide_open_android_auto),
|
||||
onAction = {
|
||||
context.startActivity(
|
||||
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", androidAutoPackage, null)),
|
||||
@@ -295,9 +289,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
// --- Default handler ---------------------------------------------------------------
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Choose Gearslip.",
|
||||
explanation = "If your phone asks which app to open when you plug into the car, pick " +
|
||||
"Gearslip and tap Always.",
|
||||
title = context.getString(R.string.guide_default_title),
|
||||
explanation = context.getString(R.string.guide_default_body),
|
||||
icon = glyph(Icons.Filled.Check),
|
||||
),
|
||||
)
|
||||
@@ -305,8 +298,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
// --- Car audio: always asked, and it decides whether the adb pages below show ---------
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Car audio.",
|
||||
explanation = "Pick how music reaches the car. Calls go over Bluetooth either way.",
|
||||
title = context.getString(R.string.guide_audio_title),
|
||||
explanation = context.getString(R.string.guide_audio_body),
|
||||
icon = glyph(Icons.Filled.PlayArrow),
|
||||
content = { AudioChoice() },
|
||||
),
|
||||
@@ -316,13 +309,11 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!SpeechModels.RECOMMENDED.all { SpeechModels.isReady(context, it) }) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Voice.",
|
||||
explanation = "Ask for directions, music, or a call by voice. Your phone turns speech " +
|
||||
"into text by itself, and nothing you say leaves it. It needs " +
|
||||
"${SpeechModels.RECOMMENDED.sumOf { it.downloadMb }} MB, downloaded once. Use Wi-Fi if you can.",
|
||||
title = context.getString(R.string.guide_voice_title),
|
||||
explanation = context.getString(R.string.guide_voice_body, SpeechModels.RECOMMENDED.sumOf { it.downloadMb }),
|
||||
icon = { MicIcon(it) },
|
||||
optional = true,
|
||||
actionLabel = "Download voice",
|
||||
actionLabel = context.getString(R.string.guide_download_voice),
|
||||
onAction = { SpeechModels.downloadRecommended(context) },
|
||||
content = { VoiceDownloadStatus() },
|
||||
),
|
||||
@@ -333,9 +324,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!hasProjectMediaOp(context)) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "No audio prompt.",
|
||||
explanation = "Run this from a computer with USB debugging on, and Android trusts " +
|
||||
"Gearslip with your audio for good. Without it, you tap Start once each drive.",
|
||||
title = context.getString(R.string.guide_audio_prompt_title),
|
||||
explanation = context.getString(R.string.guide_audio_prompt_body),
|
||||
icon = glyph(Icons.Filled.Build),
|
||||
optional = true,
|
||||
usbOnly = true,
|
||||
@@ -346,10 +336,8 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!granted(context, arrayOf(Manifest.permission.WRITE_SECURE_SETTINGS))) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Readable notifications.",
|
||||
explanation = "While audio goes to the car, Android hides what notifications say, " +
|
||||
"as if you were sharing your screen. This lets Gearslip show them during a " +
|
||||
"drive and hide them again after.",
|
||||
title = context.getString(R.string.guide_readable_title),
|
||||
explanation = context.getString(R.string.guide_readable_body),
|
||||
icon = glyph(Icons.Filled.Build),
|
||||
optional = true,
|
||||
usbOnly = true,
|
||||
@@ -360,12 +348,11 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
if (!hasCallScreeningRole(context)) {
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Answer calls.",
|
||||
explanation = "Make Gearslip your caller ID app, and the car can answer and decline " +
|
||||
"calls. Without it, calls still ring, but only the phone can answer them.",
|
||||
title = context.getString(R.string.guide_answer_title),
|
||||
explanation = context.getString(R.string.guide_answer_body),
|
||||
icon = glyph(Icons.Filled.Call),
|
||||
optional = true,
|
||||
actionLabel = "Set up",
|
||||
actionLabel = context.getString(R.string.guide_set_up),
|
||||
onAction = onRequestCallScreening,
|
||||
),
|
||||
)
|
||||
@@ -375,16 +362,16 @@ private fun pages(context: Context, onRequestCallScreening: () -> Unit): List<Gu
|
||||
// the usage notes. On a replay, the switch shows what was chosen before.
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Good company.",
|
||||
explanation = RECOMMENDED_INTRO,
|
||||
title = context.getString(R.string.guide_apps_title),
|
||||
explanation = context.getString(R.string.recommended_intro),
|
||||
icon = { StarIcon(it) },
|
||||
content = { RecommendedAppsList() },
|
||||
),
|
||||
)
|
||||
add(
|
||||
GuidePage(
|
||||
title = "Your buttons.",
|
||||
explanation = "Pick how the car's buttons look. Change it any time in the car's settings.",
|
||||
title = context.getString(R.string.guide_buttons_title),
|
||||
explanation = context.getString(R.string.guide_buttons_body),
|
||||
icon = { ButtonsIcon(it) },
|
||||
content = { ThemePicker(it.c, it.cq, it.cqSp, key = "guide-buttons") },
|
||||
),
|
||||
@@ -403,9 +390,9 @@ private fun VoiceDownloadStatus() {
|
||||
val progress by SpeechModels.progress.collectAsState()
|
||||
val states = SpeechModels.RECOMMENDED.map { SpeechModels.state(it).collectAsState().value }
|
||||
val line = when {
|
||||
progress != null -> "Downloading, $progress%. You can carry on; it keeps going."
|
||||
states.all { it == SpeechModels.State.Ready } -> "Voice is ready."
|
||||
states.any { it is SpeechModels.State.Failed } -> "The download didn't finish. Tap Download voice to try again."
|
||||
progress != null -> stringResource(R.string.guide_voice_downloading, progress ?: 0)
|
||||
states.all { it == SpeechModels.State.Ready } -> stringResource(R.string.guide_voice_ready)
|
||||
states.any { it is SpeechModels.State.Failed } -> stringResource(R.string.guide_voice_failed)
|
||||
else -> null
|
||||
}
|
||||
LaunchedEffect(Unit) { SpeechModels.init(context) }
|
||||
@@ -421,13 +408,13 @@ private fun AudioChoice() {
|
||||
LaunchedEffect(Unit) { if (!CarSettings.pipeAudioChosen) CarSettings.setPipeAudio(false) }
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
AudioCard(
|
||||
"Bluetooth", "Recommended",
|
||||
"No prompt each drive, and your map app's voice reaches the car.",
|
||||
stringResource(R.string.guide_audio_bluetooth), stringResource(R.string.guide_recommended),
|
||||
stringResource(R.string.guide_audio_bluetooth_body),
|
||||
selected = !usb,
|
||||
) { CarSettings.setPipeAudio(false) }
|
||||
AudioCard(
|
||||
"USB", null,
|
||||
"Clearer sound with no delay. Each drive, Android asks you to share your screen, which lets Gearslip send the sound.",
|
||||
stringResource(R.string.guide_audio_usb), null,
|
||||
stringResource(R.string.guide_audio_usb_body),
|
||||
selected = usb,
|
||||
) { CarSettings.setPipeAudio(true) }
|
||||
}
|
||||
@@ -535,7 +522,7 @@ fun SetupGuideScreen(onRequestCallScreening: () -> Unit, onDone: () -> Unit, onl
|
||||
Spacer(Modifier.height(cq(7f)))
|
||||
if (page.optional) {
|
||||
Text(
|
||||
"OPTIONAL", color = c.primary,
|
||||
stringResource(R.string.guide_optional), color = c.primary,
|
||||
style = TextStyle(fontSize = cqSp(3.4f), fontWeight = FontWeight.Bold, letterSpacing = cqSp(0.4f)),
|
||||
modifier = Modifier.up(entrance(index, 150, 700), cq(7f)),
|
||||
)
|
||||
@@ -564,7 +551,7 @@ fun SetupGuideScreen(onRequestCallScreening: () -> Unit, onDone: () -> Unit, onl
|
||||
page.content?.invoke(GuideScale(c, ::cq, ::cqSp))
|
||||
if (page.permissions != null && alreadyGranted) {
|
||||
Text(
|
||||
"Already allowed.", color = c.primary,
|
||||
stringResource(R.string.guide_already_allowed), color = c.primary,
|
||||
style = TextStyle(fontSize = cqSp(4.2f), fontWeight = FontWeight.SemiBold),
|
||||
)
|
||||
}
|
||||
@@ -585,14 +572,14 @@ fun SetupGuideScreen(onRequestCallScreening: () -> Unit, onDone: () -> Unit, onl
|
||||
val next = { index += 1 }
|
||||
when {
|
||||
page.permissions != null && !alreadyGranted -> {
|
||||
PillRow(c, ::cq, ::cqSp, back, "Allow") { launcher.launch(page.permissions) }
|
||||
Pill(c, ::cqSp, "Skip for now", PillStyle.TEXT, Modifier.align(Alignment.CenterHorizontally), next)
|
||||
PillRow(c, ::cq, ::cqSp, back, stringResource(R.string.guide_allow)) { launcher.launch(page.permissions) }
|
||||
Pill(c, ::cqSp, stringResource(R.string.guide_skip), PillStyle.TEXT, Modifier.align(Alignment.CenterHorizontally), next)
|
||||
}
|
||||
page.onAction != null -> {
|
||||
Pill(c, ::cqSp, page.actionLabel ?: "Open", PillStyle.OUTLINED, Modifier.fillMaxWidth().padding(bottom = cq(3f)), page.onAction)
|
||||
PillRow(c, ::cq, ::cqSp, back, "Next", next)
|
||||
Pill(c, ::cqSp, page.actionLabel ?: stringResource(R.string.guide_open), PillStyle.OUTLINED, Modifier.fillMaxWidth().padding(bottom = cq(3f)), page.onAction)
|
||||
PillRow(c, ::cq, ::cqSp, back, stringResource(R.string.guide_next), next)
|
||||
}
|
||||
else -> PillRow(c, ::cq, ::cqSp, back, if (index == pages.lastIndex) "Done" else "Next", next)
|
||||
else -> PillRow(c, ::cq, ::cqSp, back, stringResource(if (index == pages.lastIndex) R.string.guide_done else R.string.guide_next), next)
|
||||
}
|
||||
Spacer(Modifier.height(cq(6f)))
|
||||
}
|
||||
@@ -613,7 +600,7 @@ private fun ScrollHint(c: IntroColors, cq: (Float) -> Dp, cqSp: (Float) -> TextU
|
||||
contentAlignment = Alignment.BottomCenter,
|
||||
) {
|
||||
Text(
|
||||
"Scroll for more ↓",
|
||||
stringResource(R.string.guide_scroll_more),
|
||||
color = c.primary,
|
||||
style = TextStyle(fontSize = cqSp(4f), fontWeight = FontWeight.Bold),
|
||||
modifier = Modifier.padding(bottom = cq(2f)).offset(y = cq(1.2f) * bob),
|
||||
@@ -657,7 +644,7 @@ internal fun PillRow(
|
||||
onBack: (() -> Unit)?, label: String, onClick: () -> Unit,
|
||||
) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(cq(3f))) {
|
||||
if (onBack != null) Pill(c, cqSp, "Back", PillStyle.OUTLINED, Modifier.weight(1f), onBack)
|
||||
if (onBack != null) Pill(c, cqSp, stringResource(R.string.guide_back), PillStyle.OUTLINED, Modifier.weight(1f), onBack)
|
||||
Pill(c, cqSp, label, PillStyle.FILLED, Modifier.weight(if (onBack != null) 1.4f else 1f), onClick)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -42,7 +44,6 @@ import app.seb3thehacker.gearslip.SessionStatus
|
||||
import app.seb3thehacker.gearslip.update.UpdateChecker
|
||||
import app.seb3thehacker.gearslip.update.UpdateChecker.State
|
||||
|
||||
private const val UNPLUG_FIRST = "Unplug from the car to install. Installing closes Gearslip."
|
||||
|
||||
/** Shown on Home once a newer version is found; hidden otherwise. */
|
||||
@Composable
|
||||
@@ -64,37 +65,37 @@ fun UpdateCard() {
|
||||
shape = MaterialTheme.shapes.extraLarge,
|
||||
) {
|
||||
Column(Modifier.fillMaxWidth().padding(20.dp)) {
|
||||
Text("Gearslip ${release.version} is out", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(R.string.update_card_title, release.version), style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.SemiBold)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
when (val s = state) {
|
||||
is State.Downloading -> {
|
||||
Text("Downloading… ${(s.fraction * 100).toInt()}%", style = MaterialTheme.typography.bodyMedium)
|
||||
Text(stringResource(R.string.update_downloading, (s.fraction * 100).toInt()), style = MaterialTheme.typography.bodyMedium)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
LinearProgressIndicator(progress = { s.fraction }, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
is State.Ready -> {
|
||||
val connected = UpdateChecker.carConnected
|
||||
Text(
|
||||
if (connected) UNPLUG_FIRST else "Downloaded. Android asks before it installs.",
|
||||
if (connected) stringResource(R.string.update_unplug_first) else stringResource(R.string.update_downloaded_ask),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Button(onClick = { UpdateChecker.install(context) }, enabled = !connected) { Text("Install") }
|
||||
Button(onClick = { UpdateChecker.install(context) }, enabled = !connected) { Text(stringResource(R.string.update_install)) }
|
||||
}
|
||||
else -> {
|
||||
Text(
|
||||
(s as? State.Failed)?.message ?: "Download it now. Android asks before it installs.",
|
||||
(s as? State.Failed)?.message ?: stringResource(R.string.update_download_now),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = { UpdateChecker.download(context, release) }) {
|
||||
Text(if (s is State.Failed) "Try again" else "Update")
|
||||
Text(stringResource(if (s is State.Failed) R.string.update_try_again else R.string.update_update))
|
||||
}
|
||||
if (release.pageUrl.isNotBlank()) {
|
||||
TextButton(onClick = {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(release.pageUrl)))
|
||||
}) { Text("What's new") }
|
||||
}) { Text(stringResource(R.string.settings_whats_new)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,13 +107,14 @@ fun UpdateCard() {
|
||||
}
|
||||
|
||||
/** What the update check found, in a line. */
|
||||
@Composable
|
||||
private fun statusLine(state: State): String = when (state) {
|
||||
State.Idle -> if (BuildConfig.DEBUG) "Debug builds check only when you tap Check now" else "Gearslip checks once a day"
|
||||
State.Checking -> "Checking…"
|
||||
State.UpToDate -> "You have the latest version"
|
||||
is State.Available -> "Version ${state.release.version} is out"
|
||||
is State.Downloading -> "Downloading… ${(state.fraction * 100).toInt()}%"
|
||||
is State.Ready -> if (UpdateChecker.carConnected) UNPLUG_FIRST else "Version ${state.release.version} is ready to install"
|
||||
State.Idle -> stringResource(if (BuildConfig.DEBUG) R.string.update_debug_manual else R.string.update_checks_daily)
|
||||
State.Checking -> stringResource(R.string.update_checking)
|
||||
State.UpToDate -> stringResource(R.string.update_latest)
|
||||
is State.Available -> stringResource(R.string.update_version_out, state.release.version)
|
||||
is State.Downloading -> stringResource(R.string.update_downloading, (state.fraction * 100).toInt())
|
||||
is State.Ready -> if (UpdateChecker.carConnected) stringResource(R.string.update_unplug_first) else stringResource(R.string.update_version_ready, state.release.version)
|
||||
is State.Failed -> state.message
|
||||
}
|
||||
|
||||
@@ -135,7 +137,7 @@ internal fun UpdateRow() {
|
||||
var open by remember { mutableStateOf(false) }
|
||||
val channelName = CHANNELS.firstOrNull { it.first == channel }?.second
|
||||
SettingsRow(
|
||||
"Updates",
|
||||
stringResource(R.string.settings_updates),
|
||||
Modifier.clickable { open = true },
|
||||
subtitle = listOfNotNull(channelName, statusLine(state)).joinToString(" · "),
|
||||
icon = Icons.Filled.CheckCircle,
|
||||
@@ -169,22 +171,23 @@ private fun UpdateDialog(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var auto by remember { mutableStateOf(UpdateChecker.autoCheckEnabled(context)) }
|
||||
val actionLabels = listOf(R.string.update_download, R.string.update_install, R.string.update_try_again, R.string.update_check_now).map { stringResource(it) }
|
||||
val action: Pair<String, () -> Unit>? = when (state) {
|
||||
State.Checking, is State.Downloading -> null
|
||||
is State.Available -> "Download" to { UpdateChecker.download(context, state.release) }
|
||||
is State.Ready -> if (UpdateChecker.carConnected) null else "Install" to { UpdateChecker.install(context) }
|
||||
is State.Failed -> "Try again" to {
|
||||
is State.Available -> actionLabels[0] to { UpdateChecker.download(context, state.release) }
|
||||
is State.Ready -> if (UpdateChecker.carConnected) null else actionLabels[1] to { UpdateChecker.install(context) }
|
||||
is State.Failed -> actionLabels[2] to {
|
||||
state.release?.let { UpdateChecker.download(context, it) } ?: UpdateChecker.check(context)
|
||||
}
|
||||
else -> "Check now" to { UpdateChecker.check(context) }
|
||||
else -> actionLabels[3] to { UpdateChecker.check(context) }
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Updates") },
|
||||
title = { Text(stringResource(R.string.settings_updates)) },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
"Version ${BuildConfig.VERSION_NAME}. ${statusLine(state)}.",
|
||||
stringResource(R.string.update_dialog_status, BuildConfig.VERSION_NAME, statusLine(state)),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 12.dp),
|
||||
)
|
||||
@@ -218,9 +221,9 @@ private fun UpdateDialog(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("Check automatically", style = MaterialTheme.typography.bodyLarge)
|
||||
Text(stringResource(R.string.update_check_auto), style = MaterialTheme.typography.bodyLarge)
|
||||
Text(
|
||||
if (BuildConfig.DEBUG) "Debug builds check only when you tap Check now" else "Once a day",
|
||||
stringResource(if (BuildConfig.DEBUG) R.string.update_debug_manual else R.string.update_once_a_day),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -232,6 +235,6 @@ private fun UpdateDialog(
|
||||
confirmButton = {
|
||||
action?.let { (label, run) -> TextButton(onClick = run) { Text(label) } }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Done") } },
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.guide_done)) } },
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -53,21 +56,9 @@ import androidx.compose.ui.unit.dp
|
||||
import app.seb3thehacker.gearslip.BuildConfig
|
||||
import app.seb3thehacker.gearslip.stats.UsageStats
|
||||
|
||||
internal const val USAGE_STATS_TITLE = "Help show which cars work"
|
||||
|
||||
private const val INTRO_START =
|
||||
"Turn this on, and Gearslip sends a short note when you open it and after each drive. "
|
||||
private const val INTRO_PRIVACY =
|
||||
"The note holds no name, account, or location. A random number counts your phone once, and the server keeps no IP address."
|
||||
private const val INTRO_END =
|
||||
" The notes show which cars work and where the others fail."
|
||||
|
||||
/** The pitch for the usage notes, in setup and Settings, with the privacy promise in bold so it isn't missed. */
|
||||
internal val USAGE_STATS_INTRO: AnnotatedString = buildAnnotatedString {
|
||||
append(INTRO_START)
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { append(INTRO_PRIVACY) }
|
||||
append(INTRO_END)
|
||||
}
|
||||
internal fun usageStatsIntro(context: Context): AnnotatedString =
|
||||
AnnotatedString.fromHtml(context.getString(R.string.usage_intro))
|
||||
|
||||
/** The usage notes card for the setup guide: the same rows Settings shows. */
|
||||
@Composable
|
||||
@@ -90,9 +81,9 @@ internal fun ColumnScope.UsageStatsRows() {
|
||||
DisposableEffect(Unit) { onDispose { UsageStats.choicesClosed(context) } }
|
||||
|
||||
SettingsRow(
|
||||
"Send usage notes",
|
||||
stringResource(R.string.usage_send),
|
||||
Modifier.toggleable(on, role = Role.Switch) { on = it; save() },
|
||||
subtitle = buildNote(),
|
||||
subtitle = buildNote(context),
|
||||
icon = Icons.Filled.Send,
|
||||
trailing = { Switch(checked = on, onCheckedChange = null) },
|
||||
)
|
||||
@@ -100,31 +91,31 @@ internal fun ColumnScope.UsageStatsRows() {
|
||||
Column {
|
||||
SettingsDivider()
|
||||
Text(
|
||||
"Also share",
|
||||
stringResource(R.string.usage_also_share),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 56.dp, end = 16.dp, top = 12.dp, bottom = 4.dp),
|
||||
)
|
||||
Choice("Android version and system", choices.android) { choices = choices.copy(android = it); save() }
|
||||
Choice("Phone model", choices.phone) { choices = choices.copy(phone = it); save() }
|
||||
Choice("Each car and whether it connected", choices.cars) {
|
||||
Choice(stringResource(R.string.usage_share_android), choices.android) { choices = choices.copy(android = it); save() }
|
||||
Choice(stringResource(R.string.usage_share_phone), choices.phone) { choices = choices.copy(phone = it); save() }
|
||||
Choice(stringResource(R.string.usage_share_cars), choices.cars) {
|
||||
choices = choices.copy(cars = it)
|
||||
save()
|
||||
}
|
||||
Choice("Each drive and how it ended", choices.failures) {
|
||||
Choice(stringResource(R.string.usage_share_drives), choices.failures) {
|
||||
choices = choices.copy(failures = it)
|
||||
save()
|
||||
}
|
||||
SettingsDivider()
|
||||
SettingsRow(
|
||||
"What gets sent",
|
||||
stringResource(R.string.usage_what_sent),
|
||||
Modifier.clickable { noteOpen = !noteOpen },
|
||||
subtitle = "Every field, exactly as it goes out",
|
||||
subtitle = stringResource(R.string.usage_what_sent_sub),
|
||||
icon = Icons.Filled.Info,
|
||||
trailing = {
|
||||
Icon(
|
||||
if (noteOpen) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
|
||||
contentDescription = if (noteOpen) "Hide the note" else "Show the note",
|
||||
contentDescription = stringResource(if (noteOpen) R.string.usage_hide_note else R.string.usage_show_note),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
},
|
||||
@@ -167,15 +158,15 @@ private fun notePreview(context: Context): String {
|
||||
val preview = UsageStats.preview(context, everything = true)
|
||||
// Until the first drive the list is empty, so show what one entry looks like.
|
||||
val example = if (preview.optJSONArray("sessions")?.length() == 0) {
|
||||
"\n\nExample, not sent: one drive adds this to \"sessions\"\n" + UsageStats.exampleSession.toString(2)
|
||||
"\n\n" + context.getString(R.string.usage_example) + "\n" + UsageStats.exampleSession.toString(2)
|
||||
} else ""
|
||||
return preview.toString(2) + example
|
||||
}
|
||||
|
||||
/** What's different about where this build's notes go; null for a normal release. */
|
||||
private fun buildNote(): String? = when {
|
||||
BuildConfig.STATS_URL.isBlank() -> "This build has no stats server, so it sends nothing"
|
||||
BuildConfig.DEBUG -> "Debug build: notes are kept apart and never counted in the stats"
|
||||
private fun buildNote(context: Context): String? = when {
|
||||
BuildConfig.STATS_URL.isBlank() -> context.getString(R.string.usage_no_server)
|
||||
BuildConfig.DEBUG -> context.getString(R.string.usage_debug_build)
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -197,18 +188,18 @@ internal fun WorkedCard(car: String, onSeeWhatsSent: () -> Unit, modifier: Modif
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.Check, contentDescription = null)
|
||||
Text(
|
||||
WorkedPrompt.TITLE_START + car,
|
||||
if (car.isEmpty()) stringResource(R.string.worked_title_unknown) else stringResource(R.string.worked_title, car),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
)
|
||||
}
|
||||
Text(
|
||||
"Share that so the next driver knows it works? No name, account, or location.",
|
||||
stringResource(R.string.worked_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
buildNote()?.let {
|
||||
buildNote(context)?.let {
|
||||
Text(it, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 4.dp))
|
||||
}
|
||||
Row(
|
||||
@@ -217,11 +208,11 @@ internal fun WorkedCard(car: String, onSeeWhatsSent: () -> Unit, modifier: Modif
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
TextButton(onClick = onSeeWhatsSent, contentPadding = PaddingValues(horizontal = 0.dp)) {
|
||||
Text("What gets sent")
|
||||
Text(stringResource(R.string.usage_what_sent))
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
OutlinedButton(onClick = { WorkedPrompt.answer(context, share = false) }) { Text("Not now") }
|
||||
Button(onClick = { WorkedPrompt.answer(context, share = true) }) { Text("Share") }
|
||||
OutlinedButton(onClick = { WorkedPrompt.answer(context, share = false) }) { Text(stringResource(R.string.worked_not_now)) }
|
||||
Button(onClick = { WorkedPrompt.answer(context, share = true) }) { Text(stringResource(R.string.worked_share)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -35,11 +37,9 @@ internal fun VoiceModelSection() {
|
||||
remember { SpeechModels.init(context) }
|
||||
val selected by SpeechModels.selected.collectAsState()
|
||||
val final by SpeechModels.final.collectAsState()
|
||||
SettingsSection(title = "Voice: live words") {
|
||||
SettingsSection(title = stringResource(R.string.voice_live_title)) {
|
||||
Text(
|
||||
"The voice assistant turns speech into text on this phone, and nothing you say leaves it. " +
|
||||
"It needs two models, about 270 MB together, downloaded once. " +
|
||||
"This one writes your words as you speak.",
|
||||
stringResource(R.string.voice_live_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
@@ -49,10 +49,9 @@ internal fun VoiceModelSection() {
|
||||
ModelRow(model, model == selected)
|
||||
}
|
||||
}
|
||||
SettingsSection(title = "Voice: final words") {
|
||||
SettingsSection(title = stringResource(R.string.voice_final_title)) {
|
||||
Text(
|
||||
"When you stop talking, this model reads the whole phrase again. It catches names and " +
|
||||
"places the live model misses.",
|
||||
stringResource(R.string.voice_final_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
@@ -67,9 +66,9 @@ internal fun VoiceModelSection() {
|
||||
) {
|
||||
RadioButton(final == null, onClick = null, modifier = Modifier.padding(8.dp))
|
||||
Column {
|
||||
Text("None", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.voice_none), style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
"Keep the live words. Fastest, least accurate",
|
||||
stringResource(R.string.voice_none_sub),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
@@ -95,12 +94,12 @@ private fun ModelRow(model: Model, chosen: Boolean) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(chosen, onClick = null, modifier = Modifier.padding(8.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("${model.label} · ${model.downloadMb} MB", style = MaterialTheme.typography.titleMedium)
|
||||
Text(stringResource(R.string.voice_model_size, model.label, model.downloadMb), style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
when (val s = state) {
|
||||
State.Ready -> "Downloaded. ${model.detail}"
|
||||
is State.Downloading -> "Downloading, ${s.percent}%"
|
||||
is State.Failed -> "Download failed: ${s.reason}"
|
||||
State.Ready -> stringResource(R.string.voice_downloaded, model.detail)
|
||||
is State.Downloading -> stringResource(R.string.voice_downloading, s.percent)
|
||||
is State.Failed -> stringResource(R.string.cert_download_failed, s.reason)
|
||||
State.Missing -> model.detail
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
@@ -109,9 +108,9 @@ private fun ModelRow(model: Model, chosen: Boolean) {
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
when (state) {
|
||||
State.Ready -> TextButton(onClick = { SpeechModels.delete(context, model) }) { Text("Delete") }
|
||||
is State.Downloading -> TextButton(onClick = { SpeechModels.delete(context, model) }) { Text("Cancel") }
|
||||
else -> OutlinedButton(onClick = { SpeechModels.download(context, model) }) { Text("Download") }
|
||||
State.Ready -> TextButton(onClick = { SpeechModels.delete(context, model) }) { Text(stringResource(R.string.storage_delete)) }
|
||||
is State.Downloading -> TextButton(onClick = { SpeechModels.delete(context, model) }) { Text(stringResource(R.string.cancel)) }
|
||||
else -> OutlinedButton(onClick = { SpeechModels.download(context, model) }) { Text(stringResource(R.string.update_download)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -37,10 +39,10 @@ fun WhatsNewScreen(onBack: () -> Unit) {
|
||||
containerColor = MaterialTheme.colorScheme.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("What's new") },
|
||||
title = { Text(stringResource(R.string.settings_whats_new)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -58,7 +60,7 @@ fun WhatsNewScreen(onBack: () -> Unit) {
|
||||
Changelog.releases.forEach { release ->
|
||||
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(
|
||||
if (release.versionCode == BuildConfig.VERSION_CODE) "${release.versionName} (this version)" else release.versionName,
|
||||
if (release.versionCode == BuildConfig.VERSION_CODE) stringResource(R.string.whats_new_this_version, release.versionName) else release.versionName,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.seb3thehacker.gearslip.ui
|
||||
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.seb3thehacker.gearslip.R
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
@@ -64,7 +66,7 @@ internal fun WirelessButton() {
|
||||
|
||||
if (denied) {
|
||||
Text(
|
||||
"Gearslip needs Bluetooth and nearby Wi-Fi access for this.",
|
||||
stringResource(R.string.wireless_needs_permission),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
// Once Android stops asking, the only way back is the app's own permission settings.
|
||||
@@ -77,7 +79,7 @@ internal fun WirelessButton() {
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(bottom = 4.dp),
|
||||
) { Text("Enable Nearby devices permission") }
|
||||
) { Text(stringResource(R.string.wireless_enable_permission)) }
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
@@ -88,17 +90,17 @@ internal fun WirelessButton() {
|
||||
) {
|
||||
Box(Modifier.size(28.dp)) { WifiIcon(MaterialTheme.colorScheme.onPrimary) }
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Text("Connect wirelessly", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
||||
Text(stringResource(R.string.wireless_connect), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
||||
}
|
||||
|
||||
devices?.let { list ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { devices = null },
|
||||
title = { Text("Which car?") },
|
||||
title = { Text(stringResource(R.string.wireless_which_car)) },
|
||||
text = {
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||
if (list.isEmpty()) {
|
||||
Text("Your phone isn't paired with anything yet. Pair it with the car first.")
|
||||
Text(stringResource(R.string.wireless_none_paired))
|
||||
}
|
||||
list.forEach { device ->
|
||||
Text(
|
||||
@@ -118,7 +120,7 @@ internal fun WirelessButton() {
|
||||
// The car has to be paired before it shows up here, and pairing happens in Android's settings.
|
||||
if (list.isNotEmpty()) {
|
||||
Text(
|
||||
"Car not listed? Pair it with your phone first.",
|
||||
stringResource(R.string.wireless_not_listed),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
@@ -132,9 +134,9 @@ internal fun WirelessButton() {
|
||||
runCatching {
|
||||
context.startActivity(Intent(Settings.ACTION_BLUETOOTH_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
}
|
||||
}) { Text("Bluetooth settings") }
|
||||
}) { Text(stringResource(R.string.bluetooth_settings)) }
|
||||
},
|
||||
confirmButton = { TextButton(onClick = { devices = null }) { Text("Cancel") } },
|
||||
confirmButton = { TextButton(onClick = { devices = null }) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
unqualifiedResLocale=en-US
|
||||
@@ -0,0 +1,864 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">يتيح لـ Gearslip نقل اللمسات من شاشة السيارة إلى الهاتف أثناء مشاركة الشاشة. لا يقرأ Gearslip محتوى الشاشة.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">ساعد السائقين الآخرين.</string>
|
||||
<string name="guide_safety_title">قُد بأمان.</string>
|
||||
<string name="guide_safety_body">Gearslip تطبيق غير رسمي. لم تصنعه أي شركة سيارات ولم تراجعه، وقد يتوقف عن العمل دون سابق إنذار. اضبطه قبل أن تنطلق، ثم أبقِ عينيك على الطريق.</string>
|
||||
<string name="guide_mic_title">الميكروفون.</string>
|
||||
<string name="guide_mic_body">يستخدمه Gearslip لإرسال موسيقاك إلى السيارة ولسماعك عندما تطلب شيئًا أو ترد على رسالة. ما تقوله يبقى على الهاتف.</string>
|
||||
<string name="guide_location_title">الموقع.</string>
|
||||
<string name="guide_location_body">تحتاجه خريطة السيارة والبحث عن الأماكن القريبة. من دونه لا تظهر خريطة في لوحة المعلومات.</string>
|
||||
<string name="guide_calendar_title">التقويم.</string>
|
||||
<string name="guide_calendar_body">تعرض شاشة السيارة موعدك التالي. يقرأ Gearslip تقويمك فقط.</string>
|
||||
<string name="guide_calls_title">المكالمات وجهات الاتصال.</string>
|
||||
<string name="guide_calls_body">تعرض السيارة اسم المتصل، ويمكن لتطبيق الهاتف فيها طلب رقم أو الاتصال بإحدى جهات اتصالك. يقرأها Gearslip فقط.</string>
|
||||
<string name="guide_bluetooth_title">البلوتوث.</string>
|
||||
<string name="guide_bluetooth_body">في أول مرة توصل فيها الهاتف، يعرض Gearslip إقرانه بالسيارة لتُسمع المكالمات عبر مكبرات صوتها.</string>
|
||||
<string name="guide_notifications_title">الإشعارات.</string>
|
||||
<string name="guide_notifications_body">أثناء اتصال Gearslip يعرض إشعارًا فيه زر للإيقاف. من دون هذا الإذن يخفيه Android.</string>
|
||||
<string name="guide_messages_title">الرسائل.</string>
|
||||
<string name="guide_messages_body">تعرض السيارة رسائلك وتقرؤها بصوت عالٍ وتتيح لك الرد. فعّل Gearslip في القائمة التي ستفتح.</string>
|
||||
<string name="guide_android_auto_title">أوقف Android Auto.</string>
|
||||
<string name="guide_android_auto_body">لا يزال Android Auto مثبتًا، وهو ينازع Gearslip على اتصال USB بالسيارة. عطّله أو أزل تثبيته في الشاشة التي ستفتح.</string>
|
||||
<string name="guide_default_title">اختر Gearslip.</string>
|
||||
<string name="guide_default_body">إذا سألك الهاتف عن التطبيق الذي يُفتح عند توصيله بالسيارة، فاختر Gearslip ثم انقر على «دائمًا».</string>
|
||||
<string name="guide_audio_title">صوت السيارة.</string>
|
||||
<string name="guide_audio_body">اختر كيف تصل الموسيقى إلى السيارة. المكالمات تمر عبر البلوتوث في كلتا الحالتين.</string>
|
||||
<string name="guide_voice_title">الصوت.</string>
|
||||
<string name="guide_voice_body">اطلب الاتجاهات أو الموسيقى أو مكالمة بصوتك. يحوّل هاتفك الكلام إلى نص بنفسه، ولا يخرج منه شيء مما تقوله. يحتاج إلى %1$d ميغابايت تُنزَّل مرة واحدة. استخدم Wi-Fi إن أمكن.</string>
|
||||
<string name="guide_audio_prompt_title">من دون سؤال عن الصوت.</string>
|
||||
<string name="guide_audio_prompt_body">شغّل هذا من حاسوب مفعّل فيه تصحيح أخطاء USB، فيأتمن Android تطبيق Gearslip على صوتك نهائيًا. من دونه تنقر على «ابدأ» مرة في كل رحلة.</string>
|
||||
<string name="guide_readable_title">إشعارات مقروءة.</string>
|
||||
<string name="guide_readable_body">أثناء إرسال الصوت إلى السيارة يخفي Android محتوى الإشعارات كما لو كنت تشارك شاشتك. يتيح هذا لـ Gearslip عرضها أثناء الرحلة ثم إخفاءها مجددًا بعدها.</string>
|
||||
<string name="guide_answer_title">الرد على المكالمات.</string>
|
||||
<string name="guide_answer_body">اجعل Gearslip تطبيق معرّف المتصل، فتستطيع السيارة الرد على المكالمات ورفضها. من دون ذلك يرن الهاتف، لكن لا يمكن الرد إلا منه.</string>
|
||||
<string name="guide_apps_title">رفقة طيبة.</string>
|
||||
<string name="guide_buttons_title">أزرارك.</string>
|
||||
<string name="guide_buttons_body">اختر شكل الأزرار في السيارة. يمكنك تغييره في أي وقت من إعدادات السيارة.</string>
|
||||
<string name="guide_open_notification_access">فتح الوصول إلى الإشعارات</string>
|
||||
<string name="guide_open_android_auto">فتح معلومات تطبيق Android Auto</string>
|
||||
<string name="guide_download_voice">تنزيل الصوت</string>
|
||||
<string name="guide_set_up">إعداد</string>
|
||||
<string name="guide_voice_downloading">جارٍ التنزيل، %1$d%%. يمكنك المتابعة، فالتنزيل مستمر.</string>
|
||||
<string name="guide_voice_ready">الصوت جاهز.</string>
|
||||
<string name="guide_voice_failed">لم يكتمل التنزيل. انقر على «تنزيل الصوت» للمحاولة مجددًا.</string>
|
||||
<string name="guide_audio_bluetooth">بلوتوث</string>
|
||||
<string name="guide_recommended">موصى به</string>
|
||||
<string name="guide_audio_bluetooth_body">لا سؤال في كل رحلة، ويصل صوت تطبيق الخرائط إلى السيارة.</string>
|
||||
<string name="guide_audio_usb">عبر Gearslip</string>
|
||||
<string name="guide_audio_usb_body">صوت أنقى بلا تأخير. في كل رحلة يطلب Android منك مشاركة الشاشة، وهذا ما يتيح لـ Gearslip إرسال الصوت.</string>
|
||||
<string name="guide_optional">اختياري</string>
|
||||
<string name="guide_already_allowed">مسموح به بالفعل.</string>
|
||||
<string name="guide_scroll_more">مرّر لعرض المزيد ↓</string>
|
||||
<string name="guide_allow">سماح</string>
|
||||
<string name="guide_skip">تخطٍّ الآن</string>
|
||||
<string name="guide_open">فتح</string>
|
||||
<string name="guide_next">التالي</string>
|
||||
<string name="guide_done">تم</string>
|
||||
<string name="guide_back">رجوع</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">فعّل هذا، فيرسل Gearslip ملاحظة قصيرة عند فتحه وبعد كل رحلة. <b>لا تتضمن الملاحظة اسمًا أو حسابًا أو موقعًا. يُحتسب هاتفك مرة واحدة برقم عشوائي، ولا يحتفظ الخادم بأي عنوان IP.</b> تبيّن الملاحظات السيارات التي يعمل فيها، وأين يتعثر في غيرها.</string>
|
||||
<string name="usage_send">إرسال ملاحظات الاستخدام</string>
|
||||
<string name="usage_also_share">مشاركة أيضًا</string>
|
||||
<string name="usage_share_android">إصدار Android والنظام</string>
|
||||
<string name="usage_share_phone">طراز الهاتف</string>
|
||||
<string name="usage_share_cars">كل سيارة وهل اتصلت</string>
|
||||
<string name="usage_share_drives">كل رحلة وكيف انتهت</string>
|
||||
<string name="usage_what_sent">ما الذي يُرسل</string>
|
||||
<string name="usage_what_sent_sub">كل حقل كما يُرسل تمامًا</string>
|
||||
<string name="usage_hide_note">إخفاء الملاحظة</string>
|
||||
<string name="usage_show_note">عرض الملاحظة</string>
|
||||
<string name="usage_example">مثال، لا يُرسل: رحلة واحدة تضيف هذا إلى «sessions»</string>
|
||||
<string name="usage_no_server">لا يحتوي هذا الإصدار على خادم إحصاءات، لذلك لا يرسل شيئًا</string>
|
||||
<string name="usage_debug_build">إصدار تصحيح: تُحفظ الملاحظات منفصلة ولا تُحتسب أبدًا في الإحصاءات</string>
|
||||
<string name="worked_title">عمل Gearslip مع سيارتك %1$s</string>
|
||||
<string name="worked_title_unknown">عمل Gearslip مع سيارتك</string>
|
||||
<string name="worked_body">هل تشارك ذلك ليعرف السائق التالي أنه يعمل؟ من دون اسم أو حساب أو موقع.</string>
|
||||
<string name="worked_not_now">ليس الآن</string>
|
||||
<string name="worked_share">مشاركة</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">تعمل هذه التطبيقات المجانية على شاشة السيارة في Gearslip. يفتح كل زر صفحة التطبيق على GitHub.</string>
|
||||
<string name="recommended_title">تطبيقات موصى بها</string>
|
||||
<string name="back">رجوع</string>
|
||||
<string name="recommended_installed">مثبّت</string>
|
||||
<string name="recommended_maps">الخرائط</string>
|
||||
<string name="recommended_music">الموسيقى</string>
|
||||
<string name="recommended_podcasts">البودكاست</string>
|
||||
<string name="recommended_other">تطبيقات رائعة أخرى</string>
|
||||
<string name="recommended_about_organic">خرائط ومسارات دون اتصال.</string>
|
||||
<string name="recommended_about_vela">ملاحة خطوة بخطوة من دون Google.</string>
|
||||
<string name="recommended_about_ytm">مشغّل لـ YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">بودكاست، مع الفصول وكل شيء.</string>
|
||||
<string name="recommended_about_rfsentinel">يكتشف أجهزة التتبع والكاميرات القريبة.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">الإعدادات</string>
|
||||
<string name="settings_whats_new">الجديد</string>
|
||||
<string name="settings_whats_new_sub">اقرأ ملاحظات إصدار %1$s على GitHub</string>
|
||||
<string name="settings_support">ادعم Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip مجاني، بلا إعلانات ولا تتبع. دعمك يبقيه مستمرًا.</string>
|
||||
<string name="settings_usage_notes">ملاحظات الاستخدام</string>
|
||||
<string name="settings_updates">التحديثات</string>
|
||||
<string name="settings_about">حول</string>
|
||||
<string name="settings_replay">إعادة دليل الإعداد</string>
|
||||
<string name="settings_replay_sub">المرور بخطوات التشغيل الأول مجددًا</string>
|
||||
<string name="settings_connection">الاتصال</string>
|
||||
<string name="settings_use_in_car">استخدام Gearslip في السيارة</string>
|
||||
<string name="settings_use_in_car_on">يستجيب Gearslip عندما توصل الهاتف بسيارة</string>
|
||||
<string name="settings_use_in_car_off">متوقف: يمكن لـ Android Auto أخذ الاتصال بدلًا منه</string>
|
||||
<string name="settings_apps">التطبيقات</string>
|
||||
<string name="settings_certificate">الشهادة</string>
|
||||
<string name="cert_next_connection">تسري التغييرات عند الاتصال التالي.</string>
|
||||
<string name="cert_checking">جارٍ التحقق…</string>
|
||||
<string name="cert_valid_until">صالحة حتى %1$s</string>
|
||||
<string name="cert_hide_details">إخفاء التفاصيل</string>
|
||||
<string name="cert_show_details">عرض التفاصيل</string>
|
||||
<string name="cert_subject">الموضوع</string>
|
||||
<string name="cert_issuer">الجهة المُصدِرة</string>
|
||||
<string name="cert_downloading">جارٍ التنزيل…</string>
|
||||
<string name="cert_download">تنزيل من aasdk</string>
|
||||
<string name="cert_downloaded">نُزّلت الشهادة.</string>
|
||||
<string name="cert_download_failed">فشل التنزيل: %1$s</string>
|
||||
<string name="cert_download_sub">الشهادة التي ينشرها مشروع aasdk مفتوح المصدر على GitHub. تقبلها معظم السيارات، لكن بعض السيارات من نحو عام 2020 فما بعد ترفضها</string>
|
||||
<string name="cert_import">استيراد ملفي الخاص</string>
|
||||
<string name="cert_import_sub">ملف .p12 مع مفتاحه الخاص. استخدم ملفك عندما ترفض سيارتك شهادة aasdk</string>
|
||||
<string name="cert_remove_imported">إزالة الشهادة المستوردة</string>
|
||||
<string name="cert_remove_downloaded">إزالة الشهادة المنزّلة</string>
|
||||
<string name="cert_removed_imported">أُزيلت الشهادة المستوردة.</string>
|
||||
<string name="cert_removed_downloaded">أُزيلت الشهادة المنزّلة.</string>
|
||||
<string name="cert_imported">استُوردت الشهادة.</string>
|
||||
<string name="cert_import_failed">تعذّر الاستيراد: كلمة المرور خاطئة، أو الملف ليس ملف PKCS#12 يحتوي على مفتاح خاص.</string>
|
||||
<string name="cert_password_title">كلمة مرور الشهادة</string>
|
||||
<string name="cert_password">كلمة المرور</string>
|
||||
<string name="cert_password_hint">اتركها فارغة إذا لم يكن للملف كلمة مرور.</string>
|
||||
<string name="cert_import_button">استيراد</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">إلغاء</string>
|
||||
<string name="cert_none">لم تُحمَّل أي شهادة (موقّعة ذاتيًا؛ ترفضها السيارات)</string>
|
||||
<string name="cert_kind_imported">مستوردة: %1$s</string>
|
||||
<string name="cert_kind_downloaded">منزّلة: %1$s</string>
|
||||
<string name="cert_kind_adb">موضوعة عبر adb: %1$s</string>
|
||||
<string name="cert_kind_self">موقّعة ذاتيًا: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">الشاشة</string>
|
||||
<string name="car_group_nav_bar">شريط التنقل والمشغّل</string>
|
||||
<string name="car_group_sound">الصوت والكلام</string>
|
||||
<string name="car_group_warnings">التحذيرات</string>
|
||||
<string name="car_group_more">المزيد</string>
|
||||
<string name="choice_size_normal">عادي</string>
|
||||
<string name="choice_size_large">كبير</string>
|
||||
<string name="choice_size_xlarge">كبير جدًا</string>
|
||||
<string name="choice_theme_phone">مثل الهاتف</string>
|
||||
<string name="choice_theme_light">فاتح</string>
|
||||
<string name="choice_theme_dark">داكن</string>
|
||||
<string name="choice_dark_black">أسود</string>
|
||||
<string name="choice_dark_black_colour">أسود وملوّن</string>
|
||||
<string name="choice_dark_deep_colour">لون عميق</string>
|
||||
<string name="choice_dark_grey">رمادي</string>
|
||||
<string name="choice_auto">تلقائي</string>
|
||||
<string name="choice_night_day">نهار</string>
|
||||
<string name="choice_night_night">ليل</string>
|
||||
<string name="choice_units_imperial">إمبراطوري</string>
|
||||
<string name="choice_units_metric">متري</string>
|
||||
<string name="choice_nav_show">إظهار دائمًا</string>
|
||||
<string name="choice_nav_hide_navigating">إخفاء أثناء الملاحة</string>
|
||||
<string name="choice_nav_hide">إخفاء حتى النقر</string>
|
||||
<string name="choice_open_home">الرئيسية</string>
|
||||
<string name="summary_player_left">المشغّل على اليسار</string>
|
||||
<string name="summary_player_right">المشغّل على اليمين</string>
|
||||
<string name="summary_usb_audio">الصوت عبر Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">صوت البلوتوث</string>
|
||||
<string name="summary_directions_on">الإرشادات الصوتية مفعّلة</string>
|
||||
<string name="summary_directions_off">الإرشادات الصوتية متوقفة</string>
|
||||
<string name="summary_bluetooth_on">البلوتوث مفعّل</string>
|
||||
<string name="summary_bluetooth_off">البلوتوث متوقف</string>
|
||||
<string name="summary_location_on">الموقع مفعّل</string>
|
||||
<string name="summary_location_off">الموقع متوقف</string>
|
||||
<string name="web_page">صفحة ويب</string>
|
||||
<string name="summary_experimental_shown">الميزات التجريبية ظاهرة</string>
|
||||
<string name="summary_experimental_hidden">الميزات التجريبية مخفية</string>
|
||||
<string name="home_car">السيارة</string>
|
||||
<string name="car_group_same">هذه الإعدادات نفسها الموجودة في «الإعدادات» داخل السيارة. أي تغيير هنا يظهر هناك أيضًا.</string>
|
||||
<string name="car_open_on_connect">الفتح عند الاتصال</string>
|
||||
<string name="car_size">الحجم</string>
|
||||
<string name="car_app_theme">مظهر التطبيق</string>
|
||||
<string name="car_dark_mode">الوضع الداكن</string>
|
||||
<string name="car_buttons">الأزرار</string>
|
||||
<string name="car_map_day_night">الخريطة نهار/ليل</string>
|
||||
<string name="car_units">الوحدات</string>
|
||||
<string name="car_nav_bar">شريط التنقل</string>
|
||||
<string name="car_player_nav_bar">المشغّل في شريط التنقل</string>
|
||||
<string name="car_skip_buttons">أزرار التخطي</string>
|
||||
<string name="car_song_title">عنوان الأغنية</string>
|
||||
<string name="car_player_beside_map">المشغّل بجانب الخريطة</string>
|
||||
<string name="car_right">يمين</string>
|
||||
<string name="car_left">يسار</string>
|
||||
<string name="car_autoplay">التشغيل التلقائي عند الاتصال</string>
|
||||
<string name="car_more_lyrics">مصادر أكثر لكلمات الأغاني</string>
|
||||
<string name="car_more_lyrics_note">يسأل أيضًا BetterLyrics وKuGou، فيحصلان على اسم الأغنية والفنان. عند الإيقاف يسأل LRCLIB فقط.</string>
|
||||
<string name="car_spoken_directions">الإرشادات الصوتية</string>
|
||||
<string name="car_spoken_directions_note">أوقفها إذا كان صوت تطبيق الخرائط نفسه يُسمع في السيارة بالفعل.</string>
|
||||
<string name="car_phone_speech">استخدام التعرّف على الكلام في الهاتف</string>
|
||||
<string name="car_phone_speech_note">في معظم الهواتف هو تعرّف Google، وقد يرسل صوتك إلى Google.</string>
|
||||
<string name="car_warn_bluetooth">التحذير عندما لا يكون البلوتوث متصلًا</string>
|
||||
<string name="car_warn_location">التحذير عندما يكون الموقع متوقفًا</string>
|
||||
<string name="car_experimental">الميزات التجريبية</string>
|
||||
<string name="car_vehicle_data">بيانات المركبة</string>
|
||||
<string name="car_vehicle_data_note">يضيف مربع «بيانات المركبة»: عرض مباشر لحساسات السيارة. أسماء الحقول تخمين إلى أن ترى أحدها يتغير.</string>
|
||||
<string name="car_adaptive_video">جودة فيديو متكيفة</string>
|
||||
<string name="car_adaptive_video_note">يخفض جودة الصورة عندما لا تواكب السيارة أو شبكة Wi-Fi فيها، ويرفعها مجددًا حين تستطيع. يسري عند الاتصال التالي.</string>
|
||||
<string name="web_page_note">يعرضها تطبيق المتصفح في السيارة. إذا تُركت فارغة يُفتح DuckDuckGo. يسري عند الاتصال التالي.</string>
|
||||
<string name="car_audio">صوت السيارة</string>
|
||||
<string name="car_audio_bt">بلوتوث (موصى به)</string>
|
||||
<string name="car_audio_bt_sub">لا سؤال في كل رحلة، ويصل صوت تطبيق الخرائط إلى السيارة</string>
|
||||
<string name="car_audio_usb_sub">صوت أنقى بلا تأخير. في كل رحلة يطلب Android منك مشاركة الشاشة، وهذا ما يتيح لـ Gearslip إرسال الصوت</string>
|
||||
<string name="car_audio_usb_footer">شغّل الأمر الموجود في «المساعدة» مرة واحدة، فيتوقف Android عن السؤال.</string>
|
||||
<string name="car_audio_bt_footer">المكالمات تمر عبر البلوتوث في كلتا الحالتين.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">حجم شاشة السيارة</string>
|
||||
<string name="screen_use_cars">حجم السيارة</string>
|
||||
<string name="screen_preview_size">حجم المعاينة</string>
|
||||
<string name="screen_preview_note">الحجم الذي تُرسم به معاينة السيارة، مثل 1920x720.</string>
|
||||
<string name="screen_car_screen">شاشة السيارة</string>
|
||||
<string name="screen_footer">يستخدم Gearslip الحجم الذي تبلغ عنه السيارة ما لم تضبط حجمًا بنفسك.</string>
|
||||
<string name="screen_size">حجم الشاشة</string>
|
||||
<string name="screen_from_car">من السيارة</string>
|
||||
<string name="screen_crop">قص الحواف</string>
|
||||
<string name="screen_crop_sub">لسيارة تقطع الأعلى والأسفل أو الجانبين</string>
|
||||
<string name="screen_squash">ضغط ليتلاءم</string>
|
||||
<string name="screen_squash_sub">لسيارة تضغط الصورة كلها فتبدو الدوائر بيضاوية</string>
|
||||
<string name="screen_preview_title">حجم معاينة السيارة</string>
|
||||
<string name="screen_last_car">آخر سيارة</string>
|
||||
<string name="screen_last_car_sub">حجم آخر سيارة اتصلت بها</string>
|
||||
<string name="screen_this_screen">هذه الشاشة</string>
|
||||
<string name="screen_this_screen_sub">يملأ شاشة هذا الجهاز، لوحدة رأس تشغّل Gearslip بنفسها</string>
|
||||
<string name="screen_custom">مخصص</string>
|
||||
<string name="screen_enter_size">أدخل حجمًا</string>
|
||||
<string name="screen_change_custom">تغيير الحجم المخصص</string>
|
||||
<string name="screen_width">العرض</string>
|
||||
<string name="screen_height">الارتفاع</string>
|
||||
<string name="save">حفظ</string>
|
||||
|
||||
<string name="screen_car_size_note">استخدم هذا إذا بدت صورة السيارة ممطوطة. أدخل الحجم الحقيقي للشاشة، مثل 1280x480. يسري في المرة التالية التي تتصل فيها.</string>
|
||||
<string name="screen_set_by_you">%1$s، ضبطته أنت</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">طريقة الاتصال</string>
|
||||
<string name="help_which_cars">السيارات التي يعمل فيها</string>
|
||||
<string name="help_full_list">عرض القائمة الكاملة</string>
|
||||
<string name="help_first_drive">قبل رحلتك الأولى</string>
|
||||
<string name="help_in_car">في السيارة</string>
|
||||
<string name="help_usb_audio">الصوت عبر Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">أوقف Android Auto.</string>
|
||||
<string name="help_aa_off_detail">عطّل تطبيق Android Auto أو أزل تثبيته، لتتواصل السيارة مع Gearslip بدلًا منه.</string>
|
||||
<string name="help_cert">حمّل شهادة.</string>
|
||||
<string name="help_cert_detail">من «الإعدادات» نزّل شهادة aasdk التي تقبلها معظم السيارات. إذا رفضتها سيارتك فاستورد شهادتك الخاصة.</string>
|
||||
<string name="help_mic">اسمح بالميكروفون.</string>
|
||||
<string name="help_mic_detail">من دونه لا يسمح Android لأي تطبيق بتمرير الموسيقى إلى السيارة. لا يستمع Gearslip إلا عندما تنقر على الميكروفون أو تضغط زر التحدث على المقود.</string>
|
||||
<string name="help_plug_in">وصّل الكابل أو اتصل لاسلكيًا.</string>
|
||||
<string name="help_plug_in_detail">يفتح Gearslip تلقائيًا عندما تتصل السيارة. للاتصال اللاسلكي، أقرن الهاتف بالسيارة أولًا ثم انقر على «اتصال لاسلكي» في الشاشة الرئيسية.</string>
|
||||
<string name="help_pair">أقرن عبر البلوتوث للمكالمات.</string>
|
||||
<string name="help_pair_detail">تُسمع المكالمات في السيارة بعد إقران الهاتف والسيارة. يعرض Gearslip الإقران في أول مرة توصل فيها الهاتف.</string>
|
||||
<string name="help_answer">ردّ على المكالمات من شاشة السيارة.</string>
|
||||
<string name="help_answer_detail">اضبط Gearslip تطبيقًا لمعرّف المتصل والرسائل المزعجة. من دون ذلك يرن الهاتف، لكن لا يمكن الرد إلا منه.</string>
|
||||
<string name="help_prompt">أوقف سؤال الصوت في كل رحلة.</string>
|
||||
<string name="help_prompt_detail">وصّل الهاتف بحاسوب مفعّل فيه تصحيح أخطاء USB وشغّل هذا مرة واحدة. إن تخطيته فستنقر على «ابدأ» كلما انطلقت.</string>
|
||||
<string name="help_readable">أبقِ الإشعارات مقروءة.</string>
|
||||
<string name="help_readable_detail">يخفي Android محتوى الإشعارات أثناء مشاركة الصوت. شغّل هذا أيضًا، فيرفع Gearslip ذلك فقط أثناء ذهاب الموسيقى إلى السيارة.</string>
|
||||
<string name="help_which_cars_body">تقبل معظم السيارات شهادة aasdk. منذ نحو عام 2020 أضافت بعض السيارات فحوصات أمان أحدث ترفضها، وفي هذه السيارات يحتاج Gearslip إلى شهادة خاصة بك.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">نُزّل. سيسأل Android قبل التثبيت.</string>
|
||||
<string name="update_install">تثبيت</string>
|
||||
<string name="update_download_now">نزّله الآن. سيسأل Android قبل التثبيت.</string>
|
||||
<string name="update_try_again">إعادة المحاولة</string>
|
||||
<string name="update_update">تحديث</string>
|
||||
<string name="update_checking">جارٍ التحقق…</string>
|
||||
<string name="update_latest">لديك أحدث إصدار</string>
|
||||
<string name="update_download">تنزيل</string>
|
||||
<string name="update_check_now">التحقق الآن</string>
|
||||
<string name="update_check_auto">التحقق تلقائيًا</string>
|
||||
<string name="update_once_a_day">مرة يوميًا</string>
|
||||
|
||||
<string name="update_unplug_first">افصل الهاتف عن السيارة للتثبيت. التثبيت يغلق Gearslip.</string>
|
||||
<string name="update_card_title">صدر Gearslip %1$s</string>
|
||||
<string name="update_downloading">جارٍ التنزيل… %1$d%%</string>
|
||||
<string name="update_debug_manual">إصدارات التصحيح لا تتحقق إلا عند النقر على «التحقق الآن»</string>
|
||||
<string name="update_checks_daily">يتحقق Gearslip مرة يوميًا</string>
|
||||
<string name="update_version_out">صدر الإصدار %1$s</string>
|
||||
<string name="update_version_ready">الإصدار %1$s جاهز للتثبيت</string>
|
||||
<string name="update_dialog_status">الإصدار %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">تبرّع أسبوعيًا أو شهريًا</string>
|
||||
<string name="about_bmac_sub" formatted="false">تبرّع مرة أو شهريًا بالبطاقة. يحتفظ Buy Me a Coffee بنسبة 5%</string>
|
||||
<string name="about_give_once">تبرّع مرة واحدة</string>
|
||||
<string name="about_any_amount">أي مبلغ، بالبطاقة</string>
|
||||
<string name="about_monero_sub">افتح محفظتك أو انسخ العنوان</string>
|
||||
<string name="about_monero_title">الدعم عبر Monero</string>
|
||||
<string name="about_monero_body">أرسل أي مبلغ إلى هذا العنوان من محفظة Monero.</string>
|
||||
<string name="about_open_wallet">فتح في المحفظة</string>
|
||||
<string name="about_copy_address">نسخ العنوان</string>
|
||||
<string name="close">إغلاق</string>
|
||||
<string name="about_matrix">انضم إلى الدردشة على Matrix</string>
|
||||
<string name="about_matrix_sub">تحدث مع سائقين آخرين عن السيارات والتطبيقات وGearslip</string>
|
||||
<string name="about_translate">ساعد في ترجمة Gearslip</string>
|
||||
<string name="about_translate_sub">صحّح لغتك أو أضفها على Weblate. بدأت بعض الترجمات ترجمةً آلية.</string>
|
||||
<string name="about_source">الشيفرة المصدرية</string>
|
||||
<string name="about_source_sub">اقرأ شيفرة Gearslip أو عدّلها بموجب رخصة AGPL</string>
|
||||
<string name="about_voice_credits">شكر خاص للصوت</string>
|
||||
|
||||
<string name="about_no_wallet">لم يُعثر على تطبيق محفظة Monero. انسخ العنوان بدلًا من ذلك.</string>
|
||||
<string name="about_monero_copied">نُسخ عنوان Monero</string>
|
||||
<string name="about_voice_credits_sub">التعرّف على الكلام بواسطة sherpa-onnx (Apache 2.0)، مع Kroko ASR من Banafo (CC BY-SA 4.0)، وParakeet وCanary من NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">إعداد الشهادة</string>
|
||||
<string name="certsetup_installed">ثُبّتت الشهادة.</string>
|
||||
<string name="certsetup_continue">متابعة</string>
|
||||
<string name="certsetup_failed">فشل التنزيل</string>
|
||||
<string name="certsetup_retry">إعادة المحاولة</string>
|
||||
<string name="certsetup_import_sub">ملف .p12 مع مفتاحه الخاص. استخدم ملفك عندما ترفض سيارتك شهادة aasdk.</string>
|
||||
<string name="certsetup_skip">تخطي إعداد الاتصال بالسيارة</string>
|
||||
|
||||
<string name="certsetup_loaded">لا تقبل السيارات إلا الشهادات الصادرة عن Google، ولديك واحدة محمّلة.</string>
|
||||
<string name="certsetup_choose">لا تقبل السيارات إلا الشهادات الصادرة عن Google. لا يتضمن Gearslip أي شهادة، فاختر مصدر شهادتك.</string>
|
||||
<string name="certsetup_aasdk_sub">ينشر مشروع aasdk مفتوح المصدر هذه الشهادة على github.com/opencardev/aasdk. تقبلها معظم السيارات. بعض السيارات من نحو عام 2020 فما بعد ترفضها، وعندها عليك استيراد شهادتك الخاصة.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">مساحة تخزين Gearslip</string>
|
||||
<string name="storage_measuring">جارٍ القياس…</string>
|
||||
<string name="storage_lyrics">كلمات الأغاني</string>
|
||||
<string name="storage_clear">مسح</string>
|
||||
<string name="storage_voice">نماذج الصوت</string>
|
||||
<string name="storage_remove">إزالة</string>
|
||||
<string name="storage_logs">السجلات القديمة</string>
|
||||
<string name="storage_other">ملفات مؤقتة أخرى</string>
|
||||
<string name="storage_delete_all">حذف كل بيانات Gearslip</string>
|
||||
|
||||
<string name="storage_total">تشغل بيانات Gearslip %1$s من مساحة تخزين الهاتف.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="zero">%1$d أغنية · %2$s. تُنزَّل الكلمات الممسوحة مجددًا عندما تفتحها.</item>
|
||||
<item quantity="one">%1$d أغنية · %2$s. تُنزَّل الكلمات الممسوحة مجددًا عندما تفتحها.</item>
|
||||
<item quantity="two">%1$d أغنيتان · %2$s. تُنزَّل الكلمات الممسوحة مجددًا عندما تفتحها.</item>
|
||||
<item quantity="few">%1$d أغانٍ · %2$s. تُنزَّل الكلمات الممسوحة مجددًا عندما تفتحها.</item>
|
||||
<item quantity="many">%1$d أغنية · %2$s. تُنزَّل الكلمات الممسوحة مجددًا عندما تفتحها.</item>
|
||||
<item quantity="other">%1$d أغنية · %2$s. تُنزَّل الكلمات الممسوحة مجددًا عندما تفتحها.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. تحتاجها الأوامر الصوتية؛ نزّلها مجددًا من «الإعدادات».</string>
|
||||
<string name="storage_logs_detail">%1$s. سجلات من مرات تشغيل سابقة؛ يبقى سجل هذا التشغيل.</string>
|
||||
<string name="storage_other_detail">%1$s. تحديثات منزّلة وملفات أخرى يستطيع Gearslip إنشاءها مجددًا.</string>
|
||||
<string name="storage_delete_armed">انقر على «حذف» مرة أخرى لمسح الإعدادات والشهادات ونماذج الصوت والسجلات. يُغلق Gearslip ويبدأ من الإعداد في المرة القادمة.</string>
|
||||
<string name="storage_delete_sub">الإعدادات والشهادات ونماذج الصوت والكلمات والسجلات.</string>
|
||||
<string name="storage_delete">حذف</string>
|
||||
<string name="storage_delete_ellipsis">حذف…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">سعيد بأن الرحلة نجحت</string>
|
||||
<string name="donate_liberapay">الدعم عبر Liberapay</string>
|
||||
<string name="donate_bmac">اشترِ لي قهوة</string>
|
||||
<string name="donate_card">تبرّع مرة بالبطاقة</string>
|
||||
|
||||
<string name="donate_body">Gearslip مجاني، بلا إعلانات ولا تتبع. إن كان يوصلك إلى الطريق، فدعمك يبقيه مستمرًا.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">يحتاج Gearslip إلى الوصول إلى البلوتوث وشبكات Wi-Fi القريبة لهذا.</string>
|
||||
<string name="wireless_enable_permission">تفعيل إذن الأجهزة المجاورة</string>
|
||||
<string name="wireless_connect">اتصال لاسلكي</string>
|
||||
<string name="wireless_which_car">أي سيارة؟</string>
|
||||
<string name="wireless_none_paired">هاتفك غير مقترن بأي جهاز بعد. أقرنه بالسيارة أولًا.</string>
|
||||
<string name="wireless_not_listed">السيارة غير موجودة في القائمة؟ أقرنها بهاتفك أولًا.</string>
|
||||
<string name="bluetooth_settings">إعدادات البلوتوث</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">الصوت: كلمات مباشرة</string>
|
||||
<string name="voice_final_title">الصوت: كلمات نهائية</string>
|
||||
<string name="voice_none">لا شيء</string>
|
||||
<string name="voice_none_sub">الإبقاء على الكلمات المباشرة. الأسرع والأقل دقة</string>
|
||||
|
||||
<string name="voice_live_body">يحوّل المساعد الصوتي الكلام إلى نص على هذا الهاتف، ولا يخرج منه شيء مما تقوله. يحتاج إلى نموذجين، نحو 270 ميغابايت معًا، يُنزَّلان مرة واحدة. هذا النموذج يكتب كلماتك وأنت تتحدث.</string>
|
||||
<string name="voice_final_body">عندما تتوقف عن الكلام، يعيد هذا النموذج قراءة العبارة كاملة. يلتقط الأسماء والأماكن التي يفوّتها النموذج المباشر.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d ميغابايت</string>
|
||||
<string name="voice_downloaded">نُزّل. %1$s</string>
|
||||
<string name="voice_downloading">جارٍ التنزيل، %1$d%%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">المساعدة</string>
|
||||
<string name="home_preview">معاينة</string>
|
||||
<string name="home_logs">السجلات</string>
|
||||
<string name="home_disconnect">قطع الاتصال</string>
|
||||
<string name="home_bt_title">البلوتوث غير متصل</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · إصدار تصحيح</string>
|
||||
<string name="home_bt_body">تُشغَّل الموسيقى عبر البلوتوث، لكن هاتفك غير متصل بالسيارة. وصّله من هنا أو من قائمة البلوتوث في السيارة.</string>
|
||||
<string name="home_voice_downloading">جارٍ تنزيل الصوت، %1$d%%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">تشغيل صوت السيارة عبر البلوتوث؟</string>
|
||||
<string name="audio_never_ask">عدم السؤال أبدًا، باستخدام حاسوب</string>
|
||||
<string name="audio_use_bt">استخدام البلوتوث</string>
|
||||
<string name="audio_keep_asking">مواصلة السؤال</string>
|
||||
<string name="audio_mic_title">يحتاج صوت السيارة إلى إذن الميكروفون</string>
|
||||
<string name="audio_open_settings">فتح إعدادات التطبيق</string>
|
||||
|
||||
<string name="audio_bt_body">يعامل Android الموسيقى التي يرسلها Gearslip إلى السيارة كمشاركة للشاشة، لذا يسأل في كل رحلة، بالكابل أو لاسلكيًا. عبر البلوتوث لا يسأل أبدًا. يمكنك تغيير هذا لاحقًا من «الإعدادات».</string>
|
||||
<string name="audio_keep_usb">أو أبقِ الصوت عبر Gearslip وأوقف السؤال نهائيًا: وصّل الهاتف بحاسوب مفعّل فيه تصحيح أخطاء USB وشغّل هذا مرة واحدة.</string>
|
||||
<string name="audio_mic_body">لا يسمح Android لتطبيق بإرسال صوت التطبيقات الأخرى إلى السيارة إلا إذا كان لديه إذن الميكروفون. هذا الإذن متوقف لـ Gearslip، لذا تُشغَّل الموسيقى على الهاتف. فعّله من «الأذونات» ثم «الميكروفون».</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">إغلاق لوحة مفاتيح السيارة</string>
|
||||
<string name="typing_label">الكتابة لشاشة السيارة</string>
|
||||
<string name="typing_search">البحث على شاشة السيارة</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">انقر للنسخ</string>
|
||||
<string name="command_copied">نُسخ الأمر</string>
|
||||
<string name="whats_new_this_version">%1$s (هذا الإصدار)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">المظهر</string>
|
||||
<string name="carset_imperial">إمبراطوري (mi، ft، °F)</string>
|
||||
<string name="carset_metric">متري (km، m، °C)</string>
|
||||
<string name="carset_screen_size">حجم الشاشة (الاتصال التالي)</string>
|
||||
<string name="carset_fit">ملاءمة الشاشة</string>
|
||||
<string name="off">متوقف</string>
|
||||
<string name="on">مفعّل</string>
|
||||
<string name="carset_lyrics_from">الكلمات من</string>
|
||||
<string name="carset_more_sources">مصادر أكثر</string>
|
||||
<string name="carset_hide">إخفاء</string>
|
||||
<string name="carset_show">إظهار</string>
|
||||
<string name="carset_phone">الهاتف</string>
|
||||
<string name="carset_vehicle">المركبة</string>
|
||||
<string name="carset_video">الفيديو</string>
|
||||
<string name="carset_version">الإصدار</string>
|
||||
<string name="carset_build">البنية</string>
|
||||
<string name="carset_whats_new_after">الجديد بعد التحديثات</string>
|
||||
<string name="carset_whats_new_after_sub">عرض قائمة التغييرات مرة واحدة، في أول اتصال للسيارة بعد التحديث.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (افتراضي) - انقر لتعيين صفحة أخرى</string>
|
||||
<string name="carset_web_note">يعرضها تطبيق المتصفح في المشغّل. يسري عند الاتصال التالي.</string>
|
||||
<string name="carset_screen_fit">ملاءمة الشاشة</string>
|
||||
<string name="carset_top">الأعلى</string>
|
||||
<string name="carset_bottom">الأسفل</string>
|
||||
<string name="carset_save_vehicle">الحفظ لهذه المركبة</string>
|
||||
<string name="carset_no_profile">لا يوجد ملف مركبة مطابق</string>
|
||||
|
||||
<string name="carset_audio_note">«عبر Gearslip» يرسل صوت الهاتف عبر الاتصال بالسيارة، بالكابل أو لاسلكيًا. يطلب Android مشاركة الشاشة في كل رحلة، وبعض التطبيقات تمنع ذلك. البلوتوث يشغّل الصوت كما يفعل الهاتف عادةً، ولا يسأل Android أبدًا.</string>
|
||||
<string name="carset_directions_note">يقرأ Gearslip كل منعطف بصوت عالٍ عبر مكبرات صوت السيارة، وتنخفض الموسيقى أثناء ذلك. أوقفه إذا كان صوت تطبيق الخرائط نفسه يُسمع في السيارة بالفعل.</string>
|
||||
<string name="carset_phone_speech_note">يستمع عبر خدمة الكلام في الهاتف بدلًا من خدمة Gearslip. في معظم الهواتف هي خدمة Google، وقد ترسل صوتك إلى Google. لا توجد خدمة كهذه في GrapheneOS، لذا يواصل Gearslip استخدام خدمته.</string>
|
||||
<string name="carset_vehicle_data_note">يضيف مربع «بيانات المركبة»: عرض مباشر وخام لقناة حساسات وحدة الرأس. تسميات الحقول (السرعة، الدورات، الوقود...) مأخوذة من مشاريع هندسة عكسية أخرى ولم يُتحقق منها في هذه السيارة. اعتبرها تخمينًا لا حقيقة إلى أن ترى أحدها يتغير بنفسك.</string>
|
||||
<string name="carset_adaptive_note">يخفض جودة الصورة عندما لا تواكب السيارة، أو شبكة Wi-Fi فيها، ويرفعها مجددًا حين تستطيع. يسري عند الاتصال التالي.</string>
|
||||
<string name="carset_usable_height">الارتفاع القابل للاستخدام %1$dpx من 480</string>
|
||||
<string name="carset_saved_to">حُفظ في %1$s</string>
|
||||
<string name="carset_save_failed">تعذّر الحفظ</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">فتح على الهاتف</string>
|
||||
<string name="media_now_playing">قيد التشغيل</string>
|
||||
<string name="media_up_next">التالي</string>
|
||||
<string name="media_browse">تصفّح</string>
|
||||
<string name="media_search_hint">اكتب أغنية أو فنانًا أو ألبومًا أو قائمة تشغيل.</string>
|
||||
<string name="media_looking_lyrics">جارٍ البحث عن الكلمات…</string>
|
||||
<string name="media_nothing_playing">لا شيء قيد التشغيل</string>
|
||||
<string name="media_press_play">اضغط تشغيل لتكمل من حيث توقفت، أو ابحث عن شيء في «تصفّح».</string>
|
||||
|
||||
<string name="media_keeps_library">يحتفظ %1$s بمكتبته لنفسه.</string>
|
||||
<string name="media_needs_access">لا يزال بإمكان Gearslip التحكم به بعد منحه الوصول إلى الإشعارات على الهاتف. اسمح بذلك من الإعدادات > الإشعارات > الوصول إلى الإشعارات.</string>
|
||||
<string name="media_not_running">%1$s لا يعمل.</string>
|
||||
<string name="media_not_running_body">يُشغَّل %1$s عبر مشغّل Gearslip، لكن فقط أثناء فتحه على الهاتف. افتحه مرة ويواصل التشغيل هنا في السيارة.</string>
|
||||
<string name="media_open_app">فتح %1$s</string>
|
||||
<string name="media_refused">لم يسمح %1$s لـ Gearslip بتصفّحه.</string>
|
||||
<string name="media_refused_body">بعض تطبيقات الوسائط لا تقبل إلا المضيف الخاص بـ Google.</string>
|
||||
<string name="media_connecting">جارٍ الاتصال بـ %1$s…</string>
|
||||
<string name="media_search">البحث في %1$s</string>
|
||||
<string name="media_search_no_list">لا يستطيع %1$s عرض النتائج. اضغط بحث، وسيختار %1$s ما يُشغَّل.</string>
|
||||
<string name="media_search_unsupported">لا يستطيع %1$s البحث من السيارة.</string>
|
||||
<string name="media_searching">جارٍ البحث في %1$s…</string>
|
||||
<string name="media_search_nothing">لم يجد %1$s شيئًا عن «%2$s».</string>
|
||||
<string name="media_search_ignored">لم يشغّل %1$s شيئًا لـ «%2$s».</string>
|
||||
<string name="media_searching_for">جارٍ البحث في %1$s عن «%2$s»…</string>
|
||||
<string name="media_nothing_playing_dot">لا شيء قيد التشغيل.</string>
|
||||
<string name="media_no_lyrics">لم يُعثر على كلمات لهذا المقطع.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">للأعلى</string>
|
||||
<string name="media_loading">جارٍ التحميل…</string>
|
||||
<string name="media_browse_start">ابدأ الموسيقى من التطبيق على هاتفك. التشغيل والإيقاف المؤقت والتخطي والتقديم تعمل من هنا.</string>
|
||||
<string name="media_load_failed">تعذّر تحميل هذه القائمة.</string>
|
||||
<string name="media_nothing_here">لا شيء هنا.</string>
|
||||
<string name="media_nothing_queued">قائمة الانتظار فارغة.</string>
|
||||
<string name="media_play">تشغيل</string>
|
||||
|
||||
<string name="media_queue_empty_idle">لا شيء قيد التشغيل - ليس كل تطبيق يخبر Gearslip بما سيأتي بعد ذلك.</string>
|
||||
<string name="media_queue_empty_playing">فقط ما يُشغَّل الآن - ليس كل تطبيق يخبر Gearslip بما سيأتي بعد ذلك.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">السابق</string>
|
||||
<string name="player_next">التالي</string>
|
||||
<string name="player_repeat">تكرار</string>
|
||||
<string name="player_pause">إيقاف مؤقت</string>
|
||||
<string name="player_unlike">إلغاء الإعجاب</string>
|
||||
<string name="player_like">إعجاب</string>
|
||||
<string name="player_shuffle_off">الترتيب العشوائي متوقف</string>
|
||||
<string name="player_shuffle_on">الترتيب العشوائي مفعّل</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">اختيار تطبيق آخر</string>
|
||||
<string name="home_no_maps">لم يُعثر على تطبيقات خرائط.</string>
|
||||
<string name="home_no_maps_body">ثبّت تطبيق ملاحة يدعم Android Auto وسيبدأ هنا تلقائيًا.</string>
|
||||
<string name="home_map_stopped">توقفت الخريطة</string>
|
||||
<string name="home_map_stopped_body">تبقى مغلقة حتى تفتحها مجددًا.</string>
|
||||
<string name="home_reopen_map">إعادة فتح الخريطة</string>
|
||||
<string name="home_car_apps">تطبيقات السيارة</string>
|
||||
<string name="home_back_nav">العودة إلى شريط التنقل</string>
|
||||
<string name="home_show_player">إظهار المشغّل</string>
|
||||
<string name="home_hide_lyrics">إخفاء الكلمات</string>
|
||||
|
||||
<string name="home_starting">جارٍ تشغيل %1$s…</string>
|
||||
<string name="home_starting_map">جارٍ تشغيل الخريطة…</string>
|
||||
<string name="home_failed">تعذّر تشغيل %1$s.</string>
|
||||
<string name="home_failed_map">تعذّر تشغيل تطبيق الخرائط.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">الرئيسية</string>
|
||||
<string name="crumb_media">الوسائط</string>
|
||||
<string name="crumb_apps">التطبيقات</string>
|
||||
<string name="crumb_dashboard">لوحة المعلومات</string>
|
||||
<string name="crumb_weather">الطقس</string>
|
||||
<string name="crumb_reply">رد</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">المتصفح</string>
|
||||
<string name="app_screen_sharing">مشاركة الشاشة</string>
|
||||
<string name="app_exit">خروج</string>
|
||||
<string name="navapps_unpin">إلغاء التثبيت من شريط التنقل</string>
|
||||
<string name="navapps_full">شريط التنقل ممتلئ (%1$d مثبّتة)</string>
|
||||
<string name="navapps_pin">التثبيت في شريط التنقل</string>
|
||||
<string name="navapps_close">إغلاق التطبيق</string>
|
||||
<string name="navapps_move_left">نقل لليسار</string>
|
||||
<string name="navapps_move_right">نقل لليمين</string>
|
||||
<string name="navapps_reset">إعادة ترتيب التطبيقات</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">تجاهل</string>
|
||||
<string name="msg_read">قراءة</string>
|
||||
<string name="msg_stop">إيقاف</string>
|
||||
<string name="msg_no">لا</string>
|
||||
<string name="msg_send_now">الإرسال الآن</string>
|
||||
<string name="msg_type">كتابة</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="zero">%1$s · %2$d جديدة</item>
|
||||
<item quantity="one">%1$s · %2$d جديدة</item>
|
||||
<item quantity="two">%1$s · %2$d جديدتان</item>
|
||||
<item quantity="few">%1$s · %2$d جديدة</item>
|
||||
<item quantity="many">%1$s · %2$d جديدة</item>
|
||||
<item quantity="other">%1$s · %2$d جديدة</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">الرد على %1$s</string>
|
||||
<string name="msg_listening">جارٍ الاستماع</string>
|
||||
<string name="msg_sending_to">جارٍ الإرسال إلى %1$s</string>
|
||||
<string name="msg_sent_to">أُرسلت إلى %1$s</string>
|
||||
<string name="msg_reply_failed">تعذّر الرد</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">لم يكتمل الإقران. أقرن من قائمة البلوتوث في السيارة بدلًا من ذلك.</string>
|
||||
<string name="bt_pair_not_started">لم يبدأ الإقران. أقرن من قائمة البلوتوث في السيارة بدلًا من ذلك.</string>
|
||||
<string name="bt_paired">تم الإقران. تُسمع المكالمات الآن في السيارة.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">عدم الإظهار مجددًا</string>
|
||||
<string name="overlay_ok">حسنًا</string>
|
||||
<string name="overlay_location_off">الموقع متوقف</string>
|
||||
<string name="overlay_pair_title">الإقران بهذه السيارة؟</string>
|
||||
<string name="overlay_pair">إقران</string>
|
||||
<string name="overlay_confirm_title">أكّد على هاتفك</string>
|
||||
<string name="overlay_confirm_body">تحقق من الرمز على هاتفك وانقر على «إقران». قد تسأل السيارة أيضًا.</string>
|
||||
|
||||
<string name="overlay_bt_body">تُشغَّل الموسيقى عبر البلوتوث، لكن هاتفك غير متصل بالسيارة. وصّله من قائمة البلوتوث في السيارة أو في هاتفك.</string>
|
||||
<string name="overlay_location_body">لا تستطيع تطبيقات الخرائط إظهار مكانك أو إعطاء الاتجاهات. فعّل الموقع من الإعدادات السريعة في هاتفك عندما تركن السيارة.</string>
|
||||
<string name="overlay_pair_body_music">تُسمع المكالمات والموسيقى عبر مكبرات صوت السيارة بعد إقران الهاتف والسيارة. سيطلب منك هاتفك بعدها تأكيد رمز.</string>
|
||||
<string name="overlay_pair_body">تُسمع المكالمات عبر مكبرات صوت السيارة بعد إقران الهاتف والسيارة. سيطلب منك هاتفك بعدها تأكيد رمز.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">في انتظار تحديد الموقع.</string>
|
||||
<string name="weather_allow_location">اسمح لـ Gearslip بالوصول إلى الموقع على الهاتف لترى الطقس.</string>
|
||||
<string name="weather_unreachable">تعذّر الوصول إلى خدمة الطقس.</string>
|
||||
<string name="weather_clear">صافٍ</string>
|
||||
<string name="weather_mostly_clear">صافٍ غالبًا</string>
|
||||
<string name="weather_partly_cloudy">غائم جزئيًا</string>
|
||||
<string name="weather_overcast">ملبّد بالغيوم</string>
|
||||
<string name="weather_fog">ضباب</string>
|
||||
<string name="weather_drizzle">رذاذ</string>
|
||||
<string name="weather_rain">مطر</string>
|
||||
<string name="weather_snow">ثلج</string>
|
||||
<string name="weather_rain_showers">زخات مطر</string>
|
||||
<string name="weather_snow_showers">زخات ثلج</string>
|
||||
<string name="weather_thunderstorm">عاصفة رعدية</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">جارٍ جلب الطقس…</string>
|
||||
<string name="weather_next_hours">الساعات القادمة</string>
|
||||
<string name="weather_this_week">هذا الأسبوع</string>
|
||||
<string name="weather_today">اليوم</string>
|
||||
<string name="weather_credit">بيانات الطقس من Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">الإحساس %1$s</string>
|
||||
<string name="weather_humidity">الرطوبة %1$d%%</string>
|
||||
<string name="weather_wind">الرياح %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">اليوم</string>
|
||||
<string name="dash_getting_weather">جارٍ جلب الطقس…</string>
|
||||
<string name="dash_coming_up">قريبًا</string>
|
||||
<string name="dash_loading_calendar">جارٍ تحميل التقويم…</string>
|
||||
<string name="dash_nothing_week">لا شيء في الأسبوع القادم.</string>
|
||||
<string name="dash_all_day">طوال اليوم</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">جهات الاتصال</string>
|
||||
<string name="dialer_keypad">لوحة الأرقام</string>
|
||||
<string name="dialer_ask">السؤال على الهاتف</string>
|
||||
<string name="dialer_enter_number">أدخل رقمًا</string>
|
||||
<string name="dialer_call">اتصال</string>
|
||||
<string name="dialer_no_contacts">لم يُعثر على جهات اتصال لها رقم هاتف.</string>
|
||||
<string name="dialer_search">البحث في جهات الاتصال</string>
|
||||
|
||||
<string name="dialer_needs_permission">يحتاج Gearslip إلى إذنَي الهاتف وجهات الاتصال للاتصال من هنا. اسمح بهما على الهاتف عندما تركن السيارة.</string>
|
||||
<string name="dialer_no_match">لا توجد جهات اتصال تطابق «%1$s».</string>
|
||||
<string name="dialer_call_who">الاتصال بـ %1$s؟</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">الخروج من ملء الشاشة</string>
|
||||
<string name="carui_drive_safely">قُد بأمان</string>
|
||||
<string name="carui_understand">فهمت</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip برنامج غير رسمي: لم تصنعه أو تراجعه أي شركة سيارات، وقد يتوقف عن العمل دون سابق إنذار.</string>
|
||||
<string name="carui_never_interact">لا تستخدم هذه الشاشة أبدًا أثناء القيادة. اضبط المسار أو الأغنية قبل أن تنطلق، ثم أبقِ انتباهك على الطريق.</string>
|
||||
<string name="carui_this_app">هذا التطبيق</string>
|
||||
<string name="carui_app_unsupported">لا يعمل %1$s مع Gearslip بعد.</string>
|
||||
<string name="carui_app_unsupported_body">لم يبدأ. بعض التطبيقات لا تعمل إلا في Android Auto من Google.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">في مكالمة</string>
|
||||
<string name="navbar_end_call">إنهاء المكالمة</string>
|
||||
<string name="navbar_unmute">إلغاء كتم الصوت</string>
|
||||
<string name="navbar_mute">كتم الصوت</string>
|
||||
<string name="navbar_close_panel">إغلاق اللوحة الجانبية</string>
|
||||
<string name="navbar_dock_player">نقل المشغّل بجانب الخريطة</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">ابحث أو اكتب عنوانًا</string>
|
||||
<string name="web_forward">تقدّم</string>
|
||||
<string name="web_reload">إعادة التحميل</string>
|
||||
<string name="web_start_page">صفحة البداية</string>
|
||||
<string name="web_fullscreen">ملء الشاشة</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">الإشعارات</string>
|
||||
<string name="notif_clear_all">مسح الكل</string>
|
||||
<string name="notif_empty">لا شيء بعد. ستظهر هنا الإشعارات الجديدة من الهاتف.</string>
|
||||
<string name="notif_off">الإشعارات متوقفة. اسمح لـ Gearslip من الإعدادات > الإشعارات > الوصول إلى الإشعارات على الهاتف.</string>
|
||||
<string name="notif_replied">تم الرد</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">وضع علامة كمقروءة</string>
|
||||
<string name="msg_messages">الرسائل</string>
|
||||
<string name="msg_none_recent">لا رسائل حديثة. ستظهر هنا رسائل %1$s الجديدة فور وصولها إلى الهاتف.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="zero">%1$d أقدم</item>
|
||||
<item quantity="one">%1$d أقدم</item>
|
||||
<item quantity="two">%1$d أقدم</item>
|
||||
<item quantity="few">%1$d أقدم</item>
|
||||
<item quantity="many">%1$d أقدم</item>
|
||||
<item quantity="other">%1$d أقدم</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">إخفاء الردود السريعة</string>
|
||||
<string name="msg_quick">رد سريع</string>
|
||||
<string name="msg_you">أنت: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">جارٍ إيجاد مسار…</string>
|
||||
<string name="nav_then">ثم</string>
|
||||
<string name="nav_stop">إيقاف الملاحة</string>
|
||||
<string name="nav_finding_routes">جارٍ إيجاد المسارات…</string>
|
||||
<string name="nav_lane_recommended">المسار الموصى به</string>
|
||||
<string name="nav_lane">المسار</string>
|
||||
<string name="nav_arrive">الوصول %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">لا شيء قيد التشغيل بعد.</string>
|
||||
<string name="template_search">بحث</string>
|
||||
<string name="template_start">ابدأ</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">مكالمة واردة</string>
|
||||
<string name="call_decline">رفض</string>
|
||||
<string name="call_answer">رد</string>
|
||||
<string name="call_ringing">يرن بصوت عالٍ بالفعل - ردّ هنا أو دعه يرن.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">اللمس متوقف. فعّل Gearslip من الإعدادات > تسهيل الاستخدام على الهاتف.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">الإظهار بعد التحديثات</string>
|
||||
<string name="whatsnew_got_it">فهمت</string>
|
||||
<string name="whatsnew_title">الجديد في الإصدار %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">مسافة</string>
|
||||
<string name="keyboard_hide">إخفاء لوحة المفاتيح</string>
|
||||
<string name="keyboard_submit">إرسال</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">لا يعمل مع Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">تطبيق السيارة</string>
|
||||
<string name="stage_close">إغلاق %1$s</string>
|
||||
<string name="stage_close_app">إغلاق التطبيق</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">انعطف قليلًا إلى اليسار</string>
|
||||
<string name="dir_slight_left_onto">انعطف قليلًا إلى اليسار نحو %1$s</string>
|
||||
<string name="dir_slight_right">انعطف قليلًا إلى اليمين</string>
|
||||
<string name="dir_slight_right_onto">انعطف قليلًا إلى اليمين نحو %1$s</string>
|
||||
<string name="dir_left">انعطف يسارًا</string>
|
||||
<string name="dir_left_onto">انعطف يسارًا نحو %1$s</string>
|
||||
<string name="dir_right">انعطف يمينًا</string>
|
||||
<string name="dir_right_onto">انعطف يمينًا نحو %1$s</string>
|
||||
<string name="dir_sharp_left">انعطف بحدة إلى اليسار</string>
|
||||
<string name="dir_sharp_left_onto">انعطف بحدة إلى اليسار نحو %1$s</string>
|
||||
<string name="dir_sharp_right">انعطف بحدة إلى اليمين</string>
|
||||
<string name="dir_sharp_right_onto">انعطف بحدة إلى اليمين نحو %1$s</string>
|
||||
<string name="dir_u_turn">استدر للخلف</string>
|
||||
<string name="dir_keep_left">الزم اليسار</string>
|
||||
<string name="dir_keep_left_toward">الزم اليسار باتجاه %1$s</string>
|
||||
<string name="dir_keep_right">الزم اليمين</string>
|
||||
<string name="dir_keep_right_toward">الزم اليمين باتجاه %1$s</string>
|
||||
<string name="dir_fork_left">الزم اليسار عند التفرع</string>
|
||||
<string name="dir_fork_left_toward">الزم اليسار عند التفرع باتجاه %1$s</string>
|
||||
<string name="dir_fork_right">الزم اليمين عند التفرع</string>
|
||||
<string name="dir_fork_right_toward">الزم اليمين عند التفرع باتجاه %1$s</string>
|
||||
<string name="dir_ramp_left">اسلك المنحدر على اليسار</string>
|
||||
<string name="dir_ramp_left_onto">اسلك المنحدر على اليسار نحو %1$s</string>
|
||||
<string name="dir_ramp_right">اسلك المنحدر على اليمين</string>
|
||||
<string name="dir_ramp_right_onto">اسلك المنحدر على اليمين نحو %1$s</string>
|
||||
<string name="dir_exit_left">اسلك المخرج على اليسار</string>
|
||||
<string name="dir_exit_left_toward">اسلك المخرج على اليسار باتجاه %1$s</string>
|
||||
<string name="dir_exit_right">اسلك المخرج على اليمين</string>
|
||||
<string name="dir_exit_right_toward">اسلك المخرج على اليمين باتجاه %1$s</string>
|
||||
<string name="dir_merge_left">اندمج يسارًا</string>
|
||||
<string name="dir_merge_left_onto">اندمج يسارًا في %1$s</string>
|
||||
<string name="dir_merge_right">اندمج يمينًا</string>
|
||||
<string name="dir_merge_right_onto">اندمج يمينًا في %1$s</string>
|
||||
<string name="dir_merge">اندمج في حركة المرور</string>
|
||||
<string name="dir_merge_onto">اندمج في %1$s</string>
|
||||
<string name="dir_roundabout_through">اعبر الدوار</string>
|
||||
<string name="dir_roundabout_through_onto">اعبر الدوار نحو %1$s</string>
|
||||
<string name="dir_roundabout_enter">ادخل الدوار</string>
|
||||
<string name="dir_roundabout_exit">اخرج من الدوار</string>
|
||||
<string name="dir_roundabout_exit_onto">اخرج من الدوار نحو %1$s</string>
|
||||
<string name="dir_straight">تابع مباشرة</string>
|
||||
<string name="dir_straight_onto">تابع مباشرة نحو %1$s</string>
|
||||
<string name="dir_continue">تابع</string>
|
||||
<string name="dir_continue_onto">تابع على %1$s</string>
|
||||
<string name="dir_ferry">استقل العبّارة</string>
|
||||
<string name="dir_train">استقل القطار</string>
|
||||
<string name="dir_destination">وجهتك أمامك</string>
|
||||
<string name="dir_destination_left">وجهتك على اليسار</string>
|
||||
<string name="dir_destination_right">وجهتك على اليمين</string>
|
||||
<string name="dir_roundabout_take">عند الدوار، اسلك المخرج %1$s</string>
|
||||
<string name="dir_roundabout_take_onto">عند الدوار، اسلك المخرج %1$s نحو %2$s</string>
|
||||
<string name="dir_ordinal_1">الأول</string>
|
||||
<string name="dir_ordinal_2">الثاني</string>
|
||||
<string name="dir_ordinal_3">الثالث</string>
|
||||
<string name="dir_ordinal_4">الرابع</string>
|
||||
<string name="dir_ordinal_5">الخامس</string>
|
||||
<string name="dir_ordinal_6">السادس</string>
|
||||
<string name="dir_ordinal_7">السابع</string>
|
||||
<string name="dir_ordinal_8">الثامن</string>
|
||||
<string name="dir_in_distance">بعد %1$s، %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="zero">%1$s كيلومتر</item>
|
||||
<item quantity="one">%1$s كيلومتر</item>
|
||||
<item quantity="two">%1$s كيلومتر</item>
|
||||
<item quantity="few">%1$s كيلومترات</item>
|
||||
<item quantity="many">%1$s كيلومترًا</item>
|
||||
<item quantity="other">%1$s كيلومتر</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s كيلومتر</string>
|
||||
<string name="dir_quarter_mile">ربع ميل</string>
|
||||
<string name="dir_half_mile">نصف ميل</string>
|
||||
<string name="dir_three_quarter_mile">ثلاثة أرباع الميل</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="zero">%1$s ميل</item>
|
||||
<item quantity="one">%1$s ميل</item>
|
||||
<item quantity="two">%1$s ميل</item>
|
||||
<item quantity="few">%1$s أميال</item>
|
||||
<item quantity="many">%1$s ميلًا</item>
|
||||
<item quantity="other">%1$s ميل</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s ميل</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="zero">%1$s قدم</item>
|
||||
<item quantity="one">%1$s قدم</item>
|
||||
<item quantity="two">%1$s قدم</item>
|
||||
<item quantity="few">%1$s أقدام</item>
|
||||
<item quantity="many">%1$s قدمًا</item>
|
||||
<item quantity="other">%1$s قدم</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="zero">%1$s ياردة</item>
|
||||
<item quantity="one">%1$s ياردة</item>
|
||||
<item quantity="two">%1$s ياردة</item>
|
||||
<item quantity="few">%1$s ياردات</item>
|
||||
<item quantity="many">%1$s ياردة</item>
|
||||
<item quantity="other">%1$s ياردة</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="zero">%1$d متر</item>
|
||||
<item quantity="one">%1$d متر</item>
|
||||
<item quantity="two">%1$d متر</item>
|
||||
<item quantity="few">%1$d أمتار</item>
|
||||
<item quantity="many">%1$d مترًا</item>
|
||||
<item quantity="other">%1$d متر</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">إذا لم تتصل سيارتك فقد تكون ترفض هذه الشهادة. استورد شهادتك من «الإعدادات»، ضمن «الشهادة».</string>
|
||||
<string name="cert_downloaded_footer">السيارة لا تتصل؟ بعض السيارات من نحو عام 2020 فما بعد ترفض شهادة aasdk، فاستورد شهادتك الخاصة بدلًا منها. تسري التغييرات عند الاتصال التالي.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">بطاقة المنعطف على شاشة العدادات</string>
|
||||
<string name="car_cluster_video_note">لسيارة فيها شاشة ثانية ضمن لوحة العدادات: يعرض Gearslip المنعطف التالي هناك. يسري عند الاتصال التالي.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">غير متصل</string>
|
||||
<string name="status_idle_detail">وصّل الهاتف بمنفذ USB في السيارة، أو اتصل لاسلكيًا. يفتح Gearslip تلقائيًا عندما تستجيب السيارة.</string>
|
||||
<string name="status_connecting">جارٍ الاتصال</string>
|
||||
<string name="status_projecting">جارٍ العرض</string>
|
||||
<string name="status_projecting_detail">البث إلى وحدة الرأس.</string>
|
||||
<string name="status_disconnected">انقطع الاتصال</string>
|
||||
<string name="status_disconnected_detail">لم تعد وحدة الرأس متصلة.</string>
|
||||
<string name="places_title">الأماكن المحفوظة</string>
|
||||
<string name="places_sub">قل “navigate home” في أي تطبيق خرائط</string>
|
||||
<string name="places_intro">قل “navigate home” أو “take me to work” أو “navigate to” مع اسم اخترته، وسيرسل Gearslip العنوان إلى تطبيق الخرائط. الأوامر الصوتية بالإنجليزية فقط حاليًا.</string>
|
||||
<string name="places_home">المنزل</string>
|
||||
<string name="places_work">العمل</string>
|
||||
<string name="places_other">مكان %1$d</string>
|
||||
<string name="places_name">الاسم</string>
|
||||
<string name="places_address">العنوان</string>
|
||||
<string name="places_here">استخدام موقعي الحالي</string>
|
||||
<string name="places_finding">جارٍ البحث…</string>
|
||||
<string name="places_not_found">لم يعثر Gearslip على موقعك. تأكد من تشغيل الموقع.</string>
|
||||
<string name="places_clear">مسح</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,848 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Umožňuje Gearslipu přenášet dotyky z obrazovky auta na telefon během zrcadlení. Gearslip nečte obsah obrazovky.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Pomozte ostatním řidičům.</string>
|
||||
<string name="guide_safety_title">Jezděte bezpečně.</string>
|
||||
<string name="guide_safety_body">Gearslip není oficiální. Žádná automobilka ho nevytvořila ani neprověřila a může bez varování selhat. Nastavte ho před jízdou a pak se dívejte na silnici.</string>
|
||||
<string name="guide_mic_title">Mikrofon.</string>
|
||||
<string name="guide_mic_body">Gearslip ho používá k posílání hudby do auta a k tomu, aby vás slyšel, když o něco žádáte nebo odpovídáte na zprávu. Co řeknete, zůstane v telefonu.</string>
|
||||
<string name="guide_location_title">Poloha.</string>
|
||||
<string name="guide_location_body">Potřebuje ji mapa v autě a hledání v okolí. Bez ní nástěnka nezobrazí mapu.</string>
|
||||
<string name="guide_calendar_title">Kalendář.</string>
|
||||
<string name="guide_calendar_body">Obrazovka auta ukazuje vaši příští událost. Gearslip kalendář jen čte.</string>
|
||||
<string name="guide_calls_title">Hovory a kontakty.</string>
|
||||
<string name="guide_calls_body">Auto ukazuje jméno volajícího a jeho aplikace Telefon umí vytočit číslo nebo zavolat kontaktu. Gearslip je jen čte.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">Při prvním připojení vám Gearslip nabídne spárovat telefon s autem, aby hovory hrály z jeho reproduktorů.</string>
|
||||
<string name="guide_notifications_title">Oznámení.</string>
|
||||
<string name="guide_notifications_body">Když je Gearslip připojený, zobrazuje oznámení s tlačítkem pro ukončení. Bez tohoto oprávnění ho Android skryje.</string>
|
||||
<string name="guide_messages_title">Zprávy.</string>
|
||||
<string name="guide_messages_body">Auto zobrazuje vaše zprávy, čte je nahlas a nechá vás odpovědět. Zapněte Gearslip v seznamu, který se otevře.</string>
|
||||
<string name="guide_android_auto_title">Vypněte Android Auto.</string>
|
||||
<string name="guide_android_auto_body">Android Auto je stále nainstalované a přetahuje se s Gearslipem o USB připojení auta. Zakažte ho nebo odinstalujte na obrazovce, která se otevře.</string>
|
||||
<string name="guide_default_title">Vyberte Gearslip.</string>
|
||||
<string name="guide_default_body">Pokud se telefon po připojení k autu zeptá, kterou aplikaci otevřít, vyberte Gearslip a klepněte na Vždy.</string>
|
||||
<string name="guide_audio_title">Zvuk v autě.</string>
|
||||
<string name="guide_audio_body">Vyberte, jak se hudba dostane do auta. Hovory jdou v každém případě přes Bluetooth.</string>
|
||||
<string name="guide_voice_title">Hlas.</string>
|
||||
<string name="guide_voice_body">Požádejte hlasem o trasu, hudbu nebo hovor. Telefon sám převádí řeč na text a nic z toho, co řeknete, ho neopustí. Potřebuje %1$d MB, stažených jednou. Pokud můžete, použijte Wi-Fi.</string>
|
||||
<string name="guide_audio_prompt_title">Bez dotazu na zvuk.</string>
|
||||
<string name="guide_audio_prompt_body">Spusťte tohle z počítače se zapnutým laděním USB a Android svěří zvuk Gearslipu natrvalo. Jinak při každé jízdě jednou klepnete na Spustit.</string>
|
||||
<string name="guide_readable_title">Čitelná oznámení.</string>
|
||||
<string name="guide_readable_body">Když zvuk jde do auta, Android skrývá text oznámení, jako byste sdíleli obrazovku. Díky tomu je Gearslip může během jízdy ukázat a potom znovu skrýt.</string>
|
||||
<string name="guide_answer_title">Přijímání hovorů.</string>
|
||||
<string name="guide_answer_body">Nastavte Gearslip jako aplikaci pro identifikaci volajícího a auto bude moct hovory přijímat a odmítat. Bez toho hovory zvoní dál, ale přijmout je jde jen na telefonu.</string>
|
||||
<string name="guide_apps_title">Dobrá společnost.</string>
|
||||
<string name="guide_buttons_title">Vaše tlačítka.</string>
|
||||
<string name="guide_buttons_body">Vyberte vzhled tlačítek v autě. Změnit ho můžete kdykoli v nastavení auta.</string>
|
||||
<string name="guide_open_notification_access">Otevřít přístup k oznámením</string>
|
||||
<string name="guide_open_android_auto">Otevřít informace o aplikaci Android Auto</string>
|
||||
<string name="guide_download_voice">Stáhnout hlas</string>
|
||||
<string name="guide_set_up">Nastavit</string>
|
||||
<string name="guide_voice_downloading">Stahování, %1$d %%. Můžete pokračovat, stahování běží dál.</string>
|
||||
<string name="guide_voice_ready">Hlas je připravený.</string>
|
||||
<string name="guide_voice_failed">Stahování se nedokončilo. Klepněte na Stáhnout hlas a zkuste to znovu.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Doporučeno</string>
|
||||
<string name="guide_audio_bluetooth_body">Žádný dotaz při každé jízdě a hlas mapové aplikace se dostane do auta.</string>
|
||||
<string name="guide_audio_usb">Přes Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Čistší zvuk bez zpoždění. Při každé jízdě vás Android požádá o sdílení obrazovky, díky čemuž může Gearslip posílat zvuk.</string>
|
||||
<string name="guide_optional">VOLITELNÉ</string>
|
||||
<string name="guide_already_allowed">Už povoleno.</string>
|
||||
<string name="guide_scroll_more">Posuňte pro další ↓</string>
|
||||
<string name="guide_allow">Povolit</string>
|
||||
<string name="guide_skip">Zatím přeskočit</string>
|
||||
<string name="guide_open">Otevřít</string>
|
||||
<string name="guide_next">Další</string>
|
||||
<string name="guide_done">Hotovo</string>
|
||||
<string name="guide_back">Zpět</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Zapněte to a Gearslip pošle krátkou poznámku, když ho otevřete, a po každé jízdě. <b>Poznámka neobsahuje jméno, účet ani polohu. Náhodné číslo započítá váš telefon jednou a server neukládá žádnou IP adresu.</b> Poznámky ukazují, která auta fungují a kde ostatní selhávají.</string>
|
||||
<string name="usage_send">Posílat poznámky o používání</string>
|
||||
<string name="usage_also_share">Sdílet také</string>
|
||||
<string name="usage_share_android">Verzi a systém Androidu</string>
|
||||
<string name="usage_share_phone">Model telefonu</string>
|
||||
<string name="usage_share_cars">Každé auto a zda se připojilo</string>
|
||||
<string name="usage_share_drives">Každou jízdu a jak skončila</string>
|
||||
<string name="usage_what_sent">Co se posílá</string>
|
||||
<string name="usage_what_sent_sub">Každé pole přesně tak, jak odchází</string>
|
||||
<string name="usage_hide_note">Skrýt poznámku</string>
|
||||
<string name="usage_show_note">Zobrazit poznámku</string>
|
||||
<string name="usage_example">Příklad, neodesílá se: jedna jízda přidá tohle do „sessions“</string>
|
||||
<string name="usage_no_server">Tato verze nemá statistický server, takže nic neposílá</string>
|
||||
<string name="usage_debug_build">Ladicí verze: poznámky se drží zvlášť a nikdy se nezapočítávají</string>
|
||||
<string name="worked_title">Gearslip fungoval s vaším %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip fungoval s vaším autem</string>
|
||||
<string name="worked_body">Sdílet to, aby další řidič věděl, že to funguje? Bez jména, účtu a polohy.</string>
|
||||
<string name="worked_not_now">Teď ne</string>
|
||||
<string name="worked_share">Sdílet</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">Tyto bezplatné aplikace běží na obrazovce auta v Gearslipu. Každé tlačítko otevře stránku aplikace na GitHubu.</string>
|
||||
<string name="recommended_title">Doporučené aplikace</string>
|
||||
<string name="back">Zpět</string>
|
||||
<string name="recommended_installed">Nainstalováno</string>
|
||||
<string name="recommended_maps">Mapy</string>
|
||||
<string name="recommended_music">Hudba</string>
|
||||
<string name="recommended_podcasts">Podcasty</string>
|
||||
<string name="recommended_other">Další skvělé aplikace</string>
|
||||
<string name="recommended_about_organic">Offline mapy a trasy.</string>
|
||||
<string name="recommended_about_vela">Navigace krok za krokem bez Googlu.</string>
|
||||
<string name="recommended_about_ytm">Přehrávač YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">Podcasty i s kapitolami.</string>
|
||||
<string name="recommended_about_rfsentinel">Najde sledovací zařízení a kamery v okolí.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Nastavení</string>
|
||||
<string name="settings_whats_new">Co je nového</string>
|
||||
<string name="settings_whats_new_sub">Přečtěte si poznámky k verzi %1$s na GitHubu</string>
|
||||
<string name="settings_support">Podpořte Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip je zdarma, bez reklam a bez sledování. Vaše podpora ho udržuje při životě.</string>
|
||||
<string name="settings_usage_notes">Poznámky o používání</string>
|
||||
<string name="settings_updates">Aktualizace</string>
|
||||
<string name="settings_about">O aplikaci</string>
|
||||
<string name="settings_replay">Znovu projít průvodce nastavením</string>
|
||||
<string name="settings_replay_sub">Projít znovu kroky prvního spuštění</string>
|
||||
<string name="settings_connection">Připojení</string>
|
||||
<string name="settings_use_in_car">Používat Gearslip v autě</string>
|
||||
<string name="settings_use_in_car_on">Gearslip odpoví, když se připojíte k autu</string>
|
||||
<string name="settings_use_in_car_off">Vypnuto: připojení může převzít Android Auto</string>
|
||||
<string name="settings_apps">Aplikace</string>
|
||||
<string name="settings_certificate">Certifikát</string>
|
||||
<string name="cert_next_connection">Změny se projeví při příštím připojení.</string>
|
||||
<string name="cert_checking">Kontrola…</string>
|
||||
<string name="cert_valid_until">Platný do %1$s</string>
|
||||
<string name="cert_hide_details">Skrýt podrobnosti</string>
|
||||
<string name="cert_show_details">Zobrazit podrobnosti</string>
|
||||
<string name="cert_subject">Subjekt</string>
|
||||
<string name="cert_issuer">Vydavatel</string>
|
||||
<string name="cert_downloading">Stahování…</string>
|
||||
<string name="cert_download">Stáhnout z aasdk</string>
|
||||
<string name="cert_downloaded">Certifikát stažen.</string>
|
||||
<string name="cert_download_failed">Stažení selhalo: %1$s</string>
|
||||
<string name="cert_download_sub">Certifikát, který zveřejňuje open-source projekt aasdk na GitHubu. Většina aut ho přijme, ale některá zhruba od roku 2020 ho odmítají</string>
|
||||
<string name="cert_import">Importovat vlastní soubor</string>
|
||||
<string name="cert_import_sub">Soubor .p12 se soukromým klíčem. Použijte vlastní, když auto odmítá certifikát aasdk</string>
|
||||
<string name="cert_remove_imported">Odebrat importovaný certifikát</string>
|
||||
<string name="cert_remove_downloaded">Odebrat stažený certifikát</string>
|
||||
<string name="cert_removed_imported">Importovaný certifikát odebrán.</string>
|
||||
<string name="cert_removed_downloaded">Stažený certifikát odebrán.</string>
|
||||
<string name="cert_imported">Certifikát importován.</string>
|
||||
<string name="cert_import_failed">Import se nezdařil: špatné heslo nebo to není soubor PKCS#12 se soukromým klíčem.</string>
|
||||
<string name="cert_password_title">Heslo certifikátu</string>
|
||||
<string name="cert_password">Heslo</string>
|
||||
<string name="cert_password_hint">Nechte prázdné, pokud soubor heslo nemá.</string>
|
||||
<string name="cert_import_button">Importovat</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Zrušit</string>
|
||||
<string name="cert_none">Žádný načtený (podepsaný sám sebou; auta ho odmítají)</string>
|
||||
<string name="cert_kind_imported">Importovaný: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Stažený: %1$s</string>
|
||||
<string name="cert_kind_adb">Nahraný přes adb: %1$s</string>
|
||||
<string name="cert_kind_self">Podepsaný sám sebou: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Obrazovka</string>
|
||||
<string name="car_group_nav_bar">Navigační lišta a přehrávač</string>
|
||||
<string name="car_group_sound">Zvuk a hlas</string>
|
||||
<string name="car_group_warnings">Upozornění</string>
|
||||
<string name="car_group_more">Další</string>
|
||||
<string name="choice_size_normal">Normální</string>
|
||||
<string name="choice_size_large">Velká</string>
|
||||
<string name="choice_size_xlarge">Extra velká</string>
|
||||
<string name="choice_theme_phone">Podle telefonu</string>
|
||||
<string name="choice_theme_light">Světlý</string>
|
||||
<string name="choice_theme_dark">Tmavý</string>
|
||||
<string name="choice_dark_black">Černá</string>
|
||||
<string name="choice_dark_black_colour">Černá a barva</string>
|
||||
<string name="choice_dark_deep_colour">Sytá barva</string>
|
||||
<string name="choice_dark_grey">Šedá</string>
|
||||
<string name="choice_auto">Automaticky</string>
|
||||
<string name="choice_night_day">Den</string>
|
||||
<string name="choice_night_night">Noc</string>
|
||||
<string name="choice_units_imperial">Imperiální</string>
|
||||
<string name="choice_units_metric">Metrické</string>
|
||||
<string name="choice_nav_show">Vždy zobrazit</string>
|
||||
<string name="choice_nav_hide_navigating">Skrýt při navigaci</string>
|
||||
<string name="choice_nav_hide">Skrýt do klepnutí</string>
|
||||
<string name="choice_open_home">Domů</string>
|
||||
<string name="summary_player_left">Přehrávač vlevo</string>
|
||||
<string name="summary_player_right">Přehrávač vpravo</string>
|
||||
<string name="summary_usb_audio">Zvuk přes Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Zvuk přes Bluetooth</string>
|
||||
<string name="summary_directions_on">Hlasové pokyny zapnuty</string>
|
||||
<string name="summary_directions_off">Hlasové pokyny vypnuty</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth zapnuto</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth vypnuto</string>
|
||||
<string name="summary_location_on">Poloha zapnuta</string>
|
||||
<string name="summary_location_off">Poloha vypnuta</string>
|
||||
<string name="web_page">Webová stránka</string>
|
||||
<string name="summary_experimental_shown">Experimentální funkce zobrazeny</string>
|
||||
<string name="summary_experimental_hidden">Experimentální funkce skryty</string>
|
||||
<string name="home_car">Auto</string>
|
||||
<string name="car_group_same">Jsou to stejná nastavení jako Nastavení v autě. Změna tady se projeví i tam.</string>
|
||||
<string name="car_open_on_connect">Otevřít po připojení</string>
|
||||
<string name="car_size">Velikost</string>
|
||||
<string name="car_app_theme">Motiv aplikace</string>
|
||||
<string name="car_dark_mode">Tmavý režim</string>
|
||||
<string name="car_buttons">Tlačítka</string>
|
||||
<string name="car_map_day_night">Mapa den/noc</string>
|
||||
<string name="car_units">Jednotky</string>
|
||||
<string name="car_nav_bar">Navigační lišta</string>
|
||||
<string name="car_player_nav_bar">Přehrávač v navigační liště</string>
|
||||
<string name="car_skip_buttons">Tlačítka přeskočení</string>
|
||||
<string name="car_song_title">Název skladby</string>
|
||||
<string name="car_player_beside_map">Přehrávač vedle mapy</string>
|
||||
<string name="car_right">Vpravo</string>
|
||||
<string name="car_left">Vlevo</string>
|
||||
<string name="car_autoplay">Přehrát po připojení</string>
|
||||
<string name="car_more_lyrics">Více zdrojů textů</string>
|
||||
<string name="car_more_lyrics_note">Ptá se také BetterLyrics a KuGou, které pak dostanou název a interpreta skladby. Vypnuto se ptá jen LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Hlasové pokyny</string>
|
||||
<string name="car_spoken_directions_note">Vypněte je, pokud hlas vaší mapové aplikace už v autě hraje.</string>
|
||||
<string name="car_phone_speech">Používat rozpoznávání řeči telefonu</string>
|
||||
<string name="car_phone_speech_note">Na většině telefonů je to služba Google, která může posílat váš hlas Googlu.</string>
|
||||
<string name="car_warn_bluetooth">Upozornit, když Bluetooth není připojeno</string>
|
||||
<string name="car_warn_location">Upozornit, když je poloha vypnutá</string>
|
||||
<string name="car_experimental">Experimentální funkce</string>
|
||||
<string name="car_vehicle_data">Data vozidla</string>
|
||||
<string name="car_vehicle_data_note">Přidá dlaždici Data vozidla: živý výpis senzorů auta. Názvy polí jsou odhady, dokud neuvidíte, jak se některé mění.</string>
|
||||
<string name="car_adaptive_video">Adaptivní kvalita obrazu</string>
|
||||
<string name="car_adaptive_video_note">Sníží kvalitu obrazu, když auto nebo jeho Wi-Fi nestíhá, a zase ji zvýší, jakmile to jde. Platí od příštího připojení.</string>
|
||||
<string name="web_page_note">Zobrazuje ji aplikace Prohlížeč v autě. Prázdné pole otevře DuckDuckGo. Platí od příštího připojení.</string>
|
||||
<string name="car_audio">Zvuk v autě</string>
|
||||
<string name="car_audio_bt">Bluetooth (doporučeno)</string>
|
||||
<string name="car_audio_bt_sub">Žádný dotaz při každé jízdě a hlas mapové aplikace se dostane do auta</string>
|
||||
<string name="car_audio_usb_sub">Čistší zvuk bez zpoždění. Při každé jízdě vás Android požádá o sdílení obrazovky, díky čemuž může Gearslip posílat zvuk</string>
|
||||
<string name="car_audio_usb_footer">Spusťte jednou příkaz z Nápovědy a Android se přestane ptát.</string>
|
||||
<string name="car_audio_bt_footer">Hovory jdou v každém případě přes Bluetooth.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Velikost obrazovky auta</string>
|
||||
<string name="screen_use_cars">Podle auta</string>
|
||||
<string name="screen_preview_size">Velikost náhledu</string>
|
||||
<string name="screen_preview_note">Velikost, ve které se kreslí náhled auta, například 1920x720.</string>
|
||||
<string name="screen_car_screen">Obrazovka auta</string>
|
||||
<string name="screen_footer">Gearslip používá velikost, kterou hlásí auto, pokud nenastavíte vlastní.</string>
|
||||
<string name="screen_size">Velikost obrazovky</string>
|
||||
<string name="screen_from_car">Z auta</string>
|
||||
<string name="screen_crop">Oříznout okraje</string>
|
||||
<string name="screen_crop_sub">Pro auto, které ořezává horní a dolní část nebo boky</string>
|
||||
<string name="screen_squash">Stlačit, aby se vešlo</string>
|
||||
<string name="screen_squash_sub">Pro auto, které zmáčkne celý obraz, takže kruhy vypadají jako ovály</string>
|
||||
<string name="screen_preview_title">Velikost náhledu auta</string>
|
||||
<string name="screen_last_car">Poslední auto</string>
|
||||
<string name="screen_last_car_sub">Velikost posledního auta, ke kterému jste se připojili</string>
|
||||
<string name="screen_this_screen">Tato obrazovka</string>
|
||||
<string name="screen_this_screen_sub">Vyplní obrazovku tohoto zařízení, pro autorádio, na kterém běží přímo Gearslip</string>
|
||||
<string name="screen_custom">Vlastní</string>
|
||||
<string name="screen_enter_size">Zadejte velikost</string>
|
||||
<string name="screen_change_custom">Změnit vlastní velikost</string>
|
||||
<string name="screen_width">Šířka</string>
|
||||
<string name="screen_height">Výška</string>
|
||||
<string name="save">Uložit</string>
|
||||
|
||||
<string name="screen_car_size_note">Použijte to, pokud obraz v autě vypadá roztažený. Zadejte skutečnou velikost obrazovky, například 1280x480. Projeví se při příštím připojení.</string>
|
||||
<string name="screen_set_by_you">%1$s, nastaveno vámi</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Jak se připojit</string>
|
||||
<string name="help_which_cars">Která auta fungují</string>
|
||||
<string name="help_full_list">Zobrazit celý seznam</string>
|
||||
<string name="help_first_drive">Před první jízdou</string>
|
||||
<string name="help_in_car">V autě</string>
|
||||
<string name="help_usb_audio">Zvuk přes Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Vypněte Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Zakažte nebo odinstalujte aplikaci Android Auto, aby auto mluvilo s Gearslipem.</string>
|
||||
<string name="help_cert">Načtěte certifikát.</string>
|
||||
<string name="help_cert_detail">V Nastavení stáhněte certifikát aasdk, který přijme většina aut. Pokud ho vaše auto odmítne, importujte vlastní.</string>
|
||||
<string name="help_mic">Povolte mikrofon.</string>
|
||||
<string name="help_mic_detail">Bez něj Android nenechá aplikaci posílat hudbu do auta. Gearslip poslouchá, jen když klepnete na mikrofon nebo stisknete tlačítko hlasu na volantu.</string>
|
||||
<string name="help_plug_in">Připojte kabel nebo se připojte bezdrátově.</string>
|
||||
<string name="help_plug_in_detail">Gearslip se otevře sám, když se auto připojí. Pro bezdrátové připojení nejdřív spárujte telefon s autem a pak na Domů klepněte na Připojit bezdrátově.</string>
|
||||
<string name="help_pair">Spárujte přes Bluetooth kvůli hovorům.</string>
|
||||
<string name="help_pair_detail">Hovory hrají v autě, jakmile jsou telefon a auto spárované. Gearslip nabídne spárování při prvním připojení.</string>
|
||||
<string name="help_answer">Přijímejte hovory z obrazovky auta.</string>
|
||||
<string name="help_answer_detail">Nastavte Gearslip jako aplikaci pro identifikaci volajícího a spam. Bez toho hovory zvoní dál, ale přijmout je jde jen na telefonu.</string>
|
||||
<string name="help_prompt">Zbavte se dotazu na zvuk při každé jízdě.</string>
|
||||
<string name="help_prompt_detail">Připojte telefon k počítači se zapnutým laděním USB a jednou spusťte tohle. Když to přeskočíte, klepnete na Spustit pokaždé, když vyrazíte.</string>
|
||||
<string name="help_readable">Zachovejte čitelná oznámení.</string>
|
||||
<string name="help_readable_detail">Android skrývá text oznámení, když sdílí zvuk. Spusťte i tohle a Gearslip to zruší jen po dobu, kdy hudba jde do auta.</string>
|
||||
<string name="help_which_cars_body">Většina aut přijme certifikát aasdk. Zhruba od roku 2020 dostala některá auta novější bezpečnostní kontroly, které ho odmítají, a v nich Gearslip potřebuje váš vlastní certifikát.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Staženo. Android se před instalací zeptá.</string>
|
||||
<string name="update_install">Nainstalovat</string>
|
||||
<string name="update_download_now">Stáhněte ji teď. Android se před instalací zeptá.</string>
|
||||
<string name="update_try_again">Zkusit znovu</string>
|
||||
<string name="update_update">Aktualizovat</string>
|
||||
<string name="update_checking">Kontrola…</string>
|
||||
<string name="update_latest">Máte nejnovější verzi</string>
|
||||
<string name="update_download">Stáhnout</string>
|
||||
<string name="update_check_now">Zkontrolovat teď</string>
|
||||
<string name="update_check_auto">Kontrolovat automaticky</string>
|
||||
<string name="update_once_a_day">Jednou denně</string>
|
||||
|
||||
<string name="update_unplug_first">Pro instalaci se odpojte od auta. Instalace zavře Gearslip.</string>
|
||||
<string name="update_card_title">Vyšel Gearslip %1$s</string>
|
||||
<string name="update_downloading">Stahování… %1$d %%</string>
|
||||
<string name="update_debug_manual">Ladicí verze kontrolují jen po klepnutí na Zkontrolovat teď</string>
|
||||
<string name="update_checks_daily">Gearslip kontroluje jednou denně</string>
|
||||
<string name="update_version_out">Vyšla verze %1$s</string>
|
||||
<string name="update_version_ready">Verze %1$s je připravena k instalaci</string>
|
||||
<string name="update_dialog_status">Verze %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Přispívat týdně nebo měsíčně</string>
|
||||
<string name="about_bmac_sub" formatted="false">Přispějte jednou nebo měsíčně kartou. Buy Me a Coffee si nechá 5 %</string>
|
||||
<string name="about_give_once">Přispět jednou</string>
|
||||
<string name="about_any_amount">Libovolná částka kartou</string>
|
||||
<string name="about_monero_sub">Otevřete peněženku nebo zkopírujte adresu</string>
|
||||
<string name="about_monero_title">Podpořit přes Monero</string>
|
||||
<string name="about_monero_body">Pošlete libovolnou částku na tuto adresu ze své peněženky Monero.</string>
|
||||
<string name="about_open_wallet">Otevřít v peněžence</string>
|
||||
<string name="about_copy_address">Kopírovat adresu</string>
|
||||
<string name="close">Zavřít</string>
|
||||
<string name="about_matrix">Přidejte se do chatu na Matrixu</string>
|
||||
<string name="about_matrix_sub">Bavte se s ostatními řidiči o autech, aplikacích a Gearslipu</string>
|
||||
<string name="about_translate">Pomozte přeložit Gearslip</string>
|
||||
<string name="about_translate_sub">Opravte nebo přidejte svůj jazyk na Weblate. Některé překlady vznikly jako strojové.</string>
|
||||
<string name="about_source">Zdrojový kód</string>
|
||||
<string name="about_source_sub">Čtěte nebo měňte kód Gearslipu pod licencí AGPL</string>
|
||||
<string name="about_voice_credits">Poděkování za hlas</string>
|
||||
|
||||
<string name="about_no_wallet">Nenašla se žádná peněženka Monero. Zkopírujte raději adresu.</string>
|
||||
<string name="about_monero_copied">Adresa Monero zkopírována</string>
|
||||
<string name="about_voice_credits_sub">Rozpoznávání řeči: sherpa-onnx (Apache 2.0), s Kroko ASR od Banafo (CC BY-SA 4.0) a Parakeet a Canary od NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Nastavení certifikátu</string>
|
||||
<string name="certsetup_installed">Certifikát nainstalován.</string>
|
||||
<string name="certsetup_continue">Pokračovat</string>
|
||||
<string name="certsetup_failed">Stažení selhalo</string>
|
||||
<string name="certsetup_retry">Zkusit znovu</string>
|
||||
<string name="certsetup_import_sub">Soubor .p12 se soukromým klíčem. Použijte vlastní, když auto odmítá certifikát aasdk.</string>
|
||||
<string name="certsetup_skip">Přeskočit nastavení připojení k autu</string>
|
||||
|
||||
<string name="certsetup_loaded">Auta přijímají jen certifikáty vydané Googlem a vy jeden máte načtený.</string>
|
||||
<string name="certsetup_choose">Auta přijímají jen certifikáty vydané Googlem. Gearslip žádný neobsahuje, takže vyberte, odkud vezmete svůj.</string>
|
||||
<string name="certsetup_aasdk_sub">Open-source projekt aasdk zveřejňuje tento certifikát na github.com/opencardev/aasdk. Většina aut ho přijme. Některá zhruba od roku 2020 ho odmítají a pak musíte importovat vlastní.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Úložiště Gearslipu</string>
|
||||
<string name="storage_measuring">Měření…</string>
|
||||
<string name="storage_lyrics">Texty písní</string>
|
||||
<string name="storage_clear">Vymazat</string>
|
||||
<string name="storage_voice">Hlasové modely</string>
|
||||
<string name="storage_remove">Odebrat</string>
|
||||
<string name="storage_logs">Staré logy</string>
|
||||
<string name="storage_other">Další dočasné soubory</string>
|
||||
<string name="storage_delete_all">Smazat všechna data Gearslipu</string>
|
||||
|
||||
<string name="storage_total">Data Gearslipu zabírají %1$s úložiště telefonu.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d skladba · %2$s. Vymazané texty se znovu stáhnou, až je otevřete.</item>
|
||||
<item quantity="few">%1$d skladby · %2$s. Vymazané texty se znovu stáhnou, až je otevřete.</item>
|
||||
<item quantity="many">%1$d skladby · %2$s. Vymazané texty se znovu stáhnou, až je otevřete.</item>
|
||||
<item quantity="other">%1$d skladeb · %2$s. Vymazané texty se znovu stáhnou, až je otevřete.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Hlasové příkazy je potřebují; znovu je stáhnete v Nastavení.</string>
|
||||
<string name="storage_logs_detail">%1$s. Logy z dřívějších spuštění; log tohoto spuštění zůstává.</string>
|
||||
<string name="storage_other_detail">%1$s. Stažené aktualizace a další soubory, které Gearslip umí vytvořit znovu.</string>
|
||||
<string name="storage_delete_armed">Klepněte znovu na Smazat a vymažou se nastavení, certifikáty, hlasové modely a logy. Gearslip se zavře a příště začne nastavením.</string>
|
||||
<string name="storage_delete_sub">Nastavení, certifikáty, hlasové modely, texty a logy.</string>
|
||||
<string name="storage_delete">Smazat</string>
|
||||
<string name="storage_delete_ellipsis">Smazat…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Rád, že jízda vyšla</string>
|
||||
<string name="donate_liberapay">Podpořit na Liberapay</string>
|
||||
<string name="donate_bmac">Kupte mi kafe</string>
|
||||
<string name="donate_card">Přispět jednou kartou</string>
|
||||
|
||||
<string name="donate_body">Gearslip je zdarma, bez reklam a bez sledování. Jestli vás dostane na cestu, vaše podpora ho udrží při životě.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">Gearslip k tomu potřebuje přístup k Bluetooth a Wi-Fi v okolí.</string>
|
||||
<string name="wireless_enable_permission">Povolit oprávnění Zařízení v okolí</string>
|
||||
<string name="wireless_connect">Připojit bezdrátově</string>
|
||||
<string name="wireless_which_car">Které auto?</string>
|
||||
<string name="wireless_none_paired">Telefon zatím není s ničím spárovaný. Nejdřív ho spárujte s autem.</string>
|
||||
<string name="wireless_not_listed">Auto v seznamu není? Nejdřív ho spárujte s telefonem.</string>
|
||||
<string name="bluetooth_settings">Nastavení Bluetooth</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Hlas: slova naživo</string>
|
||||
<string name="voice_final_title">Hlas: konečná slova</string>
|
||||
<string name="voice_none">Žádný</string>
|
||||
<string name="voice_none_sub">Ponechat slova naživo. Nejrychlejší, nejméně přesné</string>
|
||||
|
||||
<string name="voice_live_body">Hlasový asistent převádí řeč na text v tomto telefonu a nic z toho, co řeknete, ho neopustí. Potřebuje dva modely, dohromady asi 270 MB, stažené jednou. Tento píše vaše slova, zatímco mluvíte.</string>
|
||||
<string name="voice_final_body">Když domluvíte, tento model přečte celou větu znovu. Zachytí jména a místa, která model naživo přehlédne.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Staženo. %1$s</string>
|
||||
<string name="voice_downloading">Stahování, %1$d %%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Nápověda</string>
|
||||
<string name="home_preview">Náhled</string>
|
||||
<string name="home_logs">Logy</string>
|
||||
<string name="home_disconnect">Odpojit</string>
|
||||
<string name="home_bt_title">Bluetooth není připojeno</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · ladicí verze</string>
|
||||
<string name="home_bt_body">Hudba hraje přes Bluetooth, ale telefon není připojený k autu. Připojte ho tady nebo v Bluetooth menu auta.</string>
|
||||
<string name="home_voice_downloading">Stahování hlasu, %1$d %%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Přehrávat zvuk v autě přes Bluetooth?</string>
|
||||
<string name="audio_never_ask">Nikdy se neptat, pomocí počítače</string>
|
||||
<string name="audio_use_bt">Použít Bluetooth</string>
|
||||
<string name="audio_keep_asking">Dál se ptát</string>
|
||||
<string name="audio_mic_title">Zvuk v autě potřebuje oprávnění k mikrofonu</string>
|
||||
<string name="audio_open_settings">Otevřít nastavení aplikace</string>
|
||||
|
||||
<string name="audio_bt_body">Android bere hudbu, kterou Gearslip posílá do auta, jako sdílení obrazovky, takže se ptá při každé jízdě, kabelem i bezdrátově. Přes Bluetooth se nikdy neptá. Později to můžete změnit v Nastavení.</string>
|
||||
<string name="audio_keep_usb">Nebo nechte zvuk jít přes Gearslip a zbavte se dotazu natrvalo: připojte telefon k počítači se zapnutým laděním USB a jednou spusťte tohle.</string>
|
||||
<string name="audio_mic_body">Android nechá aplikaci posílat zvuk jiných aplikací do auta, jen když má oprávnění k mikrofonu. Pro Gearslip je vypnuté, takže hudba hraje v telefonu. Zapněte ho v Oprávněních, pak Mikrofon.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Zavřít klávesnici auta</string>
|
||||
<string name="typing_label">Psát na obrazovku auta</string>
|
||||
<string name="typing_search">Hledat na obrazovce auta</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Klepnutím zkopírujete</string>
|
||||
<string name="command_copied">Příkaz zkopírován</string>
|
||||
<string name="whats_new_this_version">%1$s (tato verze)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Motiv</string>
|
||||
<string name="carset_imperial">Imperiální (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Metrické (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Velikost obrazovky (příští připojení)</string>
|
||||
<string name="carset_fit">Přizpůsobit obrazovce</string>
|
||||
<string name="off">Vyp.</string>
|
||||
<string name="on">Zap.</string>
|
||||
<string name="carset_lyrics_from">Texty z</string>
|
||||
<string name="carset_more_sources">Více zdrojů</string>
|
||||
<string name="carset_hide">Skrýt</string>
|
||||
<string name="carset_show">Zobrazit</string>
|
||||
<string name="carset_phone">Telefon</string>
|
||||
<string name="carset_vehicle">Vozidlo</string>
|
||||
<string name="carset_video">Obraz</string>
|
||||
<string name="carset_version">Verze</string>
|
||||
<string name="carset_build">Sestavení</string>
|
||||
<string name="carset_whats_new_after">Novinky po aktualizacích</string>
|
||||
<string name="carset_whats_new_after_sub">Zobrazit seznam změn jednou, při prvním připojení auta po aktualizaci.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (výchozí) - klepnutím nastavíte jinou stránku</string>
|
||||
<string name="carset_web_note">Zobrazuje ji aplikace Prohlížeč v launcheru. Platí od příštího připojení.</string>
|
||||
<string name="carset_screen_fit">Přizpůsobení obrazovky</string>
|
||||
<string name="carset_top">Nahoře</string>
|
||||
<string name="carset_bottom">Dole</string>
|
||||
<string name="carset_save_vehicle">Uložit pro toto vozidlo</string>
|
||||
<string name="carset_no_profile">Žádný odpovídající profil vozidla</string>
|
||||
|
||||
<string name="carset_audio_note">Přes Gearslip posílá zvuk telefonu připojením k autu, kabelem nebo bezdrátově. Android při každé jízdě žádá o sdílení obrazovky a pár aplikací to blokuje. Bluetooth hraje tak, jak by telefon hrál normálně, a Android se nikdy neptá.</string>
|
||||
<string name="carset_directions_note">Gearslip čte každé odbočení nahlas z reproduktorů auta a hudba se při tom ztiší. Vypněte to, pokud hlas vaší mapové aplikace už v autě hraje.</string>
|
||||
<string name="carset_phone_speech_note">Poslouchá přes službu řeči v telefonu místo Gearslipu. Na většině telefonů je to služba Google, která může posílat váš hlas Googlu. GrapheneOS žádnou nemá, takže Gearslip dál používá svou.</string>
|
||||
<string name="carset_vehicle_data_note">Přidá dlaždici Data vozidla: surový živý výpis kanálu senzorů autorádia. Popisky polí (rychlost, otáčky, palivo...) pocházejí z jiných projektů reverzního inženýrství a na tomto autě nejsou ověřené. Berte je jako odhad, ne fakt, dokud sami neuvidíte, jak se některé mění.</string>
|
||||
<string name="carset_adaptive_note">Sníží kvalitu obrazu, když auto, nebo jeho Wi-Fi, nestíhá, a zase ji zvýší, jakmile to jde. Platí od příštího připojení.</string>
|
||||
<string name="carset_usable_height">Využitelná výška %1$dpx z 480</string>
|
||||
<string name="carset_saved_to">Uloženo do %1$s</string>
|
||||
<string name="carset_save_failed">Uložení se nezdařilo</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Otevřít v telefonu</string>
|
||||
<string name="media_now_playing">Právě hraje</string>
|
||||
<string name="media_up_next">Další na řadě</string>
|
||||
<string name="media_browse">Procházet</string>
|
||||
<string name="media_search_hint">Napište skladbu, interpreta, album nebo playlist.</string>
|
||||
<string name="media_looking_lyrics">Hledání textu…</string>
|
||||
<string name="media_nothing_playing">Nic nehraje</string>
|
||||
<string name="media_press_play">Stiskněte přehrát a pokračujte, kde jste skončili, nebo něco najděte v Procházet.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s si svou knihovnu nechává pro sebe.</string>
|
||||
<string name="media_needs_access">Gearslip ji přesto může ovládat, jakmile bude mít v telefonu přístup k oznámením. Povolte ho v Nastavení > Oznámení > Přístup k oznámením.</string>
|
||||
<string name="media_not_running">%1$s neběží.</string>
|
||||
<string name="media_not_running_body">%1$s hraje přes přehrávač Gearslipu, ale jen když je otevřená v telefonu. Jednou ji otevřete a bude hrát dál tady v autě.</string>
|
||||
<string name="media_open_app">Otevřít %1$s</string>
|
||||
<string name="media_refused">%1$s nedovolila Gearslipu ji procházet.</string>
|
||||
<string name="media_refused_body">Některé mediální aplikace přijímají jen hostitele od Googlu.</string>
|
||||
<string name="media_connecting">Připojování k %1$s…</string>
|
||||
<string name="media_search">Hledat v %1$s</string>
|
||||
<string name="media_search_no_list">%1$s neumí vypsat výsledky. Stiskněte hledat a %1$s vybere, co pustit.</string>
|
||||
<string name="media_search_unsupported">%1$s neumí hledat z auta.</string>
|
||||
<string name="media_searching">Hledání v %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s nic nenašla pro „%2$s“.</string>
|
||||
<string name="media_search_ignored">%1$s pro „%2$s“ nic nepustila.</string>
|
||||
<string name="media_searching_for">Hledání „%2$s“ v %1$s…</string>
|
||||
<string name="media_nothing_playing_dot">Nic nehraje.</string>
|
||||
<string name="media_no_lyrics">Pro tuto skladbu nebyl nalezen text.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">Nahoru</string>
|
||||
<string name="media_loading">Načítání…</string>
|
||||
<string name="media_browse_start">Spusťte hudbu v aplikaci v telefonu. Přehrávání, pauza, přeskočení a posun fungují odsud.</string>
|
||||
<string name="media_load_failed">Tento seznam se nepodařilo načíst.</string>
|
||||
<string name="media_nothing_here">Nic tu není.</string>
|
||||
<string name="media_nothing_queued">Fronta je prázdná.</string>
|
||||
<string name="media_play">Přehrát</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Nic nehraje - ne každá aplikace Gearslipu řekne, co bude dál.</string>
|
||||
<string name="media_queue_empty_playing">Jen to, co už hraje - ne každá aplikace Gearslipu řekne, co bude dál.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Předchozí</string>
|
||||
<string name="player_next">Další</string>
|
||||
<string name="player_repeat">Opakovat</string>
|
||||
<string name="player_pause">Pozastavit</string>
|
||||
<string name="player_unlike">Už se nelíbí</string>
|
||||
<string name="player_like">Líbí se</string>
|
||||
<string name="player_shuffle_off">Náhodně vypnuto</string>
|
||||
<string name="player_shuffle_on">Náhodně zapnuto</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Vybrat jinou aplikaci</string>
|
||||
<string name="home_no_maps">Nenašly se žádné mapové aplikace.</string>
|
||||
<string name="home_no_maps_body">Nainstalujte navigační aplikaci s podporou Android Auto a spustí se tady sama.</string>
|
||||
<string name="home_map_stopped">Mapa zastavena</string>
|
||||
<string name="home_map_stopped_body">Zůstane zavřená, dokud ji znovu neotevřete.</string>
|
||||
<string name="home_reopen_map">Znovu otevřít mapu</string>
|
||||
<string name="home_car_apps">Aplikace do auta</string>
|
||||
<string name="home_back_nav">Zpět na navigační lištu</string>
|
||||
<string name="home_show_player">Zobrazit přehrávač</string>
|
||||
<string name="home_hide_lyrics">Skrýt text</string>
|
||||
|
||||
<string name="home_starting">Spouštění %1$s…</string>
|
||||
<string name="home_starting_map">Spouštění mapy…</string>
|
||||
<string name="home_failed">%1$s se nespustila.</string>
|
||||
<string name="home_failed_map">Mapová aplikace se nespustila.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Domů</string>
|
||||
<string name="crumb_media">Média</string>
|
||||
<string name="crumb_apps">Aplikace</string>
|
||||
<string name="crumb_dashboard">Nástěnka</string>
|
||||
<string name="crumb_weather">Počasí</string>
|
||||
<string name="crumb_reply">Odpovědět</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Prohlížeč</string>
|
||||
<string name="app_screen_sharing">Sdílení obrazovky</string>
|
||||
<string name="app_exit">Ukončit</string>
|
||||
<string name="navapps_unpin">Odepnout z navigační lišty</string>
|
||||
<string name="navapps_full">Navigační lišta je plná (připnuto: %1$d)</string>
|
||||
<string name="navapps_pin">Připnout na navigační lištu</string>
|
||||
<string name="navapps_close">Zavřít aplikaci</string>
|
||||
<string name="navapps_move_left">Posunout doleva</string>
|
||||
<string name="navapps_move_right">Posunout doprava</string>
|
||||
<string name="navapps_reset">Obnovit pořadí aplikací</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Zavřít</string>
|
||||
<string name="msg_read">Přečíst</string>
|
||||
<string name="msg_stop">Zastavit</string>
|
||||
<string name="msg_no">Ne</string>
|
||||
<string name="msg_send_now">Odeslat teď</string>
|
||||
<string name="msg_type">Napsat</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nová</item>
|
||||
<item quantity="few">%1$s · %2$d nové</item>
|
||||
<item quantity="many">%1$s · %2$d nové</item>
|
||||
<item quantity="other">%1$s · %2$d nových</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Odpovědět: %1$s</string>
|
||||
<string name="msg_listening">Poslouchám</string>
|
||||
<string name="msg_sending_to">Odesílání: %1$s</string>
|
||||
<string name="msg_sent_to">Odesláno: %1$s</string>
|
||||
<string name="msg_reply_failed">Odpověď se nezdařila</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">Párování se nedokončilo. Spárujte raději v Bluetooth menu auta.</string>
|
||||
<string name="bt_pair_not_started">Párování nezačalo. Spárujte raději v Bluetooth menu auta.</string>
|
||||
<string name="bt_paired">Spárováno. Hovory teď hrají v autě.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Znovu nezobrazovat</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">Poloha je vypnutá</string>
|
||||
<string name="overlay_pair_title">Spárovat s tímto autem?</string>
|
||||
<string name="overlay_pair">Spárovat</string>
|
||||
<string name="overlay_confirm_title">Potvrďte v telefonu</string>
|
||||
<string name="overlay_confirm_body">Zkontrolujte kód v telefonu a klepněte na Spárovat. Auto se může zeptat také.</string>
|
||||
|
||||
<string name="overlay_bt_body">Hudba hraje přes Bluetooth, ale telefon není připojený k autu. Připojte ho v Bluetooth menu auta nebo telefonu.</string>
|
||||
<string name="overlay_location_body">Mapové aplikace nemůžou ukázat, kde jste, ani navigovat. Až zaparkujete, zapněte polohu v rychlém nastavení telefonu.</string>
|
||||
<string name="overlay_pair_body_music">Hovory i hudba hrají z reproduktorů auta, jakmile jsou telefon a auto spárované. Telefon vás pak požádá o potvrzení kódu.</string>
|
||||
<string name="overlay_pair_body">Hovory hrají z reproduktorů auta, jakmile jsou telefon a auto spárované. Telefon vás pak požádá o potvrzení kódu.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">Čekání na určení polohy.</string>
|
||||
<string name="weather_allow_location">Povolte Gearslipu v telefonu polohu a uvidíte počasí.</string>
|
||||
<string name="weather_unreachable">Nepodařilo se spojit s meteoslužbou.</string>
|
||||
<string name="weather_clear">Jasno</string>
|
||||
<string name="weather_mostly_clear">Převážně jasno</string>
|
||||
<string name="weather_partly_cloudy">Polojasno</string>
|
||||
<string name="weather_overcast">Zataženo</string>
|
||||
<string name="weather_fog">Mlha</string>
|
||||
<string name="weather_drizzle">Mrholení</string>
|
||||
<string name="weather_rain">Déšť</string>
|
||||
<string name="weather_snow">Sníh</string>
|
||||
<string name="weather_rain_showers">Dešťové přeháňky</string>
|
||||
<string name="weather_snow_showers">Sněhové přeháňky</string>
|
||||
<string name="weather_thunderstorm">Bouřka</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Zjišťování počasí…</string>
|
||||
<string name="weather_next_hours">Příští hodiny</string>
|
||||
<string name="weather_this_week">Tento týden</string>
|
||||
<string name="weather_today">Dnes</string>
|
||||
<string name="weather_credit">Data o počasí od Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Pocitově %1$s</string>
|
||||
<string name="weather_humidity">Vlhkost %1$d %%</string>
|
||||
<string name="weather_wind">Vítr %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">Dnes</string>
|
||||
<string name="dash_getting_weather">Zjišťování počasí…</string>
|
||||
<string name="dash_coming_up">Brzy</string>
|
||||
<string name="dash_loading_calendar">Načítání kalendáře…</string>
|
||||
<string name="dash_nothing_week">Příští týden nic.</string>
|
||||
<string name="dash_all_day">Celý den</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Kontakty</string>
|
||||
<string name="dialer_keypad">Klávesnice</string>
|
||||
<string name="dialer_ask">Zeptat se v telefonu</string>
|
||||
<string name="dialer_enter_number">Zadejte číslo</string>
|
||||
<string name="dialer_call">Volat</string>
|
||||
<string name="dialer_no_contacts">Nenašly se žádné kontakty s telefonním číslem.</string>
|
||||
<string name="dialer_search">Hledat kontakty</string>
|
||||
|
||||
<string name="dialer_needs_permission">Gearslip potřebuje oprávnění Telefon a Kontakty, aby mohl volat odsud. Povolte je v telefonu, až zaparkujete.</string>
|
||||
<string name="dialer_no_match">Žádný kontakt neodpovídá „%1$s“.</string>
|
||||
<string name="dialer_call_who">Volat: %1$s?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Ukončit celou obrazovku</string>
|
||||
<string name="carui_drive_safely">Jezděte bezpečně</string>
|
||||
<string name="carui_understand">Rozumím</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip je neoficiální software: nevytvořila ho ani neprověřila žádná automobilka a může bez varování selhat.</string>
|
||||
<string name="carui_never_interact">Za jízdy tuto obrazovku nikdy neovládejte. Nastavte trasu nebo skladbu před jízdou a pak se soustřeďte na silnici.</string>
|
||||
<string name="carui_this_app">Tato aplikace</string>
|
||||
<string name="carui_app_unsupported">%1$s zatím s Gearslipem nefunguje.</string>
|
||||
<string name="carui_app_unsupported_body">Nespustila se. Některé aplikace běží jen v Android Auto od Googlu.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">Probíhá hovor</string>
|
||||
<string name="navbar_end_call">Ukončit hovor</string>
|
||||
<string name="navbar_unmute">Zapnout mikrofon</string>
|
||||
<string name="navbar_mute">Ztlumit mikrofon</string>
|
||||
<string name="navbar_close_panel">Zavřít boční panel</string>
|
||||
<string name="navbar_dock_player">Přesunout přehrávač vedle mapy</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Hledejte nebo zadejte adresu</string>
|
||||
<string name="web_forward">Vpřed</string>
|
||||
<string name="web_reload">Načíst znovu</string>
|
||||
<string name="web_start_page">Úvodní stránka</string>
|
||||
<string name="web_fullscreen">Celá obrazovka</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Oznámení</string>
|
||||
<string name="notif_clear_all">Vymazat vše</string>
|
||||
<string name="notif_empty">Zatím nic. Nová oznámení z telefonu se objeví tady.</string>
|
||||
<string name="notif_off">Oznámení jsou vypnutá. Povolte Gearslip v telefonu v Nastavení > Oznámení > Přístup k oznámením.</string>
|
||||
<string name="notif_replied">Odpovězeno</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Označit jako přečtené</string>
|
||||
<string name="msg_messages">Zprávy</string>
|
||||
<string name="msg_none_recent">Žádné nedávné zprávy. Nové zprávy z %1$s se tu objeví, jakmile dorazí do telefonu.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d starší</item>
|
||||
<item quantity="few">%1$d starší</item>
|
||||
<item quantity="many">%1$d starší</item>
|
||||
<item quantity="other">%1$d starších</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Skrýt rychlé odpovědi</string>
|
||||
<string name="msg_quick">Rychlá odpověď</string>
|
||||
<string name="msg_you">Vy: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Hledání trasy…</string>
|
||||
<string name="nav_then">Potom</string>
|
||||
<string name="nav_stop">Ukončit navigaci</string>
|
||||
<string name="nav_finding_routes">Hledání tras…</string>
|
||||
<string name="nav_lane_recommended">Doporučený jízdní pruh</string>
|
||||
<string name="nav_lane">Jízdní pruh</string>
|
||||
<string name="nav_arrive">Příjezd %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Zatím nic nehraje.</string>
|
||||
<string name="template_search">Hledat</string>
|
||||
<string name="template_start">Spustit</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Příchozí hovor</string>
|
||||
<string name="call_decline">Odmítnout</string>
|
||||
<string name="call_answer">Přijmout</string>
|
||||
<string name="call_ringing">Už nahlas zvoní - přijměte tady, nebo nechte zvonit.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">Dotyk je vypnutý. Zapněte Gearslip v telefonu v Nastavení > Přístupnost.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Zobrazit po aktualizacích</string>
|
||||
<string name="whatsnew_got_it">Rozumím</string>
|
||||
<string name="whatsnew_title">Co je nového ve verzi %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">mezera</string>
|
||||
<string name="keyboard_hide">Skrýt klávesnici</string>
|
||||
<string name="keyboard_submit">Potvrdit</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Nefunguje s Gearslipem</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">Aplikace do auta</string>
|
||||
<string name="stage_close">Zavřít %1$s</string>
|
||||
<string name="stage_close_app">Zavřít aplikaci</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Odbočte mírně vlevo</string>
|
||||
<string name="dir_slight_left_onto">Odbočte mírně vlevo na %1$s</string>
|
||||
<string name="dir_slight_right">Odbočte mírně vpravo</string>
|
||||
<string name="dir_slight_right_onto">Odbočte mírně vpravo na %1$s</string>
|
||||
<string name="dir_left">Odbočte vlevo</string>
|
||||
<string name="dir_left_onto">Odbočte vlevo na %1$s</string>
|
||||
<string name="dir_right">Odbočte vpravo</string>
|
||||
<string name="dir_right_onto">Odbočte vpravo na %1$s</string>
|
||||
<string name="dir_sharp_left">Odbočte ostře vlevo</string>
|
||||
<string name="dir_sharp_left_onto">Odbočte ostře vlevo na %1$s</string>
|
||||
<string name="dir_sharp_right">Odbočte ostře vpravo</string>
|
||||
<string name="dir_sharp_right_onto">Odbočte ostře vpravo na %1$s</string>
|
||||
<string name="dir_u_turn">Otočte se</string>
|
||||
<string name="dir_keep_left">Držte se vlevo</string>
|
||||
<string name="dir_keep_left_toward">Držte se vlevo směrem na %1$s</string>
|
||||
<string name="dir_keep_right">Držte se vpravo</string>
|
||||
<string name="dir_keep_right_toward">Držte se vpravo směrem na %1$s</string>
|
||||
<string name="dir_fork_left">Na rozcestí se držte vlevo</string>
|
||||
<string name="dir_fork_left_toward">Na rozcestí se držte vlevo směrem na %1$s</string>
|
||||
<string name="dir_fork_right">Na rozcestí se držte vpravo</string>
|
||||
<string name="dir_fork_right_toward">Na rozcestí se držte vpravo směrem na %1$s</string>
|
||||
<string name="dir_ramp_left">Vjeďte na nájezd vlevo</string>
|
||||
<string name="dir_ramp_left_onto">Vjeďte na nájezd vlevo na %1$s</string>
|
||||
<string name="dir_ramp_right">Vjeďte na nájezd vpravo</string>
|
||||
<string name="dir_ramp_right_onto">Vjeďte na nájezd vpravo na %1$s</string>
|
||||
<string name="dir_exit_left">Sjeďte výjezdem vlevo</string>
|
||||
<string name="dir_exit_left_toward">Sjeďte výjezdem vlevo směrem na %1$s</string>
|
||||
<string name="dir_exit_right">Sjeďte výjezdem vpravo</string>
|
||||
<string name="dir_exit_right_toward">Sjeďte výjezdem vpravo směrem na %1$s</string>
|
||||
<string name="dir_merge_left">Zařaďte se vlevo</string>
|
||||
<string name="dir_merge_left_onto">Zařaďte se vlevo na %1$s</string>
|
||||
<string name="dir_merge_right">Zařaďte se vpravo</string>
|
||||
<string name="dir_merge_right_onto">Zařaďte se vpravo na %1$s</string>
|
||||
<string name="dir_merge">Zařaďte se</string>
|
||||
<string name="dir_merge_onto">Zařaďte se na %1$s</string>
|
||||
<string name="dir_roundabout_through">Projeďte kruhovým objezdem</string>
|
||||
<string name="dir_roundabout_through_onto">Projeďte kruhovým objezdem na %1$s</string>
|
||||
<string name="dir_roundabout_enter">Vjeďte na kruhový objezd</string>
|
||||
<string name="dir_roundabout_exit">Vyjeďte z kruhového objezdu</string>
|
||||
<string name="dir_roundabout_exit_onto">Vyjeďte z kruhového objezdu na %1$s</string>
|
||||
<string name="dir_straight">Pokračujte rovně</string>
|
||||
<string name="dir_straight_onto">Pokračujte rovně na %1$s</string>
|
||||
<string name="dir_continue">Pokračujte</string>
|
||||
<string name="dir_continue_onto">Pokračujte po %1$s</string>
|
||||
<string name="dir_ferry">Nastupte na trajekt</string>
|
||||
<string name="dir_train">Nastupte do vlaku</string>
|
||||
<string name="dir_destination">Cíl je před vámi</string>
|
||||
<string name="dir_destination_left">Cíl je vlevo</string>
|
||||
<string name="dir_destination_right">Cíl je vpravo</string>
|
||||
<string name="dir_roundabout_take">Na kruhovém objezdu vyjeďte %1$s výjezdem</string>
|
||||
<string name="dir_roundabout_take_onto">Na kruhovém objezdu vyjeďte %1$s výjezdem na %2$s</string>
|
||||
<string name="dir_ordinal_1">prvním</string>
|
||||
<string name="dir_ordinal_2">druhým</string>
|
||||
<string name="dir_ordinal_3">třetím</string>
|
||||
<string name="dir_ordinal_4">čtvrtým</string>
|
||||
<string name="dir_ordinal_5">pátým</string>
|
||||
<string name="dir_ordinal_6">šestým</string>
|
||||
<string name="dir_ordinal_7">sedmým</string>
|
||||
<string name="dir_ordinal_8">osmým</string>
|
||||
<string name="dir_in_distance">Za %1$s %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s kilometr</item>
|
||||
<item quantity="few">%1$s kilometry</item>
|
||||
<item quantity="many">%1$s kilometru</item>
|
||||
<item quantity="other">%1$s kilometrů</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s kilometru</string>
|
||||
<string name="dir_quarter_mile">čtvrt míle</string>
|
||||
<string name="dir_half_mile">půl míle</string>
|
||||
<string name="dir_three_quarter_mile">tři čtvrtě míle</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s míli</item>
|
||||
<item quantity="few">%1$s míle</item>
|
||||
<item quantity="many">%1$s míle</item>
|
||||
<item quantity="other">%1$s mil</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s míle</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s stopu</item>
|
||||
<item quantity="few">%1$s stopy</item>
|
||||
<item quantity="many">%1$s stopy</item>
|
||||
<item quantity="other">%1$s stop</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s yard</item>
|
||||
<item quantity="few">%1$s yardy</item>
|
||||
<item quantity="many">%1$s yardu</item>
|
||||
<item quantity="other">%1$s yardů</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d metr</item>
|
||||
<item quantity="few">%1$d metry</item>
|
||||
<item quantity="many">%1$d metru</item>
|
||||
<item quantity="other">%1$d metrů</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Pokud se auto nepřipojí, možná tento certifikát odmítá. Importujte vlastní v Nastavení v části Certifikát.</string>
|
||||
<string name="cert_downloaded_footer">Auto se nepřipojí? Některá auta zhruba od roku 2020 odmítají certifikát aasdk, takže importujte vlastní. Změny se projeví při příštím připojení.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Karta odbočení na přístrojovém štítu</string>
|
||||
<string name="car_cluster_video_note">Pro auto s druhou obrazovkou v přístrojovém štítu: Gearslip na ní ukáže příští odbočení. Platí od příštího připojení.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Nepřipojeno</string>
|
||||
<string name="status_idle_detail">Připojte telefon do USB portu auta nebo se připojte bezdrátově. Gearslip se otevře sám, když auto odpoví.</string>
|
||||
<string name="status_connecting">Připojování</string>
|
||||
<string name="status_projecting">Promítání</string>
|
||||
<string name="status_projecting_detail">Streamování do autorádia.</string>
|
||||
<string name="status_disconnected">Odpojeno</string>
|
||||
<string name="status_disconnected_detail">Autorádio už není připojené.</string>
|
||||
<string name="places_title">Uložená místa</string>
|
||||
<string name="places_sub">Řekněte „navigate home“ v jakékoli mapové aplikaci</string>
|
||||
<string name="places_intro">Řekněte „navigate home“, „take me to work“ nebo „navigate to“ s vlastním názvem a Gearslip pošle adresu do mapové aplikace. Hlasové příkazy zatím fungují jen anglicky.</string>
|
||||
<string name="places_home">Domov</string>
|
||||
<string name="places_work">Práce</string>
|
||||
<string name="places_other">Místo %1$d</string>
|
||||
<string name="places_name">Název</string>
|
||||
<string name="places_address">Adresa</string>
|
||||
<string name="places_here">Použít mou polohu</string>
|
||||
<string name="places_finding">Hledám…</string>
|
||||
<string name="places_not_found">Gearslip nenašel vaši polohu. Zkontrolujte, že je poloha zapnutá.</string>
|
||||
<string name="places_clear">Vymazat</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,710 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d Song · %2$s. Gelöschte Texte herunterladen wieder, wenn Sie sie öffnen.</item>
|
||||
<item quantity="other">%1$d Songs · %2$s. Gelöschte Texte herunterladen wieder, wenn Sie sie öffnen.</item>
|
||||
</plurals>
|
||||
<string name="carset_theme">Thema</string>
|
||||
<string name="carset_imperial">Imperial (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Metrische (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Bildschirmgröße (nächste Verbindung)</string>
|
||||
<string name="carset_fit">Passend zum Bildschirm</string>
|
||||
<string name="off">Aus</string>
|
||||
<string name="on">Auf</string>
|
||||
<string name="carset_lyrics_from">Englisch from</string>
|
||||
<string name="carset_more_sources">Mehr Quellen</string>
|
||||
<string name="carset_hide">Hirse</string>
|
||||
<string name="carset_show">Show</string>
|
||||
<string name="carset_phone">Telefon</string>
|
||||
<string name="carset_vehicle">Fahrzeug</string>
|
||||
<string name="carset_video">Video</string>
|
||||
<string name="carset_version">Version</string>
|
||||
<string name="carset_build">Bauen</string>
|
||||
<string name="carset_whats_new_after">Was ist neu nach Updates</string>
|
||||
<string name="carset_whats_new_after_sub">Zeigen Sie die Liste der Änderungen einmal, das erste Mal, dass das Auto nach einem Update verbindet.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (Standard) - Tippen Sie auf eine andere Seite</string>
|
||||
<string name="carset_web_note">Gezeigt von der Browser-App auf dem Launcher. Gilt für die nächste Verbindung.</string>
|
||||
<string name="carset_screen_fit">Bildschirm passend</string>
|
||||
<string name="carset_top">Top</string>
|
||||
<string name="carset_bottom">Boden</string>
|
||||
<string name="carset_save_vehicle">Speichern für dieses Fahrzeug</string>
|
||||
<string name="carset_no_profile">Kein Fahrzeugprofil angepasst</string>
|
||||
<string name="carset_audio_note">Über Gearslip schickt den Ton des Handys über die Verbindung zum Auto, per Kabel oder kabellos. Android fragt bei jeder Fahrt, ob Sie Ihren Bildschirm teilen möchten, und ein paar Apps blockieren das. Bluetooth spielt wie sonst auch, und Android fragt nie.</string>
|
||||
<string name="carset_directions_note">Gearslip liest jede Drehung laut durch die Lautsprecher des Autos, und die Musik taucht, während es spricht. Schalten Sie es aus, wenn die eigene Stimme Ihrer Karten-App bereits im Auto spielt.</string>
|
||||
<string name="carset_phone_speech_note">Hört durch den eigenen Sprachdienst des Telefons statt Gearslips. Auf den meisten Telefonen, das ist Google, die Ihre Stimme an Google senden kann. GrapheneOS hat keine, also hält Gearslip seine eigenen.</string>
|
||||
<string name="carset_vehicle_data_note">Fügt eine Fahrzeugdatenfliese hinzu: eine lebendige, rohe Deponie des Sensorkanals der Kopfeinheit. Feldetiketten (Geschwindigkeit, Drehzahl, Kraftstoff...) werden von anderen Reverse-Engineering-Projekten übernommen und unified gegen dieses Auto - behandeln Sie sie als Vermutung, nicht als Tatsache, bis Sie eine Änderung für sich selbst beobachtet haben.</string>
|
||||
<string name="carset_adaptive_note">Erniedrigt die Qualität des Bildes, wenn das Auto oder sein Wi-Fi nicht aufhalten kann, und erhebt es wieder, wenn es kann. Wirkt auf die nächste Verbindung.</string>
|
||||
<string name="carset_usable_height">Einsatzhöhe %1$dpx von 480</string>
|
||||
<string name="carset_saved_to">Gespeichert auf %1$s</string>
|
||||
<string name="carset_save_failed">Konnte nicht retten</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
<string name="media_open_on_phone">Telefonisch öffnen</string>
|
||||
<string name="media_now_playing">Jetzt spielen</string>
|
||||
<string name="media_up_next">Nach oben</string>
|
||||
<string name="media_browse">Nach oben</string>
|
||||
<string name="media_search_hint">Geben Sie einen Song, Künstler, Album oder Playlist ein.</string>
|
||||
<string name="media_looking_lyrics">Auf der Suche nach Texten..</string>
|
||||
<string name="media_nothing_playing">Nichts spielt</string>
|
||||
<string name="media_press_play">Drücken Sie das Spiel, um zu holen, wo Sie weg waren, oder finden Sie etwas in Browse.</string>
|
||||
<string name="media_keeps_library">%1$s hält seine Bibliothek für sich.</string>
|
||||
<string name="media_needs_access">Gearslip kann es immer noch steuern, wenn es Benachrichtigung auf dem Telefon hat. Erlauben Sie es unter Einstellungen > Notifications > Notification access.</string>
|
||||
<string name="media_not_running">%1$s läuft nicht.</string>
|
||||
<string name="media_not_running_body">%1$s spielt durch Gearslips eigenen Player, aber nur solange es am Telefon geöffnet ist. Öffnen Sie es einmal und es spielt hier im Auto.</string>
|
||||
<string name="media_open_app">%1$s</string>
|
||||
<string name="media_refused">%1$s würde Gearslip nicht blättern lassen.</string>
|
||||
<string name="media_refused_body">Einige Medien-Apps akzeptieren nur den eigenen Host von Google.</string>
|
||||
<string name="media_connecting">Anschluss an %1$s..</string>
|
||||
<string name="media_search">Suche %1$s</string>
|
||||
<string name="media_search_no_list">%1$s kann keine Ergebnisse auflisten. Drücken Sie die Suche, und %1$s wählt, was zu spielen ist.</string>
|
||||
<string name="media_search_unsupported">%1$s kann nicht aus dem Auto suchen.</string>
|
||||
<string name="media_searching">Suche %1$s..</string>
|
||||
<string name="media_search_nothing">%1$s fand nichts für \"%2$s\".</string>
|
||||
<string name="media_search_ignored">%1$s hat nichts für \"%2$s\" gespielt.</string>
|
||||
<string name="media_searching_for">Suche %1$s für \"%2$s\"..</string>
|
||||
<string name="media_nothing_playing_dot">Nichts zu spielen.</string>
|
||||
<string name="media_no_lyrics">Keine Songtexte für diesen Titel gefunden.</string>
|
||||
<string name="media_up">Nach oben</string>
|
||||
<string name="media_loading">Die..</string>
|
||||
<string name="media_browse_start">Starten Sie Musik in der App auf Ihrem Telefon. Spielen, Pausen, überspringen und suchen Arbeit von hier.</string>
|
||||
<string name="media_load_failed">Könnte diese Liste nicht laden.</string>
|
||||
<string name="media_nothing_here">Nichts hier.</string>
|
||||
<string name="media_nothing_queued">Nichts abgeschreckt.</string>
|
||||
<string name="media_play">Spielen</string>
|
||||
<string name="media_queue_empty_idle">Nichts spielen - nicht jede App sagt Gearslip, was als nächstes kommt.</string>
|
||||
<string name="media_queue_empty_playing">Nur was bereits spielt - nicht jede App sagt Gearslip, was als nächstes kommt.</string>
|
||||
<string name="player_previous">Vorherige</string>
|
||||
<string name="player_next">Nächste</string>
|
||||
<string name="player_repeat">Aufhebung</string>
|
||||
<string name="player_pause">Pause</string>
|
||||
<string name="player_unlike">Im Gegensatz zu</string>
|
||||
<string name="player_like">Wie</string>
|
||||
<string name="player_shuffle_off">Schnauze</string>
|
||||
<string name="player_shuffle_on">Shuffle auf</string>
|
||||
<string name="home_choose_app">Wählen Sie eine andere App</string>
|
||||
<string name="home_no_maps">Keine Mapping-Apps gefunden.</string>
|
||||
<string name="home_no_maps_body">Installieren Sie eine Navigations-App, die Android Auto unterstützt und es wird hier auf eigene Faust beginnen.</string>
|
||||
<string name="home_map_stopped">Karte aufgehört</string>
|
||||
<string name="home_map_stopped_body">Es bleibt geschlossen, bis Sie es wieder öffnen.</string>
|
||||
<string name="home_reopen_map">Wiedereröffnung der Karte</string>
|
||||
<string name="home_car_apps">Auto-Apps</string>
|
||||
<string name="home_back_nav">Zurück zur nav bar</string>
|
||||
<string name="home_show_player">Den Player anzeigen</string>
|
||||
<string name="home_hide_lyrics">Hide Songtexte</string>
|
||||
<string name="home_starting">Starten %1$s..</string>
|
||||
<string name="home_starting_map">Beginn der Karte..</string>
|
||||
<string name="home_failed">%1$s würde nicht starten.</string>
|
||||
<string name="home_failed_map">Die Karten-App würde nicht starten.</string>
|
||||
<string name="crumb_home">Home</string>
|
||||
<string name="crumb_media">Medien</string>
|
||||
<string name="crumb_apps">Apps</string>
|
||||
<string name="crumb_dashboard">Das ist das</string>
|
||||
<string name="crumb_weather">Wetter</string>
|
||||
<string name="crumb_reply">Antwort</string>
|
||||
<string name="app_browser">Browser</string>
|
||||
<string name="app_screen_sharing">Bildschirm teilen</string>
|
||||
<string name="app_exit">Aus</string>
|
||||
<string name="navapps_unpin">Unpin von nav bar</string>
|
||||
<string name="navapps_full">Nav Bar ist voll (%1$d gepinnt)</string>
|
||||
<string name="navapps_pin">Pin zu nav bar</string>
|
||||
<string name="navapps_close">App schließen</string>
|
||||
<string name="navapps_move_left">Nach links</string>
|
||||
<string name="navapps_move_right">Los</string>
|
||||
<string name="navapps_reset">Zurück zur Übersicht</string>
|
||||
<string name="msg_dismiss">Entlassung</string>
|
||||
<string name="msg_read">Weiterlesen</string>
|
||||
<string name="msg_stop">Stopp</string>
|
||||
<string name="msg_no">Nein</string>
|
||||
<string name="msg_send_now">Jetzt lesen</string>
|
||||
<string name="msg_type">Typ</string>
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d neu</item>
|
||||
<item quantity="other">%1$s · %2$d neu</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Antwort auf %1$s</string>
|
||||
<string name="msg_listening">Hören</string>
|
||||
<string name="msg_sending_to">Senden an %1$s</string>
|
||||
<string name="msg_sent_to">Sent zu %1$s</string>
|
||||
<string name="msg_reply_failed">Kann nicht antworten</string>
|
||||
<string name="bt_pair_unfinished">Die Paarung ist nicht beendet. Kombinieren Sie stattdessen aus dem Bluetooth-Menü des Autos.</string>
|
||||
<string name="bt_pair_not_started">Die Paarung hat nicht angefangen. Kombinieren Sie stattdessen aus dem Bluetooth-Menü des Autos.</string>
|
||||
<string name="bt_paired">Ein Paar. Ruft jetzt durch das Auto.</string>
|
||||
<string name="overlay_dont_show">Nicht mehr zeigen</string>
|
||||
<string name="overlay_ok">OKAY</string>
|
||||
<string name="overlay_location_off">Lage ist ab</string>
|
||||
<string name="overlay_pair_title">Mit dem Auto?</string>
|
||||
<string name="overlay_pair">Paar</string>
|
||||
<string name="overlay_confirm_title">Bestätigen Sie Ihr Telefon</string>
|
||||
<string name="overlay_confirm_body">Überprüfen Sie den Code auf Ihrem Telefon und tippen Sie auf Paar. Das Auto kann auch fragen.</string>
|
||||
<string name="overlay_bt_body">Musik spielt über Bluetooth, aber Ihr Telefon ist nicht mit dem Auto verbunden. Schließen Sie es aus dem Bluetooth-Menü des Autos oder dem Telefon an.</string>
|
||||
<string name="overlay_location_body">Karten-Apps können nicht anzeigen, wo Sie sind oder Anweisungen geben. Schalten Sie die Lage in den schnellen Einstellungen Ihres Telefons ein, wenn Sie geparkt sind.</string>
|
||||
<string name="overlay_pair_body_music">Anrufe und Musik spielen durch die Lautsprecher des Autos, sobald Ihr Telefon und das Auto gepaart sind. Ihr Telefon bittet Sie, einen Code zu bestätigen.</string>
|
||||
<string name="overlay_pair_body">Anrufe spielen durch die Lautsprecher des Autos, sobald Ihr Telefon und das Auto gepaart sind. Ihr Telefon bittet Sie, einen Code zu bestätigen.</string>
|
||||
<string name="weather_waiting_fix">Warten auf einen Standort-Reparatur.</string>
|
||||
<string name="weather_allow_location">Erlauben Sie Ort für Gearslip am Telefon, um das Wetter zu sehen.</string>
|
||||
<string name="weather_unreachable">Könnte nicht den Wetterservice erreichen.</string>
|
||||
<string name="weather_clear">Klar</string>
|
||||
<string name="weather_mostly_clear">Meist klar</string>
|
||||
<string name="weather_partly_cloudy">Teilweise bewölkt</string>
|
||||
<string name="weather_overcast">Overcast</string>
|
||||
<string name="weather_fog">Fohlen</string>
|
||||
<string name="weather_drizzle">Dr</string>
|
||||
<string name="weather_rain">Regen</string>
|
||||
<string name="weather_snow">Schnee</string>
|
||||
<string name="weather_rain_showers">Regenduschen</string>
|
||||
<string name="weather_snow_showers">Schneeschauer</string>
|
||||
<string name="weather_thunderstorm">Gewitter</string>
|
||||
<string name="weather_getting">Das Wetter..</string>
|
||||
<string name="weather_next_hours">Nächste Stunden</string>
|
||||
<string name="weather_this_week">Diese Woche</string>
|
||||
<string name="weather_today">Heute</string>
|
||||
<string name="weather_credit">Wetterdaten von Open-Meteo.com (CC BY 4.0)</string>
|
||||
<string name="weather_feels_like">Fühlt sich wie %1$s</string>
|
||||
<string name="weather_humidity">Luftfeuchtigkeit %1$d%%</string>
|
||||
<string name="weather_wind">Wind %1$s</string>
|
||||
<string name="dash_today">Heute</string>
|
||||
<string name="dash_getting_weather">Das Wetter..</string>
|
||||
<string name="dash_coming_up">Aufstehen</string>
|
||||
<string name="dash_loading_calendar">Kalender laden..</string>
|
||||
<string name="dash_nothing_week">Nichts in der nächsten Woche.</string>
|
||||
<string name="dash_all_day">Tag</string>
|
||||
<string name="dialer_contacts">Ansprechpartner</string>
|
||||
<string name="dialer_keypad">Schlüsselwort</string>
|
||||
<string name="dialer_ask">Fragen Sie am Telefon</string>
|
||||
<string name="dialer_enter_number">Geben Sie eine Nummer ein</string>
|
||||
<string name="dialer_call">Anruf</string>
|
||||
<string name="dialer_no_contacts">Keine Kontakte mit einer Telefonnummer gefunden.</string>
|
||||
<string name="dialer_search">Kontaktsuche</string>
|
||||
<string name="dialer_needs_permission">Gearslip braucht das Telefon und Kontakte Berechtigungen, von hier zu wählen. Lassen Sie sie am Telefon, wenn Sie geparkt sind.</string>
|
||||
<string name="dialer_no_match">Keine Kontakte passen \"%1$s\".</string>
|
||||
<string name="dialer_call_who">Rufen Sie %1$s an?</string>
|
||||
<string name="carui_exit_fullscreen">Vollbildschirm ausfüllen</string>
|
||||
<string name="carui_drive_safely">Fahren Sie sicher</string>
|
||||
<string name="carui_understand">Ich verstehe</string>
|
||||
<string name="carui_unofficial">Gearslip ist inoffizielle Software: es wird nicht von einem Carmaker gemacht oder überprüft, und es kann ohne Warnung ausfallen.</string>
|
||||
<string name="carui_never_interact">Interaktion mit diesem Bildschirm nie während des Fahrens. Setzen Sie die Route oder das Lied, bevor Sie gehen, dann halten Sie Ihre Aufmerksamkeit auf der Straße.</string>
|
||||
<string name="carui_this_app">Diese App</string>
|
||||
<string name="carui_app_unsupported">%1$s funktioniert noch nicht mit Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">Es hat nicht angefangen. Einige Apps laufen nur auf Googles Android Auto.</string>
|
||||
<string name="navbar_on_call">Auf einen Anruf</string>
|
||||
<string name="navbar_end_call">Endanruf</string>
|
||||
<string name="navbar_unmute">Unmut</string>
|
||||
<string name="navbar_mute">Mut</string>
|
||||
<string name="navbar_close_panel">Schließen Sie die Seitenplatte</string>
|
||||
<string name="navbar_dock_player">Den Player neben die Karte setzen</string>
|
||||
<string name="web_search">Suchen oder geben Sie eine Adresse</string>
|
||||
<string name="web_forward">Vorwärts</string>
|
||||
<string name="web_reload">Nachladen</string>
|
||||
<string name="web_start_page">Startseite</string>
|
||||
<string name="web_fullscreen">Vollbildschirm</string>
|
||||
<string name="notif_title">Mitteilungen</string>
|
||||
<string name="notif_clear_all">Alles löschen</string>
|
||||
<string name="notif_empty">Noch nichts. Neue Benachrichtigungen vom Telefon werden hier angezeigt.</string>
|
||||
<string name="notif_off">Meldungen sind aus. Gearslip unter Einstellungen > Notifications > Notification Zugriff auf das Telefon.</string>
|
||||
<string name="notif_replied">Wiederholen</string>
|
||||
<string name="msg_mark_read">Mark lesen</string>
|
||||
<string name="msg_messages">Nachrichten</string>
|
||||
<string name="msg_none_recent">Keine neuen Nachrichten. Neue %1$s Nachrichten erscheinen hier, da sie am Telefon ankommen.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d früher</item>
|
||||
<item quantity="other">%1$d früher</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Schnelle Antworten verbergen</string>
|
||||
<string name="msg_quick">Antwort</string>
|
||||
<string name="msg_you">Sie:</string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
<string name="nav_finding_route">Eine Route finden..</string>
|
||||
<string name="nav_then">Dann</string>
|
||||
<string name="nav_stop">Navigation beenden</string>
|
||||
<string name="nav_finding_routes">Routen finden..</string>
|
||||
<string name="nav_lane_recommended">Empfohlene Spur</string>
|
||||
<string name="nav_lane">Lane</string>
|
||||
<string name="nav_arrive">Ankunft %1$s</string>
|
||||
<string name="template_nothing_playing">Nichts spielt noch.</string>
|
||||
<string name="template_search">Suche</string>
|
||||
<string name="template_start">Start</string>
|
||||
<string name="call_incoming">Eingehender Anruf</string>
|
||||
<string name="call_decline">Deko</string>
|
||||
<string name="call_answer">Antwort</string>
|
||||
<string name="call_ringing">Sie klingeln schon laut - antworte hier, oder lass es klingeln.</string>
|
||||
<string name="phoneapps_touch_off">Touch ist aus. Aktivieren Sie Gearslip unter Einstellungen > Zugänglichkeit am Telefon.</string>
|
||||
<string name="whatsnew_show_after">Nach Updates anzeigen</string>
|
||||
<string name="whatsnew_got_it">Verstanden</string>
|
||||
<string name="whatsnew_title">Was ist neu in %1$s</string>
|
||||
<string name="keyboard_space">raum</string>
|
||||
<string name="keyboard_hide">Tastatur verbergen</string>
|
||||
<string name="keyboard_submit">Einreichung</string>
|
||||
<string name="launcher_broken">Arbeitet nicht mit Gearslip</string>
|
||||
<string name="stage_car_app">Auto App</string>
|
||||
<string name="stage_close">Schließen %1$s</string>
|
||||
<string name="stage_close_app">Schließen Sie die App</string>
|
||||
<string name="dir_slight_left">Etwas links abbiegen</string>
|
||||
<string name="dir_slight_left_onto">Biegen Sie etwas links auf %1$s</string>
|
||||
<string name="dir_slight_right">Leicht rechts abbiegen</string>
|
||||
<string name="dir_slight_right_onto">Biegen Sie etwas rechts auf %1$s</string>
|
||||
<string name="dir_left">Links</string>
|
||||
<string name="dir_left_onto">Links auf %1$s</string>
|
||||
<string name="dir_right">Rechts</string>
|
||||
<string name="dir_right_onto">Rechts auf %1$s</string>
|
||||
<string name="dir_sharp_left">Biegen Sie scharf links ab</string>
|
||||
<string name="dir_sharp_left_onto">Biegen Sie scharf links auf %1$s</string>
|
||||
<string name="dir_sharp_right">Biegen Sie scharf rechts ab</string>
|
||||
<string name="dir_sharp_right_onto">Biegen Sie scharf rechts auf %1$s</string>
|
||||
<string name="dir_u_turn">Machen Sie einen U-turn</string>
|
||||
<string name="dir_keep_left">Nach links</string>
|
||||
<string name="dir_keep_left_toward">Links nach %1$s</string>
|
||||
<string name="dir_keep_right">Nach rechts</string>
|
||||
<string name="dir_keep_right_toward">Rechts auf %1$s</string>
|
||||
<string name="dir_fork_left">Links auf der Gabel</string>
|
||||
<string name="dir_fork_left_toward">Links auf der Gabel nach %1$s</string>
|
||||
<string name="dir_fork_right">Direkt an der Gabel halten</string>
|
||||
<string name="dir_fork_right_toward">Direkt an der Gabel in Richtung %1$s</string>
|
||||
<string name="dir_ramp_left">Nehmen Sie die Rampe links</string>
|
||||
<string name="dir_ramp_left_onto">Nehmen Sie die Rampe links auf %1$s</string>
|
||||
<string name="dir_ramp_right">Nehmen Sie die Rampe rechts</string>
|
||||
<string name="dir_ramp_right_onto">Nehmen Sie die Rampe rechts auf %1$s</string>
|
||||
<string name="dir_exit_left">Ausfahrt links</string>
|
||||
<string name="dir_exit_left_toward">Ausfahrt links nach %1$s</string>
|
||||
<string name="dir_exit_right">Ausfahrt rechts</string>
|
||||
<string name="dir_exit_right_toward">Ausfahrt rechts Richtung %1$s</string>
|
||||
<string name="dir_merge_left">Verschmelzung links</string>
|
||||
<string name="dir_merge_left_onto">Links auf %1$s</string>
|
||||
<string name="dir_merge_right">Verschmelzung rechts</string>
|
||||
<string name="dir_merge_right_onto">Zusammenführen rechts auf %1$s</string>
|
||||
<string name="dir_merge">Verschmelzung</string>
|
||||
<string name="dir_merge_onto">Zusammenführen auf %1$s</string>
|
||||
<string name="dir_roundabout_through">Geh durch den Kreisverkehr</string>
|
||||
<string name="dir_roundabout_through_onto">Gehen Sie durch den Kreisverkehr auf %1$s</string>
|
||||
<string name="dir_roundabout_enter">Betreten Sie den Kreisverkehr</string>
|
||||
<string name="dir_roundabout_exit">Den Kreisverkehr ausfahren</string>
|
||||
<string name="dir_roundabout_exit_onto">Fahren Sie den Kreisverkehr auf %1$s</string>
|
||||
<string name="dir_straight">Weiter gerade</string>
|
||||
<string name="dir_straight_onto">Fahren Sie gerade auf %1$s</string>
|
||||
<string name="dir_continue">Fortsetzung</string>
|
||||
<string name="dir_continue_onto">Weiter auf %1$s</string>
|
||||
<string name="dir_ferry">Nehmen Sie die Fähre</string>
|
||||
<string name="dir_train">Nimm den Zug</string>
|
||||
<string name="dir_destination">Ihr Ziel ist voraus</string>
|
||||
<string name="dir_destination_left">Ihr Ziel ist links</string>
|
||||
<string name="dir_destination_right">Ihr Ziel ist rechts</string>
|
||||
<string name="dir_roundabout_take">Am Kreisverkehr, nehmen Sie die %1$s Ausfahrt</string>
|
||||
<string name="dir_roundabout_take_onto">Am Kreisverkehr nehmen Sie die %1$s Ausfahrt auf %2$s</string>
|
||||
<string name="dir_ordinal_1">erste</string>
|
||||
<string name="dir_ordinal_2">zweite</string>
|
||||
<string name="dir_ordinal_3">dritte</string>
|
||||
<string name="dir_ordinal_4">vgl</string>
|
||||
<string name="dir_ordinal_5">5</string>
|
||||
<string name="dir_ordinal_6">6</string>
|
||||
<string name="dir_ordinal_7">7</string>
|
||||
<string name="dir_ordinal_8">achte</string>
|
||||
<string name="dir_in_distance">In %1$s, %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s Kilometer</item>
|
||||
<item quantity="other">%1$s Kilometer</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s Kilometer</string>
|
||||
<string name="dir_quarter_mile">viertel meile</string>
|
||||
<string name="dir_half_mile">eine halbe meile</string>
|
||||
<string name="dir_three_quarter_mile">drei viertel einer meile</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s Mio</item>
|
||||
<item quantity="other">%1$s Meilen</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s Meilen</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s Fuß</item>
|
||||
<item quantity="other">%1$s Füße</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s</item>
|
||||
<item quantity="other">%1$s</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d Meter</item>
|
||||
<item quantity="other">%1$d Meter</item>
|
||||
</plurals>
|
||||
<string name="touch_relay_description">Ermöglicht Gearslip Replay-Touchen vom Auto-Bildschirm auf das Telefon beim Spiegeln. Gearslip liest keine Bildschirminhalte.</string>
|
||||
<string name="guide_usage_title">Hilf anderen Fahrern.</string>
|
||||
<string name="guide_safety_title">Fahren Sie sicher.</string>
|
||||
<string name="guide_safety_body">Gearslip ist inoffiziell. Kein Autohersteller hat es gemacht oder überprüft, und es kann ohne Warnung ausfallen. Stellen Sie es auf, bevor Sie gehen, dann halten Sie Ihre Augen auf der Straße.</string>
|
||||
<string name="guide_mic_title">Mikrofon.</string>
|
||||
<string name="guide_mic_body">Gearslip verwendet es, um Ihre Musik an das Auto zu senden und Sie zu hören, wenn Sie nach etwas oder Antwort auf eine Nachricht fragen. Was du sagst, bleibt am Telefon.</string>
|
||||
<string name="guide_location_title">Location.</string>
|
||||
<string name="guide_location_body">Die Karte des Autos und die nahe gelegenen Suchanfragen brauchen es. Ohne sie zeigt das Dashboard keine Karte.</string>
|
||||
<string name="guide_calendar_title">Kalender.</string>
|
||||
<string name="guide_calendar_body">Der Auto-Bildschirm zeigt Ihre nächste Veranstaltung. Gearslip liest nur Ihren Kalender.</string>
|
||||
<string name="guide_calls_title">Anrufe und Kontakte.</string>
|
||||
<string name="guide_calls_body">Das Auto zeigt, wer mit dem Namen anruft, und seine Telefon-App kann eine Nummer wählen oder einen Ihrer Kontakte anrufen. Gearslip liest sie nur.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">Das erste Mal, wenn Sie einstecken, bietet Gearslip, um Ihr Telefon mit dem Auto zu paaren, so Anrufe spielen durch seine Lautsprecher.</string>
|
||||
<string name="guide_notifications_title">Mitteilungen.</string>
|
||||
<string name="guide_notifications_body">Während Gearslip angeschlossen ist, zeigt es eine Benachrichtigung mit einer Taste zu stoppen. Ohne diese, Android versteckt es.</string>
|
||||
<string name="guide_messages_title">Nachrichten.</string>
|
||||
<string name="guide_messages_body">Das Auto zeigt Ihre Nachrichten, liest sie laut und lässt Sie antworten. Schalten Sie Gearslip auf in der Liste dies öffnet.</string>
|
||||
<string name="guide_android_auto_title">Schalten Sie Android Auto aus.</string>
|
||||
<string name="guide_android_auto_body">Android Auto ist noch installiert, und es kämpft Gearslip für die USB-Verbindung des Autos. Deaktivieren oder deinstallieren Sie es auf dem Bildschirm dies öffnet.</string>
|
||||
<string name="guide_default_title">Wählen Sie Gearslip.</string>
|
||||
<string name="guide_default_body">Wenn Ihr Telefon fragt, welche App zu öffnen, wenn Sie in das Auto einstecken, wählen Sie Gearslip und tippen Sie immer.</string>
|
||||
<string name="guide_audio_title">Auto Audio.</string>
|
||||
<string name="guide_audio_body">Wählen Sie, wie Musik das Auto erreicht. Anrufe gehen über Bluetooth auch.</string>
|
||||
<string name="guide_voice_title">Stimme.</string>
|
||||
<string name="guide_voice_body">Fragen Sie nach Richtungen, Musik oder einem Anruf per Stimme. Ihr Telefon verwandelt die Sprache selbst in Text und nichts, was Sie sagen, lässt sie. Es braucht %1$d MB, einmal heruntergeladen. Verwenden Sie Wi-Fi, wenn Sie können.</string>
|
||||
<string name="guide_audio_prompt_title">Keine Audio-Prompt.</string>
|
||||
<string name="guide_audio_prompt_body">Führen Sie dies von einem Computer mit USB Debugging auf, und Android vertraut Gearslip mit Ihrem Audio für gut. Ohne es tippen Sie auf Starten einmal bei jeder Fahrt.</string>
|
||||
<string name="guide_readable_title">Lesbare Benachrichtigungen.</string>
|
||||
<string name="guide_readable_body">Während Audio geht zum Auto, Android versteckt, was Benachrichtigungen sagen, als ob Sie Ihren Bildschirm teilen. Dies lässt Gearslip sie während einer Fahrt zeigen und sie danach wieder verbergen.</string>
|
||||
<string name="guide_answer_title">Anrufe beantworten.</string>
|
||||
<string name="guide_answer_body">Machen Sie Gearslip Ihre Anrufer-ID-App, und das Auto kann Anrufe beantworten und ablehnen. Ohne es, Anrufe immer noch Ring, aber nur das Telefon kann sie beantworten.</string>
|
||||
<string name="guide_apps_title">Gute Gesellschaft.</string>
|
||||
<string name="guide_buttons_title">Deine Knöpfe.</string>
|
||||
<string name="guide_buttons_body">Wählen Sie, wie die Knöpfe des Autos aussehen. Ändern Sie es jederzeit in den Einstellungen des Autos.</string>
|
||||
<string name="guide_open_notification_access">Zugang zur offenen Benachrichtigung</string>
|
||||
<string name="guide_open_android_auto">App-Info von Android Auto öffnen</string>
|
||||
<string name="guide_download_voice">Voice herunterladen</string>
|
||||
<string name="guide_set_up">Einrichten</string>
|
||||
<string name="guide_voice_downloading">Download, %1$d%%. Sie können weitertragen; es geht weiter.</string>
|
||||
<string name="guide_voice_ready">Die Stimme ist bereit.</string>
|
||||
<string name="guide_voice_failed">Der Download ist nicht beendet. Tippen Sie auf Stimme herunterladen, um es wieder zu versuchen.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Empfohlen</string>
|
||||
<string name="guide_audio_bluetooth_body">Keine Aufforderung bei jeder Fahrt, und die Stimme Ihrer Karte App erreicht das Auto.</string>
|
||||
<string name="guide_audio_usb">Über Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Klarer Sound ohne Verzögerung. Bei jeder Fahrt, Android bittet Sie, Ihren Bildschirm zu teilen, die Gearslip den Sound senden lässt.</string>
|
||||
<string name="guide_optional">OPTIONEN</string>
|
||||
<string name="guide_already_allowed">Schon erlaubt.</string>
|
||||
<string name="guide_scroll_more">Scroll für mehr ↓</string>
|
||||
<string name="guide_allow">Genehmigung</string>
|
||||
<string name="guide_skip">Jetzt überspringen</string>
|
||||
<string name="guide_open">Öffnen</string>
|
||||
<string name="guide_next">Nächste</string>
|
||||
<string name="guide_done">KAPITEL</string>
|
||||
<string name="guide_back">Zurück</string>
|
||||
<string name="usage_intro">Schalten Sie das ein, und Gearslip sendet eine kurze Notiz, wenn Sie es öffnen und nach jeder Fahrt. <b>Die Notiz enthält keinen Namen, kein Konto und keinen Standort. Eine Zufallszahl zählt Ihr Telefon einmal, und der Server speichert keine IP-Adresse.</b> Die Notizen zeigen, welche Autos funktionieren und wo die anderen scheitern.</string>
|
||||
<string name="usage_send">Nutzungshinweise senden</string>
|
||||
<string name="usage_also_share">Auch teilen</string>
|
||||
<string name="usage_share_android">Android-Version und System</string>
|
||||
<string name="usage_share_phone">Telefon Modell</string>
|
||||
<string name="usage_share_cars">Jedes Auto und ob es verbunden</string>
|
||||
<string name="usage_share_drives">Bei jeder Fahrt und wie es endete</string>
|
||||
<string name="usage_what_sent">Was wird gesendet</string>
|
||||
<string name="usage_what_sent_sub">Jedes Feld, genau wie es herauskommt</string>
|
||||
<string name="usage_hide_note">Verstecken der Notiz</string>
|
||||
<string name="usage_show_note">Die Notiz anzeigen</string>
|
||||
<string name="usage_example">Beispiel, nicht gesendet: eine Fahrt fügt dies zu \"Sitzungen\"</string>
|
||||
<string name="usage_no_server">Dieser Build hat keinen Statistikserver, so dass er nichts sendet</string>
|
||||
<string name="usage_debug_build">Debug-Build: Notizen werden getrennt gehalten und nie in der Statistik gezählt</string>
|
||||
<string name="worked_title">Gearslip arbeitete mit Ihrem %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip arbeitete mit Ihrem Auto</string>
|
||||
<string name="worked_body">Teilen Sie das, damit der nächste Fahrer weiß, dass es funktioniert? Kein Name, Konto oder Standort.</string>
|
||||
<string name="worked_not_now">Nicht jetzt</string>
|
||||
<string name="worked_share">Anteil</string>
|
||||
<string name="recommended_intro">Diese kostenlosen Apps laufen auf Gearslips Autobildschirm. Jede Schaltfläche öffnet die Seite der App auf GitHub.</string>
|
||||
<string name="recommended_title">Empfohlene Apps</string>
|
||||
<string name="back">Zurück</string>
|
||||
<string name="recommended_installed">Installiert</string>
|
||||
<string name="recommended_maps">Karten</string>
|
||||
<string name="recommended_music">Musik</string>
|
||||
<string name="recommended_podcasts">Podcasts</string>
|
||||
<string name="recommended_other">Andere coole Apps</string>
|
||||
<string name="recommended_about_organic">Offline Karten und Routing.</string>
|
||||
<string name="recommended_about_vela">Drehen Sie die Navigation ohne Google.</string>
|
||||
<string name="recommended_about_ytm">Ein YouTube-Musiker.</string>
|
||||
<string name="recommended_about_antennapod">Podcasts, Kapitel und alles.</string>
|
||||
<string name="recommended_about_rfsentinel">Findet Tracker und Kameras in der Nähe.</string>
|
||||
<string name="settings_title">Einstellungen</string>
|
||||
<string name="settings_whats_new">Was ist neu</string>
|
||||
<string name="settings_whats_new_sub">Lesen Sie die %1$s Release Notes auf GitHub</string>
|
||||
<string name="settings_support">Unterstützung Getriebe</string>
|
||||
<string name="settings_support_footer">Gearslip ist frei, ohne Anzeigen und kein Tracking. Deine Unterstützung hält sie aufrecht.</string>
|
||||
<string name="settings_usage_notes">Gebrauchshinweise</string>
|
||||
<string name="settings_updates">Aktualisierung</string>
|
||||
<string name="settings_about">Über uns</string>
|
||||
<string name="settings_replay">Replay der Setupanleitung</string>
|
||||
<string name="settings_replay_sub">Durch die ersten Schritte wieder gehen</string>
|
||||
<string name="settings_connection">Verbindung</string>
|
||||
<string name="settings_use_in_car">Verwenden Sie Gearslip im Auto</string>
|
||||
<string name="settings_use_in_car_on">Gearslip antwortet, wenn Sie in ein Auto stecken</string>
|
||||
<string name="settings_use_in_car_off">Off: Android Auto kann die Verbindung statt nehmen</string>
|
||||
<string name="settings_apps">Apps</string>
|
||||
<string name="settings_certificate">Bescheinigung</string>
|
||||
<string name="cert_next_connection">Änderungen wirken auf die nächste Verbindung.</string>
|
||||
<string name="cert_checking">Sieh..</string>
|
||||
<string name="cert_valid_until">Gültigkeit bis %1$s</string>
|
||||
<string name="cert_hide_details">Details verbergen</string>
|
||||
<string name="cert_show_details">Details anzeigen</string>
|
||||
<string name="cert_subject">Gegenstand</string>
|
||||
<string name="cert_issuer">Emittent</string>
|
||||
<string name="cert_downloading">Download..</string>
|
||||
<string name="cert_download">Download von aasdk</string>
|
||||
<string name="cert_downloaded">Zertifikat heruntergeladen.</string>
|
||||
<string name="cert_download_failed">Download versagt: %1$s</string>
|
||||
<string name="cert_download_sub">Das Zertifikat des Open-Source-Aasdk-Projekts veröffentlicht auf GitHub. Die meisten Autos akzeptieren es, aber einige von etwa 2020, um es abzulehnen</string>
|
||||
<string name="cert_import">Importieren Sie meine eigene Datei</string>
|
||||
<string name="cert_import_sub">Eine .p12-Datei mit ihrem privaten Schlüssel. Nutzen Sie Ihre eigenen, wenn Ihr Auto das aasdk-Zertifikat ablehnt</string>
|
||||
<string name="cert_remove_imported">Importiertes Zertifikat entfernen</string>
|
||||
<string name="cert_remove_downloaded">Entfernen Sie das heruntergeladene Zertifikat</string>
|
||||
<string name="cert_removed_imported">Das importierte Zertifikat wurde entfernt.</string>
|
||||
<string name="cert_removed_downloaded">Das heruntergeladene Zertifikat wurde entfernt.</string>
|
||||
<string name="cert_imported">Zertifikat importiert.</string>
|
||||
<string name="cert_import_failed">Konnte nicht importieren: falsches Passwort oder keine PKCS#12-Datei mit einem privaten Schlüssel.</string>
|
||||
<string name="cert_password_title">Zertifikat Passwort</string>
|
||||
<string name="cert_password">Passwort vergessen</string>
|
||||
<string name="cert_password_hint">Lassen Sie leer, wenn die Datei keine hat.</string>
|
||||
<string name="cert_import_button">Einfuhr</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="cert_none">Nicht beladen (selbstsigniert; Autos lehnen es ab)</string>
|
||||
<string name="cert_kind_imported">Einfuhr: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Download: %1$s</string>
|
||||
<string name="cert_kind_adb">Über adb: %1$s</string>
|
||||
<string name="cert_kind_self">Selbstsigniert: %1$s</string>
|
||||
<string name="car_group_screen">Bildschirm</string>
|
||||
<string name="car_group_nav_bar">Navigationsleiste und Player</string>
|
||||
<string name="car_group_sound">Klang und Stimme</string>
|
||||
<string name="car_group_warnings">Warnungen</string>
|
||||
<string name="car_group_more">Mehr</string>
|
||||
<string name="choice_size_normal">Normal</string>
|
||||
<string name="choice_size_large">Groß</string>
|
||||
<string name="choice_size_xlarge">Extra groß</string>
|
||||
<string name="choice_theme_phone">Wie das Handy</string>
|
||||
<string name="choice_theme_light">Licht</string>
|
||||
<string name="choice_theme_dark">Dunkel</string>
|
||||
<string name="choice_dark_black">Schwarz</string>
|
||||
<string name="choice_dark_black_colour">Schwarz und Farbe</string>
|
||||
<string name="choice_dark_deep_colour">Tiefe Farbe</string>
|
||||
<string name="choice_dark_grey">Grau</string>
|
||||
<string name="choice_auto">Auto</string>
|
||||
<string name="choice_night_day">Tag</string>
|
||||
<string name="choice_night_night">Nacht</string>
|
||||
<string name="choice_units_imperial">Kaiser</string>
|
||||
<string name="choice_units_metric">Metric</string>
|
||||
<string name="choice_nav_show">Immer anzeigen</string>
|
||||
<string name="choice_nav_hide_navigating">Verstecken beim Navigieren</string>
|
||||
<string name="choice_nav_hide">Verstecken bis gezapft</string>
|
||||
<string name="choice_open_home">Home</string>
|
||||
<string name="summary_player_left">Player links</string>
|
||||
<string name="summary_player_right">Player rechts</string>
|
||||
<string name="summary_usb_audio">Audio über Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Bluetooth Audio</string>
|
||||
<string name="summary_directions_on">Anfahrt</string>
|
||||
<string name="summary_directions_off">Ansagen aus</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth an</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth ab</string>
|
||||
<string name="summary_location_on">Standort an</string>
|
||||
<string name="summary_location_off">Standort</string>
|
||||
<string name="web_page">Seite</string>
|
||||
<string name="summary_experimental_shown">Experimentelle Merkmale gezeigt</string>
|
||||
<string name="summary_experimental_hidden">Experimentelle Features versteckt</string>
|
||||
<string name="home_car">Auto</string>
|
||||
<string name="car_group_same">Dies sind die gleichen Einstellungen wie Einstellungen im Auto. Auch hier zeigt sich eine Veränderung.</string>
|
||||
<string name="car_open_on_connect">Anschließen öffnen</string>
|
||||
<string name="car_size">Größe</string>
|
||||
<string name="car_app_theme">App Thema</string>
|
||||
<string name="car_dark_mode">Dark Mode</string>
|
||||
<string name="car_buttons">Knopf</string>
|
||||
<string name="car_map_day_night">Kartentag/Nacht</string>
|
||||
<string name="car_units">Einheiten</string>
|
||||
<string name="car_nav_bar">Nav bar</string>
|
||||
<string name="car_player_nav_bar">Player in der Navigationsleiste</string>
|
||||
<string name="car_skip_buttons">Überspringen von Tasten</string>
|
||||
<string name="car_song_title">Songtitel</string>
|
||||
<string name="car_player_beside_map">Player neben der Karte</string>
|
||||
<string name="car_right">Richtig</string>
|
||||
<string name="car_left">Links</string>
|
||||
<string name="car_autoplay">Autoplay auf Verbindung</string>
|
||||
<string name="car_more_lyrics">Mehr Textquellen</string>
|
||||
<string name="car_more_lyrics_note">Auch fragt BetterLyrics und KuGou, die dann den Namen und Künstler des Liedes bekommen. Off fragt nur LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Gesprochene Richtungen</string>
|
||||
<string name="car_spoken_directions_note">Schalten Sie es aus, wenn die eigene Stimme Ihrer Karten-App bereits im Auto spielt.</string>
|
||||
<string name="car_phone_speech">Verwenden Sie die Spracherkennung des Telefons</string>
|
||||
<string name="car_phone_speech_note">Auf den meisten Telefonen, das ist Google, die Ihre Stimme an Google senden kann.</string>
|
||||
<string name="car_warn_bluetooth">Warnung, wenn Bluetooth nicht angeschlossen ist</string>
|
||||
<string name="car_warn_location">Warnung, wenn die Lage aus ist</string>
|
||||
<string name="car_experimental">Experimentelle Merkmale</string>
|
||||
<string name="car_vehicle_data">Fahrzeugdaten</string>
|
||||
<string name="car_vehicle_data_note">Fügt eine Fahrzeugdatenfliese hinzu: eine Live-Dumpe der Autosensoren. Die Feldnamen sind Vermutungen, bis Sie eine Änderung beobachtet haben.</string>
|
||||
<string name="car_adaptive_video">Adaptive Videoqualität</string>
|
||||
<string name="car_adaptive_video_note">Erniedrigt die Qualität des Bildes, wenn das Auto oder sein WLAN nicht aufrecht erhalten kann, und erhöht es wieder, sobald es kann. Gilt für die nächste Verbindung.</string>
|
||||
<string name="web_page_note">Gezeigt von der Browser-App im Auto. Leere öffnet DuckDuckGo. Gilt für die nächste Verbindung.</string>
|
||||
<string name="car_audio">Auto Audio</string>
|
||||
<string name="car_audio_bt">Bluetooth (empfohlen)</string>
|
||||
<string name="car_audio_bt_sub">Keine Aufforderung bei jeder Fahrt, und die Stimme Ihrer Karte App erreicht das Auto</string>
|
||||
<string name="car_audio_usb_sub">Klarer Sound ohne Verzögerung. Bei jeder Fahrt, Android bittet Sie, Ihren Bildschirm zu teilen, die Gearslip senden den Sound</string>
|
||||
<string name="car_audio_usb_footer">Führen Sie den Befehl aus der Hilfe einmal aus, dann fragt Android nicht mehr.</string>
|
||||
<string name="car_audio_bt_footer">Anrufe gehen über Bluetooth auch.</string>
|
||||
<string name="screen_car_size">Größe des Bildschirms</string>
|
||||
<string name="screen_use_cars">Das Auto benutzen</string>
|
||||
<string name="screen_preview_size">Größe der Vorschau</string>
|
||||
<string name="screen_preview_note">Die Größe der Auto-Vorschau zieht an, wie 1920x720.</string>
|
||||
<string name="screen_car_screen">Auto Bildschirm</string>
|
||||
<string name="screen_footer">Gearslip verwendet die Größe der Autoberichte, es sei denn, Sie setzen eine.</string>
|
||||
<string name="screen_size">Bildschirmgröße</string>
|
||||
<string name="screen_from_car">Aus dem Auto</string>
|
||||
<string name="screen_crop">Schneiden Sie die Kanten</string>
|
||||
<string name="screen_crop_sub">Für ein Auto, das oben und unten abschneidet, oder die Seiten</string>
|
||||
<string name="screen_squash">Squash zum Passen</string>
|
||||
<string name="screen_squash_sub">Für ein Auto, das das ganze Bild drückt, also Kreise aussehen wie Ovale</string>
|
||||
<string name="screen_preview_title">Auto Vorschau Größe</string>
|
||||
<string name="screen_last_car">Letztes Auto</string>
|
||||
<string name="screen_last_car_sub">Die Größe des letzten Autos, mit dem Sie verbunden sind</string>
|
||||
<string name="screen_this_screen">Dieser Bildschirm</string>
|
||||
<string name="screen_this_screen_sub">Füllt den Bildschirm dieses Geräts, für eine Kopfeinheit, die Gearslip selbst läuft</string>
|
||||
<string name="screen_custom">Zoll</string>
|
||||
<string name="screen_enter_size">Geben Sie eine Größe ein</string>
|
||||
<string name="screen_change_custom">Ändern Sie die benutzerdefinierte Größe</string>
|
||||
<string name="screen_width">Breite</string>
|
||||
<string name="screen_height">Höhe</string>
|
||||
<string name="save">Speichern</string>
|
||||
<string name="screen_car_size_note">Verwenden Sie dies, wenn das Bild des Autos gedehnt aussieht. Betreten Sie die reale Größe des Bildschirms, wie 1280x480. Es wird das nächste Mal wirksam, wenn Sie verbinden.</string>
|
||||
<string name="screen_set_by_you">%1$s, von Ihnen eingestellt</string>
|
||||
<string name="help_title">Wie zu verbinden</string>
|
||||
<string name="help_which_cars">Welche Autos arbeiten</string>
|
||||
<string name="help_full_list">Vollständige Liste ansehen</string>
|
||||
<string name="help_first_drive">Vor der ersten Fahrt</string>
|
||||
<string name="help_in_car">Im Auto</string>
|
||||
<string name="help_usb_audio">Audio über Gearslip</string>
|
||||
<string name="help_aa_off">Schalten Sie Android Auto aus.</string>
|
||||
<string name="help_aa_off_detail">Deaktivieren oder deinstallieren Sie die Android Auto-App, so spricht das Auto mit Gearslip statt.</string>
|
||||
<string name="help_cert">Geben Sie ein Zertifikat.</string>
|
||||
<string name="help_cert_detail">In Einstellungen, laden Sie ein oder importieren Sie Ihre eigenen.</string>
|
||||
<string name="help_mic">Lassen Sie das Mikrofon.</string>
|
||||
<string name="help_mic_detail">Android lässt keine App ohne es Musik an das Auto weitergeben. Gearslip hört nur auf, wenn Sie das Mikrofon tippen oder die Sprechtaste auf Ihrem Rad drücken.</string>
|
||||
<string name="help_plug_in">Stecken oder drahtlos verbinden.</string>
|
||||
<string name="help_plug_in_detail">Gearslip öffnet sich von selbst, wenn das Auto verbindet. Für kabellos, koppeln Sie das Telefon mit dem Auto zuerst, dann tippen Sie drahtlos auf Home.</string>
|
||||
<string name="help_pair">Ein Paar über Bluetooth für Anrufe.</string>
|
||||
<string name="help_pair_detail">Anrufe spielen durch das Auto, sobald Ihr Telefon und das Auto gepaart sind. Gearslip bietet zum ersten Mal, wenn Sie einstecken.</string>
|
||||
<string name="help_answer">Anrufe vom Autobildschirm beantworten.</string>
|
||||
<string name="help_answer_detail">Setzen Sie Gearslip als Caller ID und Spam App. Ohne es, Anrufe immer noch Ring, aber nur das Telefon kann sie beantworten.</string>
|
||||
<string name="help_prompt">Stoppen Sie die Audio-Prompt bei jeder Fahrt.</string>
|
||||
<string name="help_prompt_detail">Schließen Sie das Telefon an einen Computer mit USB-Debugging an und führen Sie dieses einmal aus. Überspringen Sie es und tippen Sie auf Start jedes Mal, wenn Sie ausgeschaltet.</string>
|
||||
<string name="help_readable">Halten Sie Benachrichtigungen lesbar.</string>
|
||||
<string name="help_readable_detail">Android versteckt, was Benachrichtigungen sagen, während es Audio teilt. Führen Sie das auch, und Gearslip hebt das nur, wenn Musik zum Auto geht.</string>
|
||||
<string name="help_which_cars_body">Die meisten Autos arbeiten. Ab etwa 2020 haben einige Autos neue Sicherheitsstandards hinzugefügt, die Gearslips Zertifikat ablehnen, und Gearslip kann nicht in diesen Autos laufen.</string>
|
||||
<string name="update_downloaded_ask">Download. Android fragt, bevor es installiert.</string>
|
||||
<string name="update_install">Installieren</string>
|
||||
<string name="update_download_now">Laden Sie es jetzt herunter. Android fragt, bevor es installiert.</string>
|
||||
<string name="update_try_again">Versuchen Sie es wieder</string>
|
||||
<string name="update_update">Aktualisierung</string>
|
||||
<string name="update_checking">Sieh..</string>
|
||||
<string name="update_latest">Sie haben die neueste Version</string>
|
||||
<string name="update_download">Downloads</string>
|
||||
<string name="update_check_now">Jetzt überprüfen</string>
|
||||
<string name="update_check_auto">Prüfen Sie automatisch</string>
|
||||
<string name="update_once_a_day">Einmal pro Tag</string>
|
||||
<string name="update_unplug_first">Unplug aus dem Auto zu installieren. Installation schließt Gearslip.</string>
|
||||
<string name="update_card_title">Gearslip %1$s ist aus</string>
|
||||
<string name="update_downloading">Download... %1$d%%</string>
|
||||
<string name="update_debug_manual">Debug-Builds prüfen nur, wenn Sie auf „Jetzt überprüfen“ tippen</string>
|
||||
<string name="update_checks_daily">Gearslip überprüft einmal pro Tag</string>
|
||||
<string name="update_version_out">Version %1$s ist aus</string>
|
||||
<string name="update_version_ready">Version %1$s ist bereit zu installieren</string>
|
||||
<string name="update_dialog_status">%1$s. %2$s.</string>
|
||||
<string name="about_give_regularly">Wöchentlich oder monatlich</string>
|
||||
<string name="about_bmac_sub" formatted="false">Geben Sie einmal oder monatlich, per Karte. Kaufen Me ein Kaffee hält 5%</string>
|
||||
<string name="about_give_once">Geben Sie einmal</string>
|
||||
<string name="about_any_amount">Jede Menge, per Karte</string>
|
||||
<string name="about_monero_sub">Öffnen Sie Ihre Brieftasche oder kopieren Sie die Adresse</string>
|
||||
<string name="about_monero_title">Unterstützung mit Monero</string>
|
||||
<string name="about_monero_body">Senden Sie einen beliebigen Betrag an diese Adresse von Ihrem Monero Geldbeutel.</string>
|
||||
<string name="about_open_wallet">Geöffnet in Geldbörse</string>
|
||||
<string name="about_copy_address">Kopienadresse</string>
|
||||
<string name="close">Schließen</string>
|
||||
<string name="about_matrix">Dem Chat auf Matrix beitreten</string>
|
||||
<string name="about_matrix_sub">Sprechen Sie mit anderen Fahrern über Autos, Apps und Gearslip</string>
|
||||
<string name="about_translate">Gearslip übersetzen</string>
|
||||
<string name="about_translate_sub">Verbessern oder ergänzen Sie Ihre Sprache auf Weblate. Diese Übersetzung ist zum Teil maschinell entstanden.</string>
|
||||
<string name="about_source">Quellcode</string>
|
||||
<string name="about_source_sub">Gearslips Code unter der AGPL lesen oder ändern</string>
|
||||
<string name="about_voice_credits">Sprachgutschriften</string>
|
||||
<string name="about_no_wallet">Keine Monero Geldbörse App gefunden. Kopieren Sie stattdessen die Adresse.</string>
|
||||
<string name="about_monero_copied">Monero Adresse kopiert</string>
|
||||
<string name="about_voice_credits_sub">Spracherkennung von sherpa-onnx (Apache 2.0), mit Kroko ASR von Banafo (CC BY-SA 4.0) und Parakeet und Canary von NVIDIA (CC BY 4.0)</string>
|
||||
<string name="certsetup_title">Zertifikat Setup</string>
|
||||
<string name="certsetup_installed">Zertifikat installiert.</string>
|
||||
<string name="certsetup_continue">Fortsetzung</string>
|
||||
<string name="certsetup_failed">Fehlersuche herunterladen</string>
|
||||
<string name="certsetup_retry">Wiederkehr</string>
|
||||
<string name="certsetup_import_sub">Eine .p12-Datei mit ihrem privaten Schlüssel. Nutzen Sie Ihre eigenen, wenn Ihr Auto das Aasdk-Zertifikat ablehnt.</string>
|
||||
<string name="certsetup_skip">Automatischer Anschluss</string>
|
||||
<string name="certsetup_loaded">Autos akzeptieren nur Google-ausgestellte Zertifikate, und Sie haben eine geladen.</string>
|
||||
<string name="certsetup_choose">Autos akzeptieren nur Google-ausgestellte Zertifikate. Gearslip enthält keine, also wählen Sie, woher Sie kommen.</string>
|
||||
<string name="certsetup_aasdk_sub">Das Open-Source-Aasdk-Projekt veröffentlicht dieses Zertifikat bei github.com/opencardev/aasdk. Die meisten Autos akzeptieren es; einige ab etwa 2020 nicht.</string>
|
||||
<string name="storage_title">Gearslip Aufbewahrung</string>
|
||||
<string name="storage_measuring">Messen..</string>
|
||||
<string name="storage_lyrics">- Ja</string>
|
||||
<string name="storage_clear">Klar</string>
|
||||
<string name="storage_voice">Sprachmodelle</string>
|
||||
<string name="storage_remove">Entfernen</string>
|
||||
<string name="storage_logs">Alte Stämme</string>
|
||||
<string name="storage_other">Andere temporäre Dateien</string>
|
||||
<string name="storage_delete_all">Alle Gearslip-Daten löschen</string>
|
||||
<string name="storage_total">Die Daten von Gearslip nehmen %1$s der Speicherung des Telefons.</string>
|
||||
<string name="storage_voice_detail">%1$s. Sprachbefehle brauchen sie; laden Sie sie in den Einstellungen erneut herunter.</string>
|
||||
<string name="storage_logs_detail">%1$s. Protokolle früherer Starts; das Protokoll dieses Starts bleibt.</string>
|
||||
<string name="storage_other_detail">%1$s. Heruntergeladene Updates und andere Dateien, die Gearslip neu erstellen kann.</string>
|
||||
<string name="storage_delete_armed">Tippen Sie erneut auf Löschen, um Einstellungen, Zertifikate, Sprachmodelle und Protokolle zu löschen. Gearslip schließt und beginnt beim nächsten Mal vom Setup.</string>
|
||||
<string name="storage_delete_sub">Einstellungen, Zertifikate, Sprachmodelle, Texte und Protokolle.</string>
|
||||
<string name="storage_delete">Löschen</string>
|
||||
<string name="storage_delete_ellipsis">Löschen..</string>
|
||||
<string name="donate_title">Schön, dass der Antrieb funktioniert</string>
|
||||
<string name="donate_liberapay">Unterstützung für Liberapay</string>
|
||||
<string name="donate_bmac">Kaufen Sie mir einen Kaffee</string>
|
||||
<string name="donate_card">Einmal mit Karte</string>
|
||||
<string name="donate_body">Gearslip ist frei, ohne Anzeigen und kein Tracking. Wenn es dich auf die Straße bringt, hält es deine Unterstützung.</string>
|
||||
<string name="wireless_needs_permission">Dazu benötigt Gearslip Bluetooth und WLAN in der Nähe.</string>
|
||||
<string name="wireless_enable_permission">Enable Nearby Geräte Erlaubnis</string>
|
||||
<string name="wireless_connect">Drahtlos verbinden</string>
|
||||
<string name="wireless_which_car">Welches Auto?</string>
|
||||
<string name="wireless_none_paired">Ihr Telefon ist noch nicht mit etwas gepaart. Zuerst mit dem Auto.</string>
|
||||
<string name="wireless_not_listed">Auto nicht aufgeführt? Zuerst mit deinem Handy.</string>
|
||||
<string name="bluetooth_settings">Bluetooth-Einstellungen</string>
|
||||
<string name="voice_live_title">Sprache: live Wörter</string>
|
||||
<string name="voice_final_title">Stimme: letzte Worte</string>
|
||||
<string name="voice_none">Keine</string>
|
||||
<string name="voice_none_sub">Halten Sie die Live-Worte. Am schnellsten, am wenigsten genau</string>
|
||||
<string name="voice_live_body">Der Sprachassistent verwandelt die Rede in Text auf diesem Telefon, und nichts, was Sie sagen, lässt sie. Es braucht zwei Modelle, etwa 270 MB zusammen, einmal heruntergeladen. Dieser schreibt Ihre Worte, wie Sie sprechen.</string>
|
||||
<string name="voice_final_body">Wenn Sie aufhören zu reden, liest dieses Modell den ganzen Satz wieder. Es fängt Namen und stellt das Live-Modell Misses.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Downloaded. %1$s</string>
|
||||
<string name="voice_downloading">Download, %1$d%%</string>
|
||||
<string name="home_help">Hilfe</string>
|
||||
<string name="home_preview">Voransicht</string>
|
||||
<string name="home_logs">Logs</string>
|
||||
<string name="home_disconnect">Trennen</string>
|
||||
<string name="home_bt_title">Bluetooth ist nicht angeschlossen</string>
|
||||
<string name="home_debug_build">%1$s · Debug-Build</string>
|
||||
<string name="home_bt_body">Musik spielt über Bluetooth, aber Ihr Telefon ist nicht mit dem Auto verbunden. Schließen Sie es hier oder im Bluetooth-Menü des Autos an.</string>
|
||||
<string name="home_voice_downloading">Musik herunterladen, %1$d%%</string>
|
||||
<string name="audio_bt_title">Spielen Sie Auto-Audio über Bluetooth?</string>
|
||||
<string name="audio_never_ask">Nie fragen, mit einem Computer</string>
|
||||
<string name="audio_use_bt">Verwenden Sie Bluetooth</string>
|
||||
<string name="audio_keep_asking">Fragen Sie weiter</string>
|
||||
<string name="audio_mic_title">Auto Audio braucht die Mikrofonberechtigung</string>
|
||||
<string name="audio_open_settings">App-Einstellungen öffnen</string>
|
||||
<string name="audio_bt_body">Android behandelt Musik über USB gesendet wie teilen Sie Ihren Bildschirm, so fragt es bei jeder Fahrt. Über Bluetooth fragt es nie. Sie können dies später in Einstellungen ändern.</string>
|
||||
<string name="audio_keep_usb">Oder lassen Sie den Ton weiter über Gearslip laufen und beenden Sie die Frage für immer: Schließen Sie das Handy an einen Computer mit USB-Debugging an und führen Sie dies einmal aus.</string>
|
||||
<string name="audio_mic_body">Android lässt eine App den Sound anderer Apps nur an das Auto senden, wenn sie die Mikrofonberechtigung hat. Es ist für Gearslip ausgeschaltet, also spielt Musik am Telefon. Schalten Sie es unter Berechtigungen ein, dann Mikrofon.</string>
|
||||
<string name="typing_close">Schließen Sie die Autotastatur</string>
|
||||
<string name="typing_label">Typ für den Auto-Bildschirm</string>
|
||||
<string name="typing_search">Suche auf dem Auto-Bildschirm</string>
|
||||
<string name="command_tap_copy">Tippen Sie auf Kopieren</string>
|
||||
<string name="command_copied">Befehl kopiert</string>
|
||||
<string name="whats_new_this_version">%1$s (diese Version)</string>
|
||||
<string name="status_idle">Nicht verbunden</string>
|
||||
<string name="status_idle_detail">Schließen Sie das Handy an den USB-Anschluss des Autos an oder verbinden Sie sich kabellos. Gearslip öffnet sich von selbst, wenn das Auto antwortet.</string>
|
||||
<string name="status_connecting">Verbinden</string>
|
||||
<string name="status_projecting">Verbunden</string>
|
||||
<string name="status_projecting_detail">Das Bild läuft auf dem Bildschirm des Autos.</string>
|
||||
<string name="status_disconnected">Getrennt</string>
|
||||
<string name="status_disconnected_detail">Das Auto ist nicht mehr verbunden.</string>
|
||||
<string name="places_title">Gespeicherte Orte</string>
|
||||
<string name="places_sub">Sag „navigate home“ in jeder Karten-App</string>
|
||||
<string name="places_intro">Sag „navigate home“, „take me to work“ oder „navigate to“ mit einem eigenen Namen, und Gearslip schickt die Adresse an deine Karten-App. Sprachbefehle gibt es bisher nur auf Englisch.</string>
|
||||
<string name="places_home">Zuhause</string>
|
||||
<string name="places_work">Arbeit</string>
|
||||
<string name="places_other">Ort %1$d</string>
|
||||
<string name="places_name">Name</string>
|
||||
<string name="places_address">Adresse</string>
|
||||
<string name="places_here">Aktuellen Standort nehmen</string>
|
||||
<string name="places_finding">Suche Standort…</string>
|
||||
<string name="places_not_found">Gearslip konnte deinen Standort nicht finden. Ist die Standortermittlung an?</string>
|
||||
<string name="places_clear">Löschen</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,710 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="recommended_maps">Mapas</string>
|
||||
<string name="recommended_music">Música</string>
|
||||
<string name="recommended_podcasts">Podcasts</string>
|
||||
<string name="recommended_other">Otras aplicaciones cool</string>
|
||||
<string name="recommended_about_organic">Sin conexión mapas y rutas.</string>
|
||||
<string name="recommended_about_vela">Navegación de vuelta por vuelta sin Google.</string>
|
||||
<string name="recommended_about_ytm">Un reproductor de música de YouTube.</string>
|
||||
<string name="recommended_about_antennapod">Podcasts, capítulos y todo.</string>
|
||||
<string name="recommended_about_rfsentinel">Encuentra rastreadores y cámaras cerca.</string>
|
||||
<string name="settings_title">Ajustes</string>
|
||||
<string name="settings_whats_new">Lo nuevo</string>
|
||||
<string name="settings_whats_new_sub">Lea las notas de la versión %1$s en GitHub</string>
|
||||
<string name="settings_support">Soporte Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip es gratis, sin anuncios y sin seguimiento. Tu apoyo lo mantiene.</string>
|
||||
<string name="settings_usage_notes">Notas de uso</string>
|
||||
<string name="settings_updates">Actualizaciones</string>
|
||||
<string name="settings_about">Acerca de</string>
|
||||
<string name="settings_replay">Replay la guía de configuración</string>
|
||||
<string name="settings_replay_sub">Ir a través de los primeros pasos de nuevo</string>
|
||||
<string name="settings_connection">Conexión</string>
|
||||
<string name="settings_use_in_car">Use Gearslip en el coche</string>
|
||||
<string name="settings_use_in_car_on">Gearslip responde cuando se conecta a un coche</string>
|
||||
<string name="settings_use_in_car_off">Off: Android Auto puede tomar la conexión en su lugar</string>
|
||||
<string name="settings_apps">Aplicaciones</string>
|
||||
<string name="settings_certificate">Certificado</string>
|
||||
<string name="cert_next_connection">Los cambios tienen efecto en la próxima conexión.</string>
|
||||
<string name="cert_checking">Comprobando..</string>
|
||||
<string name="cert_valid_until">Válido hasta %1$s</string>
|
||||
<string name="cert_hide_details">Ocultar detalles</string>
|
||||
<string name="cert_show_details">Mostrar detalles</string>
|
||||
<string name="cert_subject">Asunto</string>
|
||||
<string name="cert_issuer">Periodista</string>
|
||||
<string name="cert_downloading">Descargando..</string>
|
||||
<string name="cert_download">Descarga desde aasdk</string>
|
||||
<string name="cert_downloaded">Certificado descargado.</string>
|
||||
<string name="cert_download_failed">Descarga fallido: %1$s</string>
|
||||
<string name="cert_download_sub">El certificado que publica el proyecto de código abierto en GitHub. Gearslip no incluye uno</string>
|
||||
<string name="cert_import">Importar mi propio archivo</string>
|
||||
<string name="cert_import_sub">Un archivo .p12 con su llave privada. Utilice su propio cuando su coche rechaza el certificado de aasdk</string>
|
||||
<string name="cert_remove_imported">Eliminar el certificado importado</string>
|
||||
<string name="cert_remove_downloaded">Eliminar el certificado descargado</string>
|
||||
<string name="cert_removed_imported">Quitó el certificado importado.</string>
|
||||
<string name="cert_removed_downloaded">Quitó el certificado descargado.</string>
|
||||
<string name="cert_imported">Certificado importado.</string>
|
||||
<string name="cert_import_failed">No podía importar: contraseña incorrecta, o no un archivo PKCS#12 con una clave privada.</string>
|
||||
<string name="cert_password_title">Contraseña de certificado</string>
|
||||
<string name="cert_password">Contraseña</string>
|
||||
<string name="cert_password_hint">Deja vacío si el archivo no tiene ninguno.</string>
|
||||
<string name="cert_import_button">Importación</string>
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="cert_none">Ninguna cargada (autofirmada; autos lo rechazan)</string>
|
||||
<string name="cert_kind_imported">Importado: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Descargado: %1$s</string>
|
||||
<string name="cert_kind_adb">Staged over adb: %1$s</string>
|
||||
<string name="cert_kind_self">Autor-signed: %1$s</string>
|
||||
<string name="car_group_screen">Pantalla</string>
|
||||
<string name="car_group_nav_bar">Barra de navegación y reproductor</string>
|
||||
<string name="car_group_sound">Sonido y voz</string>
|
||||
<string name="car_group_warnings">Advertencias</string>
|
||||
<string name="car_group_more">Más</string>
|
||||
<string name="choice_size_normal">Normal</string>
|
||||
<string name="choice_size_large">Grande</string>
|
||||
<string name="choice_size_xlarge">Extragrande</string>
|
||||
<string name="choice_theme_phone">Siga el teléfono</string>
|
||||
<string name="choice_theme_light">Luz</string>
|
||||
<string name="choice_theme_dark">Oscuro</string>
|
||||
<string name="choice_dark_black">Negro</string>
|
||||
<string name="choice_dark_black_colour">Negro y color</string>
|
||||
<string name="choice_dark_deep_colour">Color profundo</string>
|
||||
<string name="choice_dark_grey">Grey</string>
|
||||
<string name="choice_auto">Auto</string>
|
||||
<string name="choice_night_day">Día</string>
|
||||
<string name="choice_night_night">Buenas noches</string>
|
||||
<string name="choice_units_imperial">Imperial</string>
|
||||
<string name="choice_units_metric">Métrica</string>
|
||||
<string name="choice_nav_show">Mostrar siempre</string>
|
||||
<string name="choice_nav_hide_navigating">Esconder mientras navega</string>
|
||||
<string name="choice_nav_hide">Escóndete hasta que te toquen</string>
|
||||
<string name="choice_open_home">Home</string>
|
||||
<string name="summary_player_left">Reproductor a la izquierda</string>
|
||||
<string name="summary_player_right">Reproductor a la derecha</string>
|
||||
<string name="summary_usb_audio">Audio a través de Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Audio Bluetooth</string>
|
||||
<string name="summary_directions_on">Instrucciones sobre</string>
|
||||
<string name="summary_directions_off">Instrucciones apagadas</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth activado</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth apagado</string>
|
||||
<string name="summary_location_on">Ubicación</string>
|
||||
<string name="summary_location_off">Ubicación fuera</string>
|
||||
<string name="web_page">Página</string>
|
||||
<string name="summary_experimental_shown">Características experimentales mostradas</string>
|
||||
<string name="summary_experimental_hidden">Características experimentales ocultas</string>
|
||||
<string name="home_car">Coche</string>
|
||||
<string name="car_group_same">Estas son las mismas configuraciones que Ajustes en el coche. Un cambio aquí también muestra allí.</string>
|
||||
<string name="car_open_on_connect">Abra la conexión</string>
|
||||
<string name="car_size">Tamaño</string>
|
||||
<string name="car_app_theme">Tema de aplicación</string>
|
||||
<string name="car_dark_mode">Modo oscuro</string>
|
||||
<string name="car_buttons">Botones</string>
|
||||
<string name="car_map_day_night">Mapa día/noche</string>
|
||||
<string name="car_units">Unidades</string>
|
||||
<string name="car_nav_bar">Nav bar</string>
|
||||
<string name="car_player_nav_bar">Reproductor en la barra de navegación</string>
|
||||
<string name="car_skip_buttons">Omita los botones</string>
|
||||
<string name="car_song_title">Título de canción</string>
|
||||
<string name="car_player_beside_map">Player junto al mapa</string>
|
||||
<string name="car_right">Bien</string>
|
||||
<string name="car_left">Izquierda</string>
|
||||
<string name="car_autoplay">Autoplay en conexión</string>
|
||||
<string name="car_more_lyrics">Más fuentes de letras</string>
|
||||
<string name="car_more_lyrics_note">También pregunta BetterLyrics y KuGou, que luego consigue el nombre y artista de la canción. Fuera pide LRCLIB sólo.</string>
|
||||
<string name="car_spoken_directions">Instrucciones descritas</string>
|
||||
<string name="car_spoken_directions_note">Apágalo si la propia voz de su aplicación de mapa ya juega en el coche.</string>
|
||||
<string name="car_phone_speech">Use el reconocimiento del discurso del teléfono</string>
|
||||
<string name="car_phone_speech_note">En la mayoría de los teléfonos que son de Google, que puede enviar su voz a Google.</string>
|
||||
<string name="car_warn_bluetooth">Advertido cuando Bluetooth no está conectado</string>
|
||||
<string name="car_warn_location">Aviso cuando la ubicación está apagada</string>
|
||||
<string name="car_experimental">Características experimentales</string>
|
||||
<string name="car_vehicle_data">Datos sobre vehículos</string>
|
||||
<string name="car_vehicle_data_note">Añade una ficha de datos del vehículo: un vertedero en vivo de los sensores del coche. Los nombres de campo son adivinanzas hasta que hayas visto un cambio.</string>
|
||||
<string name="car_adaptive_video">Calidad de vídeo adaptativa</string>
|
||||
<string name="car_adaptive_video_note">Reduce la calidad de la imagen cuando el coche o su Wi-Fi no pueden mantenerse, y la levanta de nuevo una vez que pueda. Se aplica en la siguiente conexión.</string>
|
||||
<string name="web_page_note">Mostrada por la aplicación Browser en el coche. Empty abre DuckDuckGo. Se aplica en la siguiente conexión.</string>
|
||||
<string name="car_audio">Audio de coches</string>
|
||||
<string name="car_audio_bt">Bluetooth (recomendado)</string>
|
||||
<string name="car_audio_bt_sub">Sin aviso cada viaje, y la voz de su aplicación del mapa llega al coche</string>
|
||||
<string name="car_audio_usb_sub">Sonido más claro sin demora. Cada viaje, Android le pide que comparta su pantalla, que permite que Gearslip envíe el sonido</string>
|
||||
<string name="car_audio_usb_footer">Ejecuta una vez el comando de Ayuda y Android dejará de preguntar.</string>
|
||||
<string name="car_audio_bt_footer">Las llamadas pasan por Bluetooth de cualquier manera.</string>
|
||||
<string name="screen_car_size">Tamaño de la pantalla del coche</string>
|
||||
<string name="screen_use_cars">Usa el coche</string>
|
||||
<string name="screen_preview_size">Tamaño de vista previa</string>
|
||||
<string name="screen_preview_note">El tamaño de la vista previa del coche dibuja, como 1920x720.</string>
|
||||
<string name="screen_car_screen">Pantalla de coches</string>
|
||||
<string name="screen_footer">Gearslip utiliza el tamaño que el coche reporta a menos que establezca uno.</string>
|
||||
<string name="screen_size">Tamaño de la pantalla</string>
|
||||
<string name="screen_from_car">Desde el coche</string>
|
||||
<string name="screen_crop">Culpar los bordes</string>
|
||||
<string name="screen_crop_sub">Para un coche que corta la parte superior e inferior, o los lados</string>
|
||||
<string name="screen_squash">Squash para adaptarse</string>
|
||||
<string name="screen_squash_sub">Para un coche que exprime toda la imagen, así que los círculos parecen ovalados</string>
|
||||
<string name="screen_preview_title">Tamaño de la vista previa del coche</string>
|
||||
<string name="screen_last_car">Último coche</string>
|
||||
<string name="screen_last_car_sub">El tamaño del último coche al que conectas</string>
|
||||
<string name="screen_this_screen">Esta pantalla</string>
|
||||
<string name="screen_this_screen_sub">Rellena la pantalla de este dispositivo, para una unidad principal ejecutando Gearslip en sí</string>
|
||||
<string name="screen_custom">Aduanas</string>
|
||||
<string name="screen_enter_size">Introduzca un tamaño</string>
|
||||
<string name="screen_change_custom">Cambiar el tamaño personalizado</string>
|
||||
<string name="screen_width">Width</string>
|
||||
<string name="screen_height">Altura</string>
|
||||
<string name="save">Guardar</string>
|
||||
<string name="screen_car_size_note">Usa esto si la imagen del coche parece estirada. Introduzca el tamaño real de la pantalla, como 1280x480. Toma efecto la próxima vez que se conecta.</string>
|
||||
<string name="screen_set_by_you">%1$s, preparado por usted</string>
|
||||
<string name="help_title">Cómo conectarse</string>
|
||||
<string name="help_which_cars">Que coches funcionan</string>
|
||||
<string name="help_full_list">Ver la lista completa</string>
|
||||
<string name="help_first_drive">Antes de su primer viaje</string>
|
||||
<string name="help_in_car">En el coche</string>
|
||||
<string name="help_usb_audio">Audio a través de Gearslip</string>
|
||||
<string name="help_aa_off">Apaga Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Desactivar o desinstalar la aplicación Android Auto, por lo que el coche habla con Gearslip en su lugar.</string>
|
||||
<string name="help_cert">Carguen un certificado.</string>
|
||||
<string name="help_cert_detail">En Ajustes, descargar uno o importar el suyo.</string>
|
||||
<string name="help_mic">Permite el micrófono.</string>
|
||||
<string name="help_mic_detail">Android no permitirá que una aplicación pase música al coche sin ella. Gearslip solo escucha cuando pulsa el micrófono o pulsa el botón de conversación en la rueda.</string>
|
||||
<string name="help_plug_in">Enchufe, o conecte inalámbricamente.</string>
|
||||
<string name="help_plug_in_detail">Gearslip se abre por sí mismo cuando el coche se conecta. Para inalámbrico, empareja el teléfono con el coche primero, luego pulsa Conectar inalámbricamente en Inicio.</string>
|
||||
<string name="help_pair">Pare por Bluetooth para llamadas.</string>
|
||||
<string name="help_pair_detail">Las llamadas juegan a través del coche una vez que el teléfono y el coche están emparejados. Gearslip ofrece para emparejar la primera vez que se conecta.</string>
|
||||
<string name="help_answer">Contesta las llamadas desde la pantalla del coche.</string>
|
||||
<string name="help_answer_detail">Establecer Gearslip como su ID de llamada y aplicación de spam. Sin ella, las llamadas siguen sonando, pero solo el teléfono puede responderlas.</string>
|
||||
<string name="help_prompt">Detenga el impulso de audio cada viaje.</string>
|
||||
<string name="help_prompt_detail">Conecta el teléfono a un ordenador con USB depurando y ejecuta esto una vez. Skip it and you tap Start each time you set off.</string>
|
||||
<string name="help_readable">Mantenga las notificaciones legibles.</string>
|
||||
<string name="help_readable_detail">Android oculta lo que las notificaciones dicen mientras comparte audio. Ejecute esto también, y Gearslip levanta eso sólo mientras la música va al coche.</string>
|
||||
<string name="help_which_cars_body">La mayoría de los coches funcionan. A partir de 2020, algunos coches agregaron nuevos estándares de seguridad que rechazan el certificado de Gearslip, y Gearslip no puede correr en esos coches.</string>
|
||||
<string name="update_downloaded_ask">Descargado. Android pregunta antes de instalar.</string>
|
||||
<string name="update_install">Instala</string>
|
||||
<string name="update_download_now">Descárguelo ahora. Android pregunta antes de instalar.</string>
|
||||
<string name="update_try_again">Inténtalo de nuevo</string>
|
||||
<string name="update_update">Actualización</string>
|
||||
<string name="update_checking">Comprobando..</string>
|
||||
<string name="update_latest">Tienes la última versión</string>
|
||||
<string name="update_download">Descargar</string>
|
||||
<string name="update_check_now">Buscar ahora</string>
|
||||
<string name="update_check_auto">Compruebe automáticamente</string>
|
||||
<string name="update_once_a_day">Una vez al día</string>
|
||||
<string name="update_unplug_first">Desplug del coche para instalar. Instalar cierra Gearslip.</string>
|
||||
<string name="update_card_title">Gearslip %1$s está fuera</string>
|
||||
<string name="update_downloading">Descargar... %1$d%%</string>
|
||||
<string name="update_debug_manual">Las versiones de depuración solo buscan cuando tocas Buscar ahora</string>
|
||||
<string name="update_checks_daily">Controles de Gearslip una vez al día</string>
|
||||
<string name="update_version_out">La versión %1$s está fuera</string>
|
||||
<string name="update_version_ready">Versión %1$s está listo para instalar</string>
|
||||
<string name="update_dialog_status">Versión %1$s. %2$s.</string>
|
||||
<string name="about_give_regularly">Dar semanal o mensual</string>
|
||||
<string name="about_bmac_sub" formatted="false">Dar una vez o mes, por tarjeta. Comprarme un café mantiene 5%</string>
|
||||
<string name="about_give_once">Dale una vez</string>
|
||||
<string name="about_any_amount">Cualquier cantidad, por tarjeta</string>
|
||||
<string name="about_monero_sub">Abra su cartera o copie la dirección</string>
|
||||
<string name="about_monero_title">Soporte con Monero</string>
|
||||
<string name="about_monero_body">Envía cualquier cantidad a esta dirección de tu billetera Monero.</string>
|
||||
<string name="about_open_wallet">Abre la cartera</string>
|
||||
<string name="about_copy_address">Dirección de copia</string>
|
||||
<string name="close">Cerca</string>
|
||||
<string name="about_matrix">Únete al chat de Matrix</string>
|
||||
<string name="about_matrix_sub">Habla con otros conductores sobre coches, aplicaciones y Gearslip</string>
|
||||
<string name="about_translate">Ayuda a traducir Gearslip</string>
|
||||
<string name="about_translate_sub">Corrige o añade tu idioma en Weblate. Parte de esta traducción se hizo de forma automática.</string>
|
||||
<string name="about_source">Código fuente</string>
|
||||
<string name="about_source_sub">Lea o cambie el código de Gearslip bajo la AGPL</string>
|
||||
<string name="about_voice_credits">Créditos de voz</string>
|
||||
<string name="about_no_wallet">No hay aplicación Monero billetera encontrada. Copia la dirección en su lugar.</string>
|
||||
<string name="about_monero_copied">Dirección Monero copiada</string>
|
||||
<string name="about_voice_credits_sub">Reconocimiento del discurso por sherpa-onnx (Apache 2.0), con Kroko ASR por Banafo (CC BY-SA 4.0), y Parakeet y canario por NVIDIA (CC BY 4.0)</string>
|
||||
<string name="certsetup_title">Configuración de certificados</string>
|
||||
<string name="certsetup_installed">Certificado instalado.</string>
|
||||
<string name="certsetup_continue">Continuar</string>
|
||||
<string name="certsetup_failed">Descarga fallido</string>
|
||||
<string name="certsetup_retry">Retry</string>
|
||||
<string name="certsetup_import_sub">Un archivo .p12 con su llave privada. Use el suyo cuando su coche rechaza el certificado de aasdk.</string>
|
||||
<string name="certsetup_skip">Skip car connection setup</string>
|
||||
<string name="certsetup_loaded">Los coches sólo aceptan certificados emitidos por Google, y tiene uno cargado.</string>
|
||||
<string name="certsetup_choose">Los coches sólo aceptan certificados emitidos por Google. Gearslip no incluye uno, así que elige de dónde viene el tuyo.</string>
|
||||
<string name="certsetup_aasdk_sub">El proyecto aasdk de código abierto publica este certificado en github.com/opencardev/aasdk. La mayoría de los coches lo aceptan; algunos de alrededor de 2020 en no.</string>
|
||||
<string name="storage_title">Almacenamiento de Gearslip</string>
|
||||
<string name="storage_measuring">Measuring..</string>
|
||||
<string name="storage_lyrics">Letras</string>
|
||||
<string name="storage_clear">Despejado</string>
|
||||
<string name="storage_voice">Modelos de voz</string>
|
||||
<string name="storage_remove">Retirar</string>
|
||||
<string name="storage_logs">Antiguos registros</string>
|
||||
<string name="storage_other">Otros archivos temporales</string>
|
||||
<string name="storage_delete_all">Eliminar todos los datos de Gearslip</string>
|
||||
<string name="storage_total">Los datos de Gearslip toman %1$s del almacenamiento del teléfono.</string>
|
||||
<string name="storage_voice_detail">%1$s. Los comandos de voz los necesitan; descargarlos de nuevo en Ajustes.</string>
|
||||
<string name="storage_logs_detail">%1$s. Los registros de los lanzamientos anteriores; el registro de este lanzamiento se queda.</string>
|
||||
<string name="storage_other_detail">%1$s. Actualizaciones descargadas y otros archivos Gearslip puede hacer de nuevo.</string>
|
||||
<string name="storage_delete_armed">Pulsa Eliminar de nuevo para borrar ajustes, certificados, modelos de voz y registros. Gearslip cierra, y comienza desde la configuración la próxima vez.</string>
|
||||
<string name="storage_delete_sub">Ajustes, certificados, modelos de voz, letras y registros.</string>
|
||||
<string name="storage_delete">Suprimir</string>
|
||||
<string name="storage_delete_ellipsis">Eliminar..</string>
|
||||
<string name="donate_title">Me alegro de que funcionara</string>
|
||||
<string name="donate_liberapay">Apoyo a Liberapay</string>
|
||||
<string name="donate_bmac">Cómprame un café</string>
|
||||
<string name="donate_card">Dar una vez por tarjeta</string>
|
||||
<string name="donate_body">Gearslip es gratis, sin anuncios y sin seguimiento. Si te pone en el camino, tu apoyo lo mantiene.</string>
|
||||
<string name="wireless_needs_permission">Gearslip necesita acceso Bluetooth y Wi-Fi cercano para esto.</string>
|
||||
<string name="wireless_enable_permission">Activar permiso de dispositivos cercanos</string>
|
||||
<string name="wireless_connect">Conectar inalámbricamente</string>
|
||||
<string name="wireless_which_car">¿Qué coche?</string>
|
||||
<string name="wireless_none_paired">Tu teléfono no está emparejado con nada todavía. Encima con el coche primero.</string>
|
||||
<string name="wireless_not_listed">¿El auto no aparece? Pásala primero con tu teléfono.</string>
|
||||
<string name="bluetooth_settings">Ajustes Bluetooth</string>
|
||||
<string name="voice_live_title">Voz: palabras en vivo</string>
|
||||
<string name="voice_final_title">Voz: palabras finales</string>
|
||||
<string name="voice_none">Ninguno</string>
|
||||
<string name="voice_none_sub">Mantén las palabras vivas. Más rápido, menos preciso</string>
|
||||
<string name="voice_live_body">El asistente de voz convierte el discurso en texto en este teléfono, y nada de lo que dices lo deja. Necesita dos modelos, aproximadamente 270 MB juntos, descargados una vez. Este escribe tus palabras mientras hablas.</string>
|
||||
<string name="voice_final_body">Cuando dejas de hablar, este modelo lee toda la frase de nuevo. Coge nombres y coloca el modelo en vivo perdido.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Descargado. %1$s</string>
|
||||
<string name="voice_downloading">Descargar, %1$d%%</string>
|
||||
<string name="home_help">Ayuda</string>
|
||||
<string name="home_preview">Vista previa</string>
|
||||
<string name="home_logs">Logs</string>
|
||||
<string name="home_disconnect">Desconexión</string>
|
||||
<string name="home_bt_title">Bluetooth no está conectado</string>
|
||||
<string name="home_debug_build">%1$s · construcción de depuración</string>
|
||||
<string name="home_bt_body">La música juega por Bluetooth, pero su teléfono no está conectado al coche. Conéctelo aquí o desde el menú Bluetooth del coche.</string>
|
||||
<string name="home_voice_downloading">Voz de descarga, %1$d%%</string>
|
||||
<string name="audio_bt_title">¿Jugar audio de coche a través de Bluetooth?</string>
|
||||
<string name="audio_never_ask">Nunca preguntes, usando un ordenador</string>
|
||||
<string name="audio_use_bt">Utilizar Bluetooth</string>
|
||||
<string name="audio_keep_asking">Sigue preguntando</string>
|
||||
<string name="audio_mic_title">El audio del coche necesita el permiso del micrófono</string>
|
||||
<string name="audio_open_settings">Configuración de aplicaciones abiertas</string>
|
||||
<string name="audio_bt_body">Android trata la música enviada a través de USB como compartir su pantalla, por lo que pien cada viaje. Sobre Bluetooth nunca se pregunta. Puedes cambiar esto más tarde en Ajustes.</string>
|
||||
<string name="audio_keep_usb">O sigue enviando el sonido a través de Gearslip y elimina la pregunta para siempre: conecta el teléfono a un ordenador con la depuración USB activada y ejecuta esto una vez.</string>
|
||||
<string name="audio_mic_body">Android permite que una aplicación envíe el sonido de otras aplicaciones al coche sólo si tiene el permiso del micrófono. Se ha apagado para Gearslip, así que la música juega por teléfono. Enciende bajo permisos, luego micrófono.</string>
|
||||
<string name="typing_close">Cerrar el teclado del coche</string>
|
||||
<string name="typing_label">Tipo para la pantalla del coche</string>
|
||||
<string name="typing_search">Buscar en la pantalla del coche</string>
|
||||
<string name="command_tap_copy">Pulse para copiar</string>
|
||||
<string name="command_copied">Comando copiado</string>
|
||||
<string name="whats_new_this_version">%1$s (esta versión)</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d canción · %2$s. Letras limpias descargan de nuevo cuando las abres.</item>
|
||||
<item quantity="other">%1$d canciones · %2$s. Letras limpias descargan de nuevo cuando las abres.</item>
|
||||
</plurals>
|
||||
<string name="carset_theme">Tema</string>
|
||||
<string name="carset_imperial">Imperial (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Métrico (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Tamaño de la pantalla (conexión siguiente)</string>
|
||||
<string name="carset_fit">Ajuste a la pantalla</string>
|
||||
<string name="off">Fuera</string>
|
||||
<string name="on">On</string>
|
||||
<string name="carset_lyrics_from">Letras de</string>
|
||||
<string name="carset_more_sources">Más fuentes</string>
|
||||
<string name="carset_hide">Oculto</string>
|
||||
<string name="carset_show">Show</string>
|
||||
<string name="carset_phone">Teléfono</string>
|
||||
<string name="carset_vehicle">Vehículo</string>
|
||||
<string name="carset_video">Video</string>
|
||||
<string name="media_open_on_phone">Abra el teléfono</string>
|
||||
<string name="media_now_playing">Ahora jugando</string>
|
||||
<string name="media_up_next">Siguiente</string>
|
||||
<string name="media_browse">Navega</string>
|
||||
<string name="media_search_hint">Escribe una canción, artista, álbum o lista de reproducción.</string>
|
||||
<string name="media_looking_lyrics">Buscando letras..</string>
|
||||
<string name="media_nothing_playing">Nada jugando</string>
|
||||
<string name="media_press_play">Presiona el juego para recoger donde te fuiste, o encuentra algo en Navega.</string>
|
||||
<string name="media_keeps_library">%1$s mantiene su biblioteca a sí misma.</string>
|
||||
<string name="media_needs_access">Gearslip todavía puede controlarlo una vez que tenga acceso a la notificación por teléfono. Permítelo en Configuración > Notificaciones √ Acceso a notificaciones.</string>
|
||||
<string name="media_not_running">%1$s no funciona.</string>
|
||||
<string name="media_not_running_body">%1$s suena a través del propio reproductor de Gearslip, pero sólo mientras está abierto en el teléfono. Ábrelo una vez y sigue jugando aquí en el coche.</string>
|
||||
<string name="media_open_app">Abra %1$s</string>
|
||||
<string name="media_refused">%1$s no dejaría que Gearslip lo revisara.</string>
|
||||
<string name="media_refused_body">Algunas aplicaciones de medios solo aceptan el propio anfitrión de Google.</string>
|
||||
<string name="media_connecting">Conectando a %1$s..</string>
|
||||
<string name="media_search">Búsqueda %1$s</string>
|
||||
<string name="media_search_no_list">%1$s no puede listar resultados. Búsqueda de prensa, y %1$s elige qué jugar.</string>
|
||||
<string name="media_search_unsupported">%1$s no puede buscar desde el coche.</string>
|
||||
<string name="media_searching">Buscando %1$s..</string>
|
||||
<string name="media_search_nothing">%1$s no encontró nada para \"%2$s\".</string>
|
||||
<string name="media_search_ignored">%1$s no jugó nada para \"%2$s\".</string>
|
||||
<string name="media_searching_for">Buscando %1$s para \"%2$s\"..</string>
|
||||
<string name="media_nothing_playing_dot">Nada jugando.</string>
|
||||
<string name="media_no_lyrics">No hay letras encontradas para esta pista.</string>
|
||||
<string name="media_up">Arriba</string>
|
||||
<string name="media_loading">Cargando..</string>
|
||||
<string name="media_browse_start">Comience música en la aplicación en su teléfono. Juega, pausa, salta y busca trabajo desde aquí.</string>
|
||||
<string name="media_load_failed">No podía cargar esta lista.</string>
|
||||
<string name="media_nothing_here">Nada aquí.</string>
|
||||
<string name="media_nothing_queued">No hay nada apagado.</string>
|
||||
<string name="media_play">Jugar</string>
|
||||
<string name="media_queue_empty_idle">Nada jugando - no cada aplicación le dice a Gearslip lo que viene después.</string>
|
||||
<string name="media_queue_empty_playing">Sólo lo que ya está jugando - no cada aplicación le dice a Gearslip lo que viene después.</string>
|
||||
<string name="player_previous">Anterior</string>
|
||||
<string name="player_next">Siguiente</string>
|
||||
<string name="player_repeat">Repito</string>
|
||||
<string name="player_pause">Pausa</string>
|
||||
<string name="player_unlike">Diferente</string>
|
||||
<string name="player_like">Como</string>
|
||||
<string name="player_shuffle_off">Shuffle off</string>
|
||||
<string name="player_shuffle_on">Shuffle on</string>
|
||||
<string name="home_choose_app">Elija otra aplicación</string>
|
||||
<string name="home_no_maps">No hay aplicaciones de mapeo encontradas.</string>
|
||||
<string name="home_no_maps_body">Instale una aplicación de navegación que soporta Android Auto y que comenzará aquí por su cuenta.</string>
|
||||
<string name="home_map_stopped">Mapa detenido</string>
|
||||
<string name="home_map_stopped_body">Se mantiene cerrado hasta que lo abras de nuevo.</string>
|
||||
<string name="home_reopen_map">Abra el mapa</string>
|
||||
<string name="home_car_apps">Aplicaciones de coches</string>
|
||||
<string name="home_back_nav">Volver a la barra de nav</string>
|
||||
<string name="home_show_player">Mostrar el reproductor</string>
|
||||
<string name="home_hide_lyrics">Ocultar letras</string>
|
||||
<string name="home_starting">Empezando %1$s..</string>
|
||||
<string name="home_starting_map">Empezando el mapa..</string>
|
||||
<string name="home_failed">%1$s no se pudo iniciar.</string>
|
||||
<string name="home_failed_map">La aplicación del mapa no comenzaría.</string>
|
||||
<string name="crumb_home">Home</string>
|
||||
<string name="crumb_media">Media</string>
|
||||
<string name="crumb_apps">Aplicaciones</string>
|
||||
<string name="crumb_dashboard">Dashboard</string>
|
||||
<string name="crumb_weather">El tiempo</string>
|
||||
<string name="crumb_reply">Respuesta</string>
|
||||
<string name="app_browser">Navegador</string>
|
||||
<string name="app_screen_sharing">Intercambio de pantallas</string>
|
||||
<string name="app_exit">Exit</string>
|
||||
<string name="navapps_unpin">Unpin de la barra nav</string>
|
||||
<string name="navapps_full">Nav bar está lleno (%1$d grabado)</string>
|
||||
<string name="navapps_pin">Pin a nav bar</string>
|
||||
<string name="navapps_close">Cerrar app</string>
|
||||
<string name="navapps_move_left">Muévete a la izquierda</string>
|
||||
<string name="navapps_move_right">Muévete</string>
|
||||
<string name="navapps_reset">Orden de reiniciar la aplicación</string>
|
||||
<string name="msg_dismiss">Desestimación</string>
|
||||
<string name="msg_read">Leer</string>
|
||||
<string name="msg_stop">Para</string>
|
||||
<string name="msg_no">No</string>
|
||||
<string name="msg_send_now">Envía ahora</string>
|
||||
<string name="msg_type">Tipo</string>
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nuevo</item>
|
||||
<item quantity="other">%1$s · %2$d nuevo</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Respuesta a %1$s</string>
|
||||
<string name="msg_listening">Escuchando</string>
|
||||
<string name="msg_sending_to">Enviar a %1$s</string>
|
||||
<string name="msg_sent_to">Enviado a %1$s</string>
|
||||
<string name="msg_reply_failed">No podía responder</string>
|
||||
<string name="bt_pair_unfinished">La pareja no terminó. Pareja del menú Bluetooth del coche en su lugar.</string>
|
||||
<string name="bt_pair_not_started">La pareja no empezó. Pareja del menú Bluetooth del coche en su lugar.</string>
|
||||
<string name="bt_paired">Parecido. Las llamadas ahora juegan a través del coche.</string>
|
||||
<string name="overlay_dont_show">No vuelvas a aparecer</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">La ubicación está fuera</string>
|
||||
<string name="overlay_pair_title">¿Un par con este auto?</string>
|
||||
<string name="overlay_pair">Pareja</string>
|
||||
<string name="overlay_confirm_title">Confirme su teléfono</string>
|
||||
<string name="overlay_confirm_body">Comprueba el código en tu teléfono y pulsa Par. El coche también puede preguntar.</string>
|
||||
<string name="overlay_bt_body">La música juega por Bluetooth, pero su teléfono no está conectado al coche. Conéctelo desde el menú Bluetooth del coche o del teléfono.</string>
|
||||
<string name="overlay_location_body">Las aplicaciones de mapa no pueden mostrar dónde estás o dar direcciones. Enciende la ubicación en la configuración rápida de tu teléfono cuando estés estacionado.</string>
|
||||
<string name="overlay_pair_body_music">Llamadas y música tocan a través de los altavoces del coche una vez que el teléfono y el coche están emparejados. Su teléfono le pide que confirme un código.</string>
|
||||
<string name="overlay_pair_body">Las llamadas juegan a través de los altavoces del coche una vez que el teléfono y el coche están emparejados. Su teléfono le pide que confirme un código.</string>
|
||||
<string name="weather_waiting_fix">Esperando una solución de ubicación.</string>
|
||||
<string name="weather_allow_location">Permitir la ubicación para Gearslip en el teléfono para ver el tiempo.</string>
|
||||
<string name="weather_unreachable">No podía llegar al servicio meteorológico.</string>
|
||||
<string name="weather_clear">Despejado</string>
|
||||
<string name="weather_mostly_clear">Mayormente claro</string>
|
||||
<string name="weather_partly_cloudy">Parte nublada</string>
|
||||
<string name="weather_overcast">Overcast</string>
|
||||
<string name="weather_fog">Fog</string>
|
||||
<string name="weather_drizzle">Drizzle</string>
|
||||
<string name="weather_rain">Rain</string>
|
||||
<string name="weather_snow">Snow</string>
|
||||
<string name="weather_rain_showers">Duchas de lluvia</string>
|
||||
<string name="weather_snow_showers">Duchas de nieve</string>
|
||||
<string name="weather_thunderstorm">Thunderstorm</string>
|
||||
<string name="weather_getting">Conseguir el tiempo..</string>
|
||||
<string name="weather_next_hours">Siguientes horas</string>
|
||||
<string name="weather_this_week">Esta semana</string>
|
||||
<string name="weather_today">Hoy</string>
|
||||
<string name="weather_credit">Datos meteorológicos de Open-Meteo.com (CC BY 4.0)</string>
|
||||
<string name="weather_feels_like">Se siente como %1$s</string>
|
||||
<string name="weather_humidity">Humedad %1$d%%</string>
|
||||
<string name="weather_wind">Viento %1$s</string>
|
||||
<string name="dash_today">Hoy</string>
|
||||
<string name="dash_getting_weather">Tener tiempo..</string>
|
||||
<string name="dash_coming_up">Subiendo</string>
|
||||
<string name="dash_loading_calendar">Calendario de carga..</string>
|
||||
<string name="dash_nothing_week">Nada en la próxima semana.</string>
|
||||
<string name="dash_all_day">Todo el día</string>
|
||||
<string name="dialer_contacts">Contactos</string>
|
||||
<string name="dialer_keypad">Keypad</string>
|
||||
<string name="dialer_ask">Pregunta por teléfono</string>
|
||||
<string name="dialer_enter_number">Introduzca un número</string>
|
||||
<string name="dialer_call">Call</string>
|
||||
<string name="dialer_no_contacts">No hay contactos con un número de teléfono encontrado.</string>
|
||||
<string name="dialer_search">Contactos de búsqueda</string>
|
||||
<string name="dialer_needs_permission">Gearslip necesita permisos de teléfono y contactos para marcar desde aquí. Déjenlos al teléfono cuando estén estacionados.</string>
|
||||
<string name="dialer_no_match">No hay contactos \"%1$s\".</string>
|
||||
<string name="dialer_call_who">¿Llama %1$s?</string>
|
||||
<string name="carui_exit_fullscreen">Exit pantalla completa</string>
|
||||
<string name="carui_drive_safely">Conduzca con seguridad</string>
|
||||
<string name="carui_understand">Entiendo</string>
|
||||
<string name="carui_unofficial">Gearslip es un software no oficial: no es hecho o revisado por ningún fabricante de coches, y puede fallar sin aviso.</string>
|
||||
<string name="carui_never_interact">Nunca interactuar con esta pantalla mientras conduce. Pon la ruta o la canción antes de ir, entonces mantenga su atención en la carretera.</string>
|
||||
<string name="carui_this_app">Esta app</string>
|
||||
<string name="carui_app_unsupported">%1$s todavía no trabaja con Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">No empezó. Algunas aplicaciones solo funcionan en Android Auto de Google.</string>
|
||||
<string name="navbar_on_call">En una llamada</string>
|
||||
<string name="navbar_end_call">Llamada final</string>
|
||||
<string name="navbar_unmute">Unmute</string>
|
||||
<string name="navbar_mute">Mute</string>
|
||||
<string name="navbar_close_panel">Cerrar el panel lateral</string>
|
||||
<string name="navbar_dock_player">Mueva el reproductor junto al mapa</string>
|
||||
<string name="web_search">Buscar o escribir una dirección</string>
|
||||
<string name="web_forward">Adelante</string>
|
||||
<string name="web_reload">Recarga</string>
|
||||
<string name="web_start_page">Página de inicio</string>
|
||||
<string name="web_fullscreen">Pantalla completa</string>
|
||||
<string name="notif_title">Notificaciones</string>
|
||||
<string name="notif_clear_all">Despejado todo</string>
|
||||
<string name="notif_empty">Nada todavía. Las nuevas notificaciones del teléfono aparecerán aquí.</string>
|
||||
<string name="notif_off">No hay notificaciones. Permitir Gearslip bajo Ajustes > Notifications > Notifications en el teléfono.</string>
|
||||
<string name="notif_replied">Replied</string>
|
||||
<string name="msg_mark_read">Mark leyó</string>
|
||||
<string name="msg_messages">Mensajes</string>
|
||||
<string name="msg_none_recent">No hay mensajes recientes. Nuevos mensajes %1$s aparecen aquí cuando llegan por teléfono.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d anteriormente</item>
|
||||
<item quantity="other">%1$d anteriormente</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Ocultar respuestas rápidas</string>
|
||||
<string name="msg_quick">Respuesta rápida</string>
|
||||
<string name="msg_you">Tú:</string>
|
||||
<string name="msg_sender">%1$s:</string>
|
||||
<string name="nav_finding_route">Encontrar una ruta..</string>
|
||||
<string name="nav_then">Entonces..</string>
|
||||
<string name="nav_stop">Deja de navegar</string>
|
||||
<string name="nav_finding_routes">Encontrar rutas..</string>
|
||||
<string name="nav_lane_recommended">Carril recomendado</string>
|
||||
<string name="nav_lane">Lane</string>
|
||||
<string name="nav_arrive">Llegar %1$s</string>
|
||||
<string name="template_nothing_playing">Nada jugando todavía.</string>
|
||||
<string name="template_search">Buscar</string>
|
||||
<string name="template_start">Comienzo</string>
|
||||
<string name="call_incoming">Llamada entrante</string>
|
||||
<string name="call_decline">Declin</string>
|
||||
<string name="call_answer">Respuesta</string>
|
||||
<string name="call_ringing">Ya sonando en voz alta - responder aquí, o dejar que suene.</string>
|
||||
<string name="phoneapps_touch_off">El toque está apagado. Activar Gearslip bajo Ajustes > Accesibilidad por teléfono.</string>
|
||||
<string name="whatsnew_show_after">Mostrar después de actualizaciones</string>
|
||||
<string name="whatsnew_got_it">Lo tengo</string>
|
||||
<string name="whatsnew_title">Qué hay de nuevo en %1$s</string>
|
||||
<string name="keyboard_space">espacio</string>
|
||||
<string name="keyboard_hide">Ocultar teclado</string>
|
||||
<string name="keyboard_submit">Submit</string>
|
||||
<string name="launcher_broken">No funciona con Gearslip</string>
|
||||
<string name="stage_car_app">Aplicación de coches</string>
|
||||
<string name="stage_close">Cerrar %1$s</string>
|
||||
<string name="stage_close_app">Cerrar la aplicación</string>
|
||||
<string name="dir_slight_left">Gire a la izquierda</string>
|
||||
<string name="dir_slight_left_onto">Gire a la izquierda ligeramente %1$s</string>
|
||||
<string name="dir_slight_right">Gire ligeramente a la derecha</string>
|
||||
<string name="dir_slight_right_onto">Gire ligeramente a la derecha en %1$s</string>
|
||||
<string name="dir_left">Gire a la izquierda</string>
|
||||
<string name="dir_left_onto">Gire a la izquierda en %1$s</string>
|
||||
<string name="dir_right">Gire a la derecha</string>
|
||||
<string name="dir_right_onto">Gire a la derecha en %1$s</string>
|
||||
<string name="dir_sharp_left">Gire a la izquierda</string>
|
||||
<string name="dir_sharp_left_onto">Gire a la izquierda hacia %1$s</string>
|
||||
<string name="dir_sharp_right">Gire a la derecha</string>
|
||||
<string name="dir_sharp_right_onto">Gire a la derecha hacia %1$s</string>
|
||||
<string name="dir_u_turn">Haz un giro U</string>
|
||||
<string name="dir_keep_left">Mantenga la izquierda</string>
|
||||
<string name="dir_keep_left_toward">Mantenga la izquierda hacia %1$s</string>
|
||||
<string name="dir_keep_right">Bien</string>
|
||||
<string name="dir_keep_right_toward">Siga recto hacia %1$s</string>
|
||||
<string name="dir_fork_left">Mantenga la izquierda en el tenedor</string>
|
||||
<string name="dir_fork_left_toward">Mantenga la izquierda en el tenedor hacia %1$s</string>
|
||||
<string name="dir_fork_right">Mantente en el tenedor</string>
|
||||
<string name="dir_fork_right_toward">Siga recto en el tenedor hacia %1$s</string>
|
||||
<string name="dir_ramp_left">Tome la rampa a la izquierda</string>
|
||||
<string name="dir_ramp_left_onto">Tome la rampa a la izquierda en %1$s</string>
|
||||
<string name="dir_ramp_right">Tome la rampa a la derecha</string>
|
||||
<string name="dir_ramp_right_onto">Tome la rampa a la derecha en %1$s</string>
|
||||
<string name="dir_exit_left">Tome la salida a la izquierda</string>
|
||||
<string name="dir_exit_left_toward">Tome la salida a la izquierda hacia %1$s</string>
|
||||
<string name="dir_exit_right">Tome la salida a la derecha</string>
|
||||
<string name="dir_exit_right_toward">Tome la salida a la derecha hacia %1$s</string>
|
||||
<string name="dir_merge_left">Merge izquierda</string>
|
||||
<string name="dir_merge_left_onto">Incorpore a la izquierda %1$s</string>
|
||||
<string name="dir_merge_right">Merge right</string>
|
||||
<string name="dir_merge_right_onto">Gire a la derecha en %1$s</string>
|
||||
<string name="dir_merge">Merge</string>
|
||||
<string name="dir_merge_onto">Ingrese a %1$s</string>
|
||||
<string name="dir_roundabout_through">Pasa por la rotonda</string>
|
||||
<string name="dir_roundabout_through_onto">Vaya por la rotonda a %1$s</string>
|
||||
<string name="dir_roundabout_enter">Entra en la rotonda</string>
|
||||
<string name="dir_roundabout_exit">Salga de la rotonda</string>
|
||||
<string name="dir_roundabout_exit_onto">Exit the roundabout onto %1$s</string>
|
||||
<string name="dir_straight">Continuar recto</string>
|
||||
<string name="dir_straight_onto">Continuar recto hacia %1$s</string>
|
||||
<string name="dir_continue">Continuar</string>
|
||||
<string name="dir_continue_onto">Continuar %1$s</string>
|
||||
<string name="dir_ferry">Tome el ferry</string>
|
||||
<string name="dir_train">Toma el tren</string>
|
||||
<string name="dir_destination">Su destino está por delante</string>
|
||||
<string name="dir_destination_left">Su destino está a la izquierda</string>
|
||||
<string name="dir_destination_right">Su destino está a la derecha</string>
|
||||
<string name="dir_roundabout_take">En la rotonda, tome la salida %1$s</string>
|
||||
<string name="dir_roundabout_take_onto">En la rotonda, tome la salida %1$s hacia %2$s</string>
|
||||
<string name="dir_ordinal_1">primero</string>
|
||||
<string name="dir_ordinal_2">segundo</string>
|
||||
<string name="dir_ordinal_3">tercero</string>
|
||||
<string name="dir_ordinal_4">cuarto</string>
|
||||
<string name="dir_ordinal_5">quinta</string>
|
||||
<string name="dir_ordinal_6">sexto</string>
|
||||
<string name="dir_ordinal_7">vii</string>
|
||||
<string name="dir_ordinal_8">octavo</string>
|
||||
<string name="dir_in_distance">En %1$s, %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s kilómetro</item>
|
||||
<item quantity="other">%1$s kilómetros</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s kilómetros</string>
|
||||
<string name="dir_quarter_mile">un cuarto de milla</string>
|
||||
<string name="dir_half_mile">media milla</string>
|
||||
<string name="dir_three_quarter_mile">tres cuartos de milla</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s mile</item>
|
||||
<item quantity="other">%1$s miles</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s miles</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s foot</item>
|
||||
<item quantity="other">%1$s pies</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s yard</item>
|
||||
<item quantity="other">%1$s yardas</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d metro</item>
|
||||
<item quantity="other">%1$d metros</item>
|
||||
</plurals>
|
||||
<string name="touch_relay_description">Permite a Gearslip reproducir toques desde la pantalla del coche en el teléfono mientras se refleja. Gearslip no lee contenido de pantalla.</string>
|
||||
<string name="guide_usage_title">Ayuda a otros conductores.</string>
|
||||
<string name="guide_safety_title">Conduce con cuidado.</string>
|
||||
<string name="guide_safety_body">Gearslip no es oficial. Ningún fabricante de coches lo hizo o lo comprobó, y puede fallar sin aviso. Arréglalo antes de que te vayas, entonces mantén los ojos en el camino.</string>
|
||||
<string name="guide_mic_title">Micrófono.</string>
|
||||
<string name="guide_mic_body">Gearslip lo usa para enviar tu música al coche y escucharte cuando pides algo o respondes a un mensaje. Lo que dices se queda por teléfono.</string>
|
||||
<string name="guide_location_title">Ubicación.</string>
|
||||
<string name="guide_location_body">El mapa del coche y búsquedas cercanas lo necesitan. Sin ella, el dashboard no muestra mapa.</string>
|
||||
<string name="guide_calendar_title">Calendario.</string>
|
||||
<string name="guide_calendar_body">La pantalla del coche muestra su próximo evento. Gearslip sólo lee su calendario.</string>
|
||||
<string name="guide_calls_title">Llamadas y contactos.</string>
|
||||
<string name="guide_calls_body">El coche muestra quién llama por nombre, y su aplicación Phone puede marcar un número o llamar a uno de sus contactos. Gearslip sólo los lee.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">La primera vez que se conecta, Gearslip ofrece para emparejar su teléfono con el coche, así que las llamadas juegan a través de sus altavoces.</string>
|
||||
<string name="guide_notifications_title">Notificaciones.</string>
|
||||
<string name="guide_notifications_body">Mientras que Gearslip está conectado, muestra una notificación con un botón para detenerse. Sin esto, Android lo esconde.</string>
|
||||
<string name="guide_messages_title">Mensajes.</string>
|
||||
<string name="guide_messages_body">El coche muestra sus mensajes, los lee en voz alta y le permite responder. Gire Gearslip en la lista que se abre.</string>
|
||||
<string name="guide_android_auto_title">Apaga Android Auto.</string>
|
||||
<string name="guide_android_auto_body">Android Auto todavía está instalado, y lucha contra Gearslip para la conexión USB del coche. Desactivarlo o desinstalarlo en la pantalla que se abre.</string>
|
||||
<string name="guide_default_title">Elige Gearslip.</string>
|
||||
<string name="guide_default_body">Si su teléfono pide qué aplicación se abre cuando se conecta al coche, seleccione Gearslip y grifo Siempre.</string>
|
||||
<string name="guide_audio_title">Audio de coche.</string>
|
||||
<string name="guide_audio_body">Elige cómo la música llega al coche. Las llamadas pasan por Bluetooth de cualquier manera.</string>
|
||||
<string name="guide_voice_title">Voz.</string>
|
||||
<string name="guide_voice_body">Pide direcciones, música o una llamada por voz. Tu teléfono convierte el discurso en texto por sí mismo, y nada de lo que dices lo deja. Necesita %1$d MB, descargado una vez. Use Wi-Fi si puede.</string>
|
||||
<string name="guide_audio_prompt_title">Sin aviso de audio.</string>
|
||||
<string name="guide_audio_prompt_body">Ejecute esto desde un ordenador con USB depurando, y Android confía en Gearslip con su audio para siempre. Sin ella, pulsa Empieza una vez cada viaje.</string>
|
||||
<string name="guide_readable_title">Notificaciones legibles.</string>
|
||||
<string name="guide_readable_body">Mientras el audio va al coche, Android oculta lo que las notificaciones dicen, como si estuviera compartiendo su pantalla. Esto permite que Gearslip les muestre durante una unidad y los esconda de nuevo después.</string>
|
||||
<string name="guide_answer_title">Contesta.</string>
|
||||
<string name="guide_answer_body">Haga Gearslip su aplicación de identificación de llamadas, y el coche puede responder y rechazar llamadas. Sin ella, las llamadas siguen sonando, pero solo el teléfono puede responderlas.</string>
|
||||
<string name="guide_apps_title">Buena compañía.</string>
|
||||
<string name="guide_buttons_title">Tus botones.</string>
|
||||
<string name="guide_buttons_body">Elige cómo se ven los botones del coche. Cambialo en cualquier momento en la configuración del coche.</string>
|
||||
<string name="guide_open_notification_access">Acceso a la notificación abierta</string>
|
||||
<string name="guide_open_android_auto">Abrir información de la aplicación de Android Auto</string>
|
||||
<string name="guide_download_voice">Descargar voz</string>
|
||||
<string name="guide_set_up">Configuración</string>
|
||||
<string name="guide_voice_downloading">Descargar, %1$d%%. Puedes continuar; sigue adelante.</string>
|
||||
<string name="guide_voice_ready">La voz está lista.</string>
|
||||
<string name="guide_voice_failed">La descarga no terminó. Pulsa Descargar voz para probar de nuevo.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Recomendado</string>
|
||||
<string name="guide_audio_bluetooth_body">No hay aviso en cada viaje, y la voz de su aplicación del mapa llega al coche.</string>
|
||||
<string name="guide_audio_usb">A través de Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Sonido más claro sin demora. Cada viaje, Android te pide que compartas tu pantalla, lo que permite que Gearslip envíe el sonido.</string>
|
||||
<string name="guide_optional">OPTIONAL</string>
|
||||
<string name="guide_already_allowed">Ya está permitido.</string>
|
||||
<string name="guide_scroll_more">Scroll para más ↓</string>
|
||||
<string name="guide_allow">Permiso</string>
|
||||
<string name="guide_skip">Skip for now</string>
|
||||
<string name="guide_open">Abierto</string>
|
||||
<string name="guide_next">Siguiente</string>
|
||||
<string name="guide_done">Hecho</string>
|
||||
<string name="guide_back">Atrás</string>
|
||||
<string name="usage_intro">Activa esto y Gearslip envía una nota breve cuando lo abres y después de cada viaje. <b>La nota no incluye nombre, cuenta ni ubicación. Un número aleatorio cuenta tu teléfono una vez, y el servidor no guarda ninguna dirección IP.</b> Las notas muestran qué coches funcionan y dónde fallan los demás.</string>
|
||||
<string name="usage_send">Enviar notas de uso</string>
|
||||
<string name="usage_also_share">También compartir</string>
|
||||
<string name="usage_share_android">Android versión y sistema</string>
|
||||
<string name="usage_share_phone">Modelo de teléfono</string>
|
||||
<string name="usage_share_cars">Cada coche y si se conecta</string>
|
||||
<string name="usage_share_drives">Cada viaje y cómo terminó</string>
|
||||
<string name="usage_what_sent">Lo que se envía</string>
|
||||
<string name="usage_what_sent_sub">Cada campo, exactamente como sale</string>
|
||||
<string name="usage_hide_note">Ocultar la nota</string>
|
||||
<string name="usage_show_note">Mostrar la nota</string>
|
||||
<string name="usage_example">Ejemplo, no enviado: un viaje añade esto a \"sesiones\"</string>
|
||||
<string name="usage_no_server">Esta compilación no tiene un servidor de estadísticas, así que no envía nada</string>
|
||||
<string name="usage_debug_build">Versión de depuración: las notas se mantienen separadas y nunca se cuentan en las estadísticas</string>
|
||||
<string name="worked_title">Gearslip trabajó con su %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip trabajó con su coche</string>
|
||||
<string name="worked_body">¿Compartir eso para que el próximo conductor sepa que funciona? Sin nombre, cuenta o ubicación.</string>
|
||||
<string name="worked_not_now">Ahora no</string>
|
||||
<string name="worked_share">Compartir</string>
|
||||
<string name="recommended_intro">Estas aplicaciones gratuitas funcionan en la pantalla del coche de Gearslip. Cada botón abre la página de la aplicación en GitHub.</string>
|
||||
<string name="recommended_title">Aplicaciones recomendadas</string>
|
||||
<string name="back">Atrás</string>
|
||||
<string name="recommended_installed">Instalado</string>
|
||||
<string name="carset_version">Versión</string>
|
||||
<string name="carset_build">Build</string>
|
||||
<string name="carset_whats_new_after">Lo nuevo después de las actualizaciones</string>
|
||||
<string name="carset_whats_new_after_sub">Mostrar la lista de cambios una vez, la primera vez que el coche se conecta después de una actualización.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (default) - pulsar para configurar una página diferente</string>
|
||||
<string name="carset_web_note">Mostrada por la aplicación Browser en el lanzador. Se aplica en la siguiente conexión.</string>
|
||||
<string name="carset_screen_fit">Ajuste de la pantalla</string>
|
||||
<string name="carset_top">Top</string>
|
||||
<string name="carset_bottom">Bottom</string>
|
||||
<string name="carset_save_vehicle">Guardar para este vehículo</string>
|
||||
<string name="carset_no_profile">No hay perfil del vehículo igualado</string>
|
||||
<string name="carset_audio_note">A través de Gearslip envía el sonido del teléfono por la conexión con el coche, por cable o inalámbrica. Android pide compartir la pantalla en cada viaje, y algunas apps lo bloquean. Bluetooth suena como siempre en el teléfono, y Android nunca pregunta.</string>
|
||||
<string name="carset_directions_note">Gearslip lee cada vuelta en voz alta a través de los altavoces del coche, y la música dips mientras habla. Apágalo si la propia voz de su aplicación de mapa ya juega en el coche.</string>
|
||||
<string name="carset_phone_speech_note">Escucha el propio servicio de habla del teléfono en lugar de Gearslip. En la mayoría de los teléfonos que son de Google, que puede enviar su voz a Google. GrapheneOS no tiene ninguno, así que Gearslip sigue usando su propio.</string>
|
||||
<string name="carset_vehicle_data_note">Añade una ficha de datos del vehículo: un vertedero en vivo y crudo del canal de sensores de la unidad principal. Las etiquetas de campo (velocidad, RPM, combustible...) se llevan de otros proyectos de ingeniería inversa y se unverifican contra este coche - tratarlas como una conjetura, no un hecho, hasta que hayas visto un cambio para ti mismo.</string>
|
||||
<string name="carset_adaptive_note">Reduce la calidad de la imagen cuando el coche, o su Wi-Fi, no puede mantenerse al día, y la levanta de nuevo una vez que pueda. Efectúa la próxima conexión.</string>
|
||||
<string name="carset_usable_height">Altura usable %1$dpx de 480</string>
|
||||
<string name="carset_saved_to">Ahorrado a %1$s</string>
|
||||
<string name="carset_save_failed">No podía salvar</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
<string name="status_idle">Sin conexión</string>
|
||||
<string name="status_idle_detail">Conecta el teléfono al puerto USB del coche o conéctate de forma inalámbrica. Gearslip se abre solo cuando el coche responde.</string>
|
||||
<string name="status_connecting">Conectando</string>
|
||||
<string name="status_projecting">Conectado</string>
|
||||
<string name="status_projecting_detail">Mostrando en la pantalla del coche.</string>
|
||||
<string name="status_disconnected">Desconectado</string>
|
||||
<string name="status_disconnected_detail">El coche ya no está conectado.</string>
|
||||
<string name="places_title">Lugares guardados</string>
|
||||
<string name="places_sub">Di “navigate home” en cualquier app de mapas</string>
|
||||
<string name="places_intro">Di “navigate home”, “take me to work” o “navigate to” con un nombre tuyo, y Gearslip envía esa dirección a tu app de mapas. Por ahora los comandos de voz son solo en inglés.</string>
|
||||
<string name="places_home">Casa</string>
|
||||
<string name="places_work">Trabajo</string>
|
||||
<string name="places_other">Lugar %1$d</string>
|
||||
<string name="places_name">Nombre</string>
|
||||
<string name="places_address">Dirección</string>
|
||||
<string name="places_here">Usar mi ubicación</string>
|
||||
<string name="places_finding">Buscando…</string>
|
||||
<string name="places_not_found">Gearslip no encontró dónde estás. Comprueba que la ubicación esté activada.</string>
|
||||
<string name="places_clear">Borrar</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,840 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Permet à Gearslip de reproduire sur le téléphone les touchers faits sur l’écran de la voiture pendant le partage d’écran. Gearslip ne lit pas le contenu de l’écran.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Aidez les autres conducteurs.</string>
|
||||
<string name="guide_safety_title">Conduisez prudemment.</string>
|
||||
<string name="guide_safety_body">Gearslip n’est pas officiel. Aucun constructeur ne l’a conçu ni vérifié, et il peut tomber en panne sans prévenir. Réglez-le avant de partir, puis gardez les yeux sur la route.</string>
|
||||
<string name="guide_mic_title">Microphone.</string>
|
||||
<string name="guide_mic_body">Gearslip s’en sert pour envoyer votre musique à la voiture et pour vous entendre quand vous demandez quelque chose ou répondez à un message. Ce que vous dites reste sur le téléphone.</string>
|
||||
<string name="guide_location_title">Localisation.</string>
|
||||
<string name="guide_location_body">La carte de la voiture et les recherches à proximité en ont besoin. Sans elle, le tableau de bord n’affiche pas de carte.</string>
|
||||
<string name="guide_calendar_title">Agenda.</string>
|
||||
<string name="guide_calendar_body">L’écran de la voiture affiche votre prochain événement. Gearslip ne fait que lire votre agenda.</string>
|
||||
<string name="guide_calls_title">Appels et contacts.</string>
|
||||
<string name="guide_calls_body">La voiture affiche le nom de la personne qui appelle, et son appli Téléphone peut composer un numéro ou appeler un de vos contacts. Gearslip ne fait que les lire.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">La première fois que vous branchez le téléphone, Gearslip propose de l’associer à la voiture, pour que les appels passent par ses haut-parleurs.</string>
|
||||
<string name="guide_notifications_title">Notifications.</string>
|
||||
<string name="guide_notifications_body">Quand Gearslip est connecté, il affiche une notification avec un bouton pour l’arrêter. Sans cette autorisation, Android la masque.</string>
|
||||
<string name="guide_messages_title">Messages.</string>
|
||||
<string name="guide_messages_body">La voiture affiche vos messages, les lit à voix haute et vous permet d’y répondre. Activez Gearslip dans la liste qui s’ouvre.</string>
|
||||
<string name="guide_android_auto_title">Désactivez Android Auto.</string>
|
||||
<string name="guide_android_auto_body">Android Auto est toujours installé, et il dispute à Gearslip la connexion USB de la voiture. Désactivez-le ou désinstallez-le sur l’écran qui s’ouvre.</string>
|
||||
<string name="guide_default_title">Choisissez Gearslip.</string>
|
||||
<string name="guide_default_body">Si le téléphone demande quelle appli ouvrir quand vous le branchez à la voiture, choisissez Gearslip puis touchez Toujours.</string>
|
||||
<string name="guide_audio_title">Audio de la voiture.</string>
|
||||
<string name="guide_audio_body">Choisissez comment la musique arrive à la voiture. Les appels passent par Bluetooth dans tous les cas.</string>
|
||||
<string name="guide_voice_title">Voix.</string>
|
||||
<string name="guide_voice_body">Demandez un itinéraire, de la musique ou un appel à la voix. Le téléphone transforme lui-même la parole en texte, et rien de ce que vous dites n’en sort. Il faut %1$d Mo, téléchargés une seule fois. Utilisez le Wi-Fi si possible.</string>
|
||||
<string name="guide_audio_prompt_title">Plus de demande audio.</string>
|
||||
<string name="guide_audio_prompt_body">Lancez ceci depuis un ordinateur avec le débogage USB activé, et Android confie votre audio à Gearslip pour de bon. Sinon, vous touchez Démarrer une fois à chaque trajet.</string>
|
||||
<string name="guide_readable_title">Notifications lisibles.</string>
|
||||
<string name="guide_readable_body">Quand l’audio part vers la voiture, Android masque le contenu des notifications, comme si vous partagiez votre écran. Ceci permet à Gearslip de les afficher pendant le trajet et de les masquer à nouveau ensuite.</string>
|
||||
<string name="guide_answer_title">Répondre aux appels.</string>
|
||||
<string name="guide_answer_body">Faites de Gearslip votre appli d’identification de l’appelant, et la voiture pourra accepter et refuser les appels. Sinon, les appels sonnent quand même, mais seul le téléphone peut y répondre.</string>
|
||||
<string name="guide_apps_title">Bonne compagnie.</string>
|
||||
<string name="guide_buttons_title">Vos boutons.</string>
|
||||
<string name="guide_buttons_body">Choisissez l’apparence des boutons dans la voiture. Vous pouvez la changer à tout moment dans les paramètres de la voiture.</string>
|
||||
<string name="guide_open_notification_access">Ouvrir l’accès aux notifications</string>
|
||||
<string name="guide_open_android_auto">Ouvrir les infos de l’appli Android Auto</string>
|
||||
<string name="guide_download_voice">Télécharger la voix</string>
|
||||
<string name="guide_set_up">Configurer</string>
|
||||
<string name="guide_voice_downloading">Téléchargement, %1$d %%. Vous pouvez continuer, il se poursuit.</string>
|
||||
<string name="guide_voice_ready">La voix est prête.</string>
|
||||
<string name="guide_voice_failed">Le téléchargement n’a pas abouti. Touchez Télécharger la voix pour réessayer.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Recommandé</string>
|
||||
<string name="guide_audio_bluetooth_body">Aucune demande à chaque trajet, et la voix de votre appli de navigation arrive dans la voiture.</string>
|
||||
<string name="guide_audio_usb">Via Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Un son plus net, sans décalage. À chaque trajet, Android vous demande de partager votre écran, ce qui permet à Gearslip d’envoyer le son.</string>
|
||||
<string name="guide_optional">FACULTATIF</string>
|
||||
<string name="guide_already_allowed">Déjà autorisé.</string>
|
||||
<string name="guide_scroll_more">Faites défiler ↓</string>
|
||||
<string name="guide_allow">Autoriser</string>
|
||||
<string name="guide_skip">Plus tard</string>
|
||||
<string name="guide_open">Ouvrir</string>
|
||||
<string name="guide_next">Suivant</string>
|
||||
<string name="guide_done">Terminé</string>
|
||||
<string name="guide_back">Retour</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Activez ceci, et Gearslip envoie une courte note quand vous l’ouvrez et après chaque trajet. <b>La note ne contient ni nom, ni compte, ni position. Un nombre aléatoire compte votre téléphone une seule fois, et le serveur ne garde aucune adresse IP.</b> Les notes montrent quelles voitures fonctionnent et où les autres échouent.</string>
|
||||
<string name="usage_send">Envoyer des notes d’utilisation</string>
|
||||
<string name="usage_also_share">Partager aussi</string>
|
||||
<string name="usage_share_android">Version et système Android</string>
|
||||
<string name="usage_share_phone">Modèle du téléphone</string>
|
||||
<string name="usage_share_cars">Chaque voiture et si elle s’est connectée</string>
|
||||
<string name="usage_share_drives">Chaque trajet et comment il s’est terminé</string>
|
||||
<string name="usage_what_sent">Ce qui est envoyé</string>
|
||||
<string name="usage_what_sent_sub">Chaque champ, tel qu’il part</string>
|
||||
<string name="usage_hide_note">Masquer la note</string>
|
||||
<string name="usage_show_note">Afficher la note</string>
|
||||
<string name="usage_example">Exemple, non envoyé : un trajet ajoute ceci à « sessions »</string>
|
||||
<string name="usage_no_server">Cette version n’a pas de serveur de statistiques, elle n’envoie donc rien</string>
|
||||
<string name="usage_debug_build">Version de débogage : les notes sont mises à part et jamais comptées dans les statistiques</string>
|
||||
<string name="worked_title">Gearslip a fonctionné avec votre %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip a fonctionné avec votre voiture</string>
|
||||
<string name="worked_body">Le partager pour que le prochain conducteur sache que ça marche ? Ni nom, ni compte, ni position.</string>
|
||||
<string name="worked_not_now">Pas maintenant</string>
|
||||
<string name="worked_share">Partager</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">Ces applis gratuites fonctionnent sur l’écran de voiture de Gearslip. Chaque bouton ouvre la page de l’appli sur GitHub.</string>
|
||||
<string name="recommended_title">Applis recommandées</string>
|
||||
<string name="back">Retour</string>
|
||||
<string name="recommended_installed">Installée</string>
|
||||
<string name="recommended_maps">Cartes</string>
|
||||
<string name="recommended_music">Musique</string>
|
||||
<string name="recommended_podcasts">Podcasts</string>
|
||||
<string name="recommended_other">Autres applis sympas</string>
|
||||
<string name="recommended_about_organic">Cartes et itinéraires hors ligne.</string>
|
||||
<string name="recommended_about_vela">Navigation guidée sans Google.</string>
|
||||
<string name="recommended_about_ytm">Un lecteur YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">Des podcasts, chapitres compris.</string>
|
||||
<string name="recommended_about_rfsentinel">Repère les traceurs et caméras à proximité.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Paramètres</string>
|
||||
<string name="settings_whats_new">Nouveautés</string>
|
||||
<string name="settings_whats_new_sub">Lire les notes de version %1$s sur GitHub</string>
|
||||
<string name="settings_support">Soutenir Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip est gratuit, sans publicité ni pistage. Votre soutien le fait vivre.</string>
|
||||
<string name="settings_usage_notes">Notes d’utilisation</string>
|
||||
<string name="settings_updates">Mises à jour</string>
|
||||
<string name="settings_about">À propos</string>
|
||||
<string name="settings_replay">Revoir le guide de configuration</string>
|
||||
<string name="settings_replay_sub">Refaire les étapes du premier lancement</string>
|
||||
<string name="settings_connection">Connexion</string>
|
||||
<string name="settings_use_in_car">Utiliser Gearslip dans la voiture</string>
|
||||
<string name="settings_use_in_car_on">Gearslip répond quand vous branchez le téléphone à une voiture</string>
|
||||
<string name="settings_use_in_car_off">Désactivé : Android Auto peut prendre la connexion à la place</string>
|
||||
<string name="settings_apps">Applis</string>
|
||||
<string name="settings_certificate">Certificat</string>
|
||||
<string name="cert_next_connection">Les changements s’appliquent à la prochaine connexion.</string>
|
||||
<string name="cert_checking">Vérification…</string>
|
||||
<string name="cert_valid_until">Valide jusqu’au %1$s</string>
|
||||
<string name="cert_hide_details">Masquer les détails</string>
|
||||
<string name="cert_show_details">Afficher les détails</string>
|
||||
<string name="cert_subject">Sujet</string>
|
||||
<string name="cert_issuer">Émetteur</string>
|
||||
<string name="cert_downloading">Téléchargement…</string>
|
||||
<string name="cert_download">Télécharger depuis aasdk</string>
|
||||
<string name="cert_downloaded">Certificat téléchargé.</string>
|
||||
<string name="cert_download_failed">Échec du téléchargement : %1$s</string>
|
||||
<string name="cert_download_sub">Le certificat que le projet libre aasdk publie sur GitHub. La plupart des voitures l’acceptent, mais certaines à partir de 2020 environ le refusent</string>
|
||||
<string name="cert_import">Importer mon propre fichier</string>
|
||||
<string name="cert_import_sub">Un fichier .p12 avec sa clé privée. Utilisez le vôtre si votre voiture refuse le certificat aasdk</string>
|
||||
<string name="cert_remove_imported">Supprimer le certificat importé</string>
|
||||
<string name="cert_remove_downloaded">Supprimer le certificat téléchargé</string>
|
||||
<string name="cert_removed_imported">Certificat importé supprimé.</string>
|
||||
<string name="cert_removed_downloaded">Certificat téléchargé supprimé.</string>
|
||||
<string name="cert_imported">Certificat importé.</string>
|
||||
<string name="cert_import_failed">Import impossible : mot de passe incorrect, ou ce n’est pas un fichier PKCS#12 avec une clé privée.</string>
|
||||
<string name="cert_password_title">Mot de passe du certificat</string>
|
||||
<string name="cert_password">Mot de passe</string>
|
||||
<string name="cert_password_hint">Laissez vide si le fichier n’en a pas.</string>
|
||||
<string name="cert_import_button">Importer</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Annuler</string>
|
||||
<string name="cert_none">Aucun chargé (auto-signé ; les voitures le refusent)</string>
|
||||
<string name="cert_kind_imported">Importé : %1$s</string>
|
||||
<string name="cert_kind_downloaded">Téléchargé : %1$s</string>
|
||||
<string name="cert_kind_adb">Placé via adb : %1$s</string>
|
||||
<string name="cert_kind_self">Auto-signé : %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Écran</string>
|
||||
<string name="car_group_nav_bar">Barre de navigation et lecteur</string>
|
||||
<string name="car_group_sound">Son et voix</string>
|
||||
<string name="car_group_warnings">Avertissements</string>
|
||||
<string name="car_group_more">Plus</string>
|
||||
<string name="choice_size_normal">Normale</string>
|
||||
<string name="choice_size_large">Grande</string>
|
||||
<string name="choice_size_xlarge">Très grande</string>
|
||||
<string name="choice_theme_phone">Comme le téléphone</string>
|
||||
<string name="choice_theme_light">Clair</string>
|
||||
<string name="choice_theme_dark">Sombre</string>
|
||||
<string name="choice_dark_black">Noir</string>
|
||||
<string name="choice_dark_black_colour">Noir et couleur</string>
|
||||
<string name="choice_dark_deep_colour">Couleur profonde</string>
|
||||
<string name="choice_dark_grey">Gris</string>
|
||||
<string name="choice_auto">Auto</string>
|
||||
<string name="choice_night_day">Jour</string>
|
||||
<string name="choice_night_night">Nuit</string>
|
||||
<string name="choice_units_imperial">Impériales</string>
|
||||
<string name="choice_units_metric">Métriques</string>
|
||||
<string name="choice_nav_show">Toujours afficher</string>
|
||||
<string name="choice_nav_hide_navigating">Masquer pendant la navigation</string>
|
||||
<string name="choice_nav_hide">Masquer jusqu’à un toucher</string>
|
||||
<string name="choice_open_home">Accueil</string>
|
||||
<string name="summary_player_left">Lecteur à gauche</string>
|
||||
<string name="summary_player_right">Lecteur à droite</string>
|
||||
<string name="summary_usb_audio">Audio via Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Audio Bluetooth</string>
|
||||
<string name="summary_directions_on">Guidage vocal activé</string>
|
||||
<string name="summary_directions_off">Guidage vocal désactivé</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth activé</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth désactivé</string>
|
||||
<string name="summary_location_on">Localisation activée</string>
|
||||
<string name="summary_location_off">Localisation désactivée</string>
|
||||
<string name="web_page">Page web</string>
|
||||
<string name="summary_experimental_shown">Fonctions expérimentales affichées</string>
|
||||
<string name="summary_experimental_hidden">Fonctions expérimentales masquées</string>
|
||||
<string name="home_car">Voiture</string>
|
||||
<string name="car_group_same">Ce sont les mêmes réglages que Paramètres dans la voiture. Un changement ici y apparaît aussi.</string>
|
||||
<string name="car_open_on_connect">Ouvrir à la connexion</string>
|
||||
<string name="car_size">Taille</string>
|
||||
<string name="car_app_theme">Thème de l’appli</string>
|
||||
<string name="car_dark_mode">Mode sombre</string>
|
||||
<string name="car_buttons">Boutons</string>
|
||||
<string name="car_map_day_night">Carte jour/nuit</string>
|
||||
<string name="car_units">Unités</string>
|
||||
<string name="car_nav_bar">Barre de navigation</string>
|
||||
<string name="car_player_nav_bar">Lecteur dans la barre de navigation</string>
|
||||
<string name="car_skip_buttons">Boutons précédent/suivant</string>
|
||||
<string name="car_song_title">Titre du morceau</string>
|
||||
<string name="car_player_beside_map">Lecteur à côté de la carte</string>
|
||||
<string name="car_right">Droite</string>
|
||||
<string name="car_left">Gauche</string>
|
||||
<string name="car_autoplay">Lecture auto à la connexion</string>
|
||||
<string name="car_more_lyrics">Plus de sources de paroles</string>
|
||||
<string name="car_more_lyrics_note">Interroge aussi BetterLyrics et KuGou, qui reçoivent alors le titre et l’artiste du morceau. Désactivé, seul LRCLIB est interrogé.</string>
|
||||
<string name="car_spoken_directions">Guidage vocal</string>
|
||||
<string name="car_spoken_directions_note">Désactivez-le si la voix de votre appli de navigation passe déjà dans la voiture.</string>
|
||||
<string name="car_phone_speech">Utiliser la reconnaissance vocale du téléphone</string>
|
||||
<string name="car_phone_speech_note">Sur la plupart des téléphones, c’est celle de Google, qui peut envoyer votre voix à Google.</string>
|
||||
<string name="car_warn_bluetooth">Avertir quand le Bluetooth n’est pas connecté</string>
|
||||
<string name="car_warn_location">Avertir quand la localisation est désactivée</string>
|
||||
<string name="car_experimental">Fonctions expérimentales</string>
|
||||
<string name="car_vehicle_data">Données du véhicule</string>
|
||||
<string name="car_vehicle_data_note">Ajoute une tuile Données du véhicule : un relevé en direct des capteurs de la voiture. Les noms des champs sont des suppositions tant que vous n’en avez pas vu un changer.</string>
|
||||
<string name="car_adaptive_video">Qualité vidéo adaptative</string>
|
||||
<string name="car_adaptive_video_note">Baisse la qualité de l’image quand la voiture ou son Wi-Fi ne suit pas, et la remonte dès que possible. S’applique à la prochaine connexion.</string>
|
||||
<string name="web_page_note">Affichée par l’appli Navigateur dans la voiture. Vide, elle ouvre DuckDuckGo. S’applique à la prochaine connexion.</string>
|
||||
<string name="car_audio">Audio de la voiture</string>
|
||||
<string name="car_audio_bt">Bluetooth (recommandé)</string>
|
||||
<string name="car_audio_bt_sub">Aucune demande à chaque trajet, et la voix de votre appli de navigation arrive dans la voiture</string>
|
||||
<string name="car_audio_usb_sub">Un son plus net, sans décalage. À chaque trajet, Android vous demande de partager votre écran, ce qui permet à Gearslip d’envoyer le son</string>
|
||||
<string name="car_audio_usb_footer">Lancez une fois la commande de l’Aide, et Android cesse de demander.</string>
|
||||
<string name="car_audio_bt_footer">Les appels passent par Bluetooth dans tous les cas.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Taille de l’écran de la voiture</string>
|
||||
<string name="screen_use_cars">Celle de la voiture</string>
|
||||
<string name="screen_preview_size">Taille de l’aperçu</string>
|
||||
<string name="screen_preview_note">La taille à laquelle l’aperçu de la voiture est dessiné, par exemple 1920x720.</string>
|
||||
<string name="screen_car_screen">Écran de la voiture</string>
|
||||
<string name="screen_footer">Gearslip utilise la taille indiquée par la voiture, sauf si vous en définissez une.</string>
|
||||
<string name="screen_size">Taille de l’écran</string>
|
||||
<string name="screen_from_car">Selon la voiture</string>
|
||||
<string name="screen_crop">Rogner les bords</string>
|
||||
<string name="screen_crop_sub">Pour une voiture qui coupe le haut et le bas, ou les côtés</string>
|
||||
<string name="screen_squash">Compresser pour ajuster</string>
|
||||
<string name="screen_squash_sub">Pour une voiture qui écrase toute l’image, si bien que les cercles ressemblent à des ovales</string>
|
||||
<string name="screen_preview_title">Taille de l’aperçu voiture</string>
|
||||
<string name="screen_last_car">Dernière voiture</string>
|
||||
<string name="screen_last_car_sub">La taille de la dernière voiture à laquelle vous vous êtes connecté</string>
|
||||
<string name="screen_this_screen">Cet écran</string>
|
||||
<string name="screen_this_screen_sub">Remplit l’écran de cet appareil, pour un autoradio qui fait lui-même tourner Gearslip</string>
|
||||
<string name="screen_custom">Personnalisée</string>
|
||||
<string name="screen_enter_size">Saisir une taille</string>
|
||||
<string name="screen_change_custom">Modifier la taille personnalisée</string>
|
||||
<string name="screen_width">Largeur</string>
|
||||
<string name="screen_height">Hauteur</string>
|
||||
<string name="save">Enregistrer</string>
|
||||
|
||||
<string name="screen_car_size_note">Utilisez ceci si l’image dans la voiture paraît étirée. Saisissez la taille réelle de l’écran, par exemple 1280x480. Elle s’applique à la prochaine connexion.</string>
|
||||
<string name="screen_set_by_you">%1$s, définie par vous</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Comment se connecter</string>
|
||||
<string name="help_which_cars">Quelles voitures fonctionnent</string>
|
||||
<string name="help_full_list">Voir la liste complète</string>
|
||||
<string name="help_first_drive">Avant votre premier trajet</string>
|
||||
<string name="help_in_car">Dans la voiture</string>
|
||||
<string name="help_usb_audio">Audio via Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Désactivez Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Désactivez ou désinstallez l’appli Android Auto, pour que la voiture parle à Gearslip à la place.</string>
|
||||
<string name="help_cert">Chargez un certificat.</string>
|
||||
<string name="help_cert_detail">Dans les Paramètres, téléchargez le certificat aasdk, que la plupart des voitures acceptent. Si votre voiture le refuse, importez plutôt le vôtre.</string>
|
||||
<string name="help_mic">Autorisez le microphone.</string>
|
||||
<string name="help_mic_detail">Sans lui, Android ne laisse aucune appli envoyer de la musique à la voiture. Gearslip n’écoute que lorsque vous touchez le micro ou appuyez sur le bouton vocal du volant.</string>
|
||||
<string name="help_plug_in">Branchez le téléphone, ou connectez-vous sans fil.</string>
|
||||
<string name="help_plug_in_detail">Gearslip s’ouvre tout seul quand la voiture se connecte. Pour le sans-fil, associez d’abord le téléphone à la voiture, puis touchez Se connecter sans fil sur l’Accueil.</string>
|
||||
<string name="help_pair">Associez en Bluetooth pour les appels.</string>
|
||||
<string name="help_pair_detail">Les appels passent par la voiture une fois le téléphone et la voiture associés. Gearslip propose l’association la première fois que vous branchez le téléphone.</string>
|
||||
<string name="help_answer">Répondez aux appels depuis l’écran de la voiture.</string>
|
||||
<string name="help_answer_detail">Définissez Gearslip comme appli d’identification de l’appelant et anti-spam. Sinon, les appels sonnent quand même, mais seul le téléphone peut y répondre.</string>
|
||||
<string name="help_prompt">Supprimez la demande audio à chaque trajet.</string>
|
||||
<string name="help_prompt_detail">Branchez le téléphone à un ordinateur avec le débogage USB activé, et lancez ceci une fois. Sinon, vous touchez Démarrer à chaque départ.</string>
|
||||
<string name="help_readable">Gardez les notifications lisibles.</string>
|
||||
<string name="help_readable_detail">Android masque le contenu des notifications pendant qu’il partage l’audio. Lancez aussi ceci, et Gearslip lève ce masquage seulement pendant que la musique part vers la voiture.</string>
|
||||
<string name="help_which_cars_body">La plupart des voitures acceptent le certificat aasdk. Depuis 2020 environ, certaines ont ajouté des contrôles de sécurité plus récents qui le refusent, et dans ces voitures Gearslip a besoin de votre propre certificat.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Téléchargée. Android demande avant d’installer.</string>
|
||||
<string name="update_install">Installer</string>
|
||||
<string name="update_download_now">Téléchargez-la maintenant. Android demande avant d’installer.</string>
|
||||
<string name="update_try_again">Réessayer</string>
|
||||
<string name="update_update">Mettre à jour</string>
|
||||
<string name="update_checking">Vérification…</string>
|
||||
<string name="update_latest">Vous avez la dernière version</string>
|
||||
<string name="update_download">Télécharger</string>
|
||||
<string name="update_check_now">Vérifier maintenant</string>
|
||||
<string name="update_check_auto">Vérifier automatiquement</string>
|
||||
<string name="update_once_a_day">Une fois par jour</string>
|
||||
|
||||
<string name="update_unplug_first">Débranchez le téléphone de la voiture pour installer. L’installation ferme Gearslip.</string>
|
||||
<string name="update_card_title">Gearslip %1$s est sorti</string>
|
||||
<string name="update_downloading">Téléchargement… %1$d %%</string>
|
||||
<string name="update_debug_manual">Les versions de débogage ne vérifient que lorsque vous touchez Vérifier maintenant</string>
|
||||
<string name="update_checks_daily">Gearslip vérifie une fois par jour</string>
|
||||
<string name="update_version_out">La version %1$s est sortie</string>
|
||||
<string name="update_version_ready">La version %1$s est prête à être installée</string>
|
||||
<string name="update_dialog_status">Version %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Donner chaque semaine ou chaque mois</string>
|
||||
<string name="about_bmac_sub" formatted="false">Donnez une fois ou chaque mois, par carte. Buy Me a Coffee garde 5 %</string>
|
||||
<string name="about_give_once">Donner une fois</string>
|
||||
<string name="about_any_amount">N’importe quel montant, par carte</string>
|
||||
<string name="about_monero_sub">Ouvrez votre portefeuille ou copiez l’adresse</string>
|
||||
<string name="about_monero_title">Soutenir avec Monero</string>
|
||||
<string name="about_monero_body">Envoyez le montant de votre choix à cette adresse depuis votre portefeuille Monero.</string>
|
||||
<string name="about_open_wallet">Ouvrir dans le portefeuille</string>
|
||||
<string name="about_copy_address">Copier l’adresse</string>
|
||||
<string name="close">Fermer</string>
|
||||
<string name="about_matrix">Rejoindre la discussion sur Matrix</string>
|
||||
<string name="about_matrix_sub">Parlez de voitures, d’applis et de Gearslip avec d’autres conducteurs</string>
|
||||
<string name="about_translate">Aider à traduire Gearslip</string>
|
||||
<string name="about_translate_sub">Corrigez ou ajoutez votre langue sur Weblate. Certaines traductions ont commencé comme traductions automatiques.</string>
|
||||
<string name="about_source">Code source</string>
|
||||
<string name="about_source_sub">Lisez ou modifiez le code de Gearslip sous licence AGPL</string>
|
||||
<string name="about_voice_credits">Crédits de la voix</string>
|
||||
|
||||
<string name="about_no_wallet">Aucune appli de portefeuille Monero trouvée. Copiez plutôt l’adresse.</string>
|
||||
<string name="about_monero_copied">Adresse Monero copiée</string>
|
||||
<string name="about_voice_credits_sub">Reconnaissance vocale par sherpa-onnx (Apache 2.0), avec Kroko ASR de Banafo (CC BY-SA 4.0), et Parakeet et Canary de NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Configuration du certificat</string>
|
||||
<string name="certsetup_installed">Certificat installé.</string>
|
||||
<string name="certsetup_continue">Continuer</string>
|
||||
<string name="certsetup_failed">Échec du téléchargement</string>
|
||||
<string name="certsetup_retry">Réessayer</string>
|
||||
<string name="certsetup_import_sub">Un fichier .p12 avec sa clé privée. Utilisez le vôtre si votre voiture refuse le certificat aasdk.</string>
|
||||
<string name="certsetup_skip">Passer la configuration de la connexion à la voiture</string>
|
||||
|
||||
<string name="certsetup_loaded">Les voitures n’acceptent que les certificats émis par Google, et vous en avez un de chargé.</string>
|
||||
<string name="certsetup_choose">Les voitures n’acceptent que les certificats émis par Google. Gearslip n’en contient aucun, alors choisissez d’où vient le vôtre.</string>
|
||||
<string name="certsetup_aasdk_sub">Le projet libre aasdk publie ce certificat sur github.com/opencardev/aasdk. La plupart des voitures l’acceptent. Certaines à partir de 2020 environ le refusent, et il faut alors importer le vôtre.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Stockage de Gearslip</string>
|
||||
<string name="storage_measuring">Mesure…</string>
|
||||
<string name="storage_lyrics">Paroles</string>
|
||||
<string name="storage_clear">Effacer</string>
|
||||
<string name="storage_voice">Modèles vocaux</string>
|
||||
<string name="storage_remove">Supprimer</string>
|
||||
<string name="storage_logs">Anciens journaux</string>
|
||||
<string name="storage_other">Autres fichiers temporaires</string>
|
||||
<string name="storage_delete_all">Supprimer toutes les données de Gearslip</string>
|
||||
|
||||
<string name="storage_total">Les données de Gearslip occupent %1$s du stockage du téléphone.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d morceau · %2$s. Les paroles effacées se téléchargent à nouveau quand vous les ouvrez.</item>
|
||||
<item quantity="many">%1$d morceaux · %2$s. Les paroles effacées se téléchargent à nouveau quand vous les ouvrez.</item>
|
||||
<item quantity="other">%1$d morceaux · %2$s. Les paroles effacées se téléchargent à nouveau quand vous les ouvrez.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Les commandes vocales en ont besoin ; retéléchargez-les dans les Paramètres.</string>
|
||||
<string name="storage_logs_detail">%1$s. Journaux des lancements précédents ; celui de ce lancement est conservé.</string>
|
||||
<string name="storage_other_detail">%1$s. Mises à jour téléchargées et autres fichiers que Gearslip peut recréer.</string>
|
||||
<string name="storage_delete_armed">Touchez à nouveau Supprimer pour effacer les paramètres, certificats, modèles vocaux et journaux. Gearslip se ferme et repart de la configuration la prochaine fois.</string>
|
||||
<string name="storage_delete_sub">Paramètres, certificats, modèles vocaux, paroles et journaux.</string>
|
||||
<string name="storage_delete">Supprimer</string>
|
||||
<string name="storage_delete_ellipsis">Supprimer…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Content que ce trajet ait marché</string>
|
||||
<string name="donate_liberapay">Soutenir sur Liberapay</string>
|
||||
<string name="donate_bmac">Offrez-moi un café</string>
|
||||
<string name="donate_card">Donner une fois par carte</string>
|
||||
|
||||
<string name="donate_body">Gearslip est gratuit, sans publicité ni pistage. S’il vous met sur la route, votre soutien le fait vivre.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">Gearslip a besoin de l’accès au Bluetooth et au Wi-Fi à proximité pour cela.</string>
|
||||
<string name="wireless_enable_permission">Activer l’autorisation Appareils à proximité</string>
|
||||
<string name="wireless_connect">Se connecter sans fil</string>
|
||||
<string name="wireless_which_car">Quelle voiture ?</string>
|
||||
<string name="wireless_none_paired">Votre téléphone n’est encore associé à rien. Associez-le d’abord à la voiture.</string>
|
||||
<string name="wireless_not_listed">La voiture n’apparaît pas ? Associez-la d’abord à votre téléphone.</string>
|
||||
<string name="bluetooth_settings">Paramètres Bluetooth</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Voix : mots en direct</string>
|
||||
<string name="voice_final_title">Voix : mots finaux</string>
|
||||
<string name="voice_none">Aucun</string>
|
||||
<string name="voice_none_sub">Garder les mots en direct. Le plus rapide, le moins précis</string>
|
||||
|
||||
<string name="voice_live_body">L’assistant vocal transforme la parole en texte sur ce téléphone, et rien de ce que vous dites n’en sort. Il lui faut deux modèles, environ 270 Mo à eux deux, téléchargés une seule fois. Celui-ci écrit vos mots pendant que vous parlez.</string>
|
||||
<string name="voice_final_body">Quand vous arrêtez de parler, ce modèle relit toute la phrase. Il saisit les noms et les lieux que le modèle en direct manque.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d Mo</string>
|
||||
<string name="voice_downloaded">Téléchargé. %1$s</string>
|
||||
<string name="voice_downloading">Téléchargement, %1$d %%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Aide</string>
|
||||
<string name="home_preview">Aperçu</string>
|
||||
<string name="home_logs">Journaux</string>
|
||||
<string name="home_disconnect">Déconnecter</string>
|
||||
<string name="home_bt_title">Le Bluetooth n’est pas connecté</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · version de débogage</string>
|
||||
<string name="home_bt_body">La musique passe par Bluetooth, mais votre téléphone n’est pas connecté à la voiture. Connectez-le ici ou depuis le menu Bluetooth de la voiture.</string>
|
||||
<string name="home_voice_downloading">Téléchargement de la voix, %1$d %%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Diffuser l’audio de la voiture en Bluetooth ?</string>
|
||||
<string name="audio_never_ask">Ne plus demander, avec un ordinateur</string>
|
||||
<string name="audio_use_bt">Utiliser le Bluetooth</string>
|
||||
<string name="audio_keep_asking">Continuer à demander</string>
|
||||
<string name="audio_mic_title">L’audio de la voiture a besoin de l’autorisation du microphone</string>
|
||||
<string name="audio_open_settings">Ouvrir les paramètres de l’appli</string>
|
||||
|
||||
<string name="audio_bt_body">Android traite la musique que Gearslip envoie à la voiture comme un partage d’écran, il demande donc à chaque trajet, par câble ou sans fil. En Bluetooth, il ne demande jamais. Vous pourrez changer cela plus tard dans les Paramètres.</string>
|
||||
<string name="audio_keep_usb">Ou gardez le son via Gearslip et supprimez la question pour de bon : branchez le téléphone à un ordinateur avec le débogage USB activé, et lancez ceci une fois.</string>
|
||||
<string name="audio_mic_body">Android ne laisse une appli envoyer le son des autres applis à la voiture que si elle a l’autorisation du microphone. Elle est désactivée pour Gearslip, donc la musique joue sur le téléphone. Activez-la sous Autorisations, puis Microphone.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Fermer le clavier de la voiture</string>
|
||||
<string name="typing_label">Saisir pour l’écran de la voiture</string>
|
||||
<string name="typing_search">Rechercher sur l’écran de la voiture</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Touchez pour copier</string>
|
||||
<string name="command_copied">Commande copiée</string>
|
||||
<string name="whats_new_this_version">%1$s (cette version)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Thème</string>
|
||||
<string name="carset_imperial">Impériales (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Métriques (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Taille de l’écran (prochaine connexion)</string>
|
||||
<string name="carset_fit">Ajuster à l’écran</string>
|
||||
<string name="off">Désactivé</string>
|
||||
<string name="on">Activé</string>
|
||||
<string name="carset_lyrics_from">Paroles depuis</string>
|
||||
<string name="carset_more_sources">Plus de sources</string>
|
||||
<string name="carset_hide">Masquer</string>
|
||||
<string name="carset_show">Afficher</string>
|
||||
<string name="carset_phone">Téléphone</string>
|
||||
<string name="carset_vehicle">Véhicule</string>
|
||||
<string name="carset_video">Vidéo</string>
|
||||
<string name="carset_version">Version</string>
|
||||
<string name="carset_build">Build</string>
|
||||
<string name="carset_whats_new_after">Nouveautés après les mises à jour</string>
|
||||
<string name="carset_whats_new_after_sub">Afficher la liste des changements une fois, la première fois que la voiture se connecte après une mise à jour.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (par défaut) - touchez pour choisir une autre page</string>
|
||||
<string name="carset_web_note">Affichée par l’appli Navigateur du lanceur. S’applique à la prochaine connexion.</string>
|
||||
<string name="carset_screen_fit">Ajustement de l’écran</string>
|
||||
<string name="carset_top">Haut</string>
|
||||
<string name="carset_bottom">Bas</string>
|
||||
<string name="carset_save_vehicle">Enregistrer pour ce véhicule</string>
|
||||
<string name="carset_no_profile">Aucun profil de véhicule correspondant</string>
|
||||
|
||||
<string name="carset_audio_note">Via Gearslip envoie le son du téléphone par la connexion à la voiture, par câble ou sans fil. Android demande de partager votre écran à chaque trajet, et quelques applis le bloquent. Le Bluetooth joue comme le téléphone le ferait normalement, et Android ne demande jamais rien.</string>
|
||||
<string name="carset_directions_note">Gearslip lit chaque changement de direction à voix haute dans les haut-parleurs de la voiture, et la musique baisse pendant ce temps. Désactivez-le si la voix de votre appli de navigation passe déjà dans la voiture.</string>
|
||||
<string name="carset_phone_speech_note">Écoute via le service vocal du téléphone au lieu de celui de Gearslip. Sur la plupart des téléphones, c’est celui de Google, qui peut envoyer votre voix à Google. GrapheneOS n’en a pas, donc Gearslip continue d’utiliser le sien.</string>
|
||||
<string name="carset_vehicle_data_note">Ajoute une tuile Données du véhicule : un relevé brut et en direct du canal des capteurs de l’autoradio. Les noms des champs (vitesse, régime, carburant…) viennent d’autres projets de rétro-ingénierie et n’ont pas été vérifiés sur cette voiture. Considérez-les comme une supposition tant que vous n’en avez pas vu un changer vous-même.</string>
|
||||
<string name="carset_adaptive_note">Baisse la qualité de l’image quand la voiture, ou son Wi-Fi, ne suit pas, et la remonte dès que possible. S’applique à la prochaine connexion.</string>
|
||||
<string name="carset_usable_height">Hauteur utile %1$dpx sur 480</string>
|
||||
<string name="carset_saved_to">Enregistré dans %1$s</string>
|
||||
<string name="carset_save_failed">Enregistrement impossible</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Ouvrir sur le téléphone</string>
|
||||
<string name="media_now_playing">En cours de lecture</string>
|
||||
<string name="media_up_next">À suivre</string>
|
||||
<string name="media_browse">Parcourir</string>
|
||||
<string name="media_search_hint">Saisissez un morceau, un artiste, un album ou une playlist.</string>
|
||||
<string name="media_looking_lyrics">Recherche des paroles…</string>
|
||||
<string name="media_nothing_playing">Aucune lecture</string>
|
||||
<string name="media_press_play">Appuyez sur lecture pour reprendre où vous en étiez, ou trouvez quelque chose dans Parcourir.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s garde sa bibliothèque pour lui.</string>
|
||||
<string name="media_needs_access">Gearslip peut quand même le contrôler s’il a l’accès aux notifications sur le téléphone. Autorisez-le sous Paramètres > Notifications > Accès aux notifications.</string>
|
||||
<string name="media_not_running">%1$s n’est pas lancé.</string>
|
||||
<string name="media_not_running_body">%1$s joue via le lecteur de Gearslip, mais seulement s’il est ouvert sur le téléphone. Ouvrez-le une fois et il continue de jouer ici, dans la voiture.</string>
|
||||
<string name="media_open_app">Ouvrir %1$s</string>
|
||||
<string name="media_refused">%1$s n’a pas laissé Gearslip le parcourir.</string>
|
||||
<string name="media_refused_body">Certaines applis multimédias n’acceptent que l’hôte de Google.</string>
|
||||
<string name="media_connecting">Connexion à %1$s…</string>
|
||||
<string name="media_search">Rechercher dans %1$s</string>
|
||||
<string name="media_search_no_list">%1$s ne peut pas lister de résultats. Appuyez sur Rechercher, et %1$s choisit quoi jouer.</string>
|
||||
<string name="media_search_unsupported">%1$s ne permet pas de rechercher depuis la voiture.</string>
|
||||
<string name="media_searching">Recherche dans %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s n’a rien trouvé pour « %2$s ».</string>
|
||||
<string name="media_search_ignored">%1$s n’a rien lancé pour « %2$s ».</string>
|
||||
<string name="media_searching_for">Recherche de « %2$s » dans %1$s…</string>
|
||||
<string name="media_nothing_playing_dot">Aucune lecture.</string>
|
||||
<string name="media_no_lyrics">Aucune parole trouvée pour ce morceau.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">Haut</string>
|
||||
<string name="media_loading">Chargement…</string>
|
||||
<string name="media_browse_start">Lancez la musique dans l’appli du téléphone. Lecture, pause, morceau suivant et avance rapide fonctionnent d’ici.</string>
|
||||
<string name="media_load_failed">Impossible de charger cette liste.</string>
|
||||
<string name="media_nothing_here">Rien ici.</string>
|
||||
<string name="media_nothing_queued">Rien dans la file.</string>
|
||||
<string name="media_play">Lire</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Aucune lecture - toutes les applis n’indiquent pas à Gearslip ce qui vient ensuite.</string>
|
||||
<string name="media_queue_empty_playing">Seulement ce qui joue déjà - toutes les applis n’indiquent pas à Gearslip ce qui vient ensuite.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Précédent</string>
|
||||
<string name="player_next">Suivant</string>
|
||||
<string name="player_repeat">Répéter</string>
|
||||
<string name="player_pause">Pause</string>
|
||||
<string name="player_unlike">Je n’aime plus</string>
|
||||
<string name="player_like">J’aime</string>
|
||||
<string name="player_shuffle_off">Lecture aléatoire désactivée</string>
|
||||
<string name="player_shuffle_on">Lecture aléatoire activée</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Choisir une autre appli</string>
|
||||
<string name="home_no_maps">Aucune appli de cartes trouvée.</string>
|
||||
<string name="home_no_maps_body">Installez une appli de navigation compatible avec Android Auto, et elle démarrera ici d’elle-même.</string>
|
||||
<string name="home_map_stopped">Carte arrêtée</string>
|
||||
<string name="home_map_stopped_body">Elle reste fermée jusqu’à ce que vous la rouvriez.</string>
|
||||
<string name="home_reopen_map">Rouvrir la carte</string>
|
||||
<string name="home_car_apps">Applis voiture</string>
|
||||
<string name="home_back_nav">Retour à la barre de navigation</string>
|
||||
<string name="home_show_player">Afficher le lecteur</string>
|
||||
<string name="home_hide_lyrics">Masquer les paroles</string>
|
||||
|
||||
<string name="home_starting">Démarrage de %1$s…</string>
|
||||
<string name="home_starting_map">Démarrage de la carte…</string>
|
||||
<string name="home_failed">%1$s n’a pas voulu démarrer.</string>
|
||||
<string name="home_failed_map">L’appli de cartes n’a pas voulu démarrer.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Accueil</string>
|
||||
<string name="crumb_media">Médias</string>
|
||||
<string name="crumb_apps">Applis</string>
|
||||
<string name="crumb_dashboard">Tableau de bord</string>
|
||||
<string name="crumb_weather">Météo</string>
|
||||
<string name="crumb_reply">Répondre</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Navigateur</string>
|
||||
<string name="app_screen_sharing">Partage d’écran</string>
|
||||
<string name="app_exit">Quitter</string>
|
||||
<string name="navapps_unpin">Détacher de la barre de navigation</string>
|
||||
<string name="navapps_full">La barre de navigation est pleine (%1$d épinglées)</string>
|
||||
<string name="navapps_pin">Épingler à la barre de navigation</string>
|
||||
<string name="navapps_close">Fermer l’appli</string>
|
||||
<string name="navapps_move_left">Déplacer à gauche</string>
|
||||
<string name="navapps_move_right">Déplacer à droite</string>
|
||||
<string name="navapps_reset">Réinitialiser l’ordre des applis</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Ignorer</string>
|
||||
<string name="msg_read">Lire</string>
|
||||
<string name="msg_stop">Arrêter</string>
|
||||
<string name="msg_no">Non</string>
|
||||
<string name="msg_send_now">Envoyer maintenant</string>
|
||||
<string name="msg_type">Saisir</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nouveau</item>
|
||||
<item quantity="many">%1$s · %2$d nouveaux</item>
|
||||
<item quantity="other">%1$s · %2$d nouveaux</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Répondre à %1$s</string>
|
||||
<string name="msg_listening">Écoute</string>
|
||||
<string name="msg_sending_to">Envoi à %1$s</string>
|
||||
<string name="msg_sent_to">Envoyé à %1$s</string>
|
||||
<string name="msg_reply_failed">Impossible de répondre</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">L’association n’a pas abouti. Associez plutôt depuis le menu Bluetooth de la voiture.</string>
|
||||
<string name="bt_pair_not_started">L’association n’a pas démarré. Associez plutôt depuis le menu Bluetooth de la voiture.</string>
|
||||
<string name="bt_paired">Associé. Les appels passent maintenant par la voiture.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Ne plus afficher</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">La localisation est désactivée</string>
|
||||
<string name="overlay_pair_title">Associer à cette voiture ?</string>
|
||||
<string name="overlay_pair">Associer</string>
|
||||
<string name="overlay_confirm_title">Confirmez sur votre téléphone</string>
|
||||
<string name="overlay_confirm_body">Vérifiez le code sur votre téléphone et touchez Associer. La voiture peut aussi le demander.</string>
|
||||
|
||||
<string name="overlay_bt_body">La musique passe par Bluetooth, mais votre téléphone n’est pas connecté à la voiture. Connectez-le depuis le menu Bluetooth de la voiture ou celui du téléphone.</string>
|
||||
<string name="overlay_location_body">Les applis de cartes ne peuvent ni montrer où vous êtes ni vous guider. Activez la localisation dans les réglages rapides du téléphone une fois garé.</string>
|
||||
<string name="overlay_pair_body_music">Les appels et la musique passent par les haut-parleurs de la voiture une fois le téléphone et la voiture associés. Le téléphone vous demande ensuite de confirmer un code.</string>
|
||||
<string name="overlay_pair_body">Les appels passent par les haut-parleurs de la voiture une fois le téléphone et la voiture associés. Le téléphone vous demande ensuite de confirmer un code.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">En attente de la position.</string>
|
||||
<string name="weather_allow_location">Autorisez la localisation pour Gearslip sur le téléphone pour voir la météo.</string>
|
||||
<string name="weather_unreachable">Impossible de joindre le service météo.</string>
|
||||
<string name="weather_clear">Dégagé</string>
|
||||
<string name="weather_mostly_clear">Plutôt dégagé</string>
|
||||
<string name="weather_partly_cloudy">Partiellement nuageux</string>
|
||||
<string name="weather_overcast">Couvert</string>
|
||||
<string name="weather_fog">Brouillard</string>
|
||||
<string name="weather_drizzle">Bruine</string>
|
||||
<string name="weather_rain">Pluie</string>
|
||||
<string name="weather_snow">Neige</string>
|
||||
<string name="weather_rain_showers">Averses de pluie</string>
|
||||
<string name="weather_snow_showers">Averses de neige</string>
|
||||
<string name="weather_thunderstorm">Orage</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Récupération de la météo…</string>
|
||||
<string name="weather_next_hours">Prochaines heures</string>
|
||||
<string name="weather_this_week">Cette semaine</string>
|
||||
<string name="weather_today">Aujourd’hui</string>
|
||||
<string name="weather_credit">Données météo par Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Ressenti %1$s</string>
|
||||
<string name="weather_humidity">Humidité %1$d %%</string>
|
||||
<string name="weather_wind">Vent %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">Aujourd’hui</string>
|
||||
<string name="dash_getting_weather">Récupération de la météo…</string>
|
||||
<string name="dash_coming_up">À venir</string>
|
||||
<string name="dash_loading_calendar">Chargement de l’agenda…</string>
|
||||
<string name="dash_nothing_week">Rien dans la semaine à venir.</string>
|
||||
<string name="dash_all_day">Toute la journée</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Contacts</string>
|
||||
<string name="dialer_keypad">Clavier</string>
|
||||
<string name="dialer_ask">Demander sur le téléphone</string>
|
||||
<string name="dialer_enter_number">Saisir un numéro</string>
|
||||
<string name="dialer_call">Appeler</string>
|
||||
<string name="dialer_no_contacts">Aucun contact avec un numéro de téléphone trouvé.</string>
|
||||
<string name="dialer_search">Rechercher des contacts</string>
|
||||
|
||||
<string name="dialer_needs_permission">Gearslip a besoin des autorisations Téléphone et Contacts pour appeler d’ici. Autorisez-les sur le téléphone une fois garé.</string>
|
||||
<string name="dialer_no_match">Aucun contact ne correspond à « %1$s ».</string>
|
||||
<string name="dialer_call_who">Appeler %1$s ?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Quitter le plein écran</string>
|
||||
<string name="carui_drive_safely">Conduisez prudemment</string>
|
||||
<string name="carui_understand">J’ai compris</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip est un logiciel non officiel : il n’est conçu ni vérifié par aucun constructeur, et il peut tomber en panne sans prévenir.</string>
|
||||
<string name="carui_never_interact">N’utilisez jamais cet écran en conduisant. Réglez l’itinéraire ou la musique avant de partir, puis gardez votre attention sur la route.</string>
|
||||
<string name="carui_this_app">Cette appli</string>
|
||||
<string name="carui_app_unsupported">%1$s ne fonctionne pas encore avec Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">Elle n’a pas démarré. Certaines applis ne fonctionnent qu’avec Android Auto de Google.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">En appel</string>
|
||||
<string name="navbar_end_call">Raccrocher</string>
|
||||
<string name="navbar_unmute">Réactiver le micro</string>
|
||||
<string name="navbar_mute">Couper le micro</string>
|
||||
<string name="navbar_close_panel">Fermer le panneau latéral</string>
|
||||
<string name="navbar_dock_player">Placer le lecteur à côté de la carte</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Rechercher ou saisir une adresse</string>
|
||||
<string name="web_forward">Avancer</string>
|
||||
<string name="web_reload">Actualiser</string>
|
||||
<string name="web_start_page">Page d’accueil</string>
|
||||
<string name="web_fullscreen">Plein écran</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Notifications</string>
|
||||
<string name="notif_clear_all">Tout effacer</string>
|
||||
<string name="notif_empty">Rien pour l’instant. Les nouvelles notifications du téléphone apparaîtront ici.</string>
|
||||
<string name="notif_off">Les notifications sont désactivées. Autorisez Gearslip sous Paramètres > Notifications > Accès aux notifications sur le téléphone.</string>
|
||||
<string name="notif_replied">Répondu</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Marquer comme lu</string>
|
||||
<string name="msg_messages">Messages</string>
|
||||
<string name="msg_none_recent">Aucun message récent. Les nouveaux messages %1$s apparaissent ici dès qu’ils arrivent sur le téléphone.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d plus ancien</item>
|
||||
<item quantity="many">%1$d plus anciens</item>
|
||||
<item quantity="other">%1$d plus anciens</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Masquer les réponses rapides</string>
|
||||
<string name="msg_quick">Réponse rapide</string>
|
||||
<string name="msg_you">Vous : </string>
|
||||
<string name="msg_sender">%1$s : </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Recherche d’un itinéraire…</string>
|
||||
<string name="nav_then">Puis</string>
|
||||
<string name="nav_stop">Arrêter la navigation</string>
|
||||
<string name="nav_finding_routes">Recherche d’itinéraires…</string>
|
||||
<string name="nav_lane_recommended">Voie recommandée</string>
|
||||
<string name="nav_lane">Voie</string>
|
||||
<string name="nav_arrive">Arrivée %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Rien en lecture pour l’instant.</string>
|
||||
<string name="template_search">Rechercher</string>
|
||||
<string name="template_start">Démarrer</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Appel entrant</string>
|
||||
<string name="call_decline">Refuser</string>
|
||||
<string name="call_answer">Répondre</string>
|
||||
<string name="call_ringing">Ça sonne déjà à voix haute - répondez ici, ou laissez sonner.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">Le toucher est désactivé. Activez Gearslip sous Paramètres > Accessibilité sur le téléphone.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Afficher après les mises à jour</string>
|
||||
<string name="whatsnew_got_it">Compris</string>
|
||||
<string name="whatsnew_title">Nouveautés de la version %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">espace</string>
|
||||
<string name="keyboard_hide">Masquer le clavier</string>
|
||||
<string name="keyboard_submit">Valider</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Ne fonctionne pas avec Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">Appli voiture</string>
|
||||
<string name="stage_close">Fermer %1$s</string>
|
||||
<string name="stage_close_app">Fermer l’appli</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Tournez légèrement à gauche</string>
|
||||
<string name="dir_slight_left_onto">Tournez légèrement à gauche sur %1$s</string>
|
||||
<string name="dir_slight_right">Tournez légèrement à droite</string>
|
||||
<string name="dir_slight_right_onto">Tournez légèrement à droite sur %1$s</string>
|
||||
<string name="dir_left">Tournez à gauche</string>
|
||||
<string name="dir_left_onto">Tournez à gauche sur %1$s</string>
|
||||
<string name="dir_right">Tournez à droite</string>
|
||||
<string name="dir_right_onto">Tournez à droite sur %1$s</string>
|
||||
<string name="dir_sharp_left">Tournez franchement à gauche</string>
|
||||
<string name="dir_sharp_left_onto">Tournez franchement à gauche sur %1$s</string>
|
||||
<string name="dir_sharp_right">Tournez franchement à droite</string>
|
||||
<string name="dir_sharp_right_onto">Tournez franchement à droite sur %1$s</string>
|
||||
<string name="dir_u_turn">Faites demi-tour</string>
|
||||
<string name="dir_keep_left">Restez à gauche</string>
|
||||
<string name="dir_keep_left_toward">Restez à gauche en direction de %1$s</string>
|
||||
<string name="dir_keep_right">Restez à droite</string>
|
||||
<string name="dir_keep_right_toward">Restez à droite en direction de %1$s</string>
|
||||
<string name="dir_fork_left">À l’embranchement, restez à gauche</string>
|
||||
<string name="dir_fork_left_toward">À l’embranchement, restez à gauche en direction de %1$s</string>
|
||||
<string name="dir_fork_right">À l’embranchement, restez à droite</string>
|
||||
<string name="dir_fork_right_toward">À l’embranchement, restez à droite en direction de %1$s</string>
|
||||
<string name="dir_ramp_left">Prenez la bretelle à gauche</string>
|
||||
<string name="dir_ramp_left_onto">Prenez la bretelle à gauche vers %1$s</string>
|
||||
<string name="dir_ramp_right">Prenez la bretelle à droite</string>
|
||||
<string name="dir_ramp_right_onto">Prenez la bretelle à droite vers %1$s</string>
|
||||
<string name="dir_exit_left">Prenez la sortie à gauche</string>
|
||||
<string name="dir_exit_left_toward">Prenez la sortie à gauche en direction de %1$s</string>
|
||||
<string name="dir_exit_right">Prenez la sortie à droite</string>
|
||||
<string name="dir_exit_right_toward">Prenez la sortie à droite en direction de %1$s</string>
|
||||
<string name="dir_merge_left">Insérez-vous à gauche</string>
|
||||
<string name="dir_merge_left_onto">Insérez-vous à gauche sur %1$s</string>
|
||||
<string name="dir_merge_right">Insérez-vous à droite</string>
|
||||
<string name="dir_merge_right_onto">Insérez-vous à droite sur %1$s</string>
|
||||
<string name="dir_merge">Insérez-vous</string>
|
||||
<string name="dir_merge_onto">Insérez-vous sur %1$s</string>
|
||||
<string name="dir_roundabout_through">Traversez le rond-point</string>
|
||||
<string name="dir_roundabout_through_onto">Traversez le rond-point vers %1$s</string>
|
||||
<string name="dir_roundabout_enter">Entrez dans le rond-point</string>
|
||||
<string name="dir_roundabout_exit">Sortez du rond-point</string>
|
||||
<string name="dir_roundabout_exit_onto">Sortez du rond-point sur %1$s</string>
|
||||
<string name="dir_straight">Continuez tout droit</string>
|
||||
<string name="dir_straight_onto">Continuez tout droit sur %1$s</string>
|
||||
<string name="dir_continue">Continuez</string>
|
||||
<string name="dir_continue_onto">Continuez sur %1$s</string>
|
||||
<string name="dir_ferry">Prenez le ferry</string>
|
||||
<string name="dir_train">Prenez le train</string>
|
||||
<string name="dir_destination">Votre destination est devant vous</string>
|
||||
<string name="dir_destination_left">Votre destination est sur la gauche</string>
|
||||
<string name="dir_destination_right">Votre destination est sur la droite</string>
|
||||
<string name="dir_roundabout_take">Au rond-point, prenez la %1$s sortie</string>
|
||||
<string name="dir_roundabout_take_onto">Au rond-point, prenez la %1$s sortie sur %2$s</string>
|
||||
<string name="dir_ordinal_1">première</string>
|
||||
<string name="dir_ordinal_2">deuxième</string>
|
||||
<string name="dir_ordinal_3">troisième</string>
|
||||
<string name="dir_ordinal_4">quatrième</string>
|
||||
<string name="dir_ordinal_5">cinquième</string>
|
||||
<string name="dir_ordinal_6">sixième</string>
|
||||
<string name="dir_ordinal_7">septième</string>
|
||||
<string name="dir_ordinal_8">huitième</string>
|
||||
<string name="dir_in_distance">Dans %1$s, %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s kilomètre</item>
|
||||
<item quantity="many">%1$s kilomètres</item>
|
||||
<item quantity="other">%1$s kilomètres</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s kilomètres</string>
|
||||
<string name="dir_quarter_mile">un quart de mile</string>
|
||||
<string name="dir_half_mile">un demi-mile</string>
|
||||
<string name="dir_three_quarter_mile">trois quarts de mile</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s mile</item>
|
||||
<item quantity="many">%1$s miles</item>
|
||||
<item quantity="other">%1$s miles</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s miles</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s pied</item>
|
||||
<item quantity="many">%1$s pieds</item>
|
||||
<item quantity="other">%1$s pieds</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s yard</item>
|
||||
<item quantity="many">%1$s yards</item>
|
||||
<item quantity="other">%1$s yards</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d mètre</item>
|
||||
<item quantity="many">%1$d mètres</item>
|
||||
<item quantity="other">%1$d mètres</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Si votre voiture ne se connecte pas, elle refuse peut-être ce certificat. Importez le vôtre dans les Paramètres, sous Certificat.</string>
|
||||
<string name="cert_downloaded_footer">La voiture ne se connecte pas ? Certaines voitures à partir de 2020 environ refusent le certificat aasdk, importez alors le vôtre. Les changements s’appliquent à la prochaine connexion.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Carte de direction sur l’écran du combiné</string>
|
||||
<string name="car_cluster_video_note">Pour une voiture avec un second écran dans le combiné d’instruments : Gearslip y affiche le prochain changement de direction. S’applique à la prochaine connexion.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Non connecté</string>
|
||||
<string name="status_idle_detail">Branchez le téléphone au port USB de la voiture, ou connectez-vous sans fil. Gearslip s’ouvre tout seul quand la voiture répond.</string>
|
||||
<string name="status_connecting">Connexion</string>
|
||||
<string name="status_projecting">Projection</string>
|
||||
<string name="status_projecting_detail">Diffusion vers l’autoradio.</string>
|
||||
<string name="status_disconnected">Déconnecté</string>
|
||||
<string name="status_disconnected_detail">L’autoradio n’est plus connecté.</string>
|
||||
<string name="places_title">Lieux enregistrés</string>
|
||||
<string name="places_sub">Dites « navigate home » dans n’importe quelle app de cartes</string>
|
||||
<string name="places_intro">Dites « navigate home », « take me to work » ou « navigate to » suivi d’un de vos noms, et Gearslip envoie l’adresse à votre app de cartes. Les commandes vocales ne marchent qu’en anglais pour l’instant.</string>
|
||||
<string name="places_home">Domicile</string>
|
||||
<string name="places_work">Travail</string>
|
||||
<string name="places_other">Lieu %1$d</string>
|
||||
<string name="places_name">Nom</string>
|
||||
<string name="places_address">Adresse</string>
|
||||
<string name="places_here">Utiliser ma position</string>
|
||||
<string name="places_finding">Recherche…</string>
|
||||
<string name="places_not_found">Gearslip n’a pas trouvé votre position. Vérifiez que la localisation est activée.</string>
|
||||
<string name="places_clear">Effacer</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,840 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Permette a Gearslip di ripetere sul telefono i tocchi fatti sullo schermo dell’auto durante la condivisione dello schermo. Gearslip non legge il contenuto dello schermo.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Aiuta gli altri automobilisti.</string>
|
||||
<string name="guide_safety_title">Guida con prudenza.</string>
|
||||
<string name="guide_safety_body">Gearslip non è ufficiale. Nessuna casa automobilistica l’ha creato o verificato, e può smettere di funzionare senza preavviso. Configuralo prima di partire, poi tieni gli occhi sulla strada.</string>
|
||||
<string name="guide_mic_title">Microfono.</string>
|
||||
<string name="guide_mic_body">Gearslip lo usa per mandare la tua musica all’auto e per sentirti quando chiedi qualcosa o rispondi a un messaggio. Quello che dici resta sul telefono.</string>
|
||||
<string name="guide_location_title">Posizione.</string>
|
||||
<string name="guide_location_body">La mappa dell’auto e le ricerche nei dintorni ne hanno bisogno. Senza, la dashboard non mostra la mappa.</string>
|
||||
<string name="guide_calendar_title">Calendario.</string>
|
||||
<string name="guide_calendar_body">Lo schermo dell’auto mostra il tuo prossimo evento. Gearslip legge soltanto il calendario.</string>
|
||||
<string name="guide_calls_title">Chiamate e contatti.</string>
|
||||
<string name="guide_calls_body">L’auto mostra il nome di chi chiama, e la sua app Telefono può comporre un numero o chiamare uno dei tuoi contatti. Gearslip li legge soltanto.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">La prima volta che colleghi il telefono, Gearslip propone di associarlo all’auto, così le chiamate passano dai suoi altoparlanti.</string>
|
||||
<string name="guide_notifications_title">Notifiche.</string>
|
||||
<string name="guide_notifications_body">Mentre Gearslip è connesso, mostra una notifica con un pulsante per fermarlo. Senza questo permesso, Android la nasconde.</string>
|
||||
<string name="guide_messages_title">Messaggi.</string>
|
||||
<string name="guide_messages_body">L’auto mostra i tuoi messaggi, li legge ad alta voce e ti permette di rispondere. Attiva Gearslip nell’elenco che si apre.</string>
|
||||
<string name="guide_android_auto_title">Disattiva Android Auto.</string>
|
||||
<string name="guide_android_auto_body">Android Auto è ancora installato e contende a Gearslip la connessione USB dell’auto. Disattivalo o disinstallalo nella schermata che si apre.</string>
|
||||
<string name="guide_default_title">Scegli Gearslip.</string>
|
||||
<string name="guide_default_body">Se il telefono chiede quale app aprire quando lo colleghi all’auto, scegli Gearslip e tocca Sempre.</string>
|
||||
<string name="guide_audio_title">Audio dell’auto.</string>
|
||||
<string name="guide_audio_body">Scegli come la musica arriva all’auto. Le chiamate passano comunque dal Bluetooth.</string>
|
||||
<string name="guide_voice_title">Voce.</string>
|
||||
<string name="guide_voice_body">Chiedi indicazioni, musica o una chiamata a voce. Il telefono trasforma da solo la voce in testo, e niente di quello che dici esce dal telefono. Servono %1$d MB, scaricati una volta sola. Usa il Wi-Fi se puoi.</string>
|
||||
<string name="guide_audio_prompt_title">Niente richiesta audio.</string>
|
||||
<string name="guide_audio_prompt_body">Esegui questo comando da un computer con il debug USB attivo, e Android affida l’audio a Gearslip per sempre. Altrimenti tocchi Avvia una volta a ogni viaggio.</string>
|
||||
<string name="guide_readable_title">Notifiche leggibili.</string>
|
||||
<string name="guide_readable_body">Mentre l’audio va all’auto, Android nasconde il testo delle notifiche, come se stessi condividendo lo schermo. Questo permette a Gearslip di mostrarle durante il viaggio e di nasconderle di nuovo dopo.</string>
|
||||
<string name="guide_answer_title">Rispondi alle chiamate.</string>
|
||||
<string name="guide_answer_body">Imposta Gearslip come app per l’ID chiamante, e l’auto potrà rispondere e rifiutare le chiamate. Altrimenti le chiamate squillano lo stesso, ma solo il telefono può rispondere.</string>
|
||||
<string name="guide_apps_title">Buona compagnia.</string>
|
||||
<string name="guide_buttons_title">I tuoi pulsanti.</string>
|
||||
<string name="guide_buttons_body">Scegli l’aspetto dei pulsanti nell’auto. Puoi cambiarlo quando vuoi nelle impostazioni dell’auto.</string>
|
||||
<string name="guide_open_notification_access">Apri l’accesso alle notifiche</string>
|
||||
<string name="guide_open_android_auto">Apri le info app di Android Auto</string>
|
||||
<string name="guide_download_voice">Scarica la voce</string>
|
||||
<string name="guide_set_up">Configura</string>
|
||||
<string name="guide_voice_downloading">Download, %1$d%%. Puoi andare avanti, continua da solo.</string>
|
||||
<string name="guide_voice_ready">La voce è pronta.</string>
|
||||
<string name="guide_voice_failed">Il download non è finito. Tocca Scarica la voce per riprovare.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Consigliato</string>
|
||||
<string name="guide_audio_bluetooth_body">Nessuna richiesta a ogni viaggio, e la voce dell’app di navigazione arriva all’auto.</string>
|
||||
<string name="guide_audio_usb">Tramite Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Suono più pulito, senza ritardo. A ogni viaggio Android ti chiede di condividere lo schermo, il che permette a Gearslip di inviare il suono.</string>
|
||||
<string name="guide_optional">FACOLTATIVO</string>
|
||||
<string name="guide_already_allowed">Già consentito.</string>
|
||||
<string name="guide_scroll_more">Scorri per altro ↓</string>
|
||||
<string name="guide_allow">Consenti</string>
|
||||
<string name="guide_skip">Salta per ora</string>
|
||||
<string name="guide_open">Apri</string>
|
||||
<string name="guide_next">Avanti</string>
|
||||
<string name="guide_done">Fine</string>
|
||||
<string name="guide_back">Indietro</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Attiva questa opzione, e Gearslip invia una breve nota quando lo apri e dopo ogni viaggio. <b>La nota non contiene nome, account né posizione. Un numero casuale conta il tuo telefono una volta sola, e il server non conserva alcun indirizzo IP.</b> Le note mostrano quali auto funzionano e dove le altre falliscono.</string>
|
||||
<string name="usage_send">Invia note d’uso</string>
|
||||
<string name="usage_also_share">Condividi anche</string>
|
||||
<string name="usage_share_android">Versione e sistema Android</string>
|
||||
<string name="usage_share_phone">Modello del telefono</string>
|
||||
<string name="usage_share_cars">Ogni auto e se si è connessa</string>
|
||||
<string name="usage_share_drives">Ogni viaggio e come è finito</string>
|
||||
<string name="usage_what_sent">Cosa viene inviato</string>
|
||||
<string name="usage_what_sent_sub">Ogni campo, esattamente come parte</string>
|
||||
<string name="usage_hide_note">Nascondi la nota</string>
|
||||
<string name="usage_show_note">Mostra la nota</string>
|
||||
<string name="usage_example">Esempio, non inviato: un viaggio aggiunge questo a “sessions”</string>
|
||||
<string name="usage_no_server">Questa build non ha un server di statistiche, quindi non invia nulla</string>
|
||||
<string name="usage_debug_build">Build di debug: le note sono tenute a parte e mai contate nelle statistiche</string>
|
||||
<string name="worked_title">Gearslip ha funzionato con la tua %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip ha funzionato con la tua auto</string>
|
||||
<string name="worked_body">Vuoi condividerlo, così il prossimo automobilista sa che funziona? Niente nome, account o posizione.</string>
|
||||
<string name="worked_not_now">Non ora</string>
|
||||
<string name="worked_share">Condividi</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">Queste app gratuite funzionano sullo schermo dell’auto di Gearslip. Ogni pulsante apre la pagina dell’app su GitHub.</string>
|
||||
<string name="recommended_title">App consigliate</string>
|
||||
<string name="back">Indietro</string>
|
||||
<string name="recommended_installed">Installata</string>
|
||||
<string name="recommended_maps">Mappe</string>
|
||||
<string name="recommended_music">Musica</string>
|
||||
<string name="recommended_podcasts">Podcast</string>
|
||||
<string name="recommended_other">Altre app interessanti</string>
|
||||
<string name="recommended_about_organic">Mappe e percorsi offline.</string>
|
||||
<string name="recommended_about_vela">Navigazione passo passo senza Google.</string>
|
||||
<string name="recommended_about_ytm">Un lettore per YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">Podcast, capitoli compresi.</string>
|
||||
<string name="recommended_about_rfsentinel">Individua tracker e telecamere nei dintorni.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Impostazioni</string>
|
||||
<string name="settings_whats_new">Novità</string>
|
||||
<string name="settings_whats_new_sub">Leggi le note di rilascio di %1$s su GitHub</string>
|
||||
<string name="settings_support">Sostieni Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip è gratuito, senza pubblicità e senza tracciamento. Il tuo sostegno lo tiene in vita.</string>
|
||||
<string name="settings_usage_notes">Note d’uso</string>
|
||||
<string name="settings_updates">Aggiornamenti</string>
|
||||
<string name="settings_about">Informazioni</string>
|
||||
<string name="settings_replay">Rivedi la guida di configurazione</string>
|
||||
<string name="settings_replay_sub">Ripeti i passaggi del primo avvio</string>
|
||||
<string name="settings_connection">Connessione</string>
|
||||
<string name="settings_use_in_car">Usa Gearslip in auto</string>
|
||||
<string name="settings_use_in_car_on">Gearslip risponde quando colleghi il telefono a un’auto</string>
|
||||
<string name="settings_use_in_car_off">Disattivato: Android Auto può prendere la connessione al suo posto</string>
|
||||
<string name="settings_apps">App</string>
|
||||
<string name="settings_certificate">Certificato</string>
|
||||
<string name="cert_next_connection">Le modifiche valgono dalla prossima connessione.</string>
|
||||
<string name="cert_checking">Verifica…</string>
|
||||
<string name="cert_valid_until">Valido fino al %1$s</string>
|
||||
<string name="cert_hide_details">Nascondi dettagli</string>
|
||||
<string name="cert_show_details">Mostra dettagli</string>
|
||||
<string name="cert_subject">Soggetto</string>
|
||||
<string name="cert_issuer">Emittente</string>
|
||||
<string name="cert_downloading">Download…</string>
|
||||
<string name="cert_download">Scarica da aasdk</string>
|
||||
<string name="cert_downloaded">Certificato scaricato.</string>
|
||||
<string name="cert_download_failed">Download non riuscito: %1$s</string>
|
||||
<string name="cert_download_sub">Il certificato che il progetto open source aasdk pubblica su GitHub. La maggior parte delle auto lo accetta, ma alcune dal 2020 circa in poi lo rifiutano</string>
|
||||
<string name="cert_import">Importa un mio file</string>
|
||||
<string name="cert_import_sub">Un file .p12 con la sua chiave privata. Usa il tuo quando l’auto rifiuta il certificato aasdk</string>
|
||||
<string name="cert_remove_imported">Rimuovi il certificato importato</string>
|
||||
<string name="cert_remove_downloaded">Rimuovi il certificato scaricato</string>
|
||||
<string name="cert_removed_imported">Certificato importato rimosso.</string>
|
||||
<string name="cert_removed_downloaded">Certificato scaricato rimosso.</string>
|
||||
<string name="cert_imported">Certificato importato.</string>
|
||||
<string name="cert_import_failed">Importazione non riuscita: password errata, oppure non è un file PKCS#12 con una chiave privata.</string>
|
||||
<string name="cert_password_title">Password del certificato</string>
|
||||
<string name="cert_password">Password</string>
|
||||
<string name="cert_password_hint">Lascia vuoto se il file non ne ha.</string>
|
||||
<string name="cert_import_button">Importa</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Annulla</string>
|
||||
<string name="cert_none">Nessuno caricato (autofirmato; le auto lo rifiutano)</string>
|
||||
<string name="cert_kind_imported">Importato: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Scaricato: %1$s</string>
|
||||
<string name="cert_kind_adb">Caricato via adb: %1$s</string>
|
||||
<string name="cert_kind_self">Autofirmato: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Schermo</string>
|
||||
<string name="car_group_nav_bar">Barra di navigazione e lettore</string>
|
||||
<string name="car_group_sound">Suono e voce</string>
|
||||
<string name="car_group_warnings">Avvisi</string>
|
||||
<string name="car_group_more">Altro</string>
|
||||
<string name="choice_size_normal">Normale</string>
|
||||
<string name="choice_size_large">Grande</string>
|
||||
<string name="choice_size_xlarge">Molto grande</string>
|
||||
<string name="choice_theme_phone">Come il telefono</string>
|
||||
<string name="choice_theme_light">Chiaro</string>
|
||||
<string name="choice_theme_dark">Scuro</string>
|
||||
<string name="choice_dark_black">Nero</string>
|
||||
<string name="choice_dark_black_colour">Nero e colore</string>
|
||||
<string name="choice_dark_deep_colour">Colore intenso</string>
|
||||
<string name="choice_dark_grey">Grigio</string>
|
||||
<string name="choice_auto">Auto</string>
|
||||
<string name="choice_night_day">Giorno</string>
|
||||
<string name="choice_night_night">Notte</string>
|
||||
<string name="choice_units_imperial">Imperiali</string>
|
||||
<string name="choice_units_metric">Metriche</string>
|
||||
<string name="choice_nav_show">Mostra sempre</string>
|
||||
<string name="choice_nav_hide_navigating">Nascondi durante la navigazione</string>
|
||||
<string name="choice_nav_hide">Nascondi finché non tocchi</string>
|
||||
<string name="choice_open_home">Home</string>
|
||||
<string name="summary_player_left">Lettore a sinistra</string>
|
||||
<string name="summary_player_right">Lettore a destra</string>
|
||||
<string name="summary_usb_audio">Audio tramite Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Audio Bluetooth</string>
|
||||
<string name="summary_directions_on">Indicazioni vocali attive</string>
|
||||
<string name="summary_directions_off">Indicazioni vocali disattivate</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth attivo</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth disattivato</string>
|
||||
<string name="summary_location_on">Posizione attiva</string>
|
||||
<string name="summary_location_off">Posizione disattivata</string>
|
||||
<string name="web_page">Pagina web</string>
|
||||
<string name="summary_experimental_shown">Funzioni sperimentali visibili</string>
|
||||
<string name="summary_experimental_hidden">Funzioni sperimentali nascoste</string>
|
||||
<string name="home_car">Auto</string>
|
||||
<string name="car_group_same">Sono le stesse impostazioni di Impostazioni nell’auto. Una modifica qui si vede anche lì.</string>
|
||||
<string name="car_open_on_connect">Apri alla connessione</string>
|
||||
<string name="car_size">Dimensione</string>
|
||||
<string name="car_app_theme">Tema dell’app</string>
|
||||
<string name="car_dark_mode">Modalità scura</string>
|
||||
<string name="car_buttons">Pulsanti</string>
|
||||
<string name="car_map_day_night">Mappa giorno/notte</string>
|
||||
<string name="car_units">Unità</string>
|
||||
<string name="car_nav_bar">Barra di navigazione</string>
|
||||
<string name="car_player_nav_bar">Lettore nella barra di navigazione</string>
|
||||
<string name="car_skip_buttons">Pulsanti salta</string>
|
||||
<string name="car_song_title">Titolo del brano</string>
|
||||
<string name="car_player_beside_map">Lettore accanto alla mappa</string>
|
||||
<string name="car_right">Destra</string>
|
||||
<string name="car_left">Sinistra</string>
|
||||
<string name="car_autoplay">Riproduci alla connessione</string>
|
||||
<string name="car_more_lyrics">Più fonti per i testi</string>
|
||||
<string name="car_more_lyrics_note">Chiede anche a BetterLyrics e KuGou, che ricevono così titolo e artista del brano. Se disattivato, chiede solo a LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Indicazioni vocali</string>
|
||||
<string name="car_spoken_directions_note">Disattivale se la voce della tua app di navigazione si sente già in auto.</string>
|
||||
<string name="car_phone_speech">Usa il riconoscimento vocale del telefono</string>
|
||||
<string name="car_phone_speech_note">Sulla maggior parte dei telefoni è quello di Google, che può inviare la tua voce a Google.</string>
|
||||
<string name="car_warn_bluetooth">Avvisa quando il Bluetooth non è connesso</string>
|
||||
<string name="car_warn_location">Avvisa quando la posizione è disattivata</string>
|
||||
<string name="car_experimental">Funzioni sperimentali</string>
|
||||
<string name="car_vehicle_data">Dati del veicolo</string>
|
||||
<string name="car_vehicle_data_note">Aggiunge un riquadro Dati del veicolo: un elenco in diretta dei sensori dell’auto. I nomi dei campi sono ipotesi finché non ne vedi cambiare uno.</string>
|
||||
<string name="car_adaptive_video">Qualità video adattiva</string>
|
||||
<string name="car_adaptive_video_note">Abbassa la qualità dell’immagine quando l’auto o il suo Wi-Fi non riescono a stare al passo, e la rialza appena possibile. Vale dalla prossima connessione.</string>
|
||||
<string name="web_page_note">Mostrata dall’app Browser nell’auto. Se vuota apre DuckDuckGo. Vale dalla prossima connessione.</string>
|
||||
<string name="car_audio">Audio dell’auto</string>
|
||||
<string name="car_audio_bt">Bluetooth (consigliato)</string>
|
||||
<string name="car_audio_bt_sub">Nessuna richiesta a ogni viaggio, e la voce dell’app di navigazione arriva all’auto</string>
|
||||
<string name="car_audio_usb_sub">Suono più pulito, senza ritardo. A ogni viaggio Android ti chiede di condividere lo schermo, il che permette a Gearslip di inviare il suono</string>
|
||||
<string name="car_audio_usb_footer">Esegui una volta il comando nella Guida, e Android smette di chiedere.</string>
|
||||
<string name="car_audio_bt_footer">Le chiamate passano comunque dal Bluetooth.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Dimensione dello schermo dell’auto</string>
|
||||
<string name="screen_use_cars">Quella dell’auto</string>
|
||||
<string name="screen_preview_size">Dimensione dell’anteprima</string>
|
||||
<string name="screen_preview_note">La dimensione con cui viene disegnata l’anteprima dell’auto, ad esempio 1920x720.</string>
|
||||
<string name="screen_car_screen">Schermo dell’auto</string>
|
||||
<string name="screen_footer">Gearslip usa la dimensione indicata dall’auto, a meno che tu non ne imposti una.</string>
|
||||
<string name="screen_size">Dimensione dello schermo</string>
|
||||
<string name="screen_from_car">Dall’auto</string>
|
||||
<string name="screen_crop">Ritaglia i bordi</string>
|
||||
<string name="screen_crop_sub">Per un’auto che taglia la parte alta e bassa, o i lati</string>
|
||||
<string name="screen_squash">Comprimi per adattare</string>
|
||||
<string name="screen_squash_sub">Per un’auto che schiaccia tutta l’immagine, così i cerchi sembrano ovali</string>
|
||||
<string name="screen_preview_title">Dimensione dell’anteprima auto</string>
|
||||
<string name="screen_last_car">Ultima auto</string>
|
||||
<string name="screen_last_car_sub">La dimensione dell’ultima auto a cui ti sei connesso</string>
|
||||
<string name="screen_this_screen">Questo schermo</string>
|
||||
<string name="screen_this_screen_sub">Riempie lo schermo di questo dispositivo, per un’autoradio che esegue Gearslip direttamente</string>
|
||||
<string name="screen_custom">Personalizzata</string>
|
||||
<string name="screen_enter_size">Inserisci una dimensione</string>
|
||||
<string name="screen_change_custom">Cambia la dimensione personalizzata</string>
|
||||
<string name="screen_width">Larghezza</string>
|
||||
<string name="screen_height">Altezza</string>
|
||||
<string name="save">Salva</string>
|
||||
|
||||
<string name="screen_car_size_note">Usalo se l’immagine nell’auto sembra stirata. Inserisci la dimensione reale dello schermo, ad esempio 1280x480. Vale dalla prossima connessione.</string>
|
||||
<string name="screen_set_by_you">%1$s, impostata da te</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Come connettersi</string>
|
||||
<string name="help_which_cars">Quali auto funzionano</string>
|
||||
<string name="help_full_list">Vedi l’elenco completo</string>
|
||||
<string name="help_first_drive">Prima del primo viaggio</string>
|
||||
<string name="help_in_car">In auto</string>
|
||||
<string name="help_usb_audio">Audio tramite Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Disattiva Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Disattiva o disinstalla l’app Android Auto, così l’auto parla con Gearslip.</string>
|
||||
<string name="help_cert">Carica un certificato.</string>
|
||||
<string name="help_cert_detail">In Impostazioni scarica il certificato aasdk, che la maggior parte delle auto accetta. Se la tua auto lo rifiuta, importa il tuo.</string>
|
||||
<string name="help_mic">Consenti il microfono.</string>
|
||||
<string name="help_mic_detail">Senza, Android non lascia che un’app passi la musica all’auto. Gearslip ascolta solo quando tocchi il microfono o premi il tasto vocale sul volante.</string>
|
||||
<string name="help_plug_in">Collega il telefono, o connettiti senza fili.</string>
|
||||
<string name="help_plug_in_detail">Gearslip si apre da solo quando l’auto si connette. Per il wireless, associa prima il telefono all’auto, poi tocca Connetti senza fili nella Home.</string>
|
||||
<string name="help_pair">Associa via Bluetooth per le chiamate.</string>
|
||||
<string name="help_pair_detail">Le chiamate passano dall’auto quando telefono e auto sono associati. Gearslip propone l’associazione la prima volta che colleghi il telefono.</string>
|
||||
<string name="help_answer">Rispondi alle chiamate dallo schermo dell’auto.</string>
|
||||
<string name="help_answer_detail">Imposta Gearslip come app per l’ID chiamante e lo spam. Altrimenti le chiamate squillano lo stesso, ma solo il telefono può rispondere.</string>
|
||||
<string name="help_prompt">Elimina la richiesta audio a ogni viaggio.</string>
|
||||
<string name="help_prompt_detail">Collega il telefono a un computer con il debug USB attivo ed esegui questo comando una volta. Se lo salti, tocchi Avvia ogni volta che parti.</string>
|
||||
<string name="help_readable">Mantieni leggibili le notifiche.</string>
|
||||
<string name="help_readable_detail">Android nasconde il testo delle notifiche mentre condivide l’audio. Esegui anche questo, e Gearslip toglie il blocco solo mentre la musica va all’auto.</string>
|
||||
<string name="help_which_cars_body">La maggior parte delle auto accetta il certificato aasdk. Dal 2020 circa, alcune auto hanno aggiunto controlli di sicurezza più recenti che lo rifiutano, e in quelle auto Gearslip ha bisogno di un certificato tuo.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Scaricato. Android chiede prima di installare.</string>
|
||||
<string name="update_install">Installa</string>
|
||||
<string name="update_download_now">Scaricalo ora. Android chiede prima di installare.</string>
|
||||
<string name="update_try_again">Riprova</string>
|
||||
<string name="update_update">Aggiorna</string>
|
||||
<string name="update_checking">Verifica…</string>
|
||||
<string name="update_latest">Hai l’ultima versione</string>
|
||||
<string name="update_download">Scarica</string>
|
||||
<string name="update_check_now">Controlla ora</string>
|
||||
<string name="update_check_auto">Controlla automaticamente</string>
|
||||
<string name="update_once_a_day">Una volta al giorno</string>
|
||||
|
||||
<string name="update_unplug_first">Scollega il telefono dall’auto per installare. L’installazione chiude Gearslip.</string>
|
||||
<string name="update_card_title">È uscito Gearslip %1$s</string>
|
||||
<string name="update_downloading">Download… %1$d%%</string>
|
||||
<string name="update_debug_manual">Le build di debug controllano solo quando tocchi Controlla ora</string>
|
||||
<string name="update_checks_daily">Gearslip controlla una volta al giorno</string>
|
||||
<string name="update_version_out">È uscita la versione %1$s</string>
|
||||
<string name="update_version_ready">La versione %1$s è pronta da installare</string>
|
||||
<string name="update_dialog_status">Versione %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Dona ogni settimana o ogni mese</string>
|
||||
<string name="about_bmac_sub" formatted="false">Dona una volta o ogni mese, con carta. Buy Me a Coffee trattiene il 5%</string>
|
||||
<string name="about_give_once">Dona una volta</string>
|
||||
<string name="about_any_amount">Qualsiasi importo, con carta</string>
|
||||
<string name="about_monero_sub">Apri il tuo wallet o copia l’indirizzo</string>
|
||||
<string name="about_monero_title">Sostieni con Monero</string>
|
||||
<string name="about_monero_body">Invia l’importo che vuoi a questo indirizzo dal tuo wallet Monero.</string>
|
||||
<string name="about_open_wallet">Apri nel wallet</string>
|
||||
<string name="about_copy_address">Copia indirizzo</string>
|
||||
<string name="close">Chiudi</string>
|
||||
<string name="about_matrix">Unisciti alla chat su Matrix</string>
|
||||
<string name="about_matrix_sub">Parla con altri automobilisti di auto, app e Gearslip</string>
|
||||
<string name="about_translate">Aiuta a tradurre Gearslip</string>
|
||||
<string name="about_translate_sub">Correggi o aggiungi la tua lingua su Weblate. Alcune traduzioni sono nate come traduzioni automatiche.</string>
|
||||
<string name="about_source">Codice sorgente</string>
|
||||
<string name="about_source_sub">Leggi o modifica il codice di Gearslip sotto licenza AGPL</string>
|
||||
<string name="about_voice_credits">Crediti della voce</string>
|
||||
|
||||
<string name="about_no_wallet">Nessuna app wallet Monero trovata. Copia l’indirizzo invece.</string>
|
||||
<string name="about_monero_copied">Indirizzo Monero copiato</string>
|
||||
<string name="about_voice_credits_sub">Riconoscimento vocale con sherpa-onnx (Apache 2.0), con Kroko ASR di Banafo (CC BY-SA 4.0), e Parakeet e Canary di NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Configurazione del certificato</string>
|
||||
<string name="certsetup_installed">Certificato installato.</string>
|
||||
<string name="certsetup_continue">Continua</string>
|
||||
<string name="certsetup_failed">Download non riuscito</string>
|
||||
<string name="certsetup_retry">Riprova</string>
|
||||
<string name="certsetup_import_sub">Un file .p12 con la sua chiave privata. Usa il tuo quando l’auto rifiuta il certificato aasdk.</string>
|
||||
<string name="certsetup_skip">Salta la configurazione della connessione all’auto</string>
|
||||
|
||||
<string name="certsetup_loaded">Le auto accettano solo certificati emessi da Google, e ne hai uno caricato.</string>
|
||||
<string name="certsetup_choose">Le auto accettano solo certificati emessi da Google. Gearslip non ne include uno, quindi scegli da dove arriva il tuo.</string>
|
||||
<string name="certsetup_aasdk_sub">Il progetto open source aasdk pubblica questo certificato su github.com/opencardev/aasdk. La maggior parte delle auto lo accetta. Alcune dal 2020 circa in poi lo rifiutano, e allora devi importare il tuo.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Spazio di Gearslip</string>
|
||||
<string name="storage_measuring">Misurazione…</string>
|
||||
<string name="storage_lyrics">Testi</string>
|
||||
<string name="storage_clear">Cancella</string>
|
||||
<string name="storage_voice">Modelli vocali</string>
|
||||
<string name="storage_remove">Rimuovi</string>
|
||||
<string name="storage_logs">Log vecchi</string>
|
||||
<string name="storage_other">Altri file temporanei</string>
|
||||
<string name="storage_delete_all">Elimina tutti i dati di Gearslip</string>
|
||||
|
||||
<string name="storage_total">I dati di Gearslip occupano %1$s dello spazio del telefono.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d brano · %2$s. I testi cancellati si riscaricano quando li apri.</item>
|
||||
<item quantity="many">%1$d brani · %2$s. I testi cancellati si riscaricano quando li apri.</item>
|
||||
<item quantity="other">%1$d brani · %2$s. I testi cancellati si riscaricano quando li apri.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Servono ai comandi vocali; puoi riscaricarli in Impostazioni.</string>
|
||||
<string name="storage_logs_detail">%1$s. Log degli avvii precedenti; il log di questo avvio resta.</string>
|
||||
<string name="storage_other_detail">%1$s. Aggiornamenti scaricati e altri file che Gearslip può ricreare.</string>
|
||||
<string name="storage_delete_armed">Tocca di nuovo Elimina per cancellare impostazioni, certificati, modelli vocali e log. Gearslip si chiude e la prossima volta riparte dalla configurazione.</string>
|
||||
<string name="storage_delete_sub">Impostazioni, certificati, modelli vocali, testi e log.</string>
|
||||
<string name="storage_delete">Elimina</string>
|
||||
<string name="storage_delete_ellipsis">Elimina…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Bene che il viaggio sia andato</string>
|
||||
<string name="donate_liberapay">Sostieni su Liberapay</string>
|
||||
<string name="donate_bmac">Offrimi un caffè</string>
|
||||
<string name="donate_card">Dona una volta con carta</string>
|
||||
|
||||
<string name="donate_body">Gearslip è gratuito, senza pubblicità e senza tracciamento. Se ti porta in strada, il tuo sostegno lo tiene in vita.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">Per questo Gearslip ha bisogno dell’accesso al Bluetooth e al Wi-Fi nelle vicinanze.</string>
|
||||
<string name="wireless_enable_permission">Attiva il permesso Dispositivi nelle vicinanze</string>
|
||||
<string name="wireless_connect">Connetti senza fili</string>
|
||||
<string name="wireless_which_car">Quale auto?</string>
|
||||
<string name="wireless_none_paired">Il telefono non è ancora associato a nulla. Associalo prima all’auto.</string>
|
||||
<string name="wireless_not_listed">L’auto non è in elenco? Associala prima al telefono.</string>
|
||||
<string name="bluetooth_settings">Impostazioni Bluetooth</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Voce: parole in diretta</string>
|
||||
<string name="voice_final_title">Voce: parole finali</string>
|
||||
<string name="voice_none">Nessuno</string>
|
||||
<string name="voice_none_sub">Tieni le parole in diretta. Il più veloce, il meno preciso</string>
|
||||
|
||||
<string name="voice_live_body">L’assistente vocale trasforma la voce in testo su questo telefono, e niente di quello che dici ne esce. Servono due modelli, circa 270 MB in tutto, scaricati una volta sola. Questo scrive le tue parole mentre parli.</string>
|
||||
<string name="voice_final_body">Quando smetti di parlare, questo modello rilegge tutta la frase. Coglie nomi e luoghi che il modello in diretta perde.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Scaricato. %1$s</string>
|
||||
<string name="voice_downloading">Download, %1$d%%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Guida</string>
|
||||
<string name="home_preview">Anteprima</string>
|
||||
<string name="home_logs">Log</string>
|
||||
<string name="home_disconnect">Disconnetti</string>
|
||||
<string name="home_bt_title">Il Bluetooth non è connesso</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · build di debug</string>
|
||||
<string name="home_bt_body">La musica passa dal Bluetooth, ma il telefono non è connesso all’auto. Connettilo da qui o dal menu Bluetooth dell’auto.</string>
|
||||
<string name="home_voice_downloading">Download della voce, %1$d%%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Riprodurre l’audio dell’auto via Bluetooth?</string>
|
||||
<string name="audio_never_ask">Non chiedere più, con un computer</string>
|
||||
<string name="audio_use_bt">Usa Bluetooth</string>
|
||||
<string name="audio_keep_asking">Continua a chiedere</string>
|
||||
<string name="audio_mic_title">L’audio dell’auto richiede il permesso del microfono</string>
|
||||
<string name="audio_open_settings">Apri le impostazioni dell’app</string>
|
||||
|
||||
<string name="audio_bt_body">Android tratta la musica che Gearslip manda all’auto come una condivisione dello schermo, quindi chiede a ogni viaggio, via cavo o senza fili. Via Bluetooth non chiede mai. Puoi cambiarlo più tardi in Impostazioni.</string>
|
||||
<string name="audio_keep_usb">Oppure mantieni il suono tramite Gearslip ed elimina la domanda per sempre: collega il telefono a un computer con il debug USB attivo ed esegui questo comando una volta.</string>
|
||||
<string name="audio_mic_body">Android permette a un’app di inviare all’auto il suono di altre app solo se ha il permesso del microfono. Per Gearslip è disattivato, quindi la musica suona sul telefono. Attivalo in Autorizzazioni, poi Microfono.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Chiudi la tastiera dell’auto</string>
|
||||
<string name="typing_label">Scrivi per lo schermo dell’auto</string>
|
||||
<string name="typing_search">Cerca sullo schermo dell’auto</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Tocca per copiare</string>
|
||||
<string name="command_copied">Comando copiato</string>
|
||||
<string name="whats_new_this_version">%1$s (questa versione)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Tema</string>
|
||||
<string name="carset_imperial">Imperiali (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Metriche (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Dimensione dello schermo (prossima connessione)</string>
|
||||
<string name="carset_fit">Adatta allo schermo</string>
|
||||
<string name="off">Off</string>
|
||||
<string name="on">On</string>
|
||||
<string name="carset_lyrics_from">Testi da</string>
|
||||
<string name="carset_more_sources">Più fonti</string>
|
||||
<string name="carset_hide">Nascondi</string>
|
||||
<string name="carset_show">Mostra</string>
|
||||
<string name="carset_phone">Telefono</string>
|
||||
<string name="carset_vehicle">Veicolo</string>
|
||||
<string name="carset_video">Video</string>
|
||||
<string name="carset_version">Versione</string>
|
||||
<string name="carset_build">Build</string>
|
||||
<string name="carset_whats_new_after">Novità dopo gli aggiornamenti</string>
|
||||
<string name="carset_whats_new_after_sub">Mostra l’elenco delle modifiche una volta, la prima volta che l’auto si connette dopo un aggiornamento.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (predefinito) - tocca per impostare un’altra pagina</string>
|
||||
<string name="carset_web_note">Mostrata dall’app Browser nel launcher. Vale dalla prossima connessione.</string>
|
||||
<string name="carset_screen_fit">Adattamento schermo</string>
|
||||
<string name="carset_top">Alto</string>
|
||||
<string name="carset_bottom">Basso</string>
|
||||
<string name="carset_save_vehicle">Salva per questo veicolo</string>
|
||||
<string name="carset_no_profile">Nessun profilo veicolo corrispondente</string>
|
||||
|
||||
<string name="carset_audio_note">Tramite Gearslip manda il suono del telefono attraverso la connessione con l’auto, via cavo o senza fili. Android chiede di condividere lo schermo a ogni viaggio, e alcune app lo bloccano. Il Bluetooth suona come farebbe normalmente il telefono, e Android non chiede mai.</string>
|
||||
<string name="carset_directions_note">Gearslip legge ad alta voce ogni svolta dagli altoparlanti dell’auto, e la musica si abbassa mentre parla. Disattivalo se la voce della tua app di navigazione si sente già in auto.</string>
|
||||
<string name="carset_phone_speech_note">Ascolta tramite il servizio vocale del telefono invece di quello di Gearslip. Sulla maggior parte dei telefoni è quello di Google, che può inviare la tua voce a Google. GrapheneOS non ne ha uno, quindi Gearslip continua a usare il suo.</string>
|
||||
<string name="carset_vehicle_data_note">Aggiunge un riquadro Dati del veicolo: un elenco grezzo e in diretta del canale sensori dell’autoradio. Le etichette dei campi (velocità, giri, carburante...) vengono da altri progetti di reverse engineering e non sono verificate su questa auto: considerale un’ipotesi, non un fatto, finché non ne vedi cambiare una con i tuoi occhi.</string>
|
||||
<string name="carset_adaptive_note">Abbassa la qualità dell’immagine quando l’auto, o il suo Wi-Fi, non riesce a stare al passo, e la rialza appena possibile. Vale dalla prossima connessione.</string>
|
||||
<string name="carset_usable_height">Altezza utile %1$dpx su 480</string>
|
||||
<string name="carset_saved_to">Salvato in %1$s</string>
|
||||
<string name="carset_save_failed">Impossibile salvare</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Apri sul telefono</string>
|
||||
<string name="media_now_playing">In riproduzione</string>
|
||||
<string name="media_up_next">A seguire</string>
|
||||
<string name="media_browse">Sfoglia</string>
|
||||
<string name="media_search_hint">Scrivi un brano, un artista, un album o una playlist.</string>
|
||||
<string name="media_looking_lyrics">Ricerca del testo…</string>
|
||||
<string name="media_nothing_playing">Niente in riproduzione</string>
|
||||
<string name="media_press_play">Premi play per riprendere da dove eri, o trova qualcosa in Sfoglia.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s tiene la sua libreria per sé.</string>
|
||||
<string name="media_needs_access">Gearslip può comunque controllarla se ha l’accesso alle notifiche sul telefono. Consentilo in Impostazioni > Notifiche > Accesso alle notifiche.</string>
|
||||
<string name="media_not_running">%1$s non è in esecuzione.</string>
|
||||
<string name="media_not_running_body">%1$s suona tramite il lettore di Gearslip, ma solo mentre è aperta sul telefono. Aprila una volta e continua a suonare qui in auto.</string>
|
||||
<string name="media_open_app">Apri %1$s</string>
|
||||
<string name="media_refused">%1$s non ha permesso a Gearslip di sfogliarla.</string>
|
||||
<string name="media_refused_body">Alcune app multimediali accettano solo l’host di Google.</string>
|
||||
<string name="media_connecting">Connessione a %1$s…</string>
|
||||
<string name="media_search">Cerca in %1$s</string>
|
||||
<string name="media_search_no_list">%1$s non può elencare i risultati. Premi cerca, e %1$s sceglie cosa riprodurre.</string>
|
||||
<string name="media_search_unsupported">%1$s non permette di cercare dall’auto.</string>
|
||||
<string name="media_searching">Ricerca in %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s non ha trovato nulla per “%2$s”.</string>
|
||||
<string name="media_search_ignored">%1$s non ha riprodotto nulla per “%2$s”.</string>
|
||||
<string name="media_searching_for">Ricerca di “%2$s” in %1$s…</string>
|
||||
<string name="media_nothing_playing_dot">Niente in riproduzione.</string>
|
||||
<string name="media_no_lyrics">Nessun testo trovato per questo brano.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">Su</string>
|
||||
<string name="media_loading">Caricamento…</string>
|
||||
<string name="media_browse_start">Avvia la musica nell’app sul telefono. Play, pausa, salta e avanzamento funzionano da qui.</string>
|
||||
<string name="media_load_failed">Impossibile caricare questo elenco.</string>
|
||||
<string name="media_nothing_here">Qui non c’è niente.</string>
|
||||
<string name="media_nothing_queued">Niente in coda.</string>
|
||||
<string name="media_play">Riproduci</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Niente in riproduzione - non tutte le app dicono a Gearslip cosa viene dopo.</string>
|
||||
<string name="media_queue_empty_playing">Solo ciò che è già in riproduzione - non tutte le app dicono a Gearslip cosa viene dopo.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Precedente</string>
|
||||
<string name="player_next">Successivo</string>
|
||||
<string name="player_repeat">Ripeti</string>
|
||||
<string name="player_pause">Pausa</string>
|
||||
<string name="player_unlike">Non mi piace più</string>
|
||||
<string name="player_like">Mi piace</string>
|
||||
<string name="player_shuffle_off">Casuale disattivato</string>
|
||||
<string name="player_shuffle_on">Casuale attivo</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Scegli un’altra app</string>
|
||||
<string name="home_no_maps">Nessuna app di mappe trovata.</string>
|
||||
<string name="home_no_maps_body">Installa un’app di navigazione compatibile con Android Auto e partirà qui da sola.</string>
|
||||
<string name="home_map_stopped">Mappa fermata</string>
|
||||
<string name="home_map_stopped_body">Resta chiusa finché non la riapri.</string>
|
||||
<string name="home_reopen_map">Riapri la mappa</string>
|
||||
<string name="home_car_apps">App per auto</string>
|
||||
<string name="home_back_nav">Torna alla barra di navigazione</string>
|
||||
<string name="home_show_player">Mostra il lettore</string>
|
||||
<string name="home_hide_lyrics">Nascondi il testo</string>
|
||||
|
||||
<string name="home_starting">Avvio di %1$s…</string>
|
||||
<string name="home_starting_map">Avvio della mappa…</string>
|
||||
<string name="home_failed">%1$s non è partita.</string>
|
||||
<string name="home_failed_map">L’app di mappe non è partita.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Home</string>
|
||||
<string name="crumb_media">Media</string>
|
||||
<string name="crumb_apps">App</string>
|
||||
<string name="crumb_dashboard">Dashboard</string>
|
||||
<string name="crumb_weather">Meteo</string>
|
||||
<string name="crumb_reply">Rispondi</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Browser</string>
|
||||
<string name="app_screen_sharing">Condivisione schermo</string>
|
||||
<string name="app_exit">Esci</string>
|
||||
<string name="navapps_unpin">Rimuovi dalla barra di navigazione</string>
|
||||
<string name="navapps_full">La barra di navigazione è piena (%1$d fissate)</string>
|
||||
<string name="navapps_pin">Fissa nella barra di navigazione</string>
|
||||
<string name="navapps_close">Chiudi l’app</string>
|
||||
<string name="navapps_move_left">Sposta a sinistra</string>
|
||||
<string name="navapps_move_right">Sposta a destra</string>
|
||||
<string name="navapps_reset">Ripristina l’ordine delle app</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Ignora</string>
|
||||
<string name="msg_read">Leggi</string>
|
||||
<string name="msg_stop">Ferma</string>
|
||||
<string name="msg_no">No</string>
|
||||
<string name="msg_send_now">Invia ora</string>
|
||||
<string name="msg_type">Scrivi</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nuovo</item>
|
||||
<item quantity="many">%1$s · %2$d nuovi</item>
|
||||
<item quantity="other">%1$s · %2$d nuovi</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Rispondi a %1$s</string>
|
||||
<string name="msg_listening">In ascolto</string>
|
||||
<string name="msg_sending_to">Invio a %1$s</string>
|
||||
<string name="msg_sent_to">Inviato a %1$s</string>
|
||||
<string name="msg_reply_failed">Impossibile rispondere</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">L’associazione non è finita. Associa invece dal menu Bluetooth dell’auto.</string>
|
||||
<string name="bt_pair_not_started">L’associazione non è partita. Associa invece dal menu Bluetooth dell’auto.</string>
|
||||
<string name="bt_paired">Associato. Ora le chiamate passano dall’auto.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Non mostrare più</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">La posizione è disattivata</string>
|
||||
<string name="overlay_pair_title">Associare a questa auto?</string>
|
||||
<string name="overlay_pair">Associa</string>
|
||||
<string name="overlay_confirm_title">Conferma sul telefono</string>
|
||||
<string name="overlay_confirm_body">Controlla il codice sul telefono e tocca Associa. Potrebbe chiederlo anche l’auto.</string>
|
||||
|
||||
<string name="overlay_bt_body">La musica passa dal Bluetooth, ma il telefono non è connesso all’auto. Connettilo dal menu Bluetooth dell’auto o del telefono.</string>
|
||||
<string name="overlay_location_body">Le app di mappe non possono mostrare dove sei né darti indicazioni. Attiva la posizione dalle impostazioni rapide del telefono quando sei fermo.</string>
|
||||
<string name="overlay_pair_body_music">Chiamate e musica passano dagli altoparlanti dell’auto quando telefono e auto sono associati. Poi il telefono ti chiede di confermare un codice.</string>
|
||||
<string name="overlay_pair_body">Le chiamate passano dagli altoparlanti dell’auto quando telefono e auto sono associati. Poi il telefono ti chiede di confermare un codice.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">In attesa della posizione.</string>
|
||||
<string name="weather_allow_location">Consenti la posizione a Gearslip sul telefono per vedere il meteo.</string>
|
||||
<string name="weather_unreachable">Impossibile raggiungere il servizio meteo.</string>
|
||||
<string name="weather_clear">Sereno</string>
|
||||
<string name="weather_mostly_clear">Per lo più sereno</string>
|
||||
<string name="weather_partly_cloudy">Parzialmente nuvoloso</string>
|
||||
<string name="weather_overcast">Coperto</string>
|
||||
<string name="weather_fog">Nebbia</string>
|
||||
<string name="weather_drizzle">Pioviggine</string>
|
||||
<string name="weather_rain">Pioggia</string>
|
||||
<string name="weather_snow">Neve</string>
|
||||
<string name="weather_rain_showers">Rovesci di pioggia</string>
|
||||
<string name="weather_snow_showers">Rovesci di neve</string>
|
||||
<string name="weather_thunderstorm">Temporale</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Recupero del meteo…</string>
|
||||
<string name="weather_next_hours">Prossime ore</string>
|
||||
<string name="weather_this_week">Questa settimana</string>
|
||||
<string name="weather_today">Oggi</string>
|
||||
<string name="weather_credit">Dati meteo di Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Percepita %1$s</string>
|
||||
<string name="weather_humidity">Umidità %1$d%%</string>
|
||||
<string name="weather_wind">Vento %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">Oggi</string>
|
||||
<string name="dash_getting_weather">Recupero del meteo…</string>
|
||||
<string name="dash_coming_up">In arrivo</string>
|
||||
<string name="dash_loading_calendar">Caricamento del calendario…</string>
|
||||
<string name="dash_nothing_week">Niente nella prossima settimana.</string>
|
||||
<string name="dash_all_day">Tutto il giorno</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Contatti</string>
|
||||
<string name="dialer_keypad">Tastierino</string>
|
||||
<string name="dialer_ask">Chiedi sul telefono</string>
|
||||
<string name="dialer_enter_number">Inserisci un numero</string>
|
||||
<string name="dialer_call">Chiama</string>
|
||||
<string name="dialer_no_contacts">Nessun contatto con un numero di telefono trovato.</string>
|
||||
<string name="dialer_search">Cerca contatti</string>
|
||||
|
||||
<string name="dialer_needs_permission">Gearslip ha bisogno dei permessi Telefono e Contatti per chiamare da qui. Consentili sul telefono quando sei fermo.</string>
|
||||
<string name="dialer_no_match">Nessun contatto corrisponde a “%1$s”.</string>
|
||||
<string name="dialer_call_who">Chiamare %1$s?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Esci dallo schermo intero</string>
|
||||
<string name="carui_drive_safely">Guida con prudenza</string>
|
||||
<string name="carui_understand">Ho capito</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip è un software non ufficiale: non è creato né verificato da alcuna casa automobilistica, e può smettere di funzionare senza preavviso.</string>
|
||||
<string name="carui_never_interact">Non usare mai questo schermo mentre guidi. Imposta il percorso o il brano prima di partire, poi tieni l’attenzione sulla strada.</string>
|
||||
<string name="carui_this_app">Questa app</string>
|
||||
<string name="carui_app_unsupported">%1$s non funziona ancora con Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">Non è partita. Alcune app funzionano solo con Android Auto di Google.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">In chiamata</string>
|
||||
<string name="navbar_end_call">Termina chiamata</string>
|
||||
<string name="navbar_unmute">Riattiva microfono</string>
|
||||
<string name="navbar_mute">Disattiva microfono</string>
|
||||
<string name="navbar_close_panel">Chiudi il pannello laterale</string>
|
||||
<string name="navbar_dock_player">Sposta il lettore accanto alla mappa</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Cerca o scrivi un indirizzo</string>
|
||||
<string name="web_forward">Avanti</string>
|
||||
<string name="web_reload">Ricarica</string>
|
||||
<string name="web_start_page">Pagina iniziale</string>
|
||||
<string name="web_fullscreen">Schermo intero</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Notifiche</string>
|
||||
<string name="notif_clear_all">Cancella tutto</string>
|
||||
<string name="notif_empty">Ancora niente. Le nuove notifiche del telefono compariranno qui.</string>
|
||||
<string name="notif_off">Le notifiche sono disattivate. Consenti Gearslip in Impostazioni > Notifiche > Accesso alle notifiche sul telefono.</string>
|
||||
<string name="notif_replied">Risposto</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Segna come letto</string>
|
||||
<string name="msg_messages">Messaggi</string>
|
||||
<string name="msg_none_recent">Nessun messaggio recente. I nuovi messaggi di %1$s compaiono qui appena arrivano sul telefono.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d precedente</item>
|
||||
<item quantity="many">%1$d precedenti</item>
|
||||
<item quantity="other">%1$d precedenti</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Nascondi le risposte rapide</string>
|
||||
<string name="msg_quick">Risposta rapida</string>
|
||||
<string name="msg_you">Tu: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Ricerca del percorso…</string>
|
||||
<string name="nav_then">Poi</string>
|
||||
<string name="nav_stop">Interrompi la navigazione</string>
|
||||
<string name="nav_finding_routes">Ricerca dei percorsi…</string>
|
||||
<string name="nav_lane_recommended">Corsia consigliata</string>
|
||||
<string name="nav_lane">Corsia</string>
|
||||
<string name="nav_arrive">Arrivo %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Ancora niente in riproduzione.</string>
|
||||
<string name="template_search">Cerca</string>
|
||||
<string name="template_start">Avvia</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Chiamata in arrivo</string>
|
||||
<string name="call_decline">Rifiuta</string>
|
||||
<string name="call_answer">Rispondi</string>
|
||||
<string name="call_ringing">Sta già squillando ad alta voce - rispondi qui, o lascia squillare.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">Il tocco è disattivato. Attiva Gearslip in Impostazioni > Accessibilità sul telefono.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Mostra dopo gli aggiornamenti</string>
|
||||
<string name="whatsnew_got_it">Ho capito</string>
|
||||
<string name="whatsnew_title">Novità della versione %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">spazio</string>
|
||||
<string name="keyboard_hide">Nascondi tastiera</string>
|
||||
<string name="keyboard_submit">Invia</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Non funziona con Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">App per auto</string>
|
||||
<string name="stage_close">Chiudi %1$s</string>
|
||||
<string name="stage_close_app">Chiudi l’app</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Svolta leggermente a sinistra</string>
|
||||
<string name="dir_slight_left_onto">Svolta leggermente a sinistra in %1$s</string>
|
||||
<string name="dir_slight_right">Svolta leggermente a destra</string>
|
||||
<string name="dir_slight_right_onto">Svolta leggermente a destra in %1$s</string>
|
||||
<string name="dir_left">Svolta a sinistra</string>
|
||||
<string name="dir_left_onto">Svolta a sinistra in %1$s</string>
|
||||
<string name="dir_right">Svolta a destra</string>
|
||||
<string name="dir_right_onto">Svolta a destra in %1$s</string>
|
||||
<string name="dir_sharp_left">Svolta decisamente a sinistra</string>
|
||||
<string name="dir_sharp_left_onto">Svolta decisamente a sinistra in %1$s</string>
|
||||
<string name="dir_sharp_right">Svolta decisamente a destra</string>
|
||||
<string name="dir_sharp_right_onto">Svolta decisamente a destra in %1$s</string>
|
||||
<string name="dir_u_turn">Fai inversione a U</string>
|
||||
<string name="dir_keep_left">Tieni la sinistra</string>
|
||||
<string name="dir_keep_left_toward">Tieni la sinistra verso %1$s</string>
|
||||
<string name="dir_keep_right">Tieni la destra</string>
|
||||
<string name="dir_keep_right_toward">Tieni la destra verso %1$s</string>
|
||||
<string name="dir_fork_left">Al bivio tieni la sinistra</string>
|
||||
<string name="dir_fork_left_toward">Al bivio tieni la sinistra verso %1$s</string>
|
||||
<string name="dir_fork_right">Al bivio tieni la destra</string>
|
||||
<string name="dir_fork_right_toward">Al bivio tieni la destra verso %1$s</string>
|
||||
<string name="dir_ramp_left">Prendi la rampa a sinistra</string>
|
||||
<string name="dir_ramp_left_onto">Prendi la rampa a sinistra per %1$s</string>
|
||||
<string name="dir_ramp_right">Prendi la rampa a destra</string>
|
||||
<string name="dir_ramp_right_onto">Prendi la rampa a destra per %1$s</string>
|
||||
<string name="dir_exit_left">Prendi l’uscita a sinistra</string>
|
||||
<string name="dir_exit_left_toward">Prendi l’uscita a sinistra verso %1$s</string>
|
||||
<string name="dir_exit_right">Prendi l’uscita a destra</string>
|
||||
<string name="dir_exit_right_toward">Prendi l’uscita a destra verso %1$s</string>
|
||||
<string name="dir_merge_left">Immettiti a sinistra</string>
|
||||
<string name="dir_merge_left_onto">Immettiti a sinistra in %1$s</string>
|
||||
<string name="dir_merge_right">Immettiti a destra</string>
|
||||
<string name="dir_merge_right_onto">Immettiti a destra in %1$s</string>
|
||||
<string name="dir_merge">Immettiti</string>
|
||||
<string name="dir_merge_onto">Immettiti in %1$s</string>
|
||||
<string name="dir_roundabout_through">Attraversa la rotatoria</string>
|
||||
<string name="dir_roundabout_through_onto">Attraversa la rotatoria verso %1$s</string>
|
||||
<string name="dir_roundabout_enter">Entra nella rotatoria</string>
|
||||
<string name="dir_roundabout_exit">Esci dalla rotatoria</string>
|
||||
<string name="dir_roundabout_exit_onto">Esci dalla rotatoria in %1$s</string>
|
||||
<string name="dir_straight">Prosegui dritto</string>
|
||||
<string name="dir_straight_onto">Prosegui dritto in %1$s</string>
|
||||
<string name="dir_continue">Prosegui</string>
|
||||
<string name="dir_continue_onto">Prosegui in %1$s</string>
|
||||
<string name="dir_ferry">Prendi il traghetto</string>
|
||||
<string name="dir_train">Prendi il treno</string>
|
||||
<string name="dir_destination">La destinazione è davanti a te</string>
|
||||
<string name="dir_destination_left">La destinazione è sulla sinistra</string>
|
||||
<string name="dir_destination_right">La destinazione è sulla destra</string>
|
||||
<string name="dir_roundabout_take">Alla rotatoria prendi la %1$s uscita</string>
|
||||
<string name="dir_roundabout_take_onto">Alla rotatoria prendi la %1$s uscita in %2$s</string>
|
||||
<string name="dir_ordinal_1">prima</string>
|
||||
<string name="dir_ordinal_2">seconda</string>
|
||||
<string name="dir_ordinal_3">terza</string>
|
||||
<string name="dir_ordinal_4">quarta</string>
|
||||
<string name="dir_ordinal_5">quinta</string>
|
||||
<string name="dir_ordinal_6">sesta</string>
|
||||
<string name="dir_ordinal_7">settima</string>
|
||||
<string name="dir_ordinal_8">ottava</string>
|
||||
<string name="dir_in_distance">Tra %1$s, %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s chilometro</item>
|
||||
<item quantity="many">%1$s chilometri</item>
|
||||
<item quantity="other">%1$s chilometri</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s chilometri</string>
|
||||
<string name="dir_quarter_mile">un quarto di miglio</string>
|
||||
<string name="dir_half_mile">mezzo miglio</string>
|
||||
<string name="dir_three_quarter_mile">tre quarti di miglio</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s miglio</item>
|
||||
<item quantity="many">%1$s miglia</item>
|
||||
<item quantity="other">%1$s miglia</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s miglia</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s piede</item>
|
||||
<item quantity="many">%1$s piedi</item>
|
||||
<item quantity="other">%1$s piedi</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s iarda</item>
|
||||
<item quantity="many">%1$s iarde</item>
|
||||
<item quantity="other">%1$s iarde</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d metro</item>
|
||||
<item quantity="many">%1$d metri</item>
|
||||
<item quantity="other">%1$d metri</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Se l’auto non si connette, potrebbe rifiutare questo certificato. Importa il tuo in Impostazioni, sotto Certificato.</string>
|
||||
<string name="cert_downloaded_footer">L’auto non si connette? Alcune auto dal 2020 circa in poi rifiutano il certificato aasdk, quindi importa il tuo. Le modifiche valgono dalla prossima connessione.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Scheda di svolta sul quadro strumenti</string>
|
||||
<string name="car_cluster_video_note">Per un’auto con un secondo schermo nel quadro strumenti: Gearslip vi mostra la prossima svolta. Vale dalla prossima connessione.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Non connesso</string>
|
||||
<string name="status_idle_detail">Collega il telefono alla porta USB dell’auto, o connettiti senza fili. Gearslip si apre da solo quando l’auto risponde.</string>
|
||||
<string name="status_connecting">Connessione</string>
|
||||
<string name="status_projecting">Proiezione</string>
|
||||
<string name="status_projecting_detail">Streaming verso l’autoradio.</string>
|
||||
<string name="status_disconnected">Disconnesso</string>
|
||||
<string name="status_disconnected_detail">L’autoradio non è più connessa.</string>
|
||||
<string name="places_title">Luoghi salvati</string>
|
||||
<string name="places_sub">Di’ “navigate home” in qualsiasi app di mappe</string>
|
||||
<string name="places_intro">Di’ “navigate home”, “take me to work” o “navigate to” con un tuo nome, e Gearslip manda l’indirizzo alla tua app di mappe. Per ora i comandi vocali sono solo in inglese.</string>
|
||||
<string name="places_home">Casa</string>
|
||||
<string name="places_work">Lavoro</string>
|
||||
<string name="places_other">Luogo %1$d</string>
|
||||
<string name="places_name">Nome</string>
|
||||
<string name="places_address">Indirizzo</string>
|
||||
<string name="places_here">Usa la mia posizione</string>
|
||||
<string name="places_finding">Ricerca…</string>
|
||||
<string name="places_not_found">Gearslip non ha trovato la tua posizione. Controlla che la localizzazione sia attiva.</string>
|
||||
<string name="places_clear">Cancella</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,824 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">画面共有中に、車の画面でのタップを Gearslip がスマートフォンで再現できるようにします。Gearslip は画面の内容を読み取りません。</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">ほかのドライバーの役に立ちましょう。</string>
|
||||
<string name="guide_safety_title">安全運転を。</string>
|
||||
<string name="guide_safety_body">Gearslip は非公式のアプリです。自動車メーカーが作ったものでも確認したものでもなく、予告なく動かなくなることがあります。出発前に設定し、運転中は道路から目を離さないでください。</string>
|
||||
<string name="guide_mic_title">マイク</string>
|
||||
<string name="guide_mic_body">音楽を車に送るときと、何かを頼んだりメッセージに返信したりするときに声を聞くために使います。話した内容はスマートフォンの外に出ません。</string>
|
||||
<string name="guide_location_title">位置情報</string>
|
||||
<string name="guide_location_body">車の地図と周辺検索に必要です。許可しないと、ダッシュボードに地図が表示されません。</string>
|
||||
<string name="guide_calendar_title">カレンダー</string>
|
||||
<string name="guide_calendar_body">車の画面に次の予定を表示します。Gearslip はカレンダーを読み取るだけです。</string>
|
||||
<string name="guide_calls_title">通話と連絡先</string>
|
||||
<string name="guide_calls_body">着信時に相手の名前を表示し、車の電話アプリから番号を入力したり連絡先に発信したりできます。Gearslip は読み取るだけです。</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth</string>
|
||||
<string name="guide_bluetooth_body">初めて接続したとき、Gearslip がスマートフォンと車のペア設定を提案します。通話が車のスピーカーから聞こえるようになります。</string>
|
||||
<string name="guide_notifications_title">通知</string>
|
||||
<string name="guide_notifications_body">接続中は停止ボタン付きの通知を表示します。許可しないと Android がこの通知を隠します。</string>
|
||||
<string name="guide_messages_title">メッセージ</string>
|
||||
<string name="guide_messages_body">車の画面でメッセージを表示し、読み上げ、返信できます。開いたリストで Gearslip をオンにしてください。</string>
|
||||
<string name="guide_android_auto_title">Android Auto をオフにする</string>
|
||||
<string name="guide_android_auto_body">Android Auto がまだインストールされていて、車の USB 接続を Gearslip と奪い合います。開いた画面で無効にするか、アンインストールしてください。</string>
|
||||
<string name="guide_default_title">Gearslip を選ぶ</string>
|
||||
<string name="guide_default_body">車につないだときにどのアプリで開くか聞かれたら、Gearslip を選んで[常時]をタップしてください。</string>
|
||||
<string name="guide_audio_title">車のオーディオ</string>
|
||||
<string name="guide_audio_body">音楽を車に届ける方法を選んでください。通話はどちらの場合も Bluetooth を使います。</string>
|
||||
<string name="guide_voice_title">音声</string>
|
||||
<string name="guide_voice_body">道案内、音楽、通話を声で頼めます。音声の文字起こしはスマートフォンの中だけで行われ、話した内容は外に出ません。%1$d MB のダウンロードが一度だけ必要です。できれば Wi-Fi を使ってください。</string>
|
||||
<string name="guide_audio_prompt_title">音声の確認をなくす</string>
|
||||
<string name="guide_audio_prompt_body">USB デバッグをオンにしたパソコンからこのコマンドを実行すると、Android が音声の扱いを Gearslip にずっと任せるようになります。実行しない場合は、運転のたびに[開始]を一度タップします。</string>
|
||||
<string name="guide_readable_title">読める通知</string>
|
||||
<string name="guide_readable_body">音声を車に送っている間、Android は画面共有中と同じように通知の内容を隠します。これを許可すると、運転中は Gearslip が通知を表示し、終わったらまた隠します。</string>
|
||||
<string name="guide_answer_title">着信に応答する</string>
|
||||
<string name="guide_answer_body">Gearslip を発信者番号アプリにすると、車から着信に応答・拒否できます。設定しない場合も着信音は鳴りますが、応答できるのはスマートフォンだけです。</string>
|
||||
<string name="guide_apps_title">おすすめの仲間</string>
|
||||
<string name="guide_buttons_title">ボタンのデザイン</string>
|
||||
<string name="guide_buttons_body">車の画面のボタンの見た目を選んでください。車の設定からいつでも変更できます。</string>
|
||||
<string name="guide_open_notification_access">通知へのアクセスを開く</string>
|
||||
<string name="guide_open_android_auto">Android Auto のアプリ情報を開く</string>
|
||||
<string name="guide_download_voice">音声をダウンロード</string>
|
||||
<string name="guide_set_up">設定する</string>
|
||||
<string name="guide_voice_downloading">ダウンロード中(%1$d%%)。先に進んでも続きます。</string>
|
||||
<string name="guide_voice_ready">音声の準備ができました。</string>
|
||||
<string name="guide_voice_failed">ダウンロードが完了しませんでした。[音声をダウンロード]をタップしてやり直してください。</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">おすすめ</string>
|
||||
<string name="guide_audio_bluetooth_body">運転のたびの確認がなく、地図アプリの音声も車から聞こえます。</string>
|
||||
<string name="guide_audio_usb">Gearslip 経由</string>
|
||||
<string name="guide_audio_usb_body">遅延がなく音がよりクリアです。運転のたびに Android が画面共有の許可を求め、それによって Gearslip が音を送れるようになります。</string>
|
||||
<string name="guide_optional">任意</string>
|
||||
<string name="guide_already_allowed">許可済みです。</string>
|
||||
<string name="guide_scroll_more">スクロールして続きを表示 ↓</string>
|
||||
<string name="guide_allow">許可</string>
|
||||
<string name="guide_skip">今はスキップ</string>
|
||||
<string name="guide_open">開く</string>
|
||||
<string name="guide_next">次へ</string>
|
||||
<string name="guide_done">完了</string>
|
||||
<string name="guide_back">戻る</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">オンにすると、Gearslip を開いたときと運転のたびに短いメモを送ります。<b>メモに名前、アカウント、位置情報は含まれません。ランダムな番号でスマートフォンを一度だけ数え、サーバーは IP アドレスを保存しません。</b>メモから、どの車で動き、ほかの車ではどこで失敗するかがわかります。</string>
|
||||
<string name="usage_send">利用メモを送信</string>
|
||||
<string name="usage_also_share">あわせて共有する項目</string>
|
||||
<string name="usage_share_android">Android のバージョンとシステム</string>
|
||||
<string name="usage_share_phone">スマートフォンの機種</string>
|
||||
<string name="usage_share_cars">各車と接続できたかどうか</string>
|
||||
<string name="usage_share_drives">各運転とその終わり方</string>
|
||||
<string name="usage_what_sent">送信される内容</string>
|
||||
<string name="usage_what_sent_sub">送信されるとおりの全項目</string>
|
||||
<string name="usage_hide_note">メモを隠す</string>
|
||||
<string name="usage_show_note">メモを表示</string>
|
||||
<string name="usage_example">例(送信されません):1 回の運転で「sessions」にこれが追加されます</string>
|
||||
<string name="usage_no_server">このビルドには統計サーバーがないため、何も送信しません</string>
|
||||
<string name="usage_debug_build">デバッグビルド:メモは別に保管され、統計には一切数えられません</string>
|
||||
<string name="worked_title">Gearslip は %1$s で動きました</string>
|
||||
<string name="worked_title_unknown">Gearslip はお使いの車で動きました</string>
|
||||
<string name="worked_body">次のドライバーのために、動いたことを共有しますか? 名前、アカウント、位置情報は含まれません。</string>
|
||||
<string name="worked_not_now">今はしない</string>
|
||||
<string name="worked_share">共有</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">これらの無料アプリは Gearslip の車の画面で動きます。各ボタンで GitHub のアプリページが開きます。</string>
|
||||
<string name="recommended_title">おすすめアプリ</string>
|
||||
<string name="back">戻る</string>
|
||||
<string name="recommended_installed">インストール済み</string>
|
||||
<string name="recommended_maps">地図</string>
|
||||
<string name="recommended_music">音楽</string>
|
||||
<string name="recommended_podcasts">ポッドキャスト</string>
|
||||
<string name="recommended_other">ほかの便利なアプリ</string>
|
||||
<string name="recommended_about_organic">オフラインの地図とルート案内。</string>
|
||||
<string name="recommended_about_vela">Google なしのターンバイターン案内。</string>
|
||||
<string name="recommended_about_ytm">YouTube Music プレーヤー。</string>
|
||||
<string name="recommended_about_antennapod">チャプターにも対応したポッドキャスト。</string>
|
||||
<string name="recommended_about_rfsentinel">近くのトラッカーやカメラを見つけます。</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">設定</string>
|
||||
<string name="settings_whats_new">新機能</string>
|
||||
<string name="settings_whats_new_sub">GitHub で %1$s のリリースノートを読む</string>
|
||||
<string name="settings_support">Gearslip を支援</string>
|
||||
<string name="settings_support_footer">Gearslip は無料で、広告もトラッキングもありません。皆さんの支援で続いています。</string>
|
||||
<string name="settings_usage_notes">利用メモ</string>
|
||||
<string name="settings_updates">アップデート</string>
|
||||
<string name="settings_about">このアプリについて</string>
|
||||
<string name="settings_replay">セットアップガイドをもう一度</string>
|
||||
<string name="settings_replay_sub">初回起動の手順をやり直します</string>
|
||||
<string name="settings_connection">接続</string>
|
||||
<string name="settings_use_in_car">車で Gearslip を使う</string>
|
||||
<string name="settings_use_in_car_on">車につなぐと Gearslip が応答します</string>
|
||||
<string name="settings_use_in_car_off">オフ:代わりに Android Auto が接続を使えます</string>
|
||||
<string name="settings_apps">アプリ</string>
|
||||
<string name="settings_certificate">証明書</string>
|
||||
<string name="cert_next_connection">変更は次回の接続から有効になります。</string>
|
||||
<string name="cert_checking">確認中…</string>
|
||||
<string name="cert_valid_until">有効期限:%1$s</string>
|
||||
<string name="cert_hide_details">詳細を隠す</string>
|
||||
<string name="cert_show_details">詳細を表示</string>
|
||||
<string name="cert_subject">サブジェクト</string>
|
||||
<string name="cert_issuer">発行者</string>
|
||||
<string name="cert_downloading">ダウンロード中…</string>
|
||||
<string name="cert_download">aasdk からダウンロード</string>
|
||||
<string name="cert_downloaded">証明書をダウンロードしました。</string>
|
||||
<string name="cert_download_failed">ダウンロードに失敗しました:%1$s</string>
|
||||
<string name="cert_download_sub">オープンソースの aasdk プロジェクトが GitHub で公開している証明書です。ほとんどの車で使えますが、2020 年ごろ以降の一部の車では拒否されます</string>
|
||||
<string name="cert_import">自分のファイルをインポート</string>
|
||||
<string name="cert_import_sub">秘密鍵を含む .p12 ファイルです。車が aasdk の証明書を拒否する場合に使います</string>
|
||||
<string name="cert_remove_imported">インポートした証明書を削除</string>
|
||||
<string name="cert_remove_downloaded">ダウンロードした証明書を削除</string>
|
||||
<string name="cert_removed_imported">インポートした証明書を削除しました。</string>
|
||||
<string name="cert_removed_downloaded">ダウンロードした証明書を削除しました。</string>
|
||||
<string name="cert_imported">証明書をインポートしました。</string>
|
||||
<string name="cert_import_failed">インポートできません:パスワードが違うか、秘密鍵を含む PKCS#12 ファイルではありません。</string>
|
||||
<string name="cert_password_title">証明書のパスワード</string>
|
||||
<string name="cert_password">パスワード</string>
|
||||
<string name="cert_password_hint">パスワードがない場合は空欄のままにしてください。</string>
|
||||
<string name="cert_import_button">インポート</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">キャンセル</string>
|
||||
<string name="cert_none">未読み込み(自己署名。車に拒否されます)</string>
|
||||
<string name="cert_kind_imported">インポート済み:%1$s</string>
|
||||
<string name="cert_kind_downloaded">ダウンロード済み:%1$s</string>
|
||||
<string name="cert_kind_adb">adb で配置:%1$s</string>
|
||||
<string name="cert_kind_self">自己署名:%1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">画面</string>
|
||||
<string name="car_group_nav_bar">ナビバーとプレーヤー</string>
|
||||
<string name="car_group_sound">音と音声</string>
|
||||
<string name="car_group_warnings">警告</string>
|
||||
<string name="car_group_more">その他</string>
|
||||
<string name="choice_size_normal">標準</string>
|
||||
<string name="choice_size_large">大</string>
|
||||
<string name="choice_size_xlarge">特大</string>
|
||||
<string name="choice_theme_phone">スマートフォンに合わせる</string>
|
||||
<string name="choice_theme_light">ライト</string>
|
||||
<string name="choice_theme_dark">ダーク</string>
|
||||
<string name="choice_dark_black">ブラック</string>
|
||||
<string name="choice_dark_black_colour">ブラックとカラー</string>
|
||||
<string name="choice_dark_deep_colour">ディープカラー</string>
|
||||
<string name="choice_dark_grey">グレー</string>
|
||||
<string name="choice_auto">自動</string>
|
||||
<string name="choice_night_day">昼</string>
|
||||
<string name="choice_night_night">夜</string>
|
||||
<string name="choice_units_imperial">ヤード・ポンド法</string>
|
||||
<string name="choice_units_metric">メートル法</string>
|
||||
<string name="choice_nav_show">常に表示</string>
|
||||
<string name="choice_nav_hide_navigating">ナビ中は隠す</string>
|
||||
<string name="choice_nav_hide">タップするまで隠す</string>
|
||||
<string name="choice_open_home">ホーム</string>
|
||||
<string name="summary_player_left">プレーヤーは左</string>
|
||||
<string name="summary_player_right">プレーヤーは右</string>
|
||||
<string name="summary_usb_audio">音声は Gearslip 経由</string>
|
||||
<string name="summary_bluetooth_audio">Bluetooth オーディオ</string>
|
||||
<string name="summary_directions_on">音声案内オン</string>
|
||||
<string name="summary_directions_off">音声案内オフ</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth オン</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth オフ</string>
|
||||
<string name="summary_location_on">位置情報オン</string>
|
||||
<string name="summary_location_off">位置情報オフ</string>
|
||||
<string name="web_page">ウェブページ</string>
|
||||
<string name="summary_experimental_shown">試験運用機能を表示中</string>
|
||||
<string name="summary_experimental_hidden">試験運用機能は非表示</string>
|
||||
<string name="home_car">車</string>
|
||||
<string name="car_group_same">車の画面の[設定]と同じ設定です。ここで変えると車にも反映されます。</string>
|
||||
<string name="car_open_on_connect">接続時に開く</string>
|
||||
<string name="car_size">サイズ</string>
|
||||
<string name="car_app_theme">アプリのテーマ</string>
|
||||
<string name="car_dark_mode">ダークモード</string>
|
||||
<string name="car_buttons">ボタン</string>
|
||||
<string name="car_map_day_night">地図の昼/夜</string>
|
||||
<string name="car_units">単位</string>
|
||||
<string name="car_nav_bar">ナビバー</string>
|
||||
<string name="car_player_nav_bar">ナビバーのプレーヤー</string>
|
||||
<string name="car_skip_buttons">スキップボタン</string>
|
||||
<string name="car_song_title">曲名</string>
|
||||
<string name="car_player_beside_map">地図の横のプレーヤー</string>
|
||||
<string name="car_right">右</string>
|
||||
<string name="car_left">左</string>
|
||||
<string name="car_autoplay">接続時に自動再生</string>
|
||||
<string name="car_more_lyrics">歌詞の取得元を増やす</string>
|
||||
<string name="car_more_lyrics_note">BetterLyrics と KuGou にも問い合わせ、曲名とアーティスト名が送られます。オフにすると LRCLIB のみに問い合わせます。</string>
|
||||
<string name="car_spoken_directions">音声案内</string>
|
||||
<string name="car_spoken_directions_note">地図アプリ自身の音声がすでに車で流れている場合はオフにしてください。</string>
|
||||
<string name="car_phone_speech">スマートフォンの音声認識を使う</string>
|
||||
<string name="car_phone_speech_note">ほとんどのスマートフォンでは Google の音声認識で、声が Google に送られることがあります。</string>
|
||||
<string name="car_warn_bluetooth">Bluetooth 未接続時に警告</string>
|
||||
<string name="car_warn_location">位置情報オフ時に警告</string>
|
||||
<string name="car_experimental">試験運用機能</string>
|
||||
<string name="car_vehicle_data">車両データ</string>
|
||||
<string name="car_vehicle_data_note">車両データのタイルを追加します。車のセンサーの値をリアルタイムで表示します。値が変わるのを確認するまで、項目名は推測にすぎません。</string>
|
||||
<string name="car_adaptive_video">映像品質の自動調整</string>
|
||||
<string name="car_adaptive_video_note">車や車の Wi-Fi が追いつかないときは画質を下げ、余裕ができたら戻します。次回の接続から有効です。</string>
|
||||
<string name="web_page_note">車のブラウザアプリで表示します。空欄の場合は DuckDuckGo を開きます。次回の接続から有効です。</string>
|
||||
<string name="car_audio">車のオーディオ</string>
|
||||
<string name="car_audio_bt">Bluetooth(おすすめ)</string>
|
||||
<string name="car_audio_bt_sub">運転のたびの確認がなく、地図アプリの音声も車から聞こえます</string>
|
||||
<string name="car_audio_usb_sub">遅延がなく音がよりクリアです。運転のたびに Android が画面共有の許可を求め、それによって Gearslip が音を送れるようになります</string>
|
||||
<string name="car_audio_usb_footer">ヘルプのコマンドを一度実行すれば、Android は確認しなくなります。</string>
|
||||
<string name="car_audio_bt_footer">通話はどちらの場合も Bluetooth を使います。</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">車の画面サイズ</string>
|
||||
<string name="screen_use_cars">車の値を使う</string>
|
||||
<string name="screen_preview_size">プレビューのサイズ</string>
|
||||
<string name="screen_preview_note">車のプレビューを描画するサイズ(例:1920x720)。</string>
|
||||
<string name="screen_car_screen">車の画面</string>
|
||||
<string name="screen_footer">指定しない限り、Gearslip は車が報告するサイズを使います。</string>
|
||||
<string name="screen_size">画面サイズ</string>
|
||||
<string name="screen_from_car">車から取得</string>
|
||||
<string name="screen_crop">端を切り取る</string>
|
||||
<string name="screen_crop_sub">上下や左右が切れてしまう車向け</string>
|
||||
<string name="screen_squash">縮めて合わせる</string>
|
||||
<string name="screen_squash_sub">画面全体がつぶれて円が楕円に見える車向け</string>
|
||||
<string name="screen_preview_title">車のプレビューのサイズ</string>
|
||||
<string name="screen_last_car">前回の車</string>
|
||||
<string name="screen_last_car_sub">最後に接続した車のサイズ</string>
|
||||
<string name="screen_this_screen">この画面</string>
|
||||
<string name="screen_this_screen_sub">この端末の画面全体を使います。Gearslip 自体を動かすヘッドユニット向けです</string>
|
||||
<string name="screen_custom">カスタム</string>
|
||||
<string name="screen_enter_size">サイズを入力</string>
|
||||
<string name="screen_change_custom">カスタムサイズを変更</string>
|
||||
<string name="screen_width">幅</string>
|
||||
<string name="screen_height">高さ</string>
|
||||
<string name="save">保存</string>
|
||||
|
||||
<string name="screen_car_size_note">車の映像が引き伸ばされて見える場合に使います。画面の実際のサイズ(例:1280x480)を入力してください。次回の接続から有効です。</string>
|
||||
<string name="screen_set_by_you">%1$s(手動設定)</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">接続のしかた</string>
|
||||
<string name="help_which_cars">対応している車</string>
|
||||
<string name="help_full_list">一覧をすべて見る</string>
|
||||
<string name="help_first_drive">初めて運転する前に</string>
|
||||
<string name="help_in_car">車の中で</string>
|
||||
<string name="help_usb_audio">Gearslip 経由の音声</string>
|
||||
|
||||
<string name="help_aa_off">Android Auto をオフにする。</string>
|
||||
<string name="help_aa_off_detail">Android Auto アプリを無効にするかアンインストールして、車が Gearslip とやり取りするようにします。</string>
|
||||
<string name="help_cert">証明書を読み込む。</string>
|
||||
<string name="help_cert_detail">設定で、ほとんどの車が受け入れる aasdk の証明書をダウンロードしてください。車に拒否された場合は、自分の証明書をインポートします。</string>
|
||||
<string name="help_mic">マイクを許可する。</string>
|
||||
<string name="help_mic_detail">マイクの許可がないと、Android はアプリが音楽を車に送ることを認めません。Gearslip が聞くのは、マイクをタップしたときとハンドルの音声ボタンを押したときだけです。</string>
|
||||
<string name="help_plug_in">ケーブルでつなぐか、ワイヤレスで接続する。</string>
|
||||
<string name="help_plug_in_detail">車が接続すると Gearslip が自動で開きます。ワイヤレスの場合は、先にスマートフォンと車をペア設定し、ホームで[ワイヤレスで接続]をタップします。</string>
|
||||
<string name="help_pair">通話用に Bluetooth でペア設定する。</string>
|
||||
<string name="help_pair_detail">スマートフォンと車をペア設定すると、通話が車から聞こえます。初めてつないだときに Gearslip がペア設定を提案します。</string>
|
||||
<string name="help_answer">車の画面から着信に応答する。</string>
|
||||
<string name="help_answer_detail">Gearslip を発信者番号・迷惑電話アプリに設定してください。設定しない場合も着信音は鳴りますが、応答できるのはスマートフォンだけです。</string>
|
||||
<string name="help_prompt">運転のたびの音声の確認をなくす。</string>
|
||||
<string name="help_prompt_detail">USB デバッグをオンにしたパソコンにスマートフォンをつなぎ、これを一度実行します。しない場合は、出発のたびに[開始]をタップします。</string>
|
||||
<string name="help_readable">通知を読めるようにする。</string>
|
||||
<string name="help_readable_detail">音声を共有している間、Android は通知の内容を隠します。これも実行すると、音楽を車に送っている間だけ Gearslip がその制限を外します。</string>
|
||||
<string name="help_which_cars_body">ほとんどの車は aasdk の証明書を受け入れます。2020 年ごろから、一部の車にはこれを拒否する新しいセキュリティチェックが加わり、そうした車では自分の証明書が必要です。</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">ダウンロードしました。インストール前に Android が確認します。</string>
|
||||
<string name="update_install">インストール</string>
|
||||
<string name="update_download_now">今すぐダウンロードしてください。インストール前に Android が確認します。</string>
|
||||
<string name="update_try_again">再試行</string>
|
||||
<string name="update_update">アップデート</string>
|
||||
<string name="update_checking">確認中…</string>
|
||||
<string name="update_latest">最新バージョンです</string>
|
||||
<string name="update_download">ダウンロード</string>
|
||||
<string name="update_check_now">今すぐ確認</string>
|
||||
<string name="update_check_auto">自動で確認</string>
|
||||
<string name="update_once_a_day">1 日 1 回</string>
|
||||
|
||||
<string name="update_unplug_first">インストールするには車から外してください。インストールすると Gearslip が閉じます。</string>
|
||||
<string name="update_card_title">Gearslip %1$s が公開されました</string>
|
||||
<string name="update_downloading">ダウンロード中… %1$d%%</string>
|
||||
<string name="update_debug_manual">デバッグビルドは[今すぐ確認]をタップしたときだけ確認します</string>
|
||||
<string name="update_checks_daily">Gearslip は 1 日 1 回確認します</string>
|
||||
<string name="update_version_out">バージョン %1$s が公開されました</string>
|
||||
<string name="update_version_ready">バージョン %1$s をインストールできます</string>
|
||||
<string name="update_dialog_status">バージョン %1$s。%2$s。</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">毎週または毎月支援する</string>
|
||||
<string name="about_bmac_sub" formatted="false">カードで 1 回または毎月。Buy Me a Coffee の手数料は 5%</string>
|
||||
<string name="about_give_once">1 回だけ支援する</string>
|
||||
<string name="about_any_amount">金額は自由、カードで</string>
|
||||
<string name="about_monero_sub">ウォレットを開くか、アドレスをコピー</string>
|
||||
<string name="about_monero_title">Monero で支援</string>
|
||||
<string name="about_monero_body">Monero ウォレットから、このアドレスに好きな金額を送ってください。</string>
|
||||
<string name="about_open_wallet">ウォレットで開く</string>
|
||||
<string name="about_copy_address">アドレスをコピー</string>
|
||||
<string name="close">閉じる</string>
|
||||
<string name="about_matrix">Matrix のチャットに参加</string>
|
||||
<string name="about_matrix_sub">ほかのドライバーと車、アプリ、Gearslip について話せます</string>
|
||||
<string name="about_translate">Gearslip の翻訳を手伝う</string>
|
||||
<string name="about_translate_sub">Weblate で自分の言語を直したり追加したりできます。一部の翻訳は機械翻訳から始まっています。</string>
|
||||
<string name="about_source">ソースコード</string>
|
||||
<string name="about_source_sub">AGPL のもとで Gearslip のコードを読んだり変更したりできます</string>
|
||||
<string name="about_voice_credits">音声のクレジット</string>
|
||||
|
||||
<string name="about_no_wallet">Monero ウォレットアプリが見つかりません。代わりにアドレスをコピーしてください。</string>
|
||||
<string name="about_monero_copied">Monero のアドレスをコピーしました</string>
|
||||
<string name="about_voice_credits_sub">音声認識:sherpa-onnx(Apache 2.0)、Banafo の Kroko ASR(CC BY-SA 4.0)、NVIDIA の Parakeet と Canary(CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">証明書の設定</string>
|
||||
<string name="certsetup_installed">証明書をインストールしました。</string>
|
||||
<string name="certsetup_continue">続ける</string>
|
||||
<string name="certsetup_failed">ダウンロードに失敗しました</string>
|
||||
<string name="certsetup_retry">再試行</string>
|
||||
<string name="certsetup_import_sub">秘密鍵を含む .p12 ファイルです。車が aasdk の証明書を拒否する場合に使います。</string>
|
||||
<string name="certsetup_skip">車との接続設定をスキップ</string>
|
||||
|
||||
<string name="certsetup_loaded">車は Google が発行した証明書しか受け入れません。証明書はすでに読み込まれています。</string>
|
||||
<string name="certsetup_choose">車は Google が発行した証明書しか受け入れません。Gearslip には含まれていないので、入手方法を選んでください。</string>
|
||||
<string name="certsetup_aasdk_sub">オープンソースの aasdk プロジェクトが github.com/opencardev/aasdk でこの証明書を公開しています。ほとんどの車で使えます。2020 年ごろ以降の一部の車では拒否されるため、その場合は自分の証明書をインポートしてください。</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Gearslip のストレージ</string>
|
||||
<string name="storage_measuring">計算中…</string>
|
||||
<string name="storage_lyrics">歌詞</string>
|
||||
<string name="storage_clear">消去</string>
|
||||
<string name="storage_voice">音声モデル</string>
|
||||
<string name="storage_remove">削除</string>
|
||||
<string name="storage_logs">古いログ</string>
|
||||
<string name="storage_other">その他の一時ファイル</string>
|
||||
<string name="storage_delete_all">Gearslip のデータをすべて削除</string>
|
||||
|
||||
<string name="storage_total">Gearslip のデータがスマートフォンのストレージを %1$s 使っています。</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="other">%1$d 曲 · %2$s。消去した歌詞は、開いたときに再びダウンロードされます。</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s。音声コマンドに必要です。設定から再ダウンロードできます。</string>
|
||||
<string name="storage_logs_detail">%1$s。以前の起動時のログです。今回の起動のログは残ります。</string>
|
||||
<string name="storage_other_detail">%1$s。ダウンロードしたアップデートなど、Gearslip が作り直せるファイルです。</string>
|
||||
<string name="storage_delete_armed">もう一度[削除]をタップすると、設定、証明書、音声モデル、ログを消去します。Gearslip は閉じ、次回はセットアップから始まります。</string>
|
||||
<string name="storage_delete_sub">設定、証明書、音声モデル、歌詞、ログ。</string>
|
||||
<string name="storage_delete">削除</string>
|
||||
<string name="storage_delete_ellipsis">削除…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">無事に運転できてよかった</string>
|
||||
<string name="donate_liberapay">Liberapay で支援</string>
|
||||
<string name="donate_bmac">コーヒーをおごる</string>
|
||||
<string name="donate_card">カードで 1 回支援</string>
|
||||
|
||||
<string name="donate_body">Gearslip は無料で、広告もトラッキングもありません。ドライブの役に立っていたら、支援をお願いします。</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">これには Bluetooth と付近の Wi-Fi へのアクセスが必要です。</string>
|
||||
<string name="wireless_enable_permission">「付近のデバイス」の権限を有効にする</string>
|
||||
<string name="wireless_connect">ワイヤレスで接続</string>
|
||||
<string name="wireless_which_car">どの車ですか?</string>
|
||||
<string name="wireless_none_paired">スマートフォンはまだ何ともペア設定されていません。先に車とペア設定してください。</string>
|
||||
<string name="wireless_not_listed">車が表示されない場合は、先にスマートフォンとペア設定してください。</string>
|
||||
<string name="bluetooth_settings">Bluetooth の設定</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">音声:リアルタイム</string>
|
||||
<string name="voice_final_title">音声:最終確認</string>
|
||||
<string name="voice_none">なし</string>
|
||||
<string name="voice_none_sub">リアルタイムの結果をそのまま使う。最速、精度は最低</string>
|
||||
|
||||
<string name="voice_live_body">音声アシスタントはこのスマートフォンの中で声を文字に変え、話した内容は外に出ません。合計約 270 MB のモデルが 2 つ必要で、ダウンロードは一度だけです。こちらは話している間に言葉を書き起こします。</string>
|
||||
<string name="voice_final_body">話し終えると、このモデルがフレーズ全体を読み直し、リアルタイムのモデルが聞き逃した名前や地名を拾います。</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">ダウンロード済み。%1$s</string>
|
||||
<string name="voice_downloading">ダウンロード中(%1$d%%)</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">ヘルプ</string>
|
||||
<string name="home_preview">プレビュー</string>
|
||||
<string name="home_logs">ログ</string>
|
||||
<string name="home_disconnect">切断</string>
|
||||
<string name="home_bt_title">Bluetooth が接続されていません</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · デバッグビルド</string>
|
||||
<string name="home_bt_body">音楽は Bluetooth で流れますが、スマートフォンが車に接続されていません。ここか、車の Bluetooth メニューから接続してください。</string>
|
||||
<string name="home_voice_downloading">音声をダウンロード中(%1$d%%)</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">車のオーディオを Bluetooth で再生しますか?</string>
|
||||
<string name="audio_never_ask">パソコンを使って今後確認しない</string>
|
||||
<string name="audio_use_bt">Bluetooth を使う</string>
|
||||
<string name="audio_keep_asking">今後も確認する</string>
|
||||
<string name="audio_mic_title">車のオーディオにはマイクの権限が必要です</string>
|
||||
<string name="audio_open_settings">アプリの設定を開く</string>
|
||||
|
||||
<string name="audio_bt_body">Android は Gearslip が車に送る音楽を画面共有と同じように扱うため、ケーブルでもワイヤレスでも運転のたびに確認します。Bluetooth なら確認はありません。あとで設定から変更できます。</string>
|
||||
<string name="audio_keep_usb">または、音声は Gearslip 経由のまま確認をなくすこともできます。USB デバッグをオンにしたパソコンにスマートフォンをつなぎ、これを一度実行してください。</string>
|
||||
<string name="audio_mic_body">Android では、マイクの権限があるアプリしかほかのアプリの音を車に送れません。Gearslip の権限がオフになっているため、音楽はスマートフォンで流れています。[権限]→[マイク]でオンにしてください。</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">車のキーボードを閉じる</string>
|
||||
<string name="typing_label">車の画面に入力</string>
|
||||
<string name="typing_search">車の画面で検索</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">タップしてコピー</string>
|
||||
<string name="command_copied">コマンドをコピーしました</string>
|
||||
<string name="whats_new_this_version">%1$s(このバージョン)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">テーマ</string>
|
||||
<string name="carset_imperial">ヤード・ポンド法(mi、ft、°F)</string>
|
||||
<string name="carset_metric">メートル法(km、m、°C)</string>
|
||||
<string name="carset_screen_size">画面サイズ(次回の接続)</string>
|
||||
<string name="carset_fit">画面に合わせる</string>
|
||||
<string name="off">オフ</string>
|
||||
<string name="on">オン</string>
|
||||
<string name="carset_lyrics_from">歌詞の取得元</string>
|
||||
<string name="carset_more_sources">取得元を増やす</string>
|
||||
<string name="carset_hide">隠す</string>
|
||||
<string name="carset_show">表示</string>
|
||||
<string name="carset_phone">電話</string>
|
||||
<string name="carset_vehicle">車両</string>
|
||||
<string name="carset_video">映像</string>
|
||||
<string name="carset_version">バージョン</string>
|
||||
<string name="carset_build">ビルド</string>
|
||||
<string name="carset_whats_new_after">アップデート後の新機能</string>
|
||||
<string name="carset_whats_new_after_sub">アップデート後に初めて車が接続したとき、変更点の一覧を一度表示します。</string>
|
||||
<string name="carset_web_default">DuckDuckGo(デフォルト)- タップして別のページを設定</string>
|
||||
<string name="carset_web_note">ランチャーのブラウザアプリで表示します。次回の接続から有効です。</string>
|
||||
<string name="carset_screen_fit">画面の合わせ方</string>
|
||||
<string name="carset_top">上</string>
|
||||
<string name="carset_bottom">下</string>
|
||||
<string name="carset_save_vehicle">この車両に保存</string>
|
||||
<string name="carset_no_profile">一致する車両プロファイルがありません</string>
|
||||
|
||||
<string name="carset_audio_note">「Gearslip 経由」は、ケーブルでもワイヤレスでも、スマートフォンの音を車との接続で送ります。運転のたびに Android が画面共有の許可を求め、一部のアプリはこれをブロックします。Bluetooth はスマートフォン本来の再生方法で、Android の確認もありません。</string>
|
||||
<string name="carset_directions_note">Gearslip が曲がり角ごとに車のスピーカーで読み上げ、その間は音楽の音量を下げます。地図アプリ自身の音声がすでに車で流れている場合はオフにしてください。</string>
|
||||
<string name="carset_phone_speech_note">Gearslip の音声認識の代わりに、スマートフォン本体の音声サービスで聞き取ります。ほとんどのスマートフォンでは Google のもので、声が Google に送られることがあります。GrapheneOS には音声サービスがないため、Gearslip は自分の認識を使い続けます。</string>
|
||||
<string name="carset_vehicle_data_note">車両データのタイルを追加します。ヘッドユニットのセンサーチャンネルの生データをリアルタイムで表示します。項目名(速度、回転数、燃料…)はほかのリバースエンジニアリングのプロジェクトから借りたもので、この車では確認されていません。値が変わるのを自分の目で確かめるまでは、事実ではなく推測として扱ってください。</string>
|
||||
<string name="carset_adaptive_note">車や車の Wi-Fi が追いつかないときは画質を下げ、余裕ができたら戻します。次回の接続から有効です。</string>
|
||||
<string name="carset_usable_height">使える高さ:480 のうち %1$dpx</string>
|
||||
<string name="carset_saved_to">%1$s に保存しました</string>
|
||||
<string name="carset_save_failed">保存できませんでした</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">スマートフォンで開く</string>
|
||||
<string name="media_now_playing">再生中</string>
|
||||
<string name="media_up_next">次に再生</string>
|
||||
<string name="media_browse">ブラウズ</string>
|
||||
<string name="media_search_hint">曲名、アーティスト、アルバム、プレイリストを入力してください。</string>
|
||||
<string name="media_looking_lyrics">歌詞を探しています…</string>
|
||||
<string name="media_nothing_playing">再生していません</string>
|
||||
<string name="media_press_play">再生を押すと続きから再生します。ブラウズから探すこともできます。</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s はライブラリを公開していません。</string>
|
||||
<string name="media_needs_access">スマートフォンで通知へのアクセスを許可すれば、Gearslip で操作できます。[設定]>[通知]>[通知へのアクセス]で許可してください。</string>
|
||||
<string name="media_not_running">%1$s が起動していません。</string>
|
||||
<string name="media_not_running_body">%1$s は Gearslip のプレーヤーで再生されますが、スマートフォンで開いている間だけです。一度開けば、車でそのまま再生が続きます。</string>
|
||||
<string name="media_open_app">%1$s を開く</string>
|
||||
<string name="media_refused">%1$s が Gearslip からのブラウズを許可しませんでした。</string>
|
||||
<string name="media_refused_body">一部のメディアアプリは Google 純正のホストしか受け付けません。</string>
|
||||
<string name="media_connecting">%1$s に接続中…</string>
|
||||
<string name="media_search">%1$s を検索</string>
|
||||
<string name="media_search_no_list">%1$s は検索結果を一覧表示できません。検索を押すと、%1$s が再生する曲を選びます。</string>
|
||||
<string name="media_search_unsupported">%1$s は車からの検索に対応していません。</string>
|
||||
<string name="media_searching">%1$s を検索中…</string>
|
||||
<string name="media_search_nothing">%1$s で「%2$s」は見つかりませんでした。</string>
|
||||
<string name="media_search_ignored">%1$s は「%2$s」で何も再生しませんでした。</string>
|
||||
<string name="media_searching_for">%1$s で「%2$s」を検索中…</string>
|
||||
<string name="media_nothing_playing_dot">再生していません。</string>
|
||||
<string name="media_no_lyrics">この曲の歌詞は見つかりませんでした。</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">上へ</string>
|
||||
<string name="media_loading">読み込み中…</string>
|
||||
<string name="media_browse_start">スマートフォンのアプリで音楽を再生してください。再生、一時停止、スキップ、シークはここから操作できます。</string>
|
||||
<string name="media_load_failed">このリストを読み込めませんでした。</string>
|
||||
<string name="media_nothing_here">何もありません。</string>
|
||||
<string name="media_nothing_queued">キューは空です。</string>
|
||||
<string name="media_play">再生</string>
|
||||
|
||||
<string name="media_queue_empty_idle">再生していません - 次の曲を Gearslip に伝えないアプリもあります。</string>
|
||||
<string name="media_queue_empty_playing">再生中の曲のみ - 次の曲を Gearslip に伝えないアプリもあります。</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">前へ</string>
|
||||
<string name="player_next">次へ</string>
|
||||
<string name="player_repeat">リピート</string>
|
||||
<string name="player_pause">一時停止</string>
|
||||
<string name="player_unlike">高評価を取り消す</string>
|
||||
<string name="player_like">高評価</string>
|
||||
<string name="player_shuffle_off">シャッフルオフ</string>
|
||||
<string name="player_shuffle_on">シャッフルオン</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">別のアプリを選ぶ</string>
|
||||
<string name="home_no_maps">地図アプリが見つかりません。</string>
|
||||
<string name="home_no_maps_body">Android Auto に対応したナビアプリをインストールすると、ここで自動的に起動します。</string>
|
||||
<string name="home_map_stopped">地図を停止しました</string>
|
||||
<string name="home_map_stopped_body">もう一度開くまで閉じたままです。</string>
|
||||
<string name="home_reopen_map">地図を開き直す</string>
|
||||
<string name="home_car_apps">車のアプリ</string>
|
||||
<string name="home_back_nav">ナビバーに戻る</string>
|
||||
<string name="home_show_player">プレーヤーを表示</string>
|
||||
<string name="home_hide_lyrics">歌詞を隠す</string>
|
||||
|
||||
<string name="home_starting">%1$s を起動中…</string>
|
||||
<string name="home_starting_map">地図を起動中…</string>
|
||||
<string name="home_failed">%1$s を起動できませんでした。</string>
|
||||
<string name="home_failed_map">地図アプリを起動できませんでした。</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">ホーム</string>
|
||||
<string name="crumb_media">メディア</string>
|
||||
<string name="crumb_apps">アプリ</string>
|
||||
<string name="crumb_dashboard">ダッシュボード</string>
|
||||
<string name="crumb_weather">天気</string>
|
||||
<string name="crumb_reply">返信</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">ブラウザ</string>
|
||||
<string name="app_screen_sharing">画面共有</string>
|
||||
<string name="app_exit">終了</string>
|
||||
<string name="navapps_unpin">ナビバーから外す</string>
|
||||
<string name="navapps_full">ナビバーがいっぱいです(%1$d 個固定中)</string>
|
||||
<string name="navapps_pin">ナビバーに固定</string>
|
||||
<string name="navapps_close">アプリを閉じる</string>
|
||||
<string name="navapps_move_left">左へ移動</string>
|
||||
<string name="navapps_move_right">右へ移動</string>
|
||||
<string name="navapps_reset">アプリの並び順をリセット</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">閉じる</string>
|
||||
<string name="msg_read">読み上げ</string>
|
||||
<string name="msg_stop">停止</string>
|
||||
<string name="msg_no">いいえ</string>
|
||||
<string name="msg_send_now">今すぐ送信</string>
|
||||
<string name="msg_type">入力</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="other">%1$s · 新着 %2$d 件</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">%1$s に返信</string>
|
||||
<string name="msg_listening">聞き取り中</string>
|
||||
<string name="msg_sending_to">%1$s に送信中</string>
|
||||
<string name="msg_sent_to">%1$s に送信しました</string>
|
||||
<string name="msg_reply_failed">返信できませんでした</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">ペア設定が完了しませんでした。車の Bluetooth メニューからペア設定してください。</string>
|
||||
<string name="bt_pair_not_started">ペア設定が始まりませんでした。車の Bluetooth メニューからペア設定してください。</string>
|
||||
<string name="bt_paired">ペア設定しました。通話が車から聞こえるようになりました。</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">今後表示しない</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">位置情報がオフです</string>
|
||||
<string name="overlay_pair_title">この車とペア設定しますか?</string>
|
||||
<string name="overlay_pair">ペア設定</string>
|
||||
<string name="overlay_confirm_title">スマートフォンで確認してください</string>
|
||||
<string name="overlay_confirm_body">スマートフォンでコードを確認し、[ペア設定]をタップしてください。車でも確認を求められることがあります。</string>
|
||||
|
||||
<string name="overlay_bt_body">音楽は Bluetooth で流れますが、スマートフォンが車に接続されていません。車かスマートフォンの Bluetooth メニューから接続してください。</string>
|
||||
<string name="overlay_location_body">地図アプリが現在地を表示できず、道案内もできません。駐車中に、スマートフォンのクイック設定で位置情報をオンにしてください。</string>
|
||||
<string name="overlay_pair_body_music">スマートフォンと車をペア設定すると、通話と音楽が車のスピーカーから聞こえます。その後、スマートフォンでコードの確認を求められます。</string>
|
||||
<string name="overlay_pair_body">スマートフォンと車をペア設定すると、通話が車のスピーカーから聞こえます。その後、スマートフォンでコードの確認を求められます。</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">現在地を取得しています。</string>
|
||||
<string name="weather_allow_location">天気を見るには、スマートフォンで Gearslip に位置情報を許可してください。</string>
|
||||
<string name="weather_unreachable">天気サービスに接続できませんでした。</string>
|
||||
<string name="weather_clear">快晴</string>
|
||||
<string name="weather_mostly_clear">晴れ</string>
|
||||
<string name="weather_partly_cloudy">晴れ時々曇り</string>
|
||||
<string name="weather_overcast">曇り</string>
|
||||
<string name="weather_fog">霧</string>
|
||||
<string name="weather_drizzle">霧雨</string>
|
||||
<string name="weather_rain">雨</string>
|
||||
<string name="weather_snow">雪</string>
|
||||
<string name="weather_rain_showers">にわか雨</string>
|
||||
<string name="weather_snow_showers">にわか雪</string>
|
||||
<string name="weather_thunderstorm">雷雨</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">天気を取得中…</string>
|
||||
<string name="weather_next_hours">この先数時間</string>
|
||||
<string name="weather_this_week">今週</string>
|
||||
<string name="weather_today">今日</string>
|
||||
<string name="weather_credit">天気データ:Open-Meteo.com(CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">体感 %1$s</string>
|
||||
<string name="weather_humidity">湿度 %1$d%%</string>
|
||||
<string name="weather_wind">風 %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">今日</string>
|
||||
<string name="dash_getting_weather">天気を取得中…</string>
|
||||
<string name="dash_coming_up">今後の予定</string>
|
||||
<string name="dash_loading_calendar">カレンダーを読み込み中…</string>
|
||||
<string name="dash_nothing_week">今後 1 週間の予定はありません。</string>
|
||||
<string name="dash_all_day">終日</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">連絡先</string>
|
||||
<string name="dialer_keypad">キーパッド</string>
|
||||
<string name="dialer_ask">スマートフォンで確認</string>
|
||||
<string name="dialer_enter_number">番号を入力</string>
|
||||
<string name="dialer_call">発信</string>
|
||||
<string name="dialer_no_contacts">電話番号のある連絡先が見つかりません。</string>
|
||||
<string name="dialer_search">連絡先を検索</string>
|
||||
|
||||
<string name="dialer_needs_permission">ここから発信するには、電話と連絡先の権限が必要です。駐車中にスマートフォンで許可してください。</string>
|
||||
<string name="dialer_no_match">「%1$s」に一致する連絡先はありません。</string>
|
||||
<string name="dialer_call_who">%1$s に発信しますか?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">全画面表示を終了</string>
|
||||
<string name="carui_drive_safely">安全運転を</string>
|
||||
<string name="carui_understand">了解しました</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip は非公式のソフトウェアです。自動車メーカーが作ったものでも確認したものでもなく、予告なく動かなくなることがあります。</string>
|
||||
<string name="carui_never_interact">運転中はこの画面を絶対に操作しないでください。ルートや曲は出発前に設定し、運転中は道路に集中してください。</string>
|
||||
<string name="carui_this_app">このアプリ</string>
|
||||
<string name="carui_app_unsupported">%1$s はまだ Gearslip で動きません。</string>
|
||||
<string name="carui_app_unsupported_body">起動しませんでした。Google の Android Auto でしか動かないアプリもあります。</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">通話中</string>
|
||||
<string name="navbar_end_call">通話を終了</string>
|
||||
<string name="navbar_unmute">ミュート解除</string>
|
||||
<string name="navbar_mute">ミュート</string>
|
||||
<string name="navbar_close_panel">サイドパネルを閉じる</string>
|
||||
<string name="navbar_dock_player">プレーヤーを地図の横に移動</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">検索またはアドレスを入力</string>
|
||||
<string name="web_forward">進む</string>
|
||||
<string name="web_reload">再読み込み</string>
|
||||
<string name="web_start_page">スタートページ</string>
|
||||
<string name="web_fullscreen">全画面表示</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">通知</string>
|
||||
<string name="notif_clear_all">すべて消去</string>
|
||||
<string name="notif_empty">まだ何もありません。スマートフォンの新しい通知がここに表示されます。</string>
|
||||
<string name="notif_off">通知がオフです。スマートフォンの[設定]>[通知]>[通知へのアクセス]で Gearslip を許可してください。</string>
|
||||
<string name="notif_replied">返信済み</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">既読にする</string>
|
||||
<string name="msg_messages">メッセージ</string>
|
||||
<string name="msg_none_recent">最近のメッセージはありません。%1$s の新しいメッセージは、スマートフォンに届くとここに表示されます。</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="other">以前の %1$d 件</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">クイック返信を隠す</string>
|
||||
<string name="msg_quick">クイック返信</string>
|
||||
<string name="msg_you">自分:</string>
|
||||
<string name="msg_sender">%1$s:</string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">ルートを検索中…</string>
|
||||
<string name="nav_then">その後</string>
|
||||
<string name="nav_stop">ナビを終了</string>
|
||||
<string name="nav_finding_routes">ルートを検索中…</string>
|
||||
<string name="nav_lane_recommended">推奨車線</string>
|
||||
<string name="nav_lane">車線</string>
|
||||
<string name="nav_arrive">%1$s 到着</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">まだ何も再生していません。</string>
|
||||
<string name="template_search">検索</string>
|
||||
<string name="template_start">開始</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">着信</string>
|
||||
<string name="call_decline">拒否</string>
|
||||
<string name="call_answer">応答</string>
|
||||
<string name="call_ringing">すでに着信音が鳴っています - ここで応答するか、そのまま鳴らしておいてください。</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">タッチ操作がオフです。スマートフォンの[設定]>[ユーザー補助]で Gearslip を有効にしてください。</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">アップデート後に表示</string>
|
||||
<string name="whatsnew_got_it">OK</string>
|
||||
<string name="whatsnew_title">バージョン %1$s の新機能</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">スペース</string>
|
||||
<string name="keyboard_hide">キーボードを隠す</string>
|
||||
<string name="keyboard_submit">確定</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Gearslip では動きません</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">車のアプリ</string>
|
||||
<string name="stage_close">%1$s を閉じる</string>
|
||||
<string name="stage_close_app">アプリを閉じる</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">緩やかに左方向です</string>
|
||||
<string name="dir_slight_left_onto">緩やかに左方向、%1$s に入ります</string>
|
||||
<string name="dir_slight_right">緩やかに右方向です</string>
|
||||
<string name="dir_slight_right_onto">緩やかに右方向、%1$s に入ります</string>
|
||||
<string name="dir_left">左折です</string>
|
||||
<string name="dir_left_onto">左折して %1$s に入ります</string>
|
||||
<string name="dir_right">右折です</string>
|
||||
<string name="dir_right_onto">右折して %1$s に入ります</string>
|
||||
<string name="dir_sharp_left">鋭く左折です</string>
|
||||
<string name="dir_sharp_left_onto">鋭く左折して %1$s に入ります</string>
|
||||
<string name="dir_sharp_right">鋭く右折です</string>
|
||||
<string name="dir_sharp_right_onto">鋭く右折して %1$s に入ります</string>
|
||||
<string name="dir_u_turn">U ターンしてください</string>
|
||||
<string name="dir_keep_left">左側を進みます</string>
|
||||
<string name="dir_keep_left_toward">左側を進み、%1$s 方面です</string>
|
||||
<string name="dir_keep_right">右側を進みます</string>
|
||||
<string name="dir_keep_right_toward">右側を進み、%1$s 方面です</string>
|
||||
<string name="dir_fork_left">分岐を左です</string>
|
||||
<string name="dir_fork_left_toward">分岐を左、%1$s 方面です</string>
|
||||
<string name="dir_fork_right">分岐を右です</string>
|
||||
<string name="dir_fork_right_toward">分岐を右、%1$s 方面です</string>
|
||||
<string name="dir_ramp_left">左側の入口に入ります</string>
|
||||
<string name="dir_ramp_left_onto">左側の入口から %1$s に入ります</string>
|
||||
<string name="dir_ramp_right">右側の入口に入ります</string>
|
||||
<string name="dir_ramp_right_onto">右側の入口から %1$s に入ります</string>
|
||||
<string name="dir_exit_left">左側の出口です</string>
|
||||
<string name="dir_exit_left_toward">左側の出口、%1$s 方面です</string>
|
||||
<string name="dir_exit_right">右側の出口です</string>
|
||||
<string name="dir_exit_right_toward">右側の出口、%1$s 方面です</string>
|
||||
<string name="dir_merge_left">左に合流します</string>
|
||||
<string name="dir_merge_left_onto">左に合流して %1$s に入ります</string>
|
||||
<string name="dir_merge_right">右に合流します</string>
|
||||
<string name="dir_merge_right_onto">右に合流して %1$s に入ります</string>
|
||||
<string name="dir_merge">合流します</string>
|
||||
<string name="dir_merge_onto">%1$s に合流します</string>
|
||||
<string name="dir_roundabout_through">ロータリーを通過します</string>
|
||||
<string name="dir_roundabout_through_onto">ロータリーを通過して %1$s に入ります</string>
|
||||
<string name="dir_roundabout_enter">ロータリーに入ります</string>
|
||||
<string name="dir_roundabout_exit">ロータリーを出ます</string>
|
||||
<string name="dir_roundabout_exit_onto">ロータリーを出て %1$s に入ります</string>
|
||||
<string name="dir_straight">直進です</string>
|
||||
<string name="dir_straight_onto">直進して %1$s に入ります</string>
|
||||
<string name="dir_continue">道なりに進みます</string>
|
||||
<string name="dir_continue_onto">%1$s を道なりに進みます</string>
|
||||
<string name="dir_ferry">フェリーに乗ります</string>
|
||||
<string name="dir_train">列車に乗ります</string>
|
||||
<string name="dir_destination">目的地はこの先です</string>
|
||||
<string name="dir_destination_left">目的地は左側です</string>
|
||||
<string name="dir_destination_right">目的地は右側です</string>
|
||||
<string name="dir_roundabout_take">ロータリーで %1$s出口です</string>
|
||||
<string name="dir_roundabout_take_onto">ロータリーで %1$s出口、%2$s に入ります</string>
|
||||
<string name="dir_ordinal_1">1 番目の</string>
|
||||
<string name="dir_ordinal_2">2 番目の</string>
|
||||
<string name="dir_ordinal_3">3 番目の</string>
|
||||
<string name="dir_ordinal_4">4 番目の</string>
|
||||
<string name="dir_ordinal_5">5 番目の</string>
|
||||
<string name="dir_ordinal_6">6 番目の</string>
|
||||
<string name="dir_ordinal_7">7 番目の</string>
|
||||
<string name="dir_ordinal_8">8 番目の</string>
|
||||
<string name="dir_in_distance">%1$s 先、%2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="other">%1$s キロ</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s キロ</string>
|
||||
<string name="dir_quarter_mile">4 分の 1 マイル</string>
|
||||
<string name="dir_half_mile">半マイル</string>
|
||||
<string name="dir_three_quarter_mile">4 分の 3 マイル</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="other">%1$s マイル</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s マイル</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="other">%1$s フィート</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="other">%1$s ヤード</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="other">%1$d メートル</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">車が接続しない場合は、この証明書が拒否されているのかもしれません。設定の[証明書]から自分の証明書をインポートしてください。</string>
|
||||
<string name="cert_downloaded_footer">車が接続しませんか? 2020 年ごろ以降の一部の車は aasdk の証明書を拒否するため、自分の証明書をインポートしてください。変更は次回の接続から有効になります。</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">メーター画面に曲がり角カード</string>
|
||||
<string name="car_cluster_video_note">メーターパネルに 2 つ目の画面がある車向け:Gearslip が次の曲がり角をそこに表示します。次回の接続から有効です。</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">未接続</string>
|
||||
<string name="status_idle_detail">車の USB ポートにつなぐか、ワイヤレスで接続してください。車が応答すると Gearslip が自動で開きます。</string>
|
||||
<string name="status_connecting">接続中</string>
|
||||
<string name="status_projecting">投影中</string>
|
||||
<string name="status_projecting_detail">ヘッドユニットに映像を送っています。</string>
|
||||
<string name="status_disconnected">切断されました</string>
|
||||
<string name="status_disconnected_detail">ヘッドユニットとの接続が切れました。</string>
|
||||
<string name="places_title">保存した場所</string>
|
||||
<string name="places_sub">どの地図アプリでも「navigate home」と話しかけられます</string>
|
||||
<string name="places_intro">「navigate home」「take me to work」、または「navigate to」と自分で付けた名前を言うと、Gearslip がその住所を地図アプリに送ります。音声コマンドは今のところ英語のみです。</string>
|
||||
<string name="places_home">自宅</string>
|
||||
<string name="places_work">職場</string>
|
||||
<string name="places_other">場所 %1$d</string>
|
||||
<string name="places_name">名前</string>
|
||||
<string name="places_address">住所</string>
|
||||
<string name="places_here">現在地を使う</string>
|
||||
<string name="places_finding">検索中…</string>
|
||||
<string name="places_not_found">現在地が見つかりませんでした。位置情報がオンになっているか確認してください。</string>
|
||||
<string name="places_clear">消去</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,824 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">화면 공유 중에 차량 화면의 터치를 Gearslip이 휴대전화에서 재현할 수 있게 합니다. Gearslip은 화면 내용을 읽지 않습니다.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">다른 운전자를 도와주세요.</string>
|
||||
<string name="guide_safety_title">안전하게 운전하세요.</string>
|
||||
<string name="guide_safety_body">Gearslip은 비공식 앱입니다. 어떤 자동차 제조사도 만들거나 검토하지 않았으며 예고 없이 작동을 멈출 수 있습니다. 출발 전에 설정하고, 운전 중에는 도로에서 눈을 떼지 마세요.</string>
|
||||
<string name="guide_mic_title">마이크</string>
|
||||
<string name="guide_mic_body">음악을 차량으로 보내고, 무언가를 요청하거나 메시지에 답장할 때 목소리를 듣는 데 사용합니다. 말한 내용은 휴대전화 밖으로 나가지 않습니다.</string>
|
||||
<string name="guide_location_title">위치</string>
|
||||
<string name="guide_location_body">차량 지도와 주변 검색에 필요합니다. 위치가 없으면 대시보드에 지도가 표시되지 않습니다.</string>
|
||||
<string name="guide_calendar_title">캘린더</string>
|
||||
<string name="guide_calendar_body">차량 화면에 다음 일정을 표시합니다. Gearslip은 캘린더를 읽기만 합니다.</string>
|
||||
<string name="guide_calls_title">통화 및 연락처</string>
|
||||
<string name="guide_calls_body">차량에 발신자 이름이 표시되고, 차량의 전화 앱으로 번호를 누르거나 연락처에 전화할 수 있습니다. Gearslip은 읽기만 합니다.</string>
|
||||
<string name="guide_bluetooth_title">블루투스</string>
|
||||
<string name="guide_bluetooth_body">처음 연결하면 Gearslip이 휴대전화와 차량의 페어링을 제안하여 통화가 차량 스피커로 들리게 합니다.</string>
|
||||
<string name="guide_notifications_title">알림</string>
|
||||
<string name="guide_notifications_body">Gearslip이 연결되어 있는 동안 중지 버튼이 있는 알림을 표시합니다. 허용하지 않으면 Android가 이 알림을 숨깁니다.</string>
|
||||
<string name="guide_messages_title">메시지</string>
|
||||
<string name="guide_messages_body">차량에 메시지를 표시하고, 소리 내어 읽어 주며, 답장할 수 있게 합니다. 열리는 목록에서 Gearslip을 켜세요.</string>
|
||||
<string name="guide_android_auto_title">Android Auto를 끄세요.</string>
|
||||
<string name="guide_android_auto_body">Android Auto가 아직 설치되어 있어 차량의 USB 연결을 Gearslip과 다툽니다. 열리는 화면에서 사용 중지하거나 제거하세요.</string>
|
||||
<string name="guide_default_title">Gearslip을 선택하세요.</string>
|
||||
<string name="guide_default_body">차량에 연결할 때 어떤 앱으로 열지 묻는다면 Gearslip을 선택하고 항상을 탭하세요.</string>
|
||||
<string name="guide_audio_title">차량 오디오</string>
|
||||
<string name="guide_audio_body">음악을 차량으로 보내는 방법을 선택하세요. 통화는 어느 쪽이든 블루투스로 연결됩니다.</string>
|
||||
<string name="guide_voice_title">음성</string>
|
||||
<string name="guide_voice_body">길 안내, 음악, 전화를 음성으로 요청하세요. 휴대전화가 직접 음성을 텍스트로 바꾸며, 말한 내용은 밖으로 나가지 않습니다. %1$dMB를 한 번만 다운로드하면 됩니다. 가능하면 Wi-Fi를 사용하세요.</string>
|
||||
<string name="guide_audio_prompt_title">오디오 확인 없애기</string>
|
||||
<string name="guide_audio_prompt_body">USB 디버깅이 켜진 컴퓨터에서 이 명령을 실행하면 Android가 오디오를 Gearslip에 영구적으로 맡깁니다. 실행하지 않으면 운전할 때마다 시작을 한 번 탭해야 합니다.</string>
|
||||
<string name="guide_readable_title">읽을 수 있는 알림</string>
|
||||
<string name="guide_readable_body">오디오가 차량으로 가는 동안 Android는 화면을 공유할 때처럼 알림 내용을 숨깁니다. 이 설정을 하면 Gearslip이 운전 중에는 알림을 보여 주고 끝나면 다시 숨깁니다.</string>
|
||||
<string name="guide_answer_title">전화 받기</string>
|
||||
<string name="guide_answer_body">Gearslip을 발신자 표시 앱으로 설정하면 차량에서 전화를 받고 거절할 수 있습니다. 설정하지 않아도 전화는 울리지만 휴대전화에서만 받을 수 있습니다.</string>
|
||||
<string name="guide_apps_title">함께 쓰면 좋은 앱</string>
|
||||
<string name="guide_buttons_title">버튼 스타일</string>
|
||||
<string name="guide_buttons_body">차량 화면의 버튼 모양을 선택하세요. 차량 설정에서 언제든 바꿀 수 있습니다.</string>
|
||||
<string name="guide_open_notification_access">알림 액세스 열기</string>
|
||||
<string name="guide_open_android_auto">Android Auto 앱 정보 열기</string>
|
||||
<string name="guide_download_voice">음성 다운로드</string>
|
||||
<string name="guide_set_up">설정</string>
|
||||
<string name="guide_voice_downloading">다운로드 중, %1$d%%. 계속 진행해도 다운로드는 이어집니다.</string>
|
||||
<string name="guide_voice_ready">음성이 준비되었습니다.</string>
|
||||
<string name="guide_voice_failed">다운로드가 완료되지 않았습니다. 다시 시도하려면 음성 다운로드를 탭하세요.</string>
|
||||
<string name="guide_audio_bluetooth">블루투스</string>
|
||||
<string name="guide_recommended">권장</string>
|
||||
<string name="guide_audio_bluetooth_body">운전할 때마다 묻지 않으며, 지도 앱의 음성도 차량에서 들립니다.</string>
|
||||
<string name="guide_audio_usb">Gearslip을 통해</string>
|
||||
<string name="guide_audio_usb_body">지연 없이 더 깨끗한 소리입니다. 운전할 때마다 Android가 화면 공유를 요청하며, 이를 통해 Gearslip이 소리를 보냅니다.</string>
|
||||
<string name="guide_optional">선택 사항</string>
|
||||
<string name="guide_already_allowed">이미 허용되었습니다.</string>
|
||||
<string name="guide_scroll_more">스크롤하여 더 보기 ↓</string>
|
||||
<string name="guide_allow">허용</string>
|
||||
<string name="guide_skip">나중에</string>
|
||||
<string name="guide_open">열기</string>
|
||||
<string name="guide_next">다음</string>
|
||||
<string name="guide_done">완료</string>
|
||||
<string name="guide_back">뒤로</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">이 기능을 켜면 Gearslip을 열 때와 운전이 끝날 때마다 짧은 기록을 보냅니다. <b>기록에는 이름, 계정, 위치가 없습니다. 무작위 번호로 휴대전화를 한 번만 세며, 서버는 IP 주소를 저장하지 않습니다.</b> 기록을 통해 어떤 차량에서 작동하고 다른 차량에서는 어디서 실패하는지 알 수 있습니다.</string>
|
||||
<string name="usage_send">사용 기록 보내기</string>
|
||||
<string name="usage_also_share">함께 공유</string>
|
||||
<string name="usage_share_android">Android 버전 및 시스템</string>
|
||||
<string name="usage_share_phone">휴대전화 모델</string>
|
||||
<string name="usage_share_cars">각 차량과 연결 여부</string>
|
||||
<string name="usage_share_drives">각 운전과 종료 방식</string>
|
||||
<string name="usage_what_sent">보내는 내용</string>
|
||||
<string name="usage_what_sent_sub">전송되는 그대로의 모든 항목</string>
|
||||
<string name="usage_hide_note">기록 숨기기</string>
|
||||
<string name="usage_show_note">기록 보기</string>
|
||||
<string name="usage_example">예시, 전송되지 않음: 운전 한 번이 \"sessions\"에 이 내용을 추가합니다</string>
|
||||
<string name="usage_no_server">이 빌드에는 통계 서버가 없어 아무것도 보내지 않습니다</string>
|
||||
<string name="usage_debug_build">디버그 빌드: 기록은 따로 보관되며 통계에 포함되지 않습니다</string>
|
||||
<string name="worked_title">Gearslip이 %1$s에서 작동했습니다</string>
|
||||
<string name="worked_title_unknown">Gearslip이 차량에서 작동했습니다</string>
|
||||
<string name="worked_body">다음 운전자가 작동한다는 걸 알 수 있게 공유할까요? 이름, 계정, 위치는 포함되지 않습니다.</string>
|
||||
<string name="worked_not_now">나중에</string>
|
||||
<string name="worked_share">공유</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">이 무료 앱들은 Gearslip의 차량 화면에서 작동합니다. 각 버튼을 누르면 GitHub의 앱 페이지가 열립니다.</string>
|
||||
<string name="recommended_title">추천 앱</string>
|
||||
<string name="back">뒤로</string>
|
||||
<string name="recommended_installed">설치됨</string>
|
||||
<string name="recommended_maps">지도</string>
|
||||
<string name="recommended_music">음악</string>
|
||||
<string name="recommended_podcasts">팟캐스트</string>
|
||||
<string name="recommended_other">기타 멋진 앱</string>
|
||||
<string name="recommended_about_organic">오프라인 지도와 경로 안내.</string>
|
||||
<string name="recommended_about_vela">Google 없는 턴바이턴 내비게이션.</string>
|
||||
<string name="recommended_about_ytm">YouTube Music 플레이어.</string>
|
||||
<string name="recommended_about_antennapod">챕터까지 지원하는 팟캐스트.</string>
|
||||
<string name="recommended_about_rfsentinel">근처의 추적기와 카메라를 찾아냅니다.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">설정</string>
|
||||
<string name="settings_whats_new">새로운 기능</string>
|
||||
<string name="settings_whats_new_sub">GitHub에서 %1$s 릴리스 노트 읽기</string>
|
||||
<string name="settings_support">Gearslip 후원</string>
|
||||
<string name="settings_support_footer">Gearslip은 무료이며 광고와 추적이 없습니다. 여러분의 후원으로 유지됩니다.</string>
|
||||
<string name="settings_usage_notes">사용 기록</string>
|
||||
<string name="settings_updates">업데이트</string>
|
||||
<string name="settings_about">정보</string>
|
||||
<string name="settings_replay">설정 가이드 다시 보기</string>
|
||||
<string name="settings_replay_sub">처음 실행할 때의 단계를 다시 진행합니다</string>
|
||||
<string name="settings_connection">연결</string>
|
||||
<string name="settings_use_in_car">차량에서 Gearslip 사용</string>
|
||||
<string name="settings_use_in_car_on">차량에 연결하면 Gearslip이 응답합니다</string>
|
||||
<string name="settings_use_in_car_off">꺼짐: Android Auto가 대신 연결을 가져갈 수 있습니다</string>
|
||||
<string name="settings_apps">앱</string>
|
||||
<string name="settings_certificate">인증서</string>
|
||||
<string name="cert_next_connection">변경 사항은 다음 연결부터 적용됩니다.</string>
|
||||
<string name="cert_checking">확인 중…</string>
|
||||
<string name="cert_valid_until">%1$s까지 유효</string>
|
||||
<string name="cert_hide_details">세부정보 숨기기</string>
|
||||
<string name="cert_show_details">세부정보 보기</string>
|
||||
<string name="cert_subject">주체</string>
|
||||
<string name="cert_issuer">발급자</string>
|
||||
<string name="cert_downloading">다운로드 중…</string>
|
||||
<string name="cert_download">aasdk에서 다운로드</string>
|
||||
<string name="cert_downloaded">인증서를 다운로드했습니다.</string>
|
||||
<string name="cert_download_failed">다운로드 실패: %1$s</string>
|
||||
<string name="cert_download_sub">오픈소스 aasdk 프로젝트가 GitHub에 공개한 인증서입니다. 대부분의 차량에서 받아들이지만 2020년경 이후의 일부 차량은 거부합니다</string>
|
||||
<string name="cert_import">내 파일 가져오기</string>
|
||||
<string name="cert_import_sub">개인 키가 포함된 .p12 파일입니다. 차량이 aasdk 인증서를 거부할 때 사용하세요</string>
|
||||
<string name="cert_remove_imported">가져온 인증서 삭제</string>
|
||||
<string name="cert_remove_downloaded">다운로드한 인증서 삭제</string>
|
||||
<string name="cert_removed_imported">가져온 인증서를 삭제했습니다.</string>
|
||||
<string name="cert_removed_downloaded">다운로드한 인증서를 삭제했습니다.</string>
|
||||
<string name="cert_imported">인증서를 가져왔습니다.</string>
|
||||
<string name="cert_import_failed">가져올 수 없음: 비밀번호가 틀렸거나 개인 키가 있는 PKCS#12 파일이 아닙니다.</string>
|
||||
<string name="cert_password_title">인증서 비밀번호</string>
|
||||
<string name="cert_password">비밀번호</string>
|
||||
<string name="cert_password_hint">파일에 비밀번호가 없으면 비워 두세요.</string>
|
||||
<string name="cert_import_button">가져오기</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">취소</string>
|
||||
<string name="cert_none">로드되지 않음(자체 서명, 차량이 거부함)</string>
|
||||
<string name="cert_kind_imported">가져옴: %1$s</string>
|
||||
<string name="cert_kind_downloaded">다운로드됨: %1$s</string>
|
||||
<string name="cert_kind_adb">adb로 배치됨: %1$s</string>
|
||||
<string name="cert_kind_self">자체 서명: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">화면</string>
|
||||
<string name="car_group_nav_bar">내비게이션 바 및 플레이어</string>
|
||||
<string name="car_group_sound">소리 및 음성</string>
|
||||
<string name="car_group_warnings">경고</string>
|
||||
<string name="car_group_more">더보기</string>
|
||||
<string name="choice_size_normal">보통</string>
|
||||
<string name="choice_size_large">크게</string>
|
||||
<string name="choice_size_xlarge">아주 크게</string>
|
||||
<string name="choice_theme_phone">휴대전화 설정 따르기</string>
|
||||
<string name="choice_theme_light">밝게</string>
|
||||
<string name="choice_theme_dark">어둡게</string>
|
||||
<string name="choice_dark_black">검정</string>
|
||||
<string name="choice_dark_black_colour">검정과 색상</string>
|
||||
<string name="choice_dark_deep_colour">진한 색상</string>
|
||||
<string name="choice_dark_grey">회색</string>
|
||||
<string name="choice_auto">자동</string>
|
||||
<string name="choice_night_day">주간</string>
|
||||
<string name="choice_night_night">야간</string>
|
||||
<string name="choice_units_imperial">야드파운드법</string>
|
||||
<string name="choice_units_metric">미터법</string>
|
||||
<string name="choice_nav_show">항상 표시</string>
|
||||
<string name="choice_nav_hide_navigating">길 안내 중 숨기기</string>
|
||||
<string name="choice_nav_hide">탭할 때까지 숨기기</string>
|
||||
<string name="choice_open_home">홈</string>
|
||||
<string name="summary_player_left">플레이어 왼쪽</string>
|
||||
<string name="summary_player_right">플레이어 오른쪽</string>
|
||||
<string name="summary_usb_audio">Gearslip을 통한 오디오</string>
|
||||
<string name="summary_bluetooth_audio">블루투스 오디오</string>
|
||||
<string name="summary_directions_on">음성 안내 켜짐</string>
|
||||
<string name="summary_directions_off">음성 안내 꺼짐</string>
|
||||
<string name="summary_bluetooth_on">블루투스 켜짐</string>
|
||||
<string name="summary_bluetooth_off">블루투스 꺼짐</string>
|
||||
<string name="summary_location_on">위치 켜짐</string>
|
||||
<string name="summary_location_off">위치 꺼짐</string>
|
||||
<string name="web_page">웹페이지</string>
|
||||
<string name="summary_experimental_shown">실험 기능 표시됨</string>
|
||||
<string name="summary_experimental_hidden">실험 기능 숨겨짐</string>
|
||||
<string name="home_car">차량</string>
|
||||
<string name="car_group_same">차량의 설정과 같은 설정입니다. 여기서 바꾸면 차량에도 반영됩니다.</string>
|
||||
<string name="car_open_on_connect">연결 시 열기</string>
|
||||
<string name="car_size">크기</string>
|
||||
<string name="car_app_theme">앱 테마</string>
|
||||
<string name="car_dark_mode">다크 모드</string>
|
||||
<string name="car_buttons">버튼</string>
|
||||
<string name="car_map_day_night">지도 주간/야간</string>
|
||||
<string name="car_units">단위</string>
|
||||
<string name="car_nav_bar">내비게이션 바</string>
|
||||
<string name="car_player_nav_bar">내비게이션 바의 플레이어</string>
|
||||
<string name="car_skip_buttons">건너뛰기 버튼</string>
|
||||
<string name="car_song_title">노래 제목</string>
|
||||
<string name="car_player_beside_map">지도 옆 플레이어</string>
|
||||
<string name="car_right">오른쪽</string>
|
||||
<string name="car_left">왼쪽</string>
|
||||
<string name="car_autoplay">연결 시 자동 재생</string>
|
||||
<string name="car_more_lyrics">가사 출처 더 보기</string>
|
||||
<string name="car_more_lyrics_note">BetterLyrics와 KuGou에도 요청하며, 이때 노래 제목과 아티스트가 전달됩니다. 끄면 LRCLIB에만 요청합니다.</string>
|
||||
<string name="car_spoken_directions">음성 안내</string>
|
||||
<string name="car_spoken_directions_note">지도 앱 자체의 음성이 이미 차량에서 나온다면 끄세요.</string>
|
||||
<string name="car_phone_speech">휴대전화의 음성 인식 사용</string>
|
||||
<string name="car_phone_speech_note">대부분의 휴대전화에서는 Google 음성 인식이며, 목소리가 Google로 전송될 수 있습니다.</string>
|
||||
<string name="car_warn_bluetooth">블루투스가 연결되지 않으면 경고</string>
|
||||
<string name="car_warn_location">위치가 꺼져 있으면 경고</string>
|
||||
<string name="car_experimental">실험 기능</string>
|
||||
<string name="car_vehicle_data">차량 데이터</string>
|
||||
<string name="car_vehicle_data_note">차량 데이터 타일을 추가합니다. 차량 센서 값을 실시간으로 보여 줍니다. 값이 바뀌는 것을 직접 보기 전까지 항목 이름은 추측입니다.</string>
|
||||
<string name="car_adaptive_video">적응형 영상 품질</string>
|
||||
<string name="car_adaptive_video_note">차량이나 차량의 Wi-Fi가 따라가지 못하면 화질을 낮추고, 여유가 생기면 다시 높입니다. 다음 연결부터 적용됩니다.</string>
|
||||
<string name="web_page_note">차량의 브라우저 앱에서 표시됩니다. 비워 두면 DuckDuckGo가 열립니다. 다음 연결부터 적용됩니다.</string>
|
||||
<string name="car_audio">차량 오디오</string>
|
||||
<string name="car_audio_bt">블루투스(권장)</string>
|
||||
<string name="car_audio_bt_sub">운전할 때마다 묻지 않으며, 지도 앱의 음성도 차량에서 들립니다</string>
|
||||
<string name="car_audio_usb_sub">지연 없이 더 깨끗한 소리입니다. 운전할 때마다 Android가 화면 공유를 요청하며, 이를 통해 Gearslip이 소리를 보냅니다</string>
|
||||
<string name="car_audio_usb_footer">도움말의 명령을 한 번 실행하면 Android가 더 이상 묻지 않습니다.</string>
|
||||
<string name="car_audio_bt_footer">통화는 어느 쪽이든 블루투스로 연결됩니다.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">차량 화면 크기</string>
|
||||
<string name="screen_use_cars">차량 값 사용</string>
|
||||
<string name="screen_preview_size">미리보기 크기</string>
|
||||
<string name="screen_preview_note">차량 미리보기를 그리는 크기입니다(예: 1920x720).</string>
|
||||
<string name="screen_car_screen">차량 화면</string>
|
||||
<string name="screen_footer">직접 설정하지 않으면 Gearslip은 차량이 알려 주는 크기를 사용합니다.</string>
|
||||
<string name="screen_size">화면 크기</string>
|
||||
<string name="screen_from_car">차량에서 가져오기</string>
|
||||
<string name="screen_crop">가장자리 자르기</string>
|
||||
<string name="screen_crop_sub">위아래나 양옆이 잘리는 차량용</string>
|
||||
<string name="screen_squash">맞게 줄이기</string>
|
||||
<string name="screen_squash_sub">화면 전체가 눌려 원이 타원처럼 보이는 차량용</string>
|
||||
<string name="screen_preview_title">차량 미리보기 크기</string>
|
||||
<string name="screen_last_car">마지막 차량</string>
|
||||
<string name="screen_last_car_sub">마지막으로 연결한 차량의 크기</string>
|
||||
<string name="screen_this_screen">이 화면</string>
|
||||
<string name="screen_this_screen_sub">이 기기의 화면을 가득 채웁니다. Gearslip을 직접 실행하는 헤드 유닛용입니다</string>
|
||||
<string name="screen_custom">사용자 지정</string>
|
||||
<string name="screen_enter_size">크기 입력</string>
|
||||
<string name="screen_change_custom">사용자 지정 크기 변경</string>
|
||||
<string name="screen_width">너비</string>
|
||||
<string name="screen_height">높이</string>
|
||||
<string name="save">저장</string>
|
||||
|
||||
<string name="screen_car_size_note">차량 화면이 늘어나 보이면 사용하세요. 화면의 실제 크기를 입력하세요(예: 1280x480). 다음 연결부터 적용됩니다.</string>
|
||||
<string name="screen_set_by_you">%1$s, 직접 설정함</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">연결 방법</string>
|
||||
<string name="help_which_cars">작동하는 차량</string>
|
||||
<string name="help_full_list">전체 목록 보기</string>
|
||||
<string name="help_first_drive">첫 운전 전에</string>
|
||||
<string name="help_in_car">차량에서</string>
|
||||
<string name="help_usb_audio">Gearslip을 통한 오디오</string>
|
||||
|
||||
<string name="help_aa_off">Android Auto를 끄세요.</string>
|
||||
<string name="help_aa_off_detail">Android Auto 앱을 사용 중지하거나 제거하여 차량이 Gearslip과 통신하게 하세요.</string>
|
||||
<string name="help_cert">인증서를 불러오세요.</string>
|
||||
<string name="help_cert_detail">설정에서 대부분의 차량이 받아들이는 aasdk 인증서를 다운로드하세요. 차량이 거부하면 직접 가진 인증서를 가져오세요.</string>
|
||||
<string name="help_mic">마이크를 허용하세요.</string>
|
||||
<string name="help_mic_detail">마이크 권한이 없으면 Android는 앱이 음악을 차량으로 보내지 못하게 합니다. Gearslip은 마이크를 탭하거나 핸들의 음성 버튼을 누를 때만 듣습니다.</string>
|
||||
<string name="help_plug_in">케이블을 꽂거나 무선으로 연결하세요.</string>
|
||||
<string name="help_plug_in_detail">차량이 연결되면 Gearslip이 자동으로 열립니다. 무선으로 연결하려면 먼저 휴대전화와 차량을 페어링한 다음 홈에서 무선 연결을 탭하세요.</string>
|
||||
<string name="help_pair">통화를 위해 블루투스로 페어링하세요.</string>
|
||||
<string name="help_pair_detail">휴대전화와 차량이 페어링되면 통화가 차량에서 들립니다. 처음 연결할 때 Gearslip이 페어링을 제안합니다.</string>
|
||||
<string name="help_answer">차량 화면에서 전화를 받으세요.</string>
|
||||
<string name="help_answer_detail">Gearslip을 발신자 표시 및 스팸 앱으로 설정하세요. 설정하지 않아도 전화는 울리지만 휴대전화에서만 받을 수 있습니다.</string>
|
||||
<string name="help_prompt">운전할 때마다 나오는 오디오 확인을 없애세요.</string>
|
||||
<string name="help_prompt_detail">USB 디버깅이 켜진 컴퓨터에 휴대전화를 연결하고 이 명령을 한 번 실행하세요. 건너뛰면 출발할 때마다 시작을 탭해야 합니다.</string>
|
||||
<string name="help_readable">알림을 읽을 수 있게 유지하세요.</string>
|
||||
<string name="help_readable_detail">Android는 오디오를 공유하는 동안 알림 내용을 숨깁니다. 이것도 실행하면 음악이 차량으로 가는 동안에만 Gearslip이 이 제한을 풉니다.</string>
|
||||
<string name="help_which_cars_body">대부분의 차량은 aasdk 인증서를 받아들입니다. 2020년경부터 일부 차량에는 이를 거부하는 새로운 보안 검사가 추가되었으며, 이런 차량에서는 직접 가진 인증서가 필요합니다.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">다운로드했습니다. 설치하기 전에 Android가 확인합니다.</string>
|
||||
<string name="update_install">설치</string>
|
||||
<string name="update_download_now">지금 다운로드하세요. 설치하기 전에 Android가 확인합니다.</string>
|
||||
<string name="update_try_again">다시 시도</string>
|
||||
<string name="update_update">업데이트</string>
|
||||
<string name="update_checking">확인 중…</string>
|
||||
<string name="update_latest">최신 버전입니다</string>
|
||||
<string name="update_download">다운로드</string>
|
||||
<string name="update_check_now">지금 확인</string>
|
||||
<string name="update_check_auto">자동으로 확인</string>
|
||||
<string name="update_once_a_day">하루 한 번</string>
|
||||
|
||||
<string name="update_unplug_first">설치하려면 차량에서 분리하세요. 설치하면 Gearslip이 종료됩니다.</string>
|
||||
<string name="update_card_title">Gearslip %1$s 출시</string>
|
||||
<string name="update_downloading">다운로드 중… %1$d%%</string>
|
||||
<string name="update_debug_manual">디버그 빌드는 지금 확인을 탭할 때만 확인합니다</string>
|
||||
<string name="update_checks_daily">Gearslip은 하루 한 번 확인합니다</string>
|
||||
<string name="update_version_out">버전 %1$s 출시</string>
|
||||
<string name="update_version_ready">버전 %1$s 설치 준비 완료</string>
|
||||
<string name="update_dialog_status">버전 %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">매주 또는 매월 후원</string>
|
||||
<string name="about_bmac_sub" formatted="false">카드로 한 번 또는 매월 후원합니다. Buy Me a Coffee 수수료는 5%입니다</string>
|
||||
<string name="about_give_once">한 번 후원</string>
|
||||
<string name="about_any_amount">원하는 금액, 카드로</string>
|
||||
<string name="about_monero_sub">지갑을 열거나 주소 복사</string>
|
||||
<string name="about_monero_title">Monero로 후원</string>
|
||||
<string name="about_monero_body">Monero 지갑에서 이 주소로 원하는 금액을 보내세요.</string>
|
||||
<string name="about_open_wallet">지갑에서 열기</string>
|
||||
<string name="about_copy_address">주소 복사</string>
|
||||
<string name="close">닫기</string>
|
||||
<string name="about_matrix">Matrix 채팅 참여</string>
|
||||
<string name="about_matrix_sub">다른 운전자들과 차량, 앱, Gearslip에 대해 이야기하세요</string>
|
||||
<string name="about_translate">Gearslip 번역 돕기</string>
|
||||
<string name="about_translate_sub">Weblate에서 내 언어를 고치거나 추가하세요. 일부 번역은 기계 번역으로 시작했습니다.</string>
|
||||
<string name="about_source">소스 코드</string>
|
||||
<string name="about_source_sub">AGPL에 따라 Gearslip의 코드를 읽거나 수정하세요</string>
|
||||
<string name="about_voice_credits">음성 크레딧</string>
|
||||
|
||||
<string name="about_no_wallet">Monero 지갑 앱을 찾을 수 없습니다. 대신 주소를 복사하세요.</string>
|
||||
<string name="about_monero_copied">Monero 주소를 복사했습니다</string>
|
||||
<string name="about_voice_credits_sub">음성 인식: sherpa-onnx(Apache 2.0), Banafo의 Kroko ASR(CC BY-SA 4.0), NVIDIA의 Parakeet 및 Canary(CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">인증서 설정</string>
|
||||
<string name="certsetup_installed">인증서를 설치했습니다.</string>
|
||||
<string name="certsetup_continue">계속</string>
|
||||
<string name="certsetup_failed">다운로드 실패</string>
|
||||
<string name="certsetup_retry">다시 시도</string>
|
||||
<string name="certsetup_import_sub">개인 키가 포함된 .p12 파일입니다. 차량이 aasdk 인증서를 거부할 때 사용하세요.</string>
|
||||
<string name="certsetup_skip">차량 연결 설정 건너뛰기</string>
|
||||
|
||||
<string name="certsetup_loaded">차량은 Google이 발급한 인증서만 받아들이며, 인증서가 이미 로드되어 있습니다.</string>
|
||||
<string name="certsetup_choose">차량은 Google이 발급한 인증서만 받아들입니다. Gearslip에는 인증서가 없으니 가져올 곳을 선택하세요.</string>
|
||||
<string name="certsetup_aasdk_sub">오픈소스 aasdk 프로젝트가 github.com/opencardev/aasdk에 이 인증서를 공개합니다. 대부분의 차량에서 받아들입니다. 2020년경 이후의 일부 차량은 거부하므로, 그럴 때는 직접 가진 인증서를 가져와야 합니다.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Gearslip 저장공간</string>
|
||||
<string name="storage_measuring">계산 중…</string>
|
||||
<string name="storage_lyrics">가사</string>
|
||||
<string name="storage_clear">지우기</string>
|
||||
<string name="storage_voice">음성 모델</string>
|
||||
<string name="storage_remove">삭제</string>
|
||||
<string name="storage_logs">오래된 로그</string>
|
||||
<string name="storage_other">기타 임시 파일</string>
|
||||
<string name="storage_delete_all">Gearslip 데이터 모두 삭제</string>
|
||||
|
||||
<string name="storage_total">Gearslip 데이터가 휴대전화 저장공간을 %1$s 사용합니다.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="other">노래 %1$d곡 · %2$s. 지운 가사는 열 때 다시 다운로드됩니다.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. 음성 명령에 필요합니다. 설정에서 다시 다운로드하세요.</string>
|
||||
<string name="storage_logs_detail">%1$s. 이전 실행의 로그입니다. 이번 실행의 로그는 남습니다.</string>
|
||||
<string name="storage_other_detail">%1$s. 다운로드한 업데이트 및 Gearslip이 다시 만들 수 있는 기타 파일입니다.</string>
|
||||
<string name="storage_delete_armed">삭제를 다시 탭하면 설정, 인증서, 음성 모델, 로그가 지워집니다. Gearslip이 종료되고 다음에는 설정부터 시작합니다.</string>
|
||||
<string name="storage_delete_sub">설정, 인증서, 음성 모델, 가사, 로그.</string>
|
||||
<string name="storage_delete">삭제</string>
|
||||
<string name="storage_delete_ellipsis">삭제…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">운전이 잘 끝나서 다행이에요</string>
|
||||
<string name="donate_liberapay">Liberapay에서 후원</string>
|
||||
<string name="donate_bmac">커피 한 잔 사 주기</string>
|
||||
<string name="donate_card">카드로 한 번 후원</string>
|
||||
|
||||
<string name="donate_body">Gearslip은 무료이며 광고와 추적이 없습니다. 운전에 도움이 되었다면 후원으로 계속 이어 갈 수 있게 해 주세요.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">이 기능을 사용하려면 블루투스 및 근처 Wi-Fi 액세스가 필요합니다.</string>
|
||||
<string name="wireless_enable_permission">근처 기기 권한 사용</string>
|
||||
<string name="wireless_connect">무선 연결</string>
|
||||
<string name="wireless_which_car">어느 차량인가요?</string>
|
||||
<string name="wireless_none_paired">휴대전화가 아직 아무것과도 페어링되지 않았습니다. 먼저 차량과 페어링하세요.</string>
|
||||
<string name="wireless_not_listed">차량이 목록에 없나요? 먼저 휴대전화와 페어링하세요.</string>
|
||||
<string name="bluetooth_settings">블루투스 설정</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">음성: 실시간 인식</string>
|
||||
<string name="voice_final_title">음성: 최종 인식</string>
|
||||
<string name="voice_none">없음</string>
|
||||
<string name="voice_none_sub">실시간 결과를 그대로 사용합니다. 가장 빠르지만 정확도는 가장 낮습니다</string>
|
||||
|
||||
<string name="voice_live_body">음성 어시스턴트는 이 휴대전화에서 음성을 텍스트로 바꾸며, 말한 내용은 밖으로 나가지 않습니다. 합쳐서 약 270MB인 모델 두 개를 한 번만 다운로드하면 됩니다. 이 모델은 말하는 동안 단어를 받아 적습니다.</string>
|
||||
<string name="voice_final_body">말을 마치면 이 모델이 문장 전체를 다시 읽어, 실시간 모델이 놓친 이름과 장소를 잡아냅니다.</string>
|
||||
<string name="voice_model_size">%1$s · %2$dMB</string>
|
||||
<string name="voice_downloaded">다운로드됨. %1$s</string>
|
||||
<string name="voice_downloading">다운로드 중, %1$d%%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">도움말</string>
|
||||
<string name="home_preview">미리보기</string>
|
||||
<string name="home_logs">로그</string>
|
||||
<string name="home_disconnect">연결 해제</string>
|
||||
<string name="home_bt_title">블루투스가 연결되지 않았습니다</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · 디버그 빌드</string>
|
||||
<string name="home_bt_body">음악은 블루투스로 재생되지만 휴대전화가 차량에 연결되어 있지 않습니다. 여기서 연결하거나 차량의 블루투스 메뉴에서 연결하세요.</string>
|
||||
<string name="home_voice_downloading">음성 다운로드 중, %1$d%%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">차량 오디오를 블루투스로 재생할까요?</string>
|
||||
<string name="audio_never_ask">컴퓨터로 설정해 다시 묻지 않기</string>
|
||||
<string name="audio_use_bt">블루투스 사용</string>
|
||||
<string name="audio_keep_asking">계속 묻기</string>
|
||||
<string name="audio_mic_title">차량 오디오에는 마이크 권한이 필요합니다</string>
|
||||
<string name="audio_open_settings">앱 설정 열기</string>
|
||||
|
||||
<string name="audio_bt_body">Android는 Gearslip이 차량으로 보내는 음악을 화면 공유처럼 취급하므로, 케이블이든 무선이든 운전할 때마다 묻습니다. 블루투스로는 묻지 않습니다. 나중에 설정에서 바꿀 수 있습니다.</string>
|
||||
<string name="audio_keep_usb">또는 소리는 Gearslip을 통해 보내면서 질문을 영구히 없앨 수 있습니다. USB 디버깅이 켜진 컴퓨터에 휴대전화를 연결하고 이 명령을 한 번 실행하세요.</string>
|
||||
<string name="audio_mic_body">Android는 마이크 권한이 있는 앱만 다른 앱의 소리를 차량으로 보낼 수 있게 합니다. Gearslip의 권한이 꺼져 있어 음악이 휴대전화에서 재생됩니다. 권한에서 마이크를 켜세요.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">차량 키보드 닫기</string>
|
||||
<string name="typing_label">차량 화면에 입력</string>
|
||||
<string name="typing_search">차량 화면에서 검색</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">탭하여 복사</string>
|
||||
<string name="command_copied">명령을 복사했습니다</string>
|
||||
<string name="whats_new_this_version">%1$s(현재 버전)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">테마</string>
|
||||
<string name="carset_imperial">야드파운드법(mi, ft, °F)</string>
|
||||
<string name="carset_metric">미터법(km, m, °C)</string>
|
||||
<string name="carset_screen_size">화면 크기(다음 연결)</string>
|
||||
<string name="carset_fit">화면에 맞추기</string>
|
||||
<string name="off">끄기</string>
|
||||
<string name="on">켜기</string>
|
||||
<string name="carset_lyrics_from">가사 출처</string>
|
||||
<string name="carset_more_sources">출처 더 보기</string>
|
||||
<string name="carset_hide">숨기기</string>
|
||||
<string name="carset_show">표시</string>
|
||||
<string name="carset_phone">휴대전화</string>
|
||||
<string name="carset_vehicle">차량</string>
|
||||
<string name="carset_video">영상</string>
|
||||
<string name="carset_version">버전</string>
|
||||
<string name="carset_build">빌드</string>
|
||||
<string name="carset_whats_new_after">업데이트 후 새로운 기능</string>
|
||||
<string name="carset_whats_new_after_sub">업데이트 후 차량이 처음 연결될 때 변경 사항 목록을 한 번 보여 줍니다.</string>
|
||||
<string name="carset_web_default">DuckDuckGo(기본값) - 탭하여 다른 페이지 설정</string>
|
||||
<string name="carset_web_note">런처의 브라우저 앱에서 표시됩니다. 다음 연결부터 적용됩니다.</string>
|
||||
<string name="carset_screen_fit">화면 맞춤</string>
|
||||
<string name="carset_top">위</string>
|
||||
<string name="carset_bottom">아래</string>
|
||||
<string name="carset_save_vehicle">이 차량에 저장</string>
|
||||
<string name="carset_no_profile">일치하는 차량 프로필 없음</string>
|
||||
|
||||
<string name="carset_audio_note">Gearslip을 통해 휴대전화의 소리를 케이블이나 무선 차량 연결로 보냅니다. 운전할 때마다 Android가 화면 공유를 요청하며, 일부 앱은 이를 차단합니다. 블루투스는 휴대전화가 평소 재생하는 방식대로 재생되고 Android가 묻지 않습니다.</string>
|
||||
<string name="carset_directions_note">Gearslip이 회전할 때마다 차량 스피커로 안내하며, 말하는 동안 음악 소리를 줄입니다. 지도 앱 자체의 음성이 이미 차량에서 나온다면 끄세요.</string>
|
||||
<string name="carset_phone_speech_note">Gearslip 대신 휴대전화 자체의 음성 서비스로 듣습니다. 대부분의 휴대전화에서는 Google 서비스이며, 목소리가 Google로 전송될 수 있습니다. GrapheneOS에는 음성 서비스가 없으므로 Gearslip은 자체 인식을 계속 사용합니다.</string>
|
||||
<string name="carset_vehicle_data_note">차량 데이터 타일을 추가합니다. 헤드 유닛 센서 채널의 원시 데이터를 실시간으로 보여 줍니다. 항목 이름(속도, RPM, 연료...)은 다른 리버스 엔지니어링 프로젝트에서 가져온 것으로 이 차량에서 확인되지 않았습니다. 값이 바뀌는 것을 직접 보기 전까지는 사실이 아닌 추측으로 여기세요.</string>
|
||||
<string name="carset_adaptive_note">차량이나 차량의 Wi-Fi가 따라가지 못하면 화질을 낮추고, 여유가 생기면 다시 높입니다. 다음 연결부터 적용됩니다.</string>
|
||||
<string name="carset_usable_height">사용 가능한 높이 480 중 %1$dpx</string>
|
||||
<string name="carset_saved_to">%1$s에 저장됨</string>
|
||||
<string name="carset_save_failed">저장할 수 없습니다</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">휴대전화에서 열기</string>
|
||||
<string name="media_now_playing">지금 재생 중</string>
|
||||
<string name="media_up_next">다음 곡</string>
|
||||
<string name="media_browse">둘러보기</string>
|
||||
<string name="media_search_hint">노래, 아티스트, 앨범, 재생목록을 입력하세요.</string>
|
||||
<string name="media_looking_lyrics">가사를 찾는 중…</string>
|
||||
<string name="media_nothing_playing">재생 중인 항목 없음</string>
|
||||
<string name="media_press_play">재생을 누르면 멈춘 곳부터 이어서 들을 수 있고, 둘러보기에서 다른 곡을 찾을 수도 있습니다.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s은(는) 라이브러리를 공개하지 않습니다.</string>
|
||||
<string name="media_needs_access">휴대전화에서 알림 액세스를 허용하면 Gearslip이 제어할 수 있습니다. 설정 > 알림 > 알림 액세스에서 허용하세요.</string>
|
||||
<string name="media_not_running">%1$s이(가) 실행 중이 아닙니다.</string>
|
||||
<string name="media_not_running_body">%1$s은(는) Gearslip 플레이어로 재생되지만, 휴대전화에서 열려 있을 때만 가능합니다. 한 번 열면 차량에서 계속 재생됩니다.</string>
|
||||
<string name="media_open_app">%1$s 열기</string>
|
||||
<string name="media_refused">%1$s이(가) Gearslip의 둘러보기를 허용하지 않았습니다.</string>
|
||||
<string name="media_refused_body">일부 미디어 앱은 Google 자체 호스트만 허용합니다.</string>
|
||||
<string name="media_connecting">%1$s에 연결 중…</string>
|
||||
<string name="media_search">%1$s 검색</string>
|
||||
<string name="media_search_no_list">%1$s은(는) 결과 목록을 보여 줄 수 없습니다. 검색을 누르면 %1$s이(가) 재생할 곡을 고릅니다.</string>
|
||||
<string name="media_search_unsupported">%1$s은(는) 차량에서 검색할 수 없습니다.</string>
|
||||
<string name="media_searching">%1$s 검색 중…</string>
|
||||
<string name="media_search_nothing">%1$s에서 \"%2$s\" 검색 결과가 없습니다.</string>
|
||||
<string name="media_search_ignored">%1$s이(가) \"%2$s\"에 대해 아무것도 재생하지 않았습니다.</string>
|
||||
<string name="media_searching_for">%1$s에서 \"%2$s\" 검색 중…</string>
|
||||
<string name="media_nothing_playing_dot">재생 중인 항목이 없습니다.</string>
|
||||
<string name="media_no_lyrics">이 트랙의 가사를 찾을 수 없습니다.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">위로</string>
|
||||
<string name="media_loading">불러오는 중…</string>
|
||||
<string name="media_browse_start">휴대전화의 앱에서 음악을 시작하세요. 재생, 일시정지, 건너뛰기, 탐색은 여기서 할 수 있습니다.</string>
|
||||
<string name="media_load_failed">이 목록을 불러올 수 없습니다.</string>
|
||||
<string name="media_nothing_here">아무것도 없습니다.</string>
|
||||
<string name="media_nothing_queued">대기열이 비어 있습니다.</string>
|
||||
<string name="media_play">재생</string>
|
||||
|
||||
<string name="media_queue_empty_idle">재생 중인 항목 없음 - 모든 앱이 다음 곡을 Gearslip에 알려 주지는 않습니다.</string>
|
||||
<string name="media_queue_empty_playing">지금 재생 중인 곡만 있음 - 모든 앱이 다음 곡을 Gearslip에 알려 주지는 않습니다.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">이전</string>
|
||||
<string name="player_next">다음</string>
|
||||
<string name="player_repeat">반복</string>
|
||||
<string name="player_pause">일시정지</string>
|
||||
<string name="player_unlike">좋아요 취소</string>
|
||||
<string name="player_like">좋아요</string>
|
||||
<string name="player_shuffle_off">셔플 끄기</string>
|
||||
<string name="player_shuffle_on">셔플 켜기</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">다른 앱 선택</string>
|
||||
<string name="home_no_maps">지도 앱을 찾을 수 없습니다.</string>
|
||||
<string name="home_no_maps_body">Android Auto를 지원하는 내비게이션 앱을 설치하면 여기서 자동으로 시작됩니다.</string>
|
||||
<string name="home_map_stopped">지도 중지됨</string>
|
||||
<string name="home_map_stopped_body">다시 열 때까지 닫혀 있습니다.</string>
|
||||
<string name="home_reopen_map">지도 다시 열기</string>
|
||||
<string name="home_car_apps">차량 앱</string>
|
||||
<string name="home_back_nav">내비게이션 바로 돌아가기</string>
|
||||
<string name="home_show_player">플레이어 표시</string>
|
||||
<string name="home_hide_lyrics">가사 숨기기</string>
|
||||
|
||||
<string name="home_starting">%1$s 시작 중…</string>
|
||||
<string name="home_starting_map">지도 시작 중…</string>
|
||||
<string name="home_failed">%1$s을(를) 시작할 수 없습니다.</string>
|
||||
<string name="home_failed_map">지도 앱을 시작할 수 없습니다.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">홈</string>
|
||||
<string name="crumb_media">미디어</string>
|
||||
<string name="crumb_apps">앱</string>
|
||||
<string name="crumb_dashboard">대시보드</string>
|
||||
<string name="crumb_weather">날씨</string>
|
||||
<string name="crumb_reply">답장</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">브라우저</string>
|
||||
<string name="app_screen_sharing">화면 공유</string>
|
||||
<string name="app_exit">종료</string>
|
||||
<string name="navapps_unpin">내비게이션 바에서 고정 해제</string>
|
||||
<string name="navapps_full">내비게이션 바가 가득 찼습니다(%1$d개 고정됨)</string>
|
||||
<string name="navapps_pin">내비게이션 바에 고정</string>
|
||||
<string name="navapps_close">앱 닫기</string>
|
||||
<string name="navapps_move_left">왼쪽으로 이동</string>
|
||||
<string name="navapps_move_right">오른쪽으로 이동</string>
|
||||
<string name="navapps_reset">앱 순서 초기화</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">닫기</string>
|
||||
<string name="msg_read">읽기</string>
|
||||
<string name="msg_stop">중지</string>
|
||||
<string name="msg_no">아니요</string>
|
||||
<string name="msg_send_now">지금 보내기</string>
|
||||
<string name="msg_type">입력</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="other">%1$s · 새 메시지 %2$d개</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">%1$s에게 답장</string>
|
||||
<string name="msg_listening">듣는 중</string>
|
||||
<string name="msg_sending_to">%1$s에게 보내는 중</string>
|
||||
<string name="msg_sent_to">%1$s에게 보냄</string>
|
||||
<string name="msg_reply_failed">답장할 수 없습니다</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">페어링이 완료되지 않았습니다. 대신 차량의 블루투스 메뉴에서 페어링하세요.</string>
|
||||
<string name="bt_pair_not_started">페어링이 시작되지 않았습니다. 대신 차량의 블루투스 메뉴에서 페어링하세요.</string>
|
||||
<string name="bt_paired">페어링했습니다. 이제 통화가 차량에서 들립니다.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">다시 표시 안 함</string>
|
||||
<string name="overlay_ok">확인</string>
|
||||
<string name="overlay_location_off">위치가 꺼져 있습니다</string>
|
||||
<string name="overlay_pair_title">이 차량과 페어링할까요?</string>
|
||||
<string name="overlay_pair">페어링</string>
|
||||
<string name="overlay_confirm_title">휴대전화에서 확인하세요</string>
|
||||
<string name="overlay_confirm_body">휴대전화에서 코드를 확인하고 페어링을 탭하세요. 차량에서도 물을 수 있습니다.</string>
|
||||
|
||||
<string name="overlay_bt_body">음악은 블루투스로 재생되지만 휴대전화가 차량에 연결되어 있지 않습니다. 차량이나 휴대전화의 블루투스 메뉴에서 연결하세요.</string>
|
||||
<string name="overlay_location_body">지도 앱이 현재 위치를 표시하거나 길을 안내할 수 없습니다. 주차한 뒤 휴대전화의 빠른 설정에서 위치를 켜세요.</string>
|
||||
<string name="overlay_pair_body_music">휴대전화와 차량이 페어링되면 통화와 음악이 차량 스피커로 나옵니다. 이후 휴대전화에서 코드를 확인하라는 요청이 나옵니다.</string>
|
||||
<string name="overlay_pair_body">휴대전화와 차량이 페어링되면 통화가 차량 스피커로 나옵니다. 이후 휴대전화에서 코드를 확인하라는 요청이 나옵니다.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">위치를 확인하는 중입니다.</string>
|
||||
<string name="weather_allow_location">날씨를 보려면 휴대전화에서 Gearslip에 위치 권한을 허용하세요.</string>
|
||||
<string name="weather_unreachable">날씨 서비스에 연결할 수 없습니다.</string>
|
||||
<string name="weather_clear">맑음</string>
|
||||
<string name="weather_mostly_clear">대체로 맑음</string>
|
||||
<string name="weather_partly_cloudy">구름 조금</string>
|
||||
<string name="weather_overcast">흐림</string>
|
||||
<string name="weather_fog">안개</string>
|
||||
<string name="weather_drizzle">이슬비</string>
|
||||
<string name="weather_rain">비</string>
|
||||
<string name="weather_snow">눈</string>
|
||||
<string name="weather_rain_showers">소나기</string>
|
||||
<string name="weather_snow_showers">소낙눈</string>
|
||||
<string name="weather_thunderstorm">뇌우</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">날씨를 가져오는 중…</string>
|
||||
<string name="weather_next_hours">앞으로 몇 시간</string>
|
||||
<string name="weather_this_week">이번 주</string>
|
||||
<string name="weather_today">오늘</string>
|
||||
<string name="weather_credit">날씨 데이터: Open-Meteo.com(CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">체감 %1$s</string>
|
||||
<string name="weather_humidity">습도 %1$d%%</string>
|
||||
<string name="weather_wind">바람 %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">오늘</string>
|
||||
<string name="dash_getting_weather">날씨를 가져오는 중…</string>
|
||||
<string name="dash_coming_up">다가오는 일정</string>
|
||||
<string name="dash_loading_calendar">캘린더를 불러오는 중…</string>
|
||||
<string name="dash_nothing_week">다음 일주일 동안 일정이 없습니다.</string>
|
||||
<string name="dash_all_day">종일</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">연락처</string>
|
||||
<string name="dialer_keypad">키패드</string>
|
||||
<string name="dialer_ask">휴대전화에서 묻기</string>
|
||||
<string name="dialer_enter_number">번호 입력</string>
|
||||
<string name="dialer_call">전화</string>
|
||||
<string name="dialer_no_contacts">전화번호가 있는 연락처가 없습니다.</string>
|
||||
<string name="dialer_search">연락처 검색</string>
|
||||
|
||||
<string name="dialer_needs_permission">여기서 전화를 걸려면 전화 및 연락처 권한이 필요합니다. 주차한 뒤 휴대전화에서 허용하세요.</string>
|
||||
<string name="dialer_no_match">\"%1$s\"와(과) 일치하는 연락처가 없습니다.</string>
|
||||
<string name="dialer_call_who">%1$s에게 전화할까요?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">전체 화면 종료</string>
|
||||
<string name="carui_drive_safely">안전하게 운전하세요</string>
|
||||
<string name="carui_understand">이해했습니다</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip은 비공식 소프트웨어입니다. 어떤 자동차 제조사도 만들거나 검토하지 않았으며, 예고 없이 작동을 멈출 수 있습니다.</string>
|
||||
<string name="carui_never_interact">운전 중에는 절대 이 화면을 조작하지 마세요. 경로나 노래는 출발 전에 설정하고, 운전 중에는 도로에 집중하세요.</string>
|
||||
<string name="carui_this_app">이 앱</string>
|
||||
<string name="carui_app_unsupported">%1$s은(는) 아직 Gearslip에서 작동하지 않습니다.</string>
|
||||
<string name="carui_app_unsupported_body">시작되지 않았습니다. 일부 앱은 Google의 Android Auto에서만 실행됩니다.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">통화 중</string>
|
||||
<string name="navbar_end_call">통화 종료</string>
|
||||
<string name="navbar_unmute">음소거 해제</string>
|
||||
<string name="navbar_mute">음소거</string>
|
||||
<string name="navbar_close_panel">측면 패널 닫기</string>
|
||||
<string name="navbar_dock_player">플레이어를 지도 옆으로 이동</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">검색하거나 주소 입력</string>
|
||||
<string name="web_forward">앞으로</string>
|
||||
<string name="web_reload">새로고침</string>
|
||||
<string name="web_start_page">시작 페이지</string>
|
||||
<string name="web_fullscreen">전체 화면</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">알림</string>
|
||||
<string name="notif_clear_all">모두 지우기</string>
|
||||
<string name="notif_empty">아직 아무것도 없습니다. 휴대전화의 새 알림이 여기에 표시됩니다.</string>
|
||||
<string name="notif_off">알림이 꺼져 있습니다. 휴대전화의 설정 > 알림 > 알림 액세스에서 Gearslip을 허용하세요.</string>
|
||||
<string name="notif_replied">답장함</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">읽음으로 표시</string>
|
||||
<string name="msg_messages">메시지</string>
|
||||
<string name="msg_none_recent">최근 메시지가 없습니다. 새 %1$s 메시지가 휴대전화에 도착하면 여기에 표시됩니다.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="other">이전 메시지 %1$d개</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">빠른 답장 숨기기</string>
|
||||
<string name="msg_quick">빠른 답장</string>
|
||||
<string name="msg_you">나: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">경로를 찾는 중…</string>
|
||||
<string name="nav_then">그다음</string>
|
||||
<string name="nav_stop">길 안내 중지</string>
|
||||
<string name="nav_finding_routes">경로를 찾는 중…</string>
|
||||
<string name="nav_lane_recommended">권장 차로</string>
|
||||
<string name="nav_lane">차로</string>
|
||||
<string name="nav_arrive">%1$s 도착</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">아직 재생 중인 항목이 없습니다.</string>
|
||||
<string name="template_search">검색</string>
|
||||
<string name="template_start">시작</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">수신 전화</string>
|
||||
<string name="call_decline">거절</string>
|
||||
<string name="call_answer">받기</string>
|
||||
<string name="call_ringing">이미 벨이 울리고 있습니다 - 여기서 받거나 그냥 울리게 두세요.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">터치가 꺼져 있습니다. 휴대전화의 설정 > 접근성에서 Gearslip을 사용하도록 설정하세요.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">업데이트 후 표시</string>
|
||||
<string name="whatsnew_got_it">확인</string>
|
||||
<string name="whatsnew_title">버전 %1$s의 새로운 기능</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">스페이스</string>
|
||||
<string name="keyboard_hide">키보드 숨기기</string>
|
||||
<string name="keyboard_submit">확인</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Gearslip에서 작동하지 않음</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">차량 앱</string>
|
||||
<string name="stage_close">%1$s 닫기</string>
|
||||
<string name="stage_close_app">앱 닫기</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">왼쪽 방향입니다</string>
|
||||
<string name="dir_slight_left_onto">왼쪽 방향으로 %1$s에 진입하세요</string>
|
||||
<string name="dir_slight_right">오른쪽 방향입니다</string>
|
||||
<string name="dir_slight_right_onto">오른쪽 방향으로 %1$s에 진입하세요</string>
|
||||
<string name="dir_left">좌회전하세요</string>
|
||||
<string name="dir_left_onto">좌회전하여 %1$s에 진입하세요</string>
|
||||
<string name="dir_right">우회전하세요</string>
|
||||
<string name="dir_right_onto">우회전하여 %1$s에 진입하세요</string>
|
||||
<string name="dir_sharp_left">급좌회전하세요</string>
|
||||
<string name="dir_sharp_left_onto">급좌회전하여 %1$s에 진입하세요</string>
|
||||
<string name="dir_sharp_right">급우회전하세요</string>
|
||||
<string name="dir_sharp_right_onto">급우회전하여 %1$s에 진입하세요</string>
|
||||
<string name="dir_u_turn">유턴하세요</string>
|
||||
<string name="dir_keep_left">왼쪽 차로를 유지하세요</string>
|
||||
<string name="dir_keep_left_toward">%1$s 방면으로 왼쪽 차로를 유지하세요</string>
|
||||
<string name="dir_keep_right">오른쪽 차로를 유지하세요</string>
|
||||
<string name="dir_keep_right_toward">%1$s 방면으로 오른쪽 차로를 유지하세요</string>
|
||||
<string name="dir_fork_left">갈림길에서 왼쪽입니다</string>
|
||||
<string name="dir_fork_left_toward">갈림길에서 %1$s 방면 왼쪽입니다</string>
|
||||
<string name="dir_fork_right">갈림길에서 오른쪽입니다</string>
|
||||
<string name="dir_fork_right_toward">갈림길에서 %1$s 방면 오른쪽입니다</string>
|
||||
<string name="dir_ramp_left">왼쪽 진입로로 들어가세요</string>
|
||||
<string name="dir_ramp_left_onto">왼쪽 진입로로 %1$s에 진입하세요</string>
|
||||
<string name="dir_ramp_right">오른쪽 진입로로 들어가세요</string>
|
||||
<string name="dir_ramp_right_onto">오른쪽 진입로로 %1$s에 진입하세요</string>
|
||||
<string name="dir_exit_left">왼쪽 출구로 나가세요</string>
|
||||
<string name="dir_exit_left_toward">%1$s 방면 왼쪽 출구로 나가세요</string>
|
||||
<string name="dir_exit_right">오른쪽 출구로 나가세요</string>
|
||||
<string name="dir_exit_right_toward">%1$s 방면 오른쪽 출구로 나가세요</string>
|
||||
<string name="dir_merge_left">왼쪽으로 합류하세요</string>
|
||||
<string name="dir_merge_left_onto">왼쪽으로 %1$s에 합류하세요</string>
|
||||
<string name="dir_merge_right">오른쪽으로 합류하세요</string>
|
||||
<string name="dir_merge_right_onto">오른쪽으로 %1$s에 합류하세요</string>
|
||||
<string name="dir_merge">합류하세요</string>
|
||||
<string name="dir_merge_onto">%1$s에 합류하세요</string>
|
||||
<string name="dir_roundabout_through">회전교차로를 통과하세요</string>
|
||||
<string name="dir_roundabout_through_onto">회전교차로를 통과하여 %1$s에 진입하세요</string>
|
||||
<string name="dir_roundabout_enter">회전교차로에 진입하세요</string>
|
||||
<string name="dir_roundabout_exit">회전교차로를 빠져나가세요</string>
|
||||
<string name="dir_roundabout_exit_onto">회전교차로를 빠져나가 %1$s에 진입하세요</string>
|
||||
<string name="dir_straight">직진하세요</string>
|
||||
<string name="dir_straight_onto">직진하여 %1$s에 진입하세요</string>
|
||||
<string name="dir_continue">계속 가세요</string>
|
||||
<string name="dir_continue_onto">%1$s을(를) 따라 계속 가세요</string>
|
||||
<string name="dir_ferry">페리를 타세요</string>
|
||||
<string name="dir_train">기차를 타세요</string>
|
||||
<string name="dir_destination">목적지가 앞에 있습니다</string>
|
||||
<string name="dir_destination_left">목적지가 왼쪽에 있습니다</string>
|
||||
<string name="dir_destination_right">목적지가 오른쪽에 있습니다</string>
|
||||
<string name="dir_roundabout_take">회전교차로에서 %1$s 출구로 나가세요</string>
|
||||
<string name="dir_roundabout_take_onto">회전교차로에서 %1$s 출구로 나가 %2$s에 진입하세요</string>
|
||||
<string name="dir_ordinal_1">첫 번째</string>
|
||||
<string name="dir_ordinal_2">두 번째</string>
|
||||
<string name="dir_ordinal_3">세 번째</string>
|
||||
<string name="dir_ordinal_4">네 번째</string>
|
||||
<string name="dir_ordinal_5">다섯 번째</string>
|
||||
<string name="dir_ordinal_6">여섯 번째</string>
|
||||
<string name="dir_ordinal_7">일곱 번째</string>
|
||||
<string name="dir_ordinal_8">여덟 번째</string>
|
||||
<string name="dir_in_distance">%1$s 앞에서 %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="other">%1$s킬로미터</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s킬로미터</string>
|
||||
<string name="dir_quarter_mile">4분의 1마일</string>
|
||||
<string name="dir_half_mile">반 마일</string>
|
||||
<string name="dir_three_quarter_mile">4분의 3마일</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="other">%1$s마일</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s마일</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="other">%1$s피트</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="other">%1$s야드</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="other">%1$d미터</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">차량이 연결되지 않으면 이 인증서를 거부하는 것일 수 있습니다. 설정의 인증서에서 직접 가진 인증서를 가져오세요.</string>
|
||||
<string name="cert_downloaded_footer">차량이 연결되지 않나요? 2020년경 이후의 일부 차량은 aasdk 인증서를 거부하므로 직접 가진 인증서를 가져오세요. 변경 사항은 다음 연결부터 적용됩니다.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">계기판 화면에 회전 안내 카드</string>
|
||||
<string name="car_cluster_video_note">계기판에 두 번째 화면이 있는 차량용: Gearslip이 그 화면에 다음 회전을 표시합니다. 다음 연결부터 적용됩니다.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">연결되지 않음</string>
|
||||
<string name="status_idle_detail">차량의 USB 포트에 연결하거나 무선으로 연결하세요. 차량이 응답하면 Gearslip이 자동으로 열립니다.</string>
|
||||
<string name="status_connecting">연결 중</string>
|
||||
<string name="status_projecting">화면 전송 중</string>
|
||||
<string name="status_projecting_detail">헤드 유닛으로 스트리밍하고 있습니다.</string>
|
||||
<string name="status_disconnected">연결 해제됨</string>
|
||||
<string name="status_disconnected_detail">헤드 유닛이 더 이상 연결되어 있지 않습니다.</string>
|
||||
<string name="places_title">저장한 장소</string>
|
||||
<string name="places_sub">어느 지도 앱에서든 “navigate home”이라고 말하세요</string>
|
||||
<string name="places_intro">“navigate home”, “take me to work” 또는 “navigate to”와 직접 붙인 이름을 말하면 Gearslip이 그 주소를 지도 앱으로 보냅니다. 음성 명령은 아직 영어만 됩니다.</string>
|
||||
<string name="places_home">집</string>
|
||||
<string name="places_work">직장</string>
|
||||
<string name="places_other">장소 %1$d</string>
|
||||
<string name="places_name">이름</string>
|
||||
<string name="places_address">주소</string>
|
||||
<string name="places_here">현재 위치 사용</string>
|
||||
<string name="places_finding">찾는 중…</string>
|
||||
<string name="places_not_found">Gearslip이 현재 위치를 찾지 못했습니다. 위치가 켜져 있는지 확인하세요.</string>
|
||||
<string name="places_clear">지우기</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,832 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Laat Gearslip aanrakingen op het autoscherm doorgeven aan de telefoon tijdens schermdelen. Gearslip leest de inhoud van het scherm niet.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Help andere bestuurders.</string>
|
||||
<string name="guide_safety_title">Rijd veilig.</string>
|
||||
<string name="guide_safety_body">Gearslip is niet officieel. Geen autofabrikant heeft het gemaakt of gecontroleerd, en het kan zonder waarschuwing uitvallen. Stel het in voordat je vertrekt en houd daarna je ogen op de weg.</string>
|
||||
<string name="guide_mic_title">Microfoon.</string>
|
||||
<string name="guide_mic_body">Gearslip gebruikt de microfoon om je muziek naar de auto te sturen en om je te horen als je iets vraagt of op een bericht antwoordt. Wat je zegt blijft op de telefoon.</string>
|
||||
<string name="guide_location_title">Locatie.</string>
|
||||
<string name="guide_location_body">De kaart in de auto en zoeken in de buurt hebben dit nodig. Zonder locatie toont het dashboard geen kaart.</string>
|
||||
<string name="guide_calendar_title">Agenda.</string>
|
||||
<string name="guide_calendar_body">Het autoscherm toont je volgende afspraak. Gearslip leest je agenda alleen.</string>
|
||||
<string name="guide_calls_title">Gesprekken en contacten.</string>
|
||||
<string name="guide_calls_body">De auto toont wie er belt, en de Telefoon-app in de auto kan een nummer kiezen of een van je contacten bellen. Gearslip leest ze alleen.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">De eerste keer dat je de kabel insteekt, biedt Gearslip aan je telefoon met de auto te koppelen, zodat gesprekken via de luidsprekers klinken.</string>
|
||||
<string name="guide_notifications_title">Meldingen.</string>
|
||||
<string name="guide_notifications_body">Zolang Gearslip verbonden is, toont het een melding met een knop om te stoppen. Zonder deze toestemming verbergt Android die.</string>
|
||||
<string name="guide_messages_title">Berichten.</string>
|
||||
<string name="guide_messages_body">De auto toont je berichten, leest ze voor en laat je antwoorden. Zet Gearslip aan in de lijst die nu opent.</string>
|
||||
<string name="guide_android_auto_title">Zet Android Auto uit.</string>
|
||||
<string name="guide_android_auto_body">Android Auto is nog geïnstalleerd en vecht met Gearslip om de USB-verbinding van de auto. Schakel het uit of verwijder het op het scherm dat nu opent.</string>
|
||||
<string name="guide_default_title">Kies Gearslip.</string>
|
||||
<string name="guide_default_body">Vraagt je telefoon welke app moet openen als je hem op de auto aansluit, kies dan Gearslip en tik op Altijd.</string>
|
||||
<string name="guide_audio_title">Autogeluid.</string>
|
||||
<string name="guide_audio_body">Kies hoe de muziek de auto bereikt. Gesprekken gaan hoe dan ook via Bluetooth.</string>
|
||||
<string name="guide_voice_title">Spraak.</string>
|
||||
<string name="guide_voice_body">Vraag met je stem om een route, muziek of een gesprek. Je telefoon zet spraak zelf om in tekst, en niets wat je zegt verlaat de telefoon. Er is %1$d MB nodig, één keer gedownload. Gebruik wifi als het kan.</string>
|
||||
<string name="guide_audio_prompt_title">Geen audiovraag.</string>
|
||||
<string name="guide_audio_prompt_body">Voer dit uit vanaf een computer met USB-foutopsporing aan, en Android vertrouwt Gearslip voorgoed met je geluid. Anders tik je elke rit één keer op Starten.</string>
|
||||
<string name="guide_readable_title">Leesbare meldingen.</string>
|
||||
<string name="guide_readable_body">Terwijl het geluid naar de auto gaat, verbergt Android de tekst van meldingen, alsof je je scherm deelt. Hiermee kan Gearslip ze tijdens de rit tonen en daarna weer verbergen.</string>
|
||||
<string name="guide_answer_title">Gesprekken aannemen.</string>
|
||||
<string name="guide_answer_body">Maak Gearslip je app voor nummerweergave, dan kan de auto gesprekken aannemen en weigeren. Zonder dit gaan gesprekken nog steeds over, maar kan alleen de telefoon ze aannemen.</string>
|
||||
<string name="guide_apps_title">Goed gezelschap.</string>
|
||||
<string name="guide_buttons_title">Jouw knoppen.</string>
|
||||
<string name="guide_buttons_body">Kies hoe de knoppen in de auto eruitzien. Je kunt dit altijd wijzigen in de instellingen van de auto.</string>
|
||||
<string name="guide_open_notification_access">Meldingstoegang openen</string>
|
||||
<string name="guide_open_android_auto">App-info van Android Auto openen</string>
|
||||
<string name="guide_download_voice">Spraak downloaden</string>
|
||||
<string name="guide_set_up">Instellen</string>
|
||||
<string name="guide_voice_downloading">Downloaden, %1$d%%. Je kunt verdergaan; het loopt door.</string>
|
||||
<string name="guide_voice_ready">Spraak is klaar.</string>
|
||||
<string name="guide_voice_failed">De download is niet afgerond. Tik op Spraak downloaden om het opnieuw te proberen.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Aanbevolen</string>
|
||||
<string name="guide_audio_bluetooth_body">Geen vraag bij elke rit, en de stem van je kaarten-app komt in de auto.</string>
|
||||
<string name="guide_audio_usb">Via Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Helderder geluid zonder vertraging. Bij elke rit vraagt Android je om je scherm te delen, zodat Gearslip het geluid kan sturen.</string>
|
||||
<string name="guide_optional">OPTIONEEL</string>
|
||||
<string name="guide_already_allowed">Al toegestaan.</string>
|
||||
<string name="guide_scroll_more">Scroll voor meer ↓</string>
|
||||
<string name="guide_allow">Toestaan</string>
|
||||
<string name="guide_skip">Nu overslaan</string>
|
||||
<string name="guide_open">Openen</string>
|
||||
<string name="guide_next">Volgende</string>
|
||||
<string name="guide_done">Klaar</string>
|
||||
<string name="guide_back">Terug</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Zet dit aan, en Gearslip stuurt een korte notitie als je het opent en na elke rit. <b>De notitie bevat geen naam, account of locatie. Een willekeurig getal telt je telefoon één keer, en de server bewaart geen IP-adres.</b> De notities laten zien welke auto\'s werken en waar de andere vastlopen.</string>
|
||||
<string name="usage_send">Gebruiksnotities versturen</string>
|
||||
<string name="usage_also_share">Ook delen</string>
|
||||
<string name="usage_share_android">Android-versie en systeem</string>
|
||||
<string name="usage_share_phone">Telefoonmodel</string>
|
||||
<string name="usage_share_cars">Elke auto en of hij verbond</string>
|
||||
<string name="usage_share_drives">Elke rit en hoe die eindigde</string>
|
||||
<string name="usage_what_sent">Wat er verstuurd wordt</string>
|
||||
<string name="usage_what_sent_sub">Elk veld, precies zoals het verstuurd wordt</string>
|
||||
<string name="usage_hide_note">Notitie verbergen</string>
|
||||
<string name="usage_show_note">Notitie tonen</string>
|
||||
<string name="usage_example">Voorbeeld, niet verstuurd: één rit voegt dit toe aan “sessions”</string>
|
||||
<string name="usage_no_server">Deze build heeft geen statistiekenserver en verstuurt dus niets</string>
|
||||
<string name="usage_debug_build">Debugbuild: notities worden apart gehouden en nooit meegeteld</string>
|
||||
<string name="worked_title">Gearslip werkte met je %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip werkte met je auto</string>
|
||||
<string name="worked_body">Delen, zodat de volgende bestuurder weet dat het werkt? Geen naam, account of locatie.</string>
|
||||
<string name="worked_not_now">Niet nu</string>
|
||||
<string name="worked_share">Delen</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">Deze gratis apps draaien op het autoscherm van Gearslip. Elke knop opent de pagina van de app op GitHub.</string>
|
||||
<string name="recommended_title">Aanbevolen apps</string>
|
||||
<string name="back">Terug</string>
|
||||
<string name="recommended_installed">Geïnstalleerd</string>
|
||||
<string name="recommended_maps">Kaarten</string>
|
||||
<string name="recommended_music">Muziek</string>
|
||||
<string name="recommended_podcasts">Podcasts</string>
|
||||
<string name="recommended_other">Andere leuke apps</string>
|
||||
<string name="recommended_about_organic">Offline kaarten en routes.</string>
|
||||
<string name="recommended_about_vela">Stapsgewijze navigatie zonder Google.</string>
|
||||
<string name="recommended_about_ytm">Een YouTube Music-speler.</string>
|
||||
<string name="recommended_about_antennapod">Podcasts, hoofdstukken en al.</string>
|
||||
<string name="recommended_about_rfsentinel">Spoort trackers en camera\'s in de buurt op.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Instellingen</string>
|
||||
<string name="settings_whats_new">Wat is er nieuw</string>
|
||||
<string name="settings_whats_new_sub">Lees de release notes van %1$s op GitHub</string>
|
||||
<string name="settings_support">Steun Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip is gratis, zonder advertenties en zonder tracking. Jouw steun houdt het draaiende.</string>
|
||||
<string name="settings_usage_notes">Gebruiksnotities</string>
|
||||
<string name="settings_updates">Updates</string>
|
||||
<string name="settings_about">Over</string>
|
||||
<string name="settings_replay">Installatiegids opnieuw doorlopen</string>
|
||||
<string name="settings_replay_sub">De stappen van de eerste start nog eens doen</string>
|
||||
<string name="settings_connection">Verbinding</string>
|
||||
<string name="settings_use_in_car">Gearslip in de auto gebruiken</string>
|
||||
<string name="settings_use_in_car_on">Gearslip reageert als je de telefoon op een auto aansluit</string>
|
||||
<string name="settings_use_in_car_off">Uit: Android Auto kan de verbinding overnemen</string>
|
||||
<string name="settings_apps">Apps</string>
|
||||
<string name="settings_certificate">Certificaat</string>
|
||||
<string name="cert_next_connection">Wijzigingen gelden vanaf de volgende verbinding.</string>
|
||||
<string name="cert_checking">Controleren…</string>
|
||||
<string name="cert_valid_until">Geldig tot %1$s</string>
|
||||
<string name="cert_hide_details">Details verbergen</string>
|
||||
<string name="cert_show_details">Details tonen</string>
|
||||
<string name="cert_subject">Onderwerp</string>
|
||||
<string name="cert_issuer">Uitgever</string>
|
||||
<string name="cert_downloading">Downloaden…</string>
|
||||
<string name="cert_download">Downloaden van aasdk</string>
|
||||
<string name="cert_downloaded">Certificaat gedownload.</string>
|
||||
<string name="cert_download_failed">Download mislukt: %1$s</string>
|
||||
<string name="cert_download_sub">Het certificaat dat het opensourceproject aasdk op GitHub publiceert. De meeste auto\'s accepteren het, maar sommige vanaf ongeveer 2020 weigeren het</string>
|
||||
<string name="cert_import">Eigen bestand importeren</string>
|
||||
<string name="cert_import_sub">Een .p12-bestand met de privésleutel. Gebruik je eigen bestand als je auto het aasdk-certificaat weigert</string>
|
||||
<string name="cert_remove_imported">Geïmporteerd certificaat verwijderen</string>
|
||||
<string name="cert_remove_downloaded">Gedownload certificaat verwijderen</string>
|
||||
<string name="cert_removed_imported">Geïmporteerd certificaat verwijderd.</string>
|
||||
<string name="cert_removed_downloaded">Gedownload certificaat verwijderd.</string>
|
||||
<string name="cert_imported">Certificaat geïmporteerd.</string>
|
||||
<string name="cert_import_failed">Importeren mislukt: verkeerd wachtwoord, of geen PKCS#12-bestand met een privésleutel.</string>
|
||||
<string name="cert_password_title">Certificaatwachtwoord</string>
|
||||
<string name="cert_password">Wachtwoord</string>
|
||||
<string name="cert_password_hint">Laat leeg als het bestand er geen heeft.</string>
|
||||
<string name="cert_import_button">Importeren</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Annuleren</string>
|
||||
<string name="cert_none">Geen geladen (zelfondertekend; auto\'s weigeren het)</string>
|
||||
<string name="cert_kind_imported">Geïmporteerd: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Gedownload: %1$s</string>
|
||||
<string name="cert_kind_adb">Via adb geplaatst: %1$s</string>
|
||||
<string name="cert_kind_self">Zelfondertekend: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Scherm</string>
|
||||
<string name="car_group_nav_bar">Navigatiebalk en speler</string>
|
||||
<string name="car_group_sound">Geluid en spraak</string>
|
||||
<string name="car_group_warnings">Waarschuwingen</string>
|
||||
<string name="car_group_more">Meer</string>
|
||||
<string name="choice_size_normal">Normaal</string>
|
||||
<string name="choice_size_large">Groot</string>
|
||||
<string name="choice_size_xlarge">Extra groot</string>
|
||||
<string name="choice_theme_phone">Zoals de telefoon</string>
|
||||
<string name="choice_theme_light">Licht</string>
|
||||
<string name="choice_theme_dark">Donker</string>
|
||||
<string name="choice_dark_black">Zwart</string>
|
||||
<string name="choice_dark_black_colour">Zwart en kleur</string>
|
||||
<string name="choice_dark_deep_colour">Diepe kleur</string>
|
||||
<string name="choice_dark_grey">Grijs</string>
|
||||
<string name="choice_auto">Automatisch</string>
|
||||
<string name="choice_night_day">Dag</string>
|
||||
<string name="choice_night_night">Nacht</string>
|
||||
<string name="choice_units_imperial">Imperiaal</string>
|
||||
<string name="choice_units_metric">Metrisch</string>
|
||||
<string name="choice_nav_show">Altijd tonen</string>
|
||||
<string name="choice_nav_hide_navigating">Verbergen tijdens navigatie</string>
|
||||
<string name="choice_nav_hide">Verbergen tot je tikt</string>
|
||||
<string name="choice_open_home">Start</string>
|
||||
<string name="summary_player_left">Speler links</string>
|
||||
<string name="summary_player_right">Speler rechts</string>
|
||||
<string name="summary_usb_audio">Geluid via Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Bluetooth-geluid</string>
|
||||
<string name="summary_directions_on">Gesproken aanwijzingen aan</string>
|
||||
<string name="summary_directions_off">Gesproken aanwijzingen uit</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth aan</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth uit</string>
|
||||
<string name="summary_location_on">Locatie aan</string>
|
||||
<string name="summary_location_off">Locatie uit</string>
|
||||
<string name="web_page">Webpagina</string>
|
||||
<string name="summary_experimental_shown">Experimentele functies zichtbaar</string>
|
||||
<string name="summary_experimental_hidden">Experimentele functies verborgen</string>
|
||||
<string name="home_car">Auto</string>
|
||||
<string name="car_group_same">Dit zijn dezelfde instellingen als Instellingen in de auto. Een wijziging hier zie je daar ook.</string>
|
||||
<string name="car_open_on_connect">Openen bij verbinden</string>
|
||||
<string name="car_size">Grootte</string>
|
||||
<string name="car_app_theme">App-thema</string>
|
||||
<string name="car_dark_mode">Donkere modus</string>
|
||||
<string name="car_buttons">Knoppen</string>
|
||||
<string name="car_map_day_night">Kaart dag/nacht</string>
|
||||
<string name="car_units">Eenheden</string>
|
||||
<string name="car_nav_bar">Navigatiebalk</string>
|
||||
<string name="car_player_nav_bar">Speler in de navigatiebalk</string>
|
||||
<string name="car_skip_buttons">Overslaanknoppen</string>
|
||||
<string name="car_song_title">Titel van het nummer</string>
|
||||
<string name="car_player_beside_map">Speler naast de kaart</string>
|
||||
<string name="car_right">Rechts</string>
|
||||
<string name="car_left">Links</string>
|
||||
<string name="car_autoplay">Automatisch afspelen bij verbinden</string>
|
||||
<string name="car_more_lyrics">Meer songtekstbronnen</string>
|
||||
<string name="car_more_lyrics_note">Vraagt ook BetterLyrics en KuGou, die dan de titel en artiest van het nummer krijgen. Uit vraagt alleen LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Gesproken aanwijzingen</string>
|
||||
<string name="car_spoken_directions_note">Zet dit uit als de stem van je kaarten-app al in de auto klinkt.</string>
|
||||
<string name="car_phone_speech">Spraakherkenning van de telefoon gebruiken</string>
|
||||
<string name="car_phone_speech_note">Op de meeste telefoons is dat die van Google, die je stem naar Google kan sturen.</string>
|
||||
<string name="car_warn_bluetooth">Waarschuwen als Bluetooth niet verbonden is</string>
|
||||
<string name="car_warn_location">Waarschuwen als locatie uit staat</string>
|
||||
<string name="car_experimental">Experimentele functies</string>
|
||||
<string name="car_vehicle_data">Voertuiggegevens</string>
|
||||
<string name="car_vehicle_data_note">Voegt een tegel Voertuiggegevens toe: een live overzicht van de sensoren van de auto. De veldnamen zijn gissingen tot je er een hebt zien veranderen.</string>
|
||||
<string name="car_adaptive_video">Adaptieve videokwaliteit</string>
|
||||
<string name="car_adaptive_video_note">Verlaagt de beeldkwaliteit als de auto of zijn wifi het niet bijhoudt, en verhoogt die weer zodra het kan. Geldt vanaf de volgende verbinding.</string>
|
||||
<string name="web_page_note">Getoond door de Browser-app in de auto. Leeg opent DuckDuckGo. Geldt vanaf de volgende verbinding.</string>
|
||||
<string name="car_audio">Autogeluid</string>
|
||||
<string name="car_audio_bt">Bluetooth (aanbevolen)</string>
|
||||
<string name="car_audio_bt_sub">Geen vraag bij elke rit, en de stem van je kaarten-app komt in de auto</string>
|
||||
<string name="car_audio_usb_sub">Helderder geluid zonder vertraging. Bij elke rit vraagt Android je om je scherm te delen, zodat Gearslip het geluid kan sturen</string>
|
||||
<string name="car_audio_usb_footer">Voer de opdracht uit Hulp één keer uit, en Android vraagt het niet meer.</string>
|
||||
<string name="car_audio_bt_footer">Gesprekken gaan hoe dan ook via Bluetooth.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Grootte van het autoscherm</string>
|
||||
<string name="screen_use_cars">Die van de auto</string>
|
||||
<string name="screen_preview_size">Grootte van de voorvertoning</string>
|
||||
<string name="screen_preview_note">De grootte waarop de autovoorvertoning tekent, zoals 1920x720.</string>
|
||||
<string name="screen_car_screen">Autoscherm</string>
|
||||
<string name="screen_footer">Gearslip gebruikt de grootte die de auto opgeeft, tenzij je er zelf een instelt.</string>
|
||||
<string name="screen_size">Schermgrootte</string>
|
||||
<string name="screen_from_car">Van de auto</string>
|
||||
<string name="screen_crop">Randen bijsnijden</string>
|
||||
<string name="screen_crop_sub">Voor een auto die de boven- en onderkant, of de zijkanten, afsnijdt</string>
|
||||
<string name="screen_squash">Samendrukken om te passen</string>
|
||||
<string name="screen_squash_sub">Voor een auto die het hele beeld platdrukt, zodat cirkels op ovalen lijken</string>
|
||||
<string name="screen_preview_title">Grootte van de autovoorvertoning</string>
|
||||
<string name="screen_last_car">Laatste auto</string>
|
||||
<string name="screen_last_car_sub">De grootte van de laatste auto waarmee je verbonden was</string>
|
||||
<string name="screen_this_screen">Dit scherm</string>
|
||||
<string name="screen_this_screen_sub">Vult het scherm van dit apparaat, voor een headunit die zelf Gearslip draait</string>
|
||||
<string name="screen_custom">Aangepast</string>
|
||||
<string name="screen_enter_size">Grootte invoeren</string>
|
||||
<string name="screen_change_custom">Aangepaste grootte wijzigen</string>
|
||||
<string name="screen_width">Breedte</string>
|
||||
<string name="screen_height">Hoogte</string>
|
||||
<string name="save">Opslaan</string>
|
||||
|
||||
<string name="screen_car_size_note">Gebruik dit als het beeld in de auto uitgerekt lijkt. Voer de echte grootte van het scherm in, zoals 1280x480. Het geldt vanaf de volgende verbinding.</string>
|
||||
<string name="screen_set_by_you">%1$s, door jou ingesteld</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Zo verbind je</string>
|
||||
<string name="help_which_cars">Welke auto\'s werken</string>
|
||||
<string name="help_full_list">Bekijk de volledige lijst</string>
|
||||
<string name="help_first_drive">Voor je eerste rit</string>
|
||||
<string name="help_in_car">In de auto</string>
|
||||
<string name="help_usb_audio">Geluid via Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Zet Android Auto uit.</string>
|
||||
<string name="help_aa_off_detail">Schakel de Android Auto-app uit of verwijder hem, zodat de auto met Gearslip praat.</string>
|
||||
<string name="help_cert">Laad een certificaat.</string>
|
||||
<string name="help_cert_detail">Download in Instellingen het aasdk-certificaat, dat de meeste auto\'s accepteren. Weigert je auto het, importeer dan je eigen certificaat.</string>
|
||||
<string name="help_mic">Sta de microfoon toe.</string>
|
||||
<string name="help_mic_detail">Zonder microfoon laat Android geen app muziek naar de auto sturen. Gearslip luistert alleen als je op de microfoon tikt of op de spraakknop op het stuur drukt.</string>
|
||||
<string name="help_plug_in">Sluit aan, of verbind draadloos.</string>
|
||||
<string name="help_plug_in_detail">Gearslip opent vanzelf als de auto verbindt. Koppel voor draadloos eerst de telefoon met de auto en tik dan op Draadloos verbinden op Start.</string>
|
||||
<string name="help_pair">Koppel via Bluetooth voor gesprekken.</string>
|
||||
<string name="help_pair_detail">Gesprekken klinken via de auto zodra je telefoon en de auto gekoppeld zijn. Gearslip biedt de koppeling aan de eerste keer dat je aansluit.</string>
|
||||
<string name="help_answer">Neem gesprekken aan op het autoscherm.</string>
|
||||
<string name="help_answer_detail">Stel Gearslip in als je app voor nummerweergave en spam. Zonder dit gaan gesprekken nog steeds over, maar kan alleen de telefoon ze aannemen.</string>
|
||||
<string name="help_prompt">Stop de audiovraag bij elke rit.</string>
|
||||
<string name="help_prompt_detail">Sluit de telefoon aan op een computer met USB-foutopsporing aan en voer dit één keer uit. Sla je het over, dan tik je op Starten bij elk vertrek.</string>
|
||||
<string name="help_readable">Houd meldingen leesbaar.</string>
|
||||
<string name="help_readable_detail">Android verbergt de tekst van meldingen terwijl het geluid deelt. Voer dit ook uit, en Gearslip heft dat alleen op terwijl de muziek naar de auto gaat.</string>
|
||||
<string name="help_which_cars_body">De meeste auto\'s accepteren het aasdk-certificaat. Vanaf ongeveer 2020 kregen sommige auto\'s nieuwere beveiligingscontroles die het weigeren, en in die auto\'s heeft Gearslip een eigen certificaat van je nodig.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Gedownload. Android vraagt het voordat het installeert.</string>
|
||||
<string name="update_install">Installeren</string>
|
||||
<string name="update_download_now">Download het nu. Android vraagt het voordat het installeert.</string>
|
||||
<string name="update_try_again">Opnieuw proberen</string>
|
||||
<string name="update_update">Bijwerken</string>
|
||||
<string name="update_checking">Controleren…</string>
|
||||
<string name="update_latest">Je hebt de nieuwste versie</string>
|
||||
<string name="update_download">Downloaden</string>
|
||||
<string name="update_check_now">Nu controleren</string>
|
||||
<string name="update_check_auto">Automatisch controleren</string>
|
||||
<string name="update_once_a_day">Eén keer per dag</string>
|
||||
|
||||
<string name="update_unplug_first">Koppel los van de auto om te installeren. Installeren sluit Gearslip.</string>
|
||||
<string name="update_card_title">Gearslip %1$s is uit</string>
|
||||
<string name="update_downloading">Downloaden… %1$d%%</string>
|
||||
<string name="update_debug_manual">Debugbuilds controleren alleen als je op Nu controleren tikt</string>
|
||||
<string name="update_checks_daily">Gearslip controleert één keer per dag</string>
|
||||
<string name="update_version_out">Versie %1$s is uit</string>
|
||||
<string name="update_version_ready">Versie %1$s is klaar om te installeren</string>
|
||||
<string name="update_dialog_status">Versie %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Geef wekelijks of maandelijks</string>
|
||||
<string name="about_bmac_sub" formatted="false">Geef één keer of maandelijks, per kaart. Buy Me a Coffee houdt 5% in</string>
|
||||
<string name="about_give_once">Eén keer geven</string>
|
||||
<string name="about_any_amount">Elk bedrag, per kaart</string>
|
||||
<string name="about_monero_sub">Open je wallet of kopieer het adres</string>
|
||||
<string name="about_monero_title">Steun met Monero</string>
|
||||
<string name="about_monero_body">Stuur elk bedrag naar dit adres vanuit je Monero-wallet.</string>
|
||||
<string name="about_open_wallet">Openen in wallet</string>
|
||||
<string name="about_copy_address">Adres kopiëren</string>
|
||||
<string name="close">Sluiten</string>
|
||||
<string name="about_matrix">Doe mee met de chat op Matrix</string>
|
||||
<string name="about_matrix_sub">Praat met andere bestuurders over auto\'s, apps en Gearslip</string>
|
||||
<string name="about_translate">Help Gearslip vertalen</string>
|
||||
<string name="about_translate_sub">Verbeter of voeg je taal toe op Weblate. Sommige vertalingen begonnen als machinevertaling.</string>
|
||||
<string name="about_source">Broncode</string>
|
||||
<string name="about_source_sub">Lees of wijzig de code van Gearslip onder de AGPL</string>
|
||||
<string name="about_voice_credits">Spraakcredits</string>
|
||||
|
||||
<string name="about_no_wallet">Geen Monero-wallet-app gevonden. Kopieer het adres in plaats daarvan.</string>
|
||||
<string name="about_monero_copied">Monero-adres gekopieerd</string>
|
||||
<string name="about_voice_credits_sub">Spraakherkenning door sherpa-onnx (Apache 2.0), met Kroko ASR van Banafo (CC BY-SA 4.0), en Parakeet en Canary van NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Certificaat instellen</string>
|
||||
<string name="certsetup_installed">Certificaat geïnstalleerd.</string>
|
||||
<string name="certsetup_continue">Doorgaan</string>
|
||||
<string name="certsetup_failed">Download mislukt</string>
|
||||
<string name="certsetup_retry">Opnieuw</string>
|
||||
<string name="certsetup_import_sub">Een .p12-bestand met de privésleutel. Gebruik je eigen bestand als je auto het aasdk-certificaat weigert.</string>
|
||||
<string name="certsetup_skip">Instellen van de autoverbinding overslaan</string>
|
||||
|
||||
<string name="certsetup_loaded">Auto\'s accepteren alleen door Google uitgegeven certificaten, en je hebt er een geladen.</string>
|
||||
<string name="certsetup_choose">Auto\'s accepteren alleen door Google uitgegeven certificaten. Gearslip heeft er geen ingebouwd, dus kies waar het jouwe vandaan komt.</string>
|
||||
<string name="certsetup_aasdk_sub">Het opensourceproject aasdk publiceert dit certificaat op github.com/opencardev/aasdk. De meeste auto\'s accepteren het. Sommige vanaf ongeveer 2020 weigeren het, en dan moet je je eigen certificaat importeren.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Opslag van Gearslip</string>
|
||||
<string name="storage_measuring">Meten…</string>
|
||||
<string name="storage_lyrics">Songteksten</string>
|
||||
<string name="storage_clear">Wissen</string>
|
||||
<string name="storage_voice">Spraakmodellen</string>
|
||||
<string name="storage_remove">Verwijderen</string>
|
||||
<string name="storage_logs">Oude logs</string>
|
||||
<string name="storage_other">Andere tijdelijke bestanden</string>
|
||||
<string name="storage_delete_all">Alle gegevens van Gearslip verwijderen</string>
|
||||
|
||||
<string name="storage_total">De gegevens van Gearslip nemen %1$s van de opslag van de telefoon in.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d nummer · %2$s. Gewiste songteksten worden opnieuw gedownload als je ze opent.</item>
|
||||
<item quantity="other">%1$d nummers · %2$s. Gewiste songteksten worden opnieuw gedownload als je ze opent.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Spraakopdrachten hebben ze nodig; download ze opnieuw in Instellingen.</string>
|
||||
<string name="storage_logs_detail">%1$s. Logs van eerdere starts; de log van deze start blijft.</string>
|
||||
<string name="storage_other_detail">%1$s. Gedownloade updates en andere bestanden die Gearslip opnieuw kan maken.</string>
|
||||
<string name="storage_delete_armed">Tik nogmaals op Verwijderen om instellingen, certificaten, spraakmodellen en logs te wissen. Gearslip sluit en begint de volgende keer opnieuw bij het instellen.</string>
|
||||
<string name="storage_delete_sub">Instellingen, certificaten, spraakmodellen, songteksten en logs.</string>
|
||||
<string name="storage_delete">Verwijderen</string>
|
||||
<string name="storage_delete_ellipsis">Verwijderen…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Fijn dat die rit lukte</string>
|
||||
<string name="donate_liberapay">Steun via Liberapay</string>
|
||||
<string name="donate_bmac">Trakteer me op koffie</string>
|
||||
<string name="donate_card">Eén keer geven per kaart</string>
|
||||
|
||||
<string name="donate_body">Gearslip is gratis, zonder advertenties en zonder tracking. Brengt het je op weg, dan houdt jouw steun het draaiende.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">Gearslip heeft hiervoor toegang tot Bluetooth en wifi in de buurt nodig.</string>
|
||||
<string name="wireless_enable_permission">Toestemming Apparaten in de buurt aanzetten</string>
|
||||
<string name="wireless_connect">Draadloos verbinden</string>
|
||||
<string name="wireless_which_car">Welke auto?</string>
|
||||
<string name="wireless_none_paired">Je telefoon is nog met niets gekoppeld. Koppel hem eerst met de auto.</string>
|
||||
<string name="wireless_not_listed">Auto staat er niet bij? Koppel hem eerst met je telefoon.</string>
|
||||
<string name="bluetooth_settings">Bluetooth-instellingen</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Spraak: live woorden</string>
|
||||
<string name="voice_final_title">Spraak: definitieve woorden</string>
|
||||
<string name="voice_none">Geen</string>
|
||||
<string name="voice_none_sub">De live woorden houden. Snelst, minst nauwkeurig</string>
|
||||
|
||||
<string name="voice_live_body">De spraakassistent zet spraak om in tekst op deze telefoon, en niets wat je zegt verlaat hem. Er zijn twee modellen nodig, samen ongeveer 270 MB, één keer gedownload. Dit model schrijft je woorden terwijl je praat.</string>
|
||||
<string name="voice_final_body">Als je stopt met praten, leest dit model de hele zin opnieuw. Het vangt namen en plaatsen op die het livemodel mist.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Gedownload. %1$s</string>
|
||||
<string name="voice_downloading">Downloaden, %1$d%%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Hulp</string>
|
||||
<string name="home_preview">Voorvertoning</string>
|
||||
<string name="home_logs">Logs</string>
|
||||
<string name="home_disconnect">Verbinding verbreken</string>
|
||||
<string name="home_bt_title">Bluetooth is niet verbonden</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · debugbuild</string>
|
||||
<string name="home_bt_body">Muziek speelt via Bluetooth, maar je telefoon is niet met de auto verbonden. Verbind hem hier of via het Bluetooth-menu van de auto.</string>
|
||||
<string name="home_voice_downloading">Spraak downloaden, %1$d%%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Autogeluid via Bluetooth afspelen?</string>
|
||||
<string name="audio_never_ask">Nooit vragen, met een computer</string>
|
||||
<string name="audio_use_bt">Bluetooth gebruiken</string>
|
||||
<string name="audio_keep_asking">Blijven vragen</string>
|
||||
<string name="audio_mic_title">Autogeluid heeft de microfoontoestemming nodig</string>
|
||||
<string name="audio_open_settings">App-instellingen openen</string>
|
||||
|
||||
<string name="audio_bt_body">Android behandelt de muziek die Gearslip naar de auto stuurt als schermdelen, en vraagt het dus bij elke rit, via kabel of draadloos. Via Bluetooth vraagt het nooit. Je kunt dit later wijzigen in Instellingen.</string>
|
||||
<string name="audio_keep_usb">Of laat het geluid via Gearslip gaan en stop de vraag voorgoed: sluit de telefoon aan op een computer met USB-foutopsporing aan en voer dit één keer uit.</string>
|
||||
<string name="audio_mic_body">Android laat een app alleen het geluid van andere apps naar de auto sturen als die app microfoontoestemming heeft. Die staat uit voor Gearslip, dus speelt de muziek op de telefoon. Zet hem aan onder Rechten en dan Microfoon.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Autotoetsenbord sluiten</string>
|
||||
<string name="typing_label">Typ voor het autoscherm</string>
|
||||
<string name="typing_search">Zoeken op het autoscherm</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Tik om te kopiëren</string>
|
||||
<string name="command_copied">Opdracht gekopieerd</string>
|
||||
<string name="whats_new_this_version">%1$s (deze versie)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Thema</string>
|
||||
<string name="carset_imperial">Imperiaal (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Metrisch (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Schermgrootte (volgende verbinding)</string>
|
||||
<string name="carset_fit">Passend maken voor het scherm</string>
|
||||
<string name="off">Uit</string>
|
||||
<string name="on">Aan</string>
|
||||
<string name="carset_lyrics_from">Songteksten van</string>
|
||||
<string name="carset_more_sources">Meer bronnen</string>
|
||||
<string name="carset_hide">Verbergen</string>
|
||||
<string name="carset_show">Tonen</string>
|
||||
<string name="carset_phone">Telefoon</string>
|
||||
<string name="carset_vehicle">Voertuig</string>
|
||||
<string name="carset_video">Video</string>
|
||||
<string name="carset_version">Versie</string>
|
||||
<string name="carset_build">Build</string>
|
||||
<string name="carset_whats_new_after">Nieuw na updates</string>
|
||||
<string name="carset_whats_new_after_sub">Toon de lijst met wijzigingen één keer, de eerste keer dat de auto na een update verbindt.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (standaard) - tik om een andere pagina in te stellen</string>
|
||||
<string name="carset_web_note">Getoond door de Browser-app in de launcher. Geldt vanaf de volgende verbinding.</string>
|
||||
<string name="carset_screen_fit">Schermpassing</string>
|
||||
<string name="carset_top">Boven</string>
|
||||
<string name="carset_bottom">Onder</string>
|
||||
<string name="carset_save_vehicle">Opslaan voor dit voertuig</string>
|
||||
<string name="carset_no_profile">Geen voertuigprofiel gevonden</string>
|
||||
|
||||
<string name="carset_audio_note">Via Gearslip stuurt het geluid van de telefoon over de autoverbinding, via kabel of draadloos. Android vraagt bij elke rit om je scherm te delen, en een paar apps blokkeren dat. Bluetooth speelt af zoals de telefoon dat normaal doet, en Android vraagt nooit iets.</string>
|
||||
<string name="carset_directions_note">Gearslip leest elke afslag voor via de luidsprekers van de auto, en de muziek gaat zachter terwijl het praat. Zet dit uit als de stem van je kaarten-app al in de auto klinkt.</string>
|
||||
<string name="carset_phone_speech_note">Luistert via de spraakdienst van de telefoon in plaats van die van Gearslip. Op de meeste telefoons is dat die van Google, die je stem naar Google kan sturen. GrapheneOS heeft er geen, dus Gearslip blijft zijn eigen gebruiken.</string>
|
||||
<string name="carset_vehicle_data_note">Voegt een tegel Voertuiggegevens toe: een live, ruw overzicht van het sensorkanaal van de headunit. De veldlabels (snelheid, toerental, brandstof...) komen uit andere reverse-engineeringprojecten en zijn niet gecontroleerd op deze auto. Zie ze als een gok, geen feit, tot je er zelf een hebt zien veranderen.</string>
|
||||
<string name="carset_adaptive_note">Verlaagt de beeldkwaliteit als de auto, of zijn wifi, het niet bijhoudt, en verhoogt die weer zodra het kan. Geldt vanaf de volgende verbinding.</string>
|
||||
<string name="carset_usable_height">Bruikbare hoogte %1$dpx van 480</string>
|
||||
<string name="carset_saved_to">Opgeslagen in %1$s</string>
|
||||
<string name="carset_save_failed">Opslaan mislukt</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Openen op de telefoon</string>
|
||||
<string name="media_now_playing">Speelt nu</string>
|
||||
<string name="media_up_next">Hierna</string>
|
||||
<string name="media_browse">Bladeren</string>
|
||||
<string name="media_search_hint">Typ een nummer, artiest, album of playlist.</string>
|
||||
<string name="media_looking_lyrics">Songtekst zoeken…</string>
|
||||
<string name="media_nothing_playing">Er speelt niets</string>
|
||||
<string name="media_press_play">Druk op afspelen om verder te gaan waar je was, of zoek iets in Bladeren.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s houdt zijn bibliotheek voor zichzelf.</string>
|
||||
<string name="media_needs_access">Gearslip kan het toch bedienen zodra het meldingstoegang heeft op de telefoon. Sta dat toe onder Instellingen > Meldingen > Meldingstoegang.</string>
|
||||
<string name="media_not_running">%1$s draait niet.</string>
|
||||
<string name="media_not_running_body">%1$s speelt via de speler van Gearslip, maar alleen zolang het open is op de telefoon. Open het één keer en het blijft hier in de auto spelen.</string>
|
||||
<string name="media_open_app">%1$s openen</string>
|
||||
<string name="media_refused">%1$s liet Gearslip er niet in bladeren.</string>
|
||||
<string name="media_refused_body">Sommige media-apps accepteren alleen de host van Google zelf.</string>
|
||||
<string name="media_connecting">Verbinden met %1$s…</string>
|
||||
<string name="media_search">Zoeken in %1$s</string>
|
||||
<string name="media_search_no_list">%1$s kan geen resultaten tonen. Druk op zoeken, en %1$s kiest wat er speelt.</string>
|
||||
<string name="media_search_unsupported">%1$s kan niet zoeken vanuit de auto.</string>
|
||||
<string name="media_searching">Zoeken in %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s vond niets voor “%2$s”.</string>
|
||||
<string name="media_search_ignored">%1$s speelde niets af voor “%2$s”.</string>
|
||||
<string name="media_searching_for">In %1$s zoeken naar “%2$s”…</string>
|
||||
<string name="media_nothing_playing_dot">Er speelt niets.</string>
|
||||
<string name="media_no_lyrics">Geen songtekst gevonden voor dit nummer.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">Omhoog</string>
|
||||
<string name="media_loading">Laden…</string>
|
||||
<string name="media_browse_start">Start muziek in de app op je telefoon. Afspelen, pauzeren, overslaan en spoelen werken vanaf hier.</string>
|
||||
<string name="media_load_failed">Kon deze lijst niet laden.</string>
|
||||
<string name="media_nothing_here">Niets hier.</string>
|
||||
<string name="media_nothing_queued">Niets in de wachtrij.</string>
|
||||
<string name="media_play">Afspelen</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Er speelt niets - niet elke app vertelt Gearslip wat er hierna komt.</string>
|
||||
<string name="media_queue_empty_playing">Alleen wat al speelt - niet elke app vertelt Gearslip wat er hierna komt.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Vorige</string>
|
||||
<string name="player_next">Volgende</string>
|
||||
<string name="player_repeat">Herhalen</string>
|
||||
<string name="player_pause">Pauzeren</string>
|
||||
<string name="player_unlike">Niet meer leuk</string>
|
||||
<string name="player_like">Leuk</string>
|
||||
<string name="player_shuffle_off">Shuffle uit</string>
|
||||
<string name="player_shuffle_on">Shuffle aan</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Andere app kiezen</string>
|
||||
<string name="home_no_maps">Geen kaarten-apps gevonden.</string>
|
||||
<string name="home_no_maps_body">Installeer een navigatie-app die Android Auto ondersteunt, dan start die hier vanzelf.</string>
|
||||
<string name="home_map_stopped">Kaart gestopt</string>
|
||||
<string name="home_map_stopped_body">Hij blijft dicht tot je hem weer opent.</string>
|
||||
<string name="home_reopen_map">Kaart opnieuw openen</string>
|
||||
<string name="home_car_apps">Auto-apps</string>
|
||||
<string name="home_back_nav">Terug naar de navigatiebalk</string>
|
||||
<string name="home_show_player">Speler tonen</string>
|
||||
<string name="home_hide_lyrics">Songtekst verbergen</string>
|
||||
|
||||
<string name="home_starting">%1$s starten…</string>
|
||||
<string name="home_starting_map">De kaart starten…</string>
|
||||
<string name="home_failed">%1$s wilde niet starten.</string>
|
||||
<string name="home_failed_map">De kaarten-app wilde niet starten.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Start</string>
|
||||
<string name="crumb_media">Media</string>
|
||||
<string name="crumb_apps">Apps</string>
|
||||
<string name="crumb_dashboard">Dashboard</string>
|
||||
<string name="crumb_weather">Weer</string>
|
||||
<string name="crumb_reply">Antwoorden</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Browser</string>
|
||||
<string name="app_screen_sharing">Scherm delen</string>
|
||||
<string name="app_exit">Afsluiten</string>
|
||||
<string name="navapps_unpin">Losmaken van navigatiebalk</string>
|
||||
<string name="navapps_full">Navigatiebalk is vol (%1$d vastgezet)</string>
|
||||
<string name="navapps_pin">Vastzetten in navigatiebalk</string>
|
||||
<string name="navapps_close">App sluiten</string>
|
||||
<string name="navapps_move_left">Naar links</string>
|
||||
<string name="navapps_move_right">Naar rechts</string>
|
||||
<string name="navapps_reset">Volgorde van apps herstellen</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Sluiten</string>
|
||||
<string name="msg_read">Voorlezen</string>
|
||||
<string name="msg_stop">Stoppen</string>
|
||||
<string name="msg_no">Nee</string>
|
||||
<string name="msg_send_now">Nu versturen</string>
|
||||
<string name="msg_type">Typen</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nieuw</item>
|
||||
<item quantity="other">%1$s · %2$d nieuw</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Antwoorden aan %1$s</string>
|
||||
<string name="msg_listening">Luisteren</string>
|
||||
<string name="msg_sending_to">Versturen naar %1$s</string>
|
||||
<string name="msg_sent_to">Verstuurd naar %1$s</string>
|
||||
<string name="msg_reply_failed">Antwoorden mislukt</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">Koppelen is niet afgerond. Koppel in plaats daarvan via het Bluetooth-menu van de auto.</string>
|
||||
<string name="bt_pair_not_started">Koppelen is niet gestart. Koppel in plaats daarvan via het Bluetooth-menu van de auto.</string>
|
||||
<string name="bt_paired">Gekoppeld. Gesprekken klinken nu via de auto.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Niet meer tonen</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">Locatie staat uit</string>
|
||||
<string name="overlay_pair_title">Koppelen met deze auto?</string>
|
||||
<string name="overlay_pair">Koppelen</string>
|
||||
<string name="overlay_confirm_title">Bevestig op je telefoon</string>
|
||||
<string name="overlay_confirm_body">Controleer de code op je telefoon en tik op Koppelen. De auto kan het ook vragen.</string>
|
||||
|
||||
<string name="overlay_bt_body">Muziek speelt via Bluetooth, maar je telefoon is niet met de auto verbonden. Verbind hem via het Bluetooth-menu van de auto of van je telefoon.</string>
|
||||
<string name="overlay_location_body">Kaarten-apps kunnen niet tonen waar je bent of de weg wijzen. Zet locatie aan in de snelle instellingen van je telefoon als je geparkeerd staat.</string>
|
||||
<string name="overlay_pair_body_music">Gesprekken en muziek klinken via de luidsprekers van de auto zodra je telefoon en de auto gekoppeld zijn. Je telefoon vraagt je daarna een code te bevestigen.</string>
|
||||
<string name="overlay_pair_body">Gesprekken klinken via de luidsprekers van de auto zodra je telefoon en de auto gekoppeld zijn. Je telefoon vraagt je daarna een code te bevestigen.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">Wachten op een locatiebepaling.</string>
|
||||
<string name="weather_allow_location">Sta locatie toe voor Gearslip op de telefoon om het weer te zien.</string>
|
||||
<string name="weather_unreachable">Kon de weerdienst niet bereiken.</string>
|
||||
<string name="weather_clear">Helder</string>
|
||||
<string name="weather_mostly_clear">Overwegend helder</string>
|
||||
<string name="weather_partly_cloudy">Gedeeltelijk bewolkt</string>
|
||||
<string name="weather_overcast">Bewolkt</string>
|
||||
<string name="weather_fog">Mist</string>
|
||||
<string name="weather_drizzle">Motregen</string>
|
||||
<string name="weather_rain">Regen</string>
|
||||
<string name="weather_snow">Sneeuw</string>
|
||||
<string name="weather_rain_showers">Regenbuien</string>
|
||||
<string name="weather_snow_showers">Sneeuwbuien</string>
|
||||
<string name="weather_thunderstorm">Onweer</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Weer ophalen…</string>
|
||||
<string name="weather_next_hours">Komende uren</string>
|
||||
<string name="weather_this_week">Deze week</string>
|
||||
<string name="weather_today">Vandaag</string>
|
||||
<string name="weather_credit">Weergegevens van Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Voelt als %1$s</string>
|
||||
<string name="weather_humidity">Luchtvochtigheid %1$d%%</string>
|
||||
<string name="weather_wind">Wind %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">Vandaag</string>
|
||||
<string name="dash_getting_weather">Weer ophalen…</string>
|
||||
<string name="dash_coming_up">Binnenkort</string>
|
||||
<string name="dash_loading_calendar">Agenda laden…</string>
|
||||
<string name="dash_nothing_week">Niets in de komende week.</string>
|
||||
<string name="dash_all_day">Hele dag</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Contacten</string>
|
||||
<string name="dialer_keypad">Toetsenblok</string>
|
||||
<string name="dialer_ask">Vragen op de telefoon</string>
|
||||
<string name="dialer_enter_number">Voer een nummer in</string>
|
||||
<string name="dialer_call">Bellen</string>
|
||||
<string name="dialer_no_contacts">Geen contacten met een telefoonnummer gevonden.</string>
|
||||
<string name="dialer_search">Contacten zoeken</string>
|
||||
|
||||
<string name="dialer_needs_permission">Gearslip heeft de rechten Telefoon en Contacten nodig om vanaf hier te bellen. Sta ze toe op de telefoon als je geparkeerd staat.</string>
|
||||
<string name="dialer_no_match">Geen contacten komen overeen met “%1$s”.</string>
|
||||
<string name="dialer_call_who">%1$s bellen?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Volledig scherm sluiten</string>
|
||||
<string name="carui_drive_safely">Rijd veilig</string>
|
||||
<string name="carui_understand">Ik begrijp het</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip is onofficiële software: het is niet gemaakt of gecontroleerd door een autofabrikant, en het kan zonder waarschuwing uitvallen.</string>
|
||||
<string name="carui_never_interact">Bedien dit scherm nooit tijdens het rijden. Stel de route of het nummer in voor je vertrekt en houd daarna je aandacht op de weg.</string>
|
||||
<string name="carui_this_app">Deze app</string>
|
||||
<string name="carui_app_unsupported">%1$s werkt nog niet met Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">De app startte niet. Sommige apps werken alleen in Android Auto van Google.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">In gesprek</string>
|
||||
<string name="navbar_end_call">Gesprek beëindigen</string>
|
||||
<string name="navbar_unmute">Dempen opheffen</string>
|
||||
<string name="navbar_mute">Dempen</string>
|
||||
<string name="navbar_close_panel">Zijpaneel sluiten</string>
|
||||
<string name="navbar_dock_player">Speler naast de kaart zetten</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Zoek of typ een adres</string>
|
||||
<string name="web_forward">Vooruit</string>
|
||||
<string name="web_reload">Opnieuw laden</string>
|
||||
<string name="web_start_page">Startpagina</string>
|
||||
<string name="web_fullscreen">Volledig scherm</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Meldingen</string>
|
||||
<string name="notif_clear_all">Alles wissen</string>
|
||||
<string name="notif_empty">Nog niets. Nieuwe meldingen van de telefoon verschijnen hier.</string>
|
||||
<string name="notif_off">Meldingen staan uit. Sta Gearslip toe onder Instellingen > Meldingen > Meldingstoegang op de telefoon.</string>
|
||||
<string name="notif_replied">Beantwoord</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Markeren als gelezen</string>
|
||||
<string name="msg_messages">Berichten</string>
|
||||
<string name="msg_none_recent">Geen recente berichten. Nieuwe berichten van %1$s verschijnen hier zodra ze op de telefoon binnenkomen.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d eerder</item>
|
||||
<item quantity="other">%1$d eerder</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Snelle antwoorden verbergen</string>
|
||||
<string name="msg_quick">Snel antwoord</string>
|
||||
<string name="msg_you">Jij: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Route zoeken…</string>
|
||||
<string name="nav_then">Daarna</string>
|
||||
<string name="nav_stop">Navigatie stoppen</string>
|
||||
<string name="nav_finding_routes">Routes zoeken…</string>
|
||||
<string name="nav_lane_recommended">Aanbevolen rijstrook</string>
|
||||
<string name="nav_lane">Rijstrook</string>
|
||||
<string name="nav_arrive">Aankomst %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Er speelt nog niets.</string>
|
||||
<string name="template_search">Zoeken</string>
|
||||
<string name="template_start">Starten</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Inkomend gesprek</string>
|
||||
<string name="call_decline">Weigeren</string>
|
||||
<string name="call_answer">Opnemen</string>
|
||||
<string name="call_ringing">Gaat al hardop over - neem hier op of laat hem overgaan.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">Aanraken staat uit. Zet Gearslip aan onder Instellingen > Toegankelijkheid op de telefoon.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Tonen na updates</string>
|
||||
<string name="whatsnew_got_it">Begrepen</string>
|
||||
<string name="whatsnew_title">Nieuw in versie %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">spatie</string>
|
||||
<string name="keyboard_hide">Toetsenbord verbergen</string>
|
||||
<string name="keyboard_submit">Verzenden</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Werkt niet met Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">Auto-app</string>
|
||||
<string name="stage_close">%1$s sluiten</string>
|
||||
<string name="stage_close_app">De app sluiten</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Flauwe bocht naar links</string>
|
||||
<string name="dir_slight_left_onto">Flauwe bocht naar links, de %1$s op</string>
|
||||
<string name="dir_slight_right">Flauwe bocht naar rechts</string>
|
||||
<string name="dir_slight_right_onto">Flauwe bocht naar rechts, de %1$s op</string>
|
||||
<string name="dir_left">Sla linksaf</string>
|
||||
<string name="dir_left_onto">Sla linksaf, de %1$s op</string>
|
||||
<string name="dir_right">Sla rechtsaf</string>
|
||||
<string name="dir_right_onto">Sla rechtsaf, de %1$s op</string>
|
||||
<string name="dir_sharp_left">Scherpe bocht naar links</string>
|
||||
<string name="dir_sharp_left_onto">Scherpe bocht naar links, de %1$s op</string>
|
||||
<string name="dir_sharp_right">Scherpe bocht naar rechts</string>
|
||||
<string name="dir_sharp_right_onto">Scherpe bocht naar rechts, de %1$s op</string>
|
||||
<string name="dir_u_turn">Keer om</string>
|
||||
<string name="dir_keep_left">Houd links aan</string>
|
||||
<string name="dir_keep_left_toward">Houd links aan richting %1$s</string>
|
||||
<string name="dir_keep_right">Houd rechts aan</string>
|
||||
<string name="dir_keep_right_toward">Houd rechts aan richting %1$s</string>
|
||||
<string name="dir_fork_left">Houd links aan bij de splitsing</string>
|
||||
<string name="dir_fork_left_toward">Houd links aan bij de splitsing richting %1$s</string>
|
||||
<string name="dir_fork_right">Houd rechts aan bij de splitsing</string>
|
||||
<string name="dir_fork_right_toward">Houd rechts aan bij de splitsing richting %1$s</string>
|
||||
<string name="dir_ramp_left">Neem de oprit links</string>
|
||||
<string name="dir_ramp_left_onto">Neem de oprit links naar %1$s</string>
|
||||
<string name="dir_ramp_right">Neem de oprit rechts</string>
|
||||
<string name="dir_ramp_right_onto">Neem de oprit rechts naar %1$s</string>
|
||||
<string name="dir_exit_left">Neem de afrit links</string>
|
||||
<string name="dir_exit_left_toward">Neem de afrit links richting %1$s</string>
|
||||
<string name="dir_exit_right">Neem de afrit rechts</string>
|
||||
<string name="dir_exit_right_toward">Neem de afrit rechts richting %1$s</string>
|
||||
<string name="dir_merge_left">Voeg links in</string>
|
||||
<string name="dir_merge_left_onto">Voeg links in op %1$s</string>
|
||||
<string name="dir_merge_right">Voeg rechts in</string>
|
||||
<string name="dir_merge_right_onto">Voeg rechts in op %1$s</string>
|
||||
<string name="dir_merge">Voeg in</string>
|
||||
<string name="dir_merge_onto">Voeg in op %1$s</string>
|
||||
<string name="dir_roundabout_through">Rijd over de rotonde</string>
|
||||
<string name="dir_roundabout_through_onto">Rijd over de rotonde naar %1$s</string>
|
||||
<string name="dir_roundabout_enter">Rijd de rotonde op</string>
|
||||
<string name="dir_roundabout_exit">Verlaat de rotonde</string>
|
||||
<string name="dir_roundabout_exit_onto">Verlaat de rotonde naar %1$s</string>
|
||||
<string name="dir_straight">Rijd rechtdoor</string>
|
||||
<string name="dir_straight_onto">Rijd rechtdoor, de %1$s op</string>
|
||||
<string name="dir_continue">Rijd door</string>
|
||||
<string name="dir_continue_onto">Rijd door op %1$s</string>
|
||||
<string name="dir_ferry">Neem de veerboot</string>
|
||||
<string name="dir_train">Neem de trein</string>
|
||||
<string name="dir_destination">Je bestemming ligt voor je</string>
|
||||
<string name="dir_destination_left">Je bestemming ligt links</string>
|
||||
<string name="dir_destination_right">Je bestemming ligt rechts</string>
|
||||
<string name="dir_roundabout_take">Neem op de rotonde de %1$s afslag</string>
|
||||
<string name="dir_roundabout_take_onto">Neem op de rotonde de %1$s afslag naar %2$s</string>
|
||||
<string name="dir_ordinal_1">eerste</string>
|
||||
<string name="dir_ordinal_2">tweede</string>
|
||||
<string name="dir_ordinal_3">derde</string>
|
||||
<string name="dir_ordinal_4">vierde</string>
|
||||
<string name="dir_ordinal_5">vijfde</string>
|
||||
<string name="dir_ordinal_6">zesde</string>
|
||||
<string name="dir_ordinal_7">zevende</string>
|
||||
<string name="dir_ordinal_8">achtste</string>
|
||||
<string name="dir_in_distance">Over %1$s %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s kilometer</item>
|
||||
<item quantity="other">%1$s kilometer</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s kilometer</string>
|
||||
<string name="dir_quarter_mile">een kwart mijl</string>
|
||||
<string name="dir_half_mile">een halve mijl</string>
|
||||
<string name="dir_three_quarter_mile">driekwart mijl</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s mijl</item>
|
||||
<item quantity="other">%1$s mijl</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s mijl</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s voet</item>
|
||||
<item quantity="other">%1$s voet</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s yard</item>
|
||||
<item quantity="other">%1$s yard</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d meter</item>
|
||||
<item quantity="other">%1$d meter</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Verbindt je auto niet, dan weigert hij misschien dit certificaat. Importeer je eigen certificaat in Instellingen, onder Certificaat.</string>
|
||||
<string name="cert_downloaded_footer">Verbindt de auto niet? Sommige auto\'s vanaf ongeveer 2020 weigeren het aasdk-certificaat, dus importeer dan je eigen. Wijzigingen gelden vanaf de volgende verbinding.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Afslagkaart op het instrumentenpaneel</string>
|
||||
<string name="car_cluster_video_note">Voor een auto met een tweede scherm in het instrumentenpaneel: Gearslip toont daar de volgende afslag. Geldt vanaf de volgende verbinding.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Niet verbonden</string>
|
||||
<string name="status_idle_detail">Sluit aan op de USB-poort van de auto, of verbind draadloos. Gearslip opent vanzelf als de auto antwoordt.</string>
|
||||
<string name="status_connecting">Verbinden</string>
|
||||
<string name="status_projecting">Projecteren</string>
|
||||
<string name="status_projecting_detail">Streamen naar de headunit.</string>
|
||||
<string name="status_disconnected">Verbinding verbroken</string>
|
||||
<string name="status_disconnected_detail">De headunit is niet meer verbonden.</string>
|
||||
<string name="places_title">Opgeslagen plekken</string>
|
||||
<string name="places_sub">Zeg “navigate home” in elke kaart-app</string>
|
||||
<string name="places_intro">Zeg “navigate home”, “take me to work” of “navigate to” met een eigen naam, en Gearslip stuurt het adres naar je kaart-app. Spraakopdrachten werken voorlopig alleen in het Engels.</string>
|
||||
<string name="places_home">Thuis</string>
|
||||
<string name="places_work">Werk</string>
|
||||
<string name="places_other">Plek %1$d</string>
|
||||
<string name="places_name">Naam</string>
|
||||
<string name="places_address">Adres</string>
|
||||
<string name="places_here">Huidige locatie gebruiken</string>
|
||||
<string name="places_finding">Zoeken…</string>
|
||||
<string name="places_not_found">Gearslip kon je locatie niet vinden. Staat locatie aan?</string>
|
||||
<string name="places_clear">Wissen</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,848 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Pozwala Gearslip przenosić dotknięcia z ekranu samochodu na telefon podczas udostępniania ekranu. Gearslip nie odczytuje zawartości ekranu.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Pomóż innym kierowcom.</string>
|
||||
<string name="guide_safety_title">Jedź bezpiecznie.</string>
|
||||
<string name="guide_safety_body">Gearslip nie jest oficjalny. Żaden producent samochodów go nie stworzył ani nie sprawdził, a może przestać działać bez ostrzeżenia. Ustaw go przed wyjazdem, a potem patrz na drogę.</string>
|
||||
<string name="guide_mic_title">Mikrofon.</string>
|
||||
<string name="guide_mic_body">Gearslip używa go, by przesyłać muzykę do samochodu i słyszeć cię, gdy o coś prosisz lub odpowiadasz na wiadomość. To, co mówisz, zostaje w telefonie.</string>
|
||||
<string name="guide_location_title">Lokalizacja.</string>
|
||||
<string name="guide_location_body">Potrzebuje jej mapa w samochodzie i wyszukiwanie w pobliżu. Bez niej pulpit nie pokazuje mapy.</string>
|
||||
<string name="guide_calendar_title">Kalendarz.</string>
|
||||
<string name="guide_calendar_body">Ekran samochodu pokazuje twoje najbliższe wydarzenie. Gearslip tylko odczytuje kalendarz.</string>
|
||||
<string name="guide_calls_title">Połączenia i kontakty.</string>
|
||||
<string name="guide_calls_body">Samochód pokazuje, kto dzwoni, a jego aplikacja Telefon może wybrać numer lub zadzwonić do kontaktu. Gearslip tylko je odczytuje.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">Przy pierwszym podłączeniu Gearslip zaproponuje sparowanie telefonu z samochodem, żeby rozmowy grały z jego głośników.</string>
|
||||
<string name="guide_notifications_title">Powiadomienia.</string>
|
||||
<string name="guide_notifications_body">Gdy Gearslip jest połączony, pokazuje powiadomienie z przyciskiem zatrzymania. Bez tego Android je ukrywa.</string>
|
||||
<string name="guide_messages_title">Wiadomości.</string>
|
||||
<string name="guide_messages_body">Samochód pokazuje twoje wiadomości, czyta je na głos i pozwala odpowiedzieć. Włącz Gearslip na liście, która się otworzy.</string>
|
||||
<string name="guide_android_auto_title">Wyłącz Android Auto.</string>
|
||||
<string name="guide_android_auto_body">Android Auto jest nadal zainstalowany i walczy z Gearslip o połączenie USB samochodu. Wyłącz go lub odinstaluj na ekranie, który się otworzy.</string>
|
||||
<string name="guide_default_title">Wybierz Gearslip.</string>
|
||||
<string name="guide_default_body">Jeśli po podłączeniu do samochodu telefon zapyta, którą aplikację otworzyć, wybierz Gearslip i kliknij Zawsze.</string>
|
||||
<string name="guide_audio_title">Dźwięk w samochodzie.</string>
|
||||
<string name="guide_audio_body">Wybierz, jak muzyka trafia do samochodu. Rozmowy i tak idą przez Bluetooth.</string>
|
||||
<string name="guide_voice_title">Głos.</string>
|
||||
<string name="guide_voice_body">Poproś głosem o trasę, muzykę lub połączenie. Telefon sam zamienia mowę na tekst i nic, co mówisz, go nie opuszcza. Potrzeba %1$d MB, pobranych raz. Użyj Wi-Fi, jeśli możesz.</string>
|
||||
<string name="guide_audio_prompt_title">Bez pytania o dźwięk.</string>
|
||||
<string name="guide_audio_prompt_body">Uruchom to z komputera z włączonym debugowaniem USB, a Android na stałe powierzy dźwięk Gearslip. Bez tego przy każdej jeździe raz klikasz Start.</string>
|
||||
<string name="guide_readable_title">Czytelne powiadomienia.</string>
|
||||
<string name="guide_readable_body">Gdy dźwięk idzie do samochodu, Android ukrywa treść powiadomień, jakbyś udostępniał ekran. To pozwala Gearslip pokazywać je w czasie jazdy i potem znów ukryć.</string>
|
||||
<string name="guide_answer_title">Odbieranie połączeń.</string>
|
||||
<string name="guide_answer_body">Ustaw Gearslip jako aplikację do identyfikacji rozmówcy, a samochód będzie mógł odbierać i odrzucać połączenia. Bez tego telefon nadal dzwoni, ale odebrać można tylko na nim.</string>
|
||||
<string name="guide_apps_title">Dobre towarzystwo.</string>
|
||||
<string name="guide_buttons_title">Twoje przyciski.</string>
|
||||
<string name="guide_buttons_body">Wybierz wygląd przycisków w samochodzie. Możesz to zmienić w każdej chwili w ustawieniach samochodu.</string>
|
||||
<string name="guide_open_notification_access">Otwórz dostęp do powiadomień</string>
|
||||
<string name="guide_open_android_auto">Otwórz informacje o aplikacji Android Auto</string>
|
||||
<string name="guide_download_voice">Pobierz głos</string>
|
||||
<string name="guide_set_up">Skonfiguruj</string>
|
||||
<string name="guide_voice_downloading">Pobieranie, %1$d%%. Możesz iść dalej, pobieranie trwa.</string>
|
||||
<string name="guide_voice_ready">Głos jest gotowy.</string>
|
||||
<string name="guide_voice_failed">Pobieranie się nie zakończyło. Kliknij Pobierz głos, by spróbować ponownie.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Zalecane</string>
|
||||
<string name="guide_audio_bluetooth_body">Bez pytania przy każdej jeździe, a głos aplikacji z mapami trafia do samochodu.</string>
|
||||
<string name="guide_audio_usb">Przez Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Czystszy dźwięk bez opóźnień. Przy każdej jeździe Android prosi o udostępnienie ekranu, co pozwala Gearslip wysyłać dźwięk.</string>
|
||||
<string name="guide_optional">OPCJONALNE</string>
|
||||
<string name="guide_already_allowed">Już dozwolone.</string>
|
||||
<string name="guide_scroll_more">Przewiń, by zobaczyć więcej ↓</string>
|
||||
<string name="guide_allow">Zezwól</string>
|
||||
<string name="guide_skip">Na razie pomiń</string>
|
||||
<string name="guide_open">Otwórz</string>
|
||||
<string name="guide_next">Dalej</string>
|
||||
<string name="guide_done">Gotowe</string>
|
||||
<string name="guide_back">Wstecz</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Włącz to, a Gearslip wyśle krótką notatkę, gdy go otworzysz, i po każdej jeździe. <b>Notatka nie zawiera imienia, konta ani lokalizacji. Losowa liczba liczy twój telefon raz, a serwer nie przechowuje adresu IP.</b> Notatki pokazują, które samochody działają, a gdzie inne zawodzą.</string>
|
||||
<string name="usage_send">Wysyłaj notatki o użyciu</string>
|
||||
<string name="usage_also_share">Udostępniaj też</string>
|
||||
<string name="usage_share_android">Wersja i system Android</string>
|
||||
<string name="usage_share_phone">Model telefonu</string>
|
||||
<string name="usage_share_cars">Każdy samochód i czy się połączył</string>
|
||||
<string name="usage_share_drives">Każda jazda i jak się zakończyła</string>
|
||||
<string name="usage_what_sent">Co jest wysyłane</string>
|
||||
<string name="usage_what_sent_sub">Każde pole, dokładnie tak, jak wychodzi</string>
|
||||
<string name="usage_hide_note">Ukryj notatkę</string>
|
||||
<string name="usage_show_note">Pokaż notatkę</string>
|
||||
<string name="usage_example">Przykład, niewysyłany: jedna jazda dodaje to do „sessions”</string>
|
||||
<string name="usage_no_server">Ta wersja nie ma serwera statystyk, więc nic nie wysyła</string>
|
||||
<string name="usage_debug_build">Wersja debugowa: notatki są trzymane osobno i nigdy nie trafiają do statystyk</string>
|
||||
<string name="worked_title">Gearslip działał z twoim %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip działał z twoim samochodem</string>
|
||||
<string name="worked_body">Udostępnić to, żeby następny kierowca wiedział, że działa? Bez imienia, konta i lokalizacji.</string>
|
||||
<string name="worked_not_now">Nie teraz</string>
|
||||
<string name="worked_share">Udostępnij</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">Te darmowe aplikacje działają na ekranie samochodowym Gearslip. Każdy przycisk otwiera stronę aplikacji na GitHubie.</string>
|
||||
<string name="recommended_title">Polecane aplikacje</string>
|
||||
<string name="back">Wstecz</string>
|
||||
<string name="recommended_installed">Zainstalowana</string>
|
||||
<string name="recommended_maps">Mapy</string>
|
||||
<string name="recommended_music">Muzyka</string>
|
||||
<string name="recommended_podcasts">Podcasty</string>
|
||||
<string name="recommended_other">Inne fajne aplikacje</string>
|
||||
<string name="recommended_about_organic">Mapy i trasy offline.</string>
|
||||
<string name="recommended_about_vela">Nawigacja zakręt po zakręcie bez Google.</string>
|
||||
<string name="recommended_about_ytm">Odtwarzacz YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">Podcasty, razem z rozdziałami.</string>
|
||||
<string name="recommended_about_rfsentinel">Wykrywa lokalizatory i kamery w pobliżu.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Ustawienia</string>
|
||||
<string name="settings_whats_new">Co nowego</string>
|
||||
<string name="settings_whats_new_sub">Przeczytaj informacje o wersji %1$s na GitHubie</string>
|
||||
<string name="settings_support">Wesprzyj Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip jest darmowy, bez reklam i bez śledzenia. Twoje wsparcie pozwala mu działać.</string>
|
||||
<string name="settings_usage_notes">Notatki o użyciu</string>
|
||||
<string name="settings_updates">Aktualizacje</string>
|
||||
<string name="settings_about">O aplikacji</string>
|
||||
<string name="settings_replay">Powtórz przewodnik konfiguracji</string>
|
||||
<string name="settings_replay_sub">Przejdź ponownie kroki pierwszego uruchomienia</string>
|
||||
<string name="settings_connection">Połączenie</string>
|
||||
<string name="settings_use_in_car">Używaj Gearslip w samochodzie</string>
|
||||
<string name="settings_use_in_car_on">Gearslip odpowiada, gdy podłączysz telefon do samochodu</string>
|
||||
<string name="settings_use_in_car_off">Wyłączone: połączenie może przejąć Android Auto</string>
|
||||
<string name="settings_apps">Aplikacje</string>
|
||||
<string name="settings_certificate">Certyfikat</string>
|
||||
<string name="cert_next_connection">Zmiany zadziałają przy następnym połączeniu.</string>
|
||||
<string name="cert_checking">Sprawdzanie…</string>
|
||||
<string name="cert_valid_until">Ważny do %1$s</string>
|
||||
<string name="cert_hide_details">Ukryj szczegóły</string>
|
||||
<string name="cert_show_details">Pokaż szczegóły</string>
|
||||
<string name="cert_subject">Podmiot</string>
|
||||
<string name="cert_issuer">Wystawca</string>
|
||||
<string name="cert_downloading">Pobieranie…</string>
|
||||
<string name="cert_download">Pobierz z aasdk</string>
|
||||
<string name="cert_downloaded">Certyfikat pobrany.</string>
|
||||
<string name="cert_download_failed">Pobieranie nieudane: %1$s</string>
|
||||
<string name="cert_download_sub">Certyfikat, który otwarty projekt aasdk publikuje na GitHubie. Większość samochodów go akceptuje, ale niektóre od około 2020 roku go odrzucają</string>
|
||||
<string name="cert_import">Importuj własny plik</string>
|
||||
<string name="cert_import_sub">Plik .p12 z kluczem prywatnym. Użyj własnego, gdy samochód odrzuca certyfikat aasdk</string>
|
||||
<string name="cert_remove_imported">Usuń zaimportowany certyfikat</string>
|
||||
<string name="cert_remove_downloaded">Usuń pobrany certyfikat</string>
|
||||
<string name="cert_removed_imported">Usunięto zaimportowany certyfikat.</string>
|
||||
<string name="cert_removed_downloaded">Usunięto pobrany certyfikat.</string>
|
||||
<string name="cert_imported">Certyfikat zaimportowany.</string>
|
||||
<string name="cert_import_failed">Nie udało się zaimportować: złe hasło albo to nie jest plik PKCS#12 z kluczem prywatnym.</string>
|
||||
<string name="cert_password_title">Hasło certyfikatu</string>
|
||||
<string name="cert_password">Hasło</string>
|
||||
<string name="cert_password_hint">Zostaw puste, jeśli plik nie ma hasła.</string>
|
||||
<string name="cert_import_button">Importuj</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Anuluj</string>
|
||||
<string name="cert_none">Brak (samopodpisany; samochody go odrzucają)</string>
|
||||
<string name="cert_kind_imported">Zaimportowany: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Pobrany: %1$s</string>
|
||||
<string name="cert_kind_adb">Wgrany przez adb: %1$s</string>
|
||||
<string name="cert_kind_self">Samopodpisany: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Ekran</string>
|
||||
<string name="car_group_nav_bar">Pasek nawigacji i odtwarzacz</string>
|
||||
<string name="car_group_sound">Dźwięk i głos</string>
|
||||
<string name="car_group_warnings">Ostrzeżenia</string>
|
||||
<string name="car_group_more">Więcej</string>
|
||||
<string name="choice_size_normal">Normalny</string>
|
||||
<string name="choice_size_large">Duży</string>
|
||||
<string name="choice_size_xlarge">Bardzo duży</string>
|
||||
<string name="choice_theme_phone">Jak w telefonie</string>
|
||||
<string name="choice_theme_light">Jasny</string>
|
||||
<string name="choice_theme_dark">Ciemny</string>
|
||||
<string name="choice_dark_black">Czarny</string>
|
||||
<string name="choice_dark_black_colour">Czarny z kolorem</string>
|
||||
<string name="choice_dark_deep_colour">Głęboki kolor</string>
|
||||
<string name="choice_dark_grey">Szary</string>
|
||||
<string name="choice_auto">Automatycznie</string>
|
||||
<string name="choice_night_day">Dzień</string>
|
||||
<string name="choice_night_night">Noc</string>
|
||||
<string name="choice_units_imperial">Imperialne</string>
|
||||
<string name="choice_units_metric">Metryczne</string>
|
||||
<string name="choice_nav_show">Zawsze pokazuj</string>
|
||||
<string name="choice_nav_hide_navigating">Ukrywaj podczas nawigacji</string>
|
||||
<string name="choice_nav_hide">Ukrywaj do dotknięcia</string>
|
||||
<string name="choice_open_home">Ekran główny</string>
|
||||
<string name="summary_player_left">Odtwarzacz po lewej</string>
|
||||
<string name="summary_player_right">Odtwarzacz po prawej</string>
|
||||
<string name="summary_usb_audio">Dźwięk przez Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Dźwięk przez Bluetooth</string>
|
||||
<string name="summary_directions_on">Wskazówki głosowe włączone</string>
|
||||
<string name="summary_directions_off">Wskazówki głosowe wyłączone</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth włączony</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth wyłączony</string>
|
||||
<string name="summary_location_on">Lokalizacja włączona</string>
|
||||
<string name="summary_location_off">Lokalizacja wyłączona</string>
|
||||
<string name="web_page">Strona internetowa</string>
|
||||
<string name="summary_experimental_shown">Funkcje eksperymentalne widoczne</string>
|
||||
<string name="summary_experimental_hidden">Funkcje eksperymentalne ukryte</string>
|
||||
<string name="home_car">Samochód</string>
|
||||
<string name="car_group_same">To te same ustawienia co Ustawienia w samochodzie. Zmiana tutaj pojawi się też tam.</string>
|
||||
<string name="car_open_on_connect">Otwieraj po połączeniu</string>
|
||||
<string name="car_size">Rozmiar</string>
|
||||
<string name="car_app_theme">Motyw aplikacji</string>
|
||||
<string name="car_dark_mode">Tryb ciemny</string>
|
||||
<string name="car_buttons">Przyciski</string>
|
||||
<string name="car_map_day_night">Mapa dzień/noc</string>
|
||||
<string name="car_units">Jednostki</string>
|
||||
<string name="car_nav_bar">Pasek nawigacji</string>
|
||||
<string name="car_player_nav_bar">Odtwarzacz na pasku nawigacji</string>
|
||||
<string name="car_skip_buttons">Przyciski przewijania</string>
|
||||
<string name="car_song_title">Tytuł utworu</string>
|
||||
<string name="car_player_beside_map">Odtwarzacz obok mapy</string>
|
||||
<string name="car_right">Prawo</string>
|
||||
<string name="car_left">Lewo</string>
|
||||
<string name="car_autoplay">Odtwarzaj po połączeniu</string>
|
||||
<string name="car_more_lyrics">Więcej źródeł tekstów</string>
|
||||
<string name="car_more_lyrics_note">Pyta też BetterLyrics i KuGou, które dostają wtedy tytuł i wykonawcę utworu. Po wyłączeniu pyta tylko LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Wskazówki głosowe</string>
|
||||
<string name="car_spoken_directions_note">Wyłącz, jeśli głos twojej aplikacji z mapami już gra w samochodzie.</string>
|
||||
<string name="car_phone_speech">Używaj rozpoznawania mowy telefonu</string>
|
||||
<string name="car_phone_speech_note">W większości telefonów to usługa Google, która może wysyłać twój głos do Google.</string>
|
||||
<string name="car_warn_bluetooth">Ostrzegaj, gdy Bluetooth nie jest połączony</string>
|
||||
<string name="car_warn_location">Ostrzegaj, gdy lokalizacja jest wyłączona</string>
|
||||
<string name="car_experimental">Funkcje eksperymentalne</string>
|
||||
<string name="car_vehicle_data">Dane pojazdu</string>
|
||||
<string name="car_vehicle_data_note">Dodaje kafelek Dane pojazdu: odczyt na żywo z czujników samochodu. Nazwy pól to domysły, dopóki nie zobaczysz, jak któreś się zmienia.</string>
|
||||
<string name="car_adaptive_video">Adaptacyjna jakość obrazu</string>
|
||||
<string name="car_adaptive_video_note">Obniża jakość obrazu, gdy samochód lub jego Wi-Fi nie nadąża, i podnosi ją, gdy znów może. Działa od następnego połączenia.</string>
|
||||
<string name="web_page_note">Pokazywana przez aplikację Przeglądarka w samochodzie. Puste otwiera DuckDuckGo. Działa od następnego połączenia.</string>
|
||||
<string name="car_audio">Dźwięk w samochodzie</string>
|
||||
<string name="car_audio_bt">Bluetooth (zalecane)</string>
|
||||
<string name="car_audio_bt_sub">Bez pytania przy każdej jeździe, a głos aplikacji z mapami trafia do samochodu</string>
|
||||
<string name="car_audio_usb_sub">Czystszy dźwięk bez opóźnień. Przy każdej jeździe Android prosi o udostępnienie ekranu, co pozwala Gearslip wysyłać dźwięk</string>
|
||||
<string name="car_audio_usb_footer">Uruchom raz polecenie z Pomocy, a Android przestanie pytać.</string>
|
||||
<string name="car_audio_bt_footer">Rozmowy i tak idą przez Bluetooth.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Rozmiar ekranu samochodu</string>
|
||||
<string name="screen_use_cars">Taki jak w samochodzie</string>
|
||||
<string name="screen_preview_size">Rozmiar podglądu</string>
|
||||
<string name="screen_preview_note">Rozmiar, w jakim rysowany jest podgląd samochodu, np. 1920x720.</string>
|
||||
<string name="screen_car_screen">Ekran samochodu</string>
|
||||
<string name="screen_footer">Gearslip używa rozmiaru podanego przez samochód, chyba że ustawisz własny.</string>
|
||||
<string name="screen_size">Rozmiar ekranu</string>
|
||||
<string name="screen_from_car">Z samochodu</string>
|
||||
<string name="screen_crop">Przytnij krawędzie</string>
|
||||
<string name="screen_crop_sub">Dla samochodu, który obcina górę i dół lub boki</string>
|
||||
<string name="screen_squash">Ściśnij, by pasowało</string>
|
||||
<string name="screen_squash_sub">Dla samochodu, który spłaszcza cały obraz, przez co koła wyglądają jak owale</string>
|
||||
<string name="screen_preview_title">Rozmiar podglądu samochodu</string>
|
||||
<string name="screen_last_car">Ostatni samochód</string>
|
||||
<string name="screen_last_car_sub">Rozmiar ostatniego samochodu, z którym się łączyłeś</string>
|
||||
<string name="screen_this_screen">Ten ekran</string>
|
||||
<string name="screen_this_screen_sub">Wypełnia ekran tego urządzenia, dla radia samochodowego, na którym działa sam Gearslip</string>
|
||||
<string name="screen_custom">Własny</string>
|
||||
<string name="screen_enter_size">Podaj rozmiar</string>
|
||||
<string name="screen_change_custom">Zmień własny rozmiar</string>
|
||||
<string name="screen_width">Szerokość</string>
|
||||
<string name="screen_height">Wysokość</string>
|
||||
<string name="save">Zapisz</string>
|
||||
|
||||
<string name="screen_car_size_note">Użyj tego, jeśli obraz w samochodzie wygląda na rozciągnięty. Podaj prawdziwy rozmiar ekranu, np. 1280x480. Zadziała przy następnym połączeniu.</string>
|
||||
<string name="screen_set_by_you">%1$s, ustawiony przez ciebie</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Jak się połączyć</string>
|
||||
<string name="help_which_cars">Które samochody działają</string>
|
||||
<string name="help_full_list">Zobacz pełną listę</string>
|
||||
<string name="help_first_drive">Przed pierwszą jazdą</string>
|
||||
<string name="help_in_car">W samochodzie</string>
|
||||
<string name="help_usb_audio">Dźwięk przez Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Wyłącz Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Wyłącz lub odinstaluj aplikację Android Auto, żeby samochód rozmawiał z Gearslip.</string>
|
||||
<string name="help_cert">Wczytaj certyfikat.</string>
|
||||
<string name="help_cert_detail">W Ustawieniach pobierz certyfikat aasdk, który akceptuje większość samochodów. Jeśli twój go odrzuca, zaimportuj własny.</string>
|
||||
<string name="help_mic">Zezwól na mikrofon.</string>
|
||||
<string name="help_mic_detail">Bez niego Android nie pozwoli aplikacji przesyłać muzyki do samochodu. Gearslip słucha tylko, gdy dotkniesz mikrofonu lub naciśniesz przycisk mowy na kierownicy.</string>
|
||||
<string name="help_plug_in">Podłącz kabel lub połącz się bezprzewodowo.</string>
|
||||
<string name="help_plug_in_detail">Gearslip otwiera się sam, gdy samochód się połączy. Do połączenia bezprzewodowego najpierw sparuj telefon z samochodem, potem kliknij Połącz bezprzewodowo na ekranie głównym.</string>
|
||||
<string name="help_pair">Sparuj przez Bluetooth dla rozmów.</string>
|
||||
<string name="help_pair_detail">Rozmowy grają w samochodzie, gdy telefon i samochód są sparowane. Gearslip proponuje parowanie przy pierwszym podłączeniu.</string>
|
||||
<string name="help_answer">Odbieraj połączenia z ekranu samochodu.</string>
|
||||
<string name="help_answer_detail">Ustaw Gearslip jako aplikację do identyfikacji rozmówcy i spamu. Bez tego telefon nadal dzwoni, ale odebrać można tylko na nim.</string>
|
||||
<string name="help_prompt">Pozbądź się pytania o dźwięk przy każdej jeździe.</string>
|
||||
<string name="help_prompt_detail">Podłącz telefon do komputera z włączonym debugowaniem USB i uruchom to raz. Bez tego przy każdym wyjeździe klikasz Start.</string>
|
||||
<string name="help_readable">Zachowaj czytelne powiadomienia.</string>
|
||||
<string name="help_readable_detail">Android ukrywa treść powiadomień, gdy udostępnia dźwięk. Uruchom też to, a Gearslip zdejmie tę blokadę tylko wtedy, gdy muzyka idzie do samochodu.</string>
|
||||
<string name="help_which_cars_body">Większość samochodów akceptuje certyfikat aasdk. Od około 2020 roku niektóre samochody dostały nowsze zabezpieczenia, które go odrzucają, i w nich Gearslip potrzebuje twojego własnego certyfikatu.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Pobrano. Android zapyta przed instalacją.</string>
|
||||
<string name="update_install">Zainstaluj</string>
|
||||
<string name="update_download_now">Pobierz teraz. Android zapyta przed instalacją.</string>
|
||||
<string name="update_try_again">Spróbuj ponownie</string>
|
||||
<string name="update_update">Aktualizuj</string>
|
||||
<string name="update_checking">Sprawdzanie…</string>
|
||||
<string name="update_latest">Masz najnowszą wersję</string>
|
||||
<string name="update_download">Pobierz</string>
|
||||
<string name="update_check_now">Sprawdź teraz</string>
|
||||
<string name="update_check_auto">Sprawdzaj automatycznie</string>
|
||||
<string name="update_once_a_day">Raz dziennie</string>
|
||||
|
||||
<string name="update_unplug_first">Odłącz od samochodu, by zainstalować. Instalacja zamyka Gearslip.</string>
|
||||
<string name="update_card_title">Wyszedł Gearslip %1$s</string>
|
||||
<string name="update_downloading">Pobieranie… %1$d%%</string>
|
||||
<string name="update_debug_manual">Wersje debugowe sprawdzają tylko po kliknięciu Sprawdź teraz</string>
|
||||
<string name="update_checks_daily">Gearslip sprawdza raz dziennie</string>
|
||||
<string name="update_version_out">Wyszła wersja %1$s</string>
|
||||
<string name="update_version_ready">Wersja %1$s jest gotowa do instalacji</string>
|
||||
<string name="update_dialog_status">Wersja %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Wpłacaj co tydzień lub co miesiąc</string>
|
||||
<string name="about_bmac_sub" formatted="false">Wpłać raz lub co miesiąc, kartą. Buy Me a Coffee zatrzymuje 5%</string>
|
||||
<string name="about_give_once">Wpłać raz</string>
|
||||
<string name="about_any_amount">Dowolna kwota, kartą</string>
|
||||
<string name="about_monero_sub">Otwórz portfel lub skopiuj adres</string>
|
||||
<string name="about_monero_title">Wesprzyj przez Monero</string>
|
||||
<string name="about_monero_body">Wyślij dowolną kwotę na ten adres ze swojego portfela Monero.</string>
|
||||
<string name="about_open_wallet">Otwórz w portfelu</string>
|
||||
<string name="about_copy_address">Kopiuj adres</string>
|
||||
<string name="close">Zamknij</string>
|
||||
<string name="about_matrix">Dołącz do czatu na Matrix</string>
|
||||
<string name="about_matrix_sub">Rozmawiaj z innymi kierowcami o samochodach, aplikacjach i Gearslip</string>
|
||||
<string name="about_translate">Pomóż przetłumaczyć Gearslip</string>
|
||||
<string name="about_translate_sub">Popraw lub dodaj swój język na Weblate. Niektóre tłumaczenia powstały jako tłumaczenia maszynowe.</string>
|
||||
<string name="about_source">Kod źródłowy</string>
|
||||
<string name="about_source_sub">Czytaj lub zmieniaj kod Gearslip na licencji AGPL</string>
|
||||
<string name="about_voice_credits">Twórcy rozpoznawania mowy</string>
|
||||
|
||||
<string name="about_no_wallet">Nie znaleziono portfela Monero. Skopiuj adres.</string>
|
||||
<string name="about_monero_copied">Skopiowano adres Monero</string>
|
||||
<string name="about_voice_credits_sub">Rozpoznawanie mowy: sherpa-onnx (Apache 2.0), z Kroko ASR od Banafo (CC BY-SA 4.0) oraz Parakeet i Canary od NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Konfiguracja certyfikatu</string>
|
||||
<string name="certsetup_installed">Certyfikat zainstalowany.</string>
|
||||
<string name="certsetup_continue">Dalej</string>
|
||||
<string name="certsetup_failed">Pobieranie nieudane</string>
|
||||
<string name="certsetup_retry">Ponów</string>
|
||||
<string name="certsetup_import_sub">Plik .p12 z kluczem prywatnym. Użyj własnego, gdy samochód odrzuca certyfikat aasdk.</string>
|
||||
<string name="certsetup_skip">Pomiń konfigurację połączenia z samochodem</string>
|
||||
|
||||
<string name="certsetup_loaded">Samochody akceptują tylko certyfikaty wydane przez Google, a ty masz jeden wczytany.</string>
|
||||
<string name="certsetup_choose">Samochody akceptują tylko certyfikaty wydane przez Google. Gearslip żadnego nie zawiera, więc wybierz, skąd weźmiesz swój.</string>
|
||||
<string name="certsetup_aasdk_sub">Otwarty projekt aasdk publikuje ten certyfikat na github.com/opencardev/aasdk. Większość samochodów go akceptuje. Niektóre od około 2020 roku go odrzucają i wtedy trzeba zaimportować własny.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Pamięć Gearslip</string>
|
||||
<string name="storage_measuring">Mierzenie…</string>
|
||||
<string name="storage_lyrics">Teksty piosenek</string>
|
||||
<string name="storage_clear">Wyczyść</string>
|
||||
<string name="storage_voice">Modele głosowe</string>
|
||||
<string name="storage_remove">Usuń</string>
|
||||
<string name="storage_logs">Stare logi</string>
|
||||
<string name="storage_other">Inne pliki tymczasowe</string>
|
||||
<string name="storage_delete_all">Usuń wszystkie dane Gearslip</string>
|
||||
|
||||
<string name="storage_total">Dane Gearslip zajmują %1$s pamięci telefonu.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d utwór · %2$s. Wyczyszczone teksty pobiorą się ponownie, gdy je otworzysz.</item>
|
||||
<item quantity="few">%1$d utwory · %2$s. Wyczyszczone teksty pobiorą się ponownie, gdy je otworzysz.</item>
|
||||
<item quantity="many">%1$d utworów · %2$s. Wyczyszczone teksty pobiorą się ponownie, gdy je otworzysz.</item>
|
||||
<item quantity="other">%1$d utworu · %2$s. Wyczyszczone teksty pobiorą się ponownie, gdy je otworzysz.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Potrzebne do poleceń głosowych; pobierz je ponownie w Ustawieniach.</string>
|
||||
<string name="storage_logs_detail">%1$s. Logi z wcześniejszych uruchomień; log bieżącego zostaje.</string>
|
||||
<string name="storage_other_detail">%1$s. Pobrane aktualizacje i inne pliki, które Gearslip może odtworzyć.</string>
|
||||
<string name="storage_delete_armed">Kliknij Usuń jeszcze raz, by skasować ustawienia, certyfikaty, modele głosowe i logi. Gearslip się zamknie i następnym razem zacznie od konfiguracji.</string>
|
||||
<string name="storage_delete_sub">Ustawienia, certyfikaty, modele głosowe, teksty i logi.</string>
|
||||
<string name="storage_delete">Usuń</string>
|
||||
<string name="storage_delete_ellipsis">Usuń…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Cieszę się, że jazda się udała</string>
|
||||
<string name="donate_liberapay">Wesprzyj na Liberapay</string>
|
||||
<string name="donate_bmac">Postaw mi kawę</string>
|
||||
<string name="donate_card">Wpłać raz kartą</string>
|
||||
|
||||
<string name="donate_body">Gearslip jest darmowy, bez reklam i bez śledzenia. Jeśli pomaga ci w drodze, twoje wsparcie pozwala mu działać.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">Do tego Gearslip potrzebuje dostępu do Bluetootha i Wi-Fi w pobliżu.</string>
|
||||
<string name="wireless_enable_permission">Włącz uprawnienie Urządzenia w pobliżu</string>
|
||||
<string name="wireless_connect">Połącz bezprzewodowo</string>
|
||||
<string name="wireless_which_car">Który samochód?</string>
|
||||
<string name="wireless_none_paired">Telefon nie jest jeszcze z niczym sparowany. Najpierw sparuj go z samochodem.</string>
|
||||
<string name="wireless_not_listed">Nie ma samochodu na liście? Najpierw sparuj go z telefonem.</string>
|
||||
<string name="bluetooth_settings">Ustawienia Bluetooth</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Głos: słowa na żywo</string>
|
||||
<string name="voice_final_title">Głos: słowa końcowe</string>
|
||||
<string name="voice_none">Brak</string>
|
||||
<string name="voice_none_sub">Zostaw słowa na żywo. Najszybciej, najmniej dokładnie</string>
|
||||
|
||||
<string name="voice_live_body">Asystent głosowy zamienia mowę na tekst w tym telefonie i nic, co mówisz, go nie opuszcza. Potrzebuje dwóch modeli, razem około 270 MB, pobranych raz. Ten zapisuje twoje słowa, gdy mówisz.</string>
|
||||
<string name="voice_final_body">Gdy przestaniesz mówić, ten model czyta całe zdanie jeszcze raz. Wyłapuje nazwy i miejsca, które gubi model na żywo.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Pobrano. %1$s</string>
|
||||
<string name="voice_downloading">Pobieranie, %1$d%%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Pomoc</string>
|
||||
<string name="home_preview">Podgląd</string>
|
||||
<string name="home_logs">Logi</string>
|
||||
<string name="home_disconnect">Rozłącz</string>
|
||||
<string name="home_bt_title">Bluetooth nie jest połączony</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · wersja debugowa</string>
|
||||
<string name="home_bt_body">Muzyka gra przez Bluetooth, ale telefon nie jest połączony z samochodem. Połącz go tutaj lub w menu Bluetooth samochodu.</string>
|
||||
<string name="home_voice_downloading">Pobieranie głosu, %1$d%%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Odtwarzać dźwięk w samochodzie przez Bluetooth?</string>
|
||||
<string name="audio_never_ask">Nigdy nie pytaj, z pomocą komputera</string>
|
||||
<string name="audio_use_bt">Użyj Bluetooth</string>
|
||||
<string name="audio_keep_asking">Pytaj dalej</string>
|
||||
<string name="audio_mic_title">Dźwięk w samochodzie wymaga uprawnienia do mikrofonu</string>
|
||||
<string name="audio_open_settings">Otwórz ustawienia aplikacji</string>
|
||||
|
||||
<string name="audio_bt_body">Android traktuje muzykę, którą Gearslip wysyła do samochodu, jak udostępnianie ekranu, więc pyta przy każdej jeździe, przez kabel lub bezprzewodowo. Przez Bluetooth nigdy nie pyta. Możesz to później zmienić w Ustawieniach.</string>
|
||||
<string name="audio_keep_usb">Albo zostaw dźwięk przez Gearslip i pozbądź się pytania na dobre: podłącz telefon do komputera z włączonym debugowaniem USB i uruchom to raz.</string>
|
||||
<string name="audio_mic_body">Android pozwala aplikacji wysyłać dźwięk innych aplikacji do samochodu tylko wtedy, gdy ma uprawnienie do mikrofonu. Dla Gearslip jest wyłączone, więc muzyka gra w telefonie. Włącz je w Uprawnieniach, potem Mikrofon.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Zamknij klawiaturę samochodu</string>
|
||||
<string name="typing_label">Pisz na ekran samochodu</string>
|
||||
<string name="typing_search">Szukaj na ekranie samochodu</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Kliknij, by skopiować</string>
|
||||
<string name="command_copied">Skopiowano polecenie</string>
|
||||
<string name="whats_new_this_version">%1$s (ta wersja)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Motyw</string>
|
||||
<string name="carset_imperial">Imperialne (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Metryczne (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Rozmiar ekranu (następne połączenie)</string>
|
||||
<string name="carset_fit">Dopasuj do ekranu</string>
|
||||
<string name="off">Wył.</string>
|
||||
<string name="on">Wł.</string>
|
||||
<string name="carset_lyrics_from">Teksty z</string>
|
||||
<string name="carset_more_sources">Więcej źródeł</string>
|
||||
<string name="carset_hide">Ukryj</string>
|
||||
<string name="carset_show">Pokaż</string>
|
||||
<string name="carset_phone">Telefon</string>
|
||||
<string name="carset_vehicle">Pojazd</string>
|
||||
<string name="carset_video">Obraz</string>
|
||||
<string name="carset_version">Wersja</string>
|
||||
<string name="carset_build">Kompilacja</string>
|
||||
<string name="carset_whats_new_after">Co nowego po aktualizacjach</string>
|
||||
<string name="carset_whats_new_after_sub">Pokaż listę zmian raz, przy pierwszym połączeniu samochodu po aktualizacji.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (domyślna) - kliknij, by ustawić inną stronę</string>
|
||||
<string name="carset_web_note">Pokazywana przez aplikację Przeglądarka w launcherze. Działa od następnego połączenia.</string>
|
||||
<string name="carset_screen_fit">Dopasowanie ekranu</string>
|
||||
<string name="carset_top">Góra</string>
|
||||
<string name="carset_bottom">Dół</string>
|
||||
<string name="carset_save_vehicle">Zapisz dla tego pojazdu</string>
|
||||
<string name="carset_no_profile">Brak pasującego profilu pojazdu</string>
|
||||
|
||||
<string name="carset_audio_note">Przez Gearslip wysyła dźwięk telefonu przez połączenie z samochodem, kablem lub bezprzewodowo. Android prosi o udostępnienie ekranu przy każdej jeździe, a kilka aplikacji to blokuje. Bluetooth gra tak, jak zwykle gra telefon, a Android nigdy nie pyta.</string>
|
||||
<string name="carset_directions_note">Gearslip odczytuje każdy manewr przez głośniki samochodu, a muzyka cichnie, gdy mówi. Wyłącz, jeśli głos twojej aplikacji z mapami już gra w samochodzie.</string>
|
||||
<string name="carset_phone_speech_note">Słucha przez usługę mowy telefonu zamiast Gearslip. W większości telefonów to usługa Google, która może wysyłać twój głos do Google. GrapheneOS jej nie ma, więc Gearslip dalej używa własnej.</string>
|
||||
<string name="carset_vehicle_data_note">Dodaje kafelek Dane pojazdu: surowy odczyt na żywo z kanału czujników radia samochodowego. Etykiety pól (prędkość, obroty, paliwo...) pochodzą z innych projektów inżynierii wstecznej i nie zostały sprawdzone na tym samochodzie. Traktuj je jako domysł, nie fakt, dopóki sam nie zobaczysz, jak któreś się zmienia.</string>
|
||||
<string name="carset_adaptive_note">Obniża jakość obrazu, gdy samochód, lub jego Wi-Fi, nie nadąża, i podnosi ją, gdy znów może. Działa od następnego połączenia.</string>
|
||||
<string name="carset_usable_height">Użyteczna wysokość %1$dpx z 480</string>
|
||||
<string name="carset_saved_to">Zapisano w %1$s</string>
|
||||
<string name="carset_save_failed">Nie udało się zapisać</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Otwórz w telefonie</string>
|
||||
<string name="media_now_playing">Teraz gra</string>
|
||||
<string name="media_up_next">Następne</string>
|
||||
<string name="media_browse">Przeglądaj</string>
|
||||
<string name="media_search_hint">Wpisz utwór, wykonawcę, album lub playlistę.</string>
|
||||
<string name="media_looking_lyrics">Szukanie tekstu…</string>
|
||||
<string name="media_nothing_playing">Nic nie gra</string>
|
||||
<string name="media_press_play">Naciśnij odtwarzanie, by wrócić tam, gdzie skończyłeś, albo znajdź coś w Przeglądaj.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s nie udostępnia swojej biblioteki.</string>
|
||||
<string name="media_needs_access">Gearslip może nią sterować, gdy ma dostęp do powiadomień w telefonie. Zezwól w Ustawienia > Powiadomienia > Dostęp do powiadomień.</string>
|
||||
<string name="media_not_running">%1$s nie jest uruchomiona.</string>
|
||||
<string name="media_not_running_body">%1$s gra przez odtwarzacz Gearslip, ale tylko wtedy, gdy jest otwarta w telefonie. Otwórz ją raz, a będzie grać tutaj w samochodzie.</string>
|
||||
<string name="media_open_app">Otwórz %1$s</string>
|
||||
<string name="media_refused">%1$s nie pozwoliła Gearslip się przeglądać.</string>
|
||||
<string name="media_refused_body">Niektóre aplikacje multimedialne akceptują tylko hosta Google.</string>
|
||||
<string name="media_connecting">Łączenie z %1$s…</string>
|
||||
<string name="media_search">Szukaj w %1$s</string>
|
||||
<string name="media_search_no_list">%1$s nie potrafi wyświetlić wyników. Naciśnij szukaj, a %1$s wybierze, co zagrać.</string>
|
||||
<string name="media_search_unsupported">%1$s nie pozwala szukać z samochodu.</string>
|
||||
<string name="media_searching">Szukanie w %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s nic nie znalazła dla „%2$s”.</string>
|
||||
<string name="media_search_ignored">%1$s nic nie zagrała dla „%2$s”.</string>
|
||||
<string name="media_searching_for">Szukanie „%2$s” w %1$s…</string>
|
||||
<string name="media_nothing_playing_dot">Nic nie gra.</string>
|
||||
<string name="media_no_lyrics">Nie znaleziono tekstu tego utworu.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">W górę</string>
|
||||
<string name="media_loading">Wczytywanie…</string>
|
||||
<string name="media_browse_start">Włącz muzykę w aplikacji w telefonie. Odtwarzanie, pauza, przewijanie i przeskakiwanie działają stąd.</string>
|
||||
<string name="media_load_failed">Nie udało się wczytać tej listy.</string>
|
||||
<string name="media_nothing_here">Nic tu nie ma.</string>
|
||||
<string name="media_nothing_queued">Kolejka jest pusta.</string>
|
||||
<string name="media_play">Odtwórz</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Nic nie gra - nie każda aplikacja mówi Gearslip, co będzie dalej.</string>
|
||||
<string name="media_queue_empty_playing">Tylko to, co już gra - nie każda aplikacja mówi Gearslip, co będzie dalej.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Poprzedni</string>
|
||||
<string name="player_next">Następny</string>
|
||||
<string name="player_repeat">Powtarzaj</string>
|
||||
<string name="player_pause">Pauza</string>
|
||||
<string name="player_unlike">Nie lubię</string>
|
||||
<string name="player_like">Lubię</string>
|
||||
<string name="player_shuffle_off">Losowo wyłączone</string>
|
||||
<string name="player_shuffle_on">Losowo włączone</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Wybierz inną aplikację</string>
|
||||
<string name="home_no_maps">Nie znaleziono aplikacji z mapami.</string>
|
||||
<string name="home_no_maps_body">Zainstaluj aplikację do nawigacji obsługującą Android Auto, a sama się tu uruchomi.</string>
|
||||
<string name="home_map_stopped">Mapa zatrzymana</string>
|
||||
<string name="home_map_stopped_body">Pozostanie zamknięta, dopóki jej nie otworzysz.</string>
|
||||
<string name="home_reopen_map">Otwórz mapę ponownie</string>
|
||||
<string name="home_car_apps">Aplikacje samochodowe</string>
|
||||
<string name="home_back_nav">Wróć do paska nawigacji</string>
|
||||
<string name="home_show_player">Pokaż odtwarzacz</string>
|
||||
<string name="home_hide_lyrics">Ukryj tekst</string>
|
||||
|
||||
<string name="home_starting">Uruchamianie %1$s…</string>
|
||||
<string name="home_starting_map">Uruchamianie mapy…</string>
|
||||
<string name="home_failed">Nie udało się uruchomić %1$s.</string>
|
||||
<string name="home_failed_map">Nie udało się uruchomić aplikacji z mapami.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Start</string>
|
||||
<string name="crumb_media">Multimedia</string>
|
||||
<string name="crumb_apps">Aplikacje</string>
|
||||
<string name="crumb_dashboard">Pulpit</string>
|
||||
<string name="crumb_weather">Pogoda</string>
|
||||
<string name="crumb_reply">Odpowiedz</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Przeglądarka</string>
|
||||
<string name="app_screen_sharing">Udostępnianie ekranu</string>
|
||||
<string name="app_exit">Wyjdź</string>
|
||||
<string name="navapps_unpin">Odepnij od paska nawigacji</string>
|
||||
<string name="navapps_full">Pasek nawigacji jest pełny (przypięto %1$d)</string>
|
||||
<string name="navapps_pin">Przypnij do paska nawigacji</string>
|
||||
<string name="navapps_close">Zamknij aplikację</string>
|
||||
<string name="navapps_move_left">Przesuń w lewo</string>
|
||||
<string name="navapps_move_right">Przesuń w prawo</string>
|
||||
<string name="navapps_reset">Przywróć kolejność aplikacji</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Odrzuć</string>
|
||||
<string name="msg_read">Przeczytaj</string>
|
||||
<string name="msg_stop">Zatrzymaj</string>
|
||||
<string name="msg_no">Nie</string>
|
||||
<string name="msg_send_now">Wyślij teraz</string>
|
||||
<string name="msg_type">Napisz</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nowa</item>
|
||||
<item quantity="few">%1$s · %2$d nowe</item>
|
||||
<item quantity="many">%1$s · %2$d nowych</item>
|
||||
<item quantity="other">%1$s · %2$d nowej</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Odpowiedz: %1$s</string>
|
||||
<string name="msg_listening">Słucham</string>
|
||||
<string name="msg_sending_to">Wysyłanie do: %1$s</string>
|
||||
<string name="msg_sent_to">Wysłano do: %1$s</string>
|
||||
<string name="msg_reply_failed">Nie udało się odpowiedzieć</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">Parowanie się nie zakończyło. Sparuj w menu Bluetooth samochodu.</string>
|
||||
<string name="bt_pair_not_started">Parowanie się nie rozpoczęło. Sparuj w menu Bluetooth samochodu.</string>
|
||||
<string name="bt_paired">Sparowano. Rozmowy grają teraz w samochodzie.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Nie pokazuj ponownie</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">Lokalizacja jest wyłączona</string>
|
||||
<string name="overlay_pair_title">Sparować z tym samochodem?</string>
|
||||
<string name="overlay_pair">Sparuj</string>
|
||||
<string name="overlay_confirm_title">Potwierdź w telefonie</string>
|
||||
<string name="overlay_confirm_body">Sprawdź kod w telefonie i kliknij Sparuj. Samochód też może zapytać.</string>
|
||||
|
||||
<string name="overlay_bt_body">Muzyka gra przez Bluetooth, ale telefon nie jest połączony z samochodem. Połącz go w menu Bluetooth samochodu lub telefonu.</string>
|
||||
<string name="overlay_location_body">Aplikacje z mapami nie pokażą, gdzie jesteś, ani nie poprowadzą. Włącz lokalizację w szybkich ustawieniach telefonu, gdy zaparkujesz.</string>
|
||||
<string name="overlay_pair_body_music">Rozmowy i muzyka grają z głośników samochodu, gdy telefon i samochód są sparowane. Telefon poprosi wtedy o potwierdzenie kodu.</string>
|
||||
<string name="overlay_pair_body">Rozmowy grają z głośników samochodu, gdy telefon i samochód są sparowane. Telefon poprosi wtedy o potwierdzenie kodu.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">Czekanie na ustalenie lokalizacji.</string>
|
||||
<string name="weather_allow_location">Zezwól Gearslip na lokalizację w telefonie, by zobaczyć pogodę.</string>
|
||||
<string name="weather_unreachable">Nie udało się połączyć z serwisem pogodowym.</string>
|
||||
<string name="weather_clear">Bezchmurnie</string>
|
||||
<string name="weather_mostly_clear">Przeważnie bezchmurnie</string>
|
||||
<string name="weather_partly_cloudy">Częściowe zachmurzenie</string>
|
||||
<string name="weather_overcast">Pochmurno</string>
|
||||
<string name="weather_fog">Mgła</string>
|
||||
<string name="weather_drizzle">Mżawka</string>
|
||||
<string name="weather_rain">Deszcz</string>
|
||||
<string name="weather_snow">Śnieg</string>
|
||||
<string name="weather_rain_showers">Przelotny deszcz</string>
|
||||
<string name="weather_snow_showers">Przelotny śnieg</string>
|
||||
<string name="weather_thunderstorm">Burza</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Pobieranie pogody…</string>
|
||||
<string name="weather_next_hours">Najbliższe godziny</string>
|
||||
<string name="weather_this_week">Ten tydzień</string>
|
||||
<string name="weather_today">Dziś</string>
|
||||
<string name="weather_credit">Dane pogodowe z Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Odczuwalna %1$s</string>
|
||||
<string name="weather_humidity">Wilgotność %1$d%%</string>
|
||||
<string name="weather_wind">Wiatr %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">Dziś</string>
|
||||
<string name="dash_getting_weather">Pobieranie pogody…</string>
|
||||
<string name="dash_coming_up">Wkrótce</string>
|
||||
<string name="dash_loading_calendar">Wczytywanie kalendarza…</string>
|
||||
<string name="dash_nothing_week">Nic w najbliższym tygodniu.</string>
|
||||
<string name="dash_all_day">Cały dzień</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Kontakty</string>
|
||||
<string name="dialer_keypad">Klawiatura</string>
|
||||
<string name="dialer_ask">Zapytaj w telefonie</string>
|
||||
<string name="dialer_enter_number">Wpisz numer</string>
|
||||
<string name="dialer_call">Zadzwoń</string>
|
||||
<string name="dialer_no_contacts">Nie znaleziono kontaktów z numerem telefonu.</string>
|
||||
<string name="dialer_search">Szukaj kontaktów</string>
|
||||
|
||||
<string name="dialer_needs_permission">Gearslip potrzebuje uprawnień Telefon i Kontakty, by dzwonić stąd. Zezwól na nie w telefonie, gdy zaparkujesz.</string>
|
||||
<string name="dialer_no_match">Żaden kontakt nie pasuje do „%1$s”.</string>
|
||||
<string name="dialer_call_who">Zadzwonić do: %1$s?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Wyjdź z pełnego ekranu</string>
|
||||
<string name="carui_drive_safely">Jedź bezpiecznie</string>
|
||||
<string name="carui_understand">Rozumiem</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip to nieoficjalne oprogramowanie: nie stworzył ani nie sprawdził go żaden producent samochodów, a może przestać działać bez ostrzeżenia.</string>
|
||||
<string name="carui_never_interact">Nigdy nie obsługuj tego ekranu w czasie jazdy. Ustaw trasę lub utwór przed wyjazdem, a potem skup się na drodze.</string>
|
||||
<string name="carui_this_app">Ta aplikacja</string>
|
||||
<string name="carui_app_unsupported">%1$s jeszcze nie działa z Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">Nie uruchomiła się. Niektóre aplikacje działają tylko w Android Auto od Google.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">W trakcie rozmowy</string>
|
||||
<string name="navbar_end_call">Zakończ rozmowę</string>
|
||||
<string name="navbar_unmute">Wyłącz wyciszenie</string>
|
||||
<string name="navbar_mute">Wycisz</string>
|
||||
<string name="navbar_close_panel">Zamknij panel boczny</string>
|
||||
<string name="navbar_dock_player">Przenieś odtwarzacz obok mapy</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Szukaj lub wpisz adres</string>
|
||||
<string name="web_forward">Dalej</string>
|
||||
<string name="web_reload">Odśwież</string>
|
||||
<string name="web_start_page">Strona startowa</string>
|
||||
<string name="web_fullscreen">Pełny ekran</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Powiadomienia</string>
|
||||
<string name="notif_clear_all">Wyczyść wszystko</string>
|
||||
<string name="notif_empty">Na razie nic. Nowe powiadomienia z telefonu pojawią się tutaj.</string>
|
||||
<string name="notif_off">Powiadomienia są wyłączone. Zezwól Gearslip w Ustawienia > Powiadomienia > Dostęp do powiadomień w telefonie.</string>
|
||||
<string name="notif_replied">Odpowiedziano</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Oznacz jako przeczytane</string>
|
||||
<string name="msg_messages">Wiadomości</string>
|
||||
<string name="msg_none_recent">Brak nowych wiadomości. Nowe wiadomości z %1$s pojawią się tutaj, gdy dotrą do telefonu.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d wcześniejsza</item>
|
||||
<item quantity="few">%1$d wcześniejsze</item>
|
||||
<item quantity="many">%1$d wcześniejszych</item>
|
||||
<item quantity="other">%1$d wcześniejszej</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Ukryj szybkie odpowiedzi</string>
|
||||
<string name="msg_quick">Szybka odpowiedź</string>
|
||||
<string name="msg_you">Ty: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Szukanie trasy…</string>
|
||||
<string name="nav_then">Potem</string>
|
||||
<string name="nav_stop">Zakończ nawigację</string>
|
||||
<string name="nav_finding_routes">Szukanie tras…</string>
|
||||
<string name="nav_lane_recommended">Zalecany pas</string>
|
||||
<string name="nav_lane">Pas</string>
|
||||
<string name="nav_arrive">Przyjazd %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Jeszcze nic nie gra.</string>
|
||||
<string name="template_search">Szukaj</string>
|
||||
<string name="template_start">Start</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Połączenie przychodzące</string>
|
||||
<string name="call_decline">Odrzuć</string>
|
||||
<string name="call_answer">Odbierz</string>
|
||||
<string name="call_ringing">Już dzwoni na głos - odbierz tutaj albo pozwól dzwonić.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">Dotyk jest wyłączony. Włącz Gearslip w Ustawienia > Ułatwienia dostępu w telefonie.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Pokazuj po aktualizacjach</string>
|
||||
<string name="whatsnew_got_it">Jasne</string>
|
||||
<string name="whatsnew_title">Co nowego w wersji %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">spacja</string>
|
||||
<string name="keyboard_hide">Ukryj klawiaturę</string>
|
||||
<string name="keyboard_submit">Zatwierdź</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Nie działa z Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">Aplikacja samochodowa</string>
|
||||
<string name="stage_close">Zamknij %1$s</string>
|
||||
<string name="stage_close_app">Zamknij aplikację</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Skręć lekko w lewo</string>
|
||||
<string name="dir_slight_left_onto">Skręć lekko w lewo w %1$s</string>
|
||||
<string name="dir_slight_right">Skręć lekko w prawo</string>
|
||||
<string name="dir_slight_right_onto">Skręć lekko w prawo w %1$s</string>
|
||||
<string name="dir_left">Skręć w lewo</string>
|
||||
<string name="dir_left_onto">Skręć w lewo w %1$s</string>
|
||||
<string name="dir_right">Skręć w prawo</string>
|
||||
<string name="dir_right_onto">Skręć w prawo w %1$s</string>
|
||||
<string name="dir_sharp_left">Skręć ostro w lewo</string>
|
||||
<string name="dir_sharp_left_onto">Skręć ostro w lewo w %1$s</string>
|
||||
<string name="dir_sharp_right">Skręć ostro w prawo</string>
|
||||
<string name="dir_sharp_right_onto">Skręć ostro w prawo w %1$s</string>
|
||||
<string name="dir_u_turn">Zawróć</string>
|
||||
<string name="dir_keep_left">Trzymaj się lewej strony</string>
|
||||
<string name="dir_keep_left_toward">Trzymaj się lewej strony w kierunku %1$s</string>
|
||||
<string name="dir_keep_right">Trzymaj się prawej strony</string>
|
||||
<string name="dir_keep_right_toward">Trzymaj się prawej strony w kierunku %1$s</string>
|
||||
<string name="dir_fork_left">Na rozwidleniu trzymaj się lewej strony</string>
|
||||
<string name="dir_fork_left_toward">Na rozwidleniu trzymaj się lewej strony w kierunku %1$s</string>
|
||||
<string name="dir_fork_right">Na rozwidleniu trzymaj się prawej strony</string>
|
||||
<string name="dir_fork_right_toward">Na rozwidleniu trzymaj się prawej strony w kierunku %1$s</string>
|
||||
<string name="dir_ramp_left">Wjedź na łącznicę po lewej</string>
|
||||
<string name="dir_ramp_left_onto">Wjedź na łącznicę po lewej w %1$s</string>
|
||||
<string name="dir_ramp_right">Wjedź na łącznicę po prawej</string>
|
||||
<string name="dir_ramp_right_onto">Wjedź na łącznicę po prawej w %1$s</string>
|
||||
<string name="dir_exit_left">Zjedź zjazdem po lewej</string>
|
||||
<string name="dir_exit_left_toward">Zjedź zjazdem po lewej w kierunku %1$s</string>
|
||||
<string name="dir_exit_right">Zjedź zjazdem po prawej</string>
|
||||
<string name="dir_exit_right_toward">Zjedź zjazdem po prawej w kierunku %1$s</string>
|
||||
<string name="dir_merge_left">Włącz się do ruchu w lewo</string>
|
||||
<string name="dir_merge_left_onto">Włącz się do ruchu w lewo na %1$s</string>
|
||||
<string name="dir_merge_right">Włącz się do ruchu w prawo</string>
|
||||
<string name="dir_merge_right_onto">Włącz się do ruchu w prawo na %1$s</string>
|
||||
<string name="dir_merge">Włącz się do ruchu</string>
|
||||
<string name="dir_merge_onto">Włącz się do ruchu na %1$s</string>
|
||||
<string name="dir_roundabout_through">Przejedź przez rondo</string>
|
||||
<string name="dir_roundabout_through_onto">Przejedź przez rondo w %1$s</string>
|
||||
<string name="dir_roundabout_enter">Wjedź na rondo</string>
|
||||
<string name="dir_roundabout_exit">Zjedź z ronda</string>
|
||||
<string name="dir_roundabout_exit_onto">Zjedź z ronda w %1$s</string>
|
||||
<string name="dir_straight">Jedź prosto</string>
|
||||
<string name="dir_straight_onto">Jedź prosto w %1$s</string>
|
||||
<string name="dir_continue">Jedź dalej</string>
|
||||
<string name="dir_continue_onto">Jedź dalej %1$s</string>
|
||||
<string name="dir_ferry">Wjedź na prom</string>
|
||||
<string name="dir_train">Wsiądź do pociągu</string>
|
||||
<string name="dir_destination">Cel jest przed tobą</string>
|
||||
<string name="dir_destination_left">Cel jest po lewej</string>
|
||||
<string name="dir_destination_right">Cel jest po prawej</string>
|
||||
<string name="dir_roundabout_take">Na rondzie wybierz %1$s zjazd</string>
|
||||
<string name="dir_roundabout_take_onto">Na rondzie wybierz %1$s zjazd w %2$s</string>
|
||||
<string name="dir_ordinal_1">pierwszy</string>
|
||||
<string name="dir_ordinal_2">drugi</string>
|
||||
<string name="dir_ordinal_3">trzeci</string>
|
||||
<string name="dir_ordinal_4">czwarty</string>
|
||||
<string name="dir_ordinal_5">piąty</string>
|
||||
<string name="dir_ordinal_6">szósty</string>
|
||||
<string name="dir_ordinal_7">siódmy</string>
|
||||
<string name="dir_ordinal_8">ósmy</string>
|
||||
<string name="dir_in_distance">Za %1$s %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s kilometr</item>
|
||||
<item quantity="few">%1$s kilometry</item>
|
||||
<item quantity="many">%1$s kilometrów</item>
|
||||
<item quantity="other">%1$s kilometra</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s kilometra</string>
|
||||
<string name="dir_quarter_mile">ćwierć mili</string>
|
||||
<string name="dir_half_mile">pół mili</string>
|
||||
<string name="dir_three_quarter_mile">trzy czwarte mili</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s milę</item>
|
||||
<item quantity="few">%1$s mile</item>
|
||||
<item quantity="many">%1$s mil</item>
|
||||
<item quantity="other">%1$s mili</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s mili</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s stopę</item>
|
||||
<item quantity="few">%1$s stopy</item>
|
||||
<item quantity="many">%1$s stóp</item>
|
||||
<item quantity="other">%1$s stopy</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s jard</item>
|
||||
<item quantity="few">%1$s jardy</item>
|
||||
<item quantity="many">%1$s jardów</item>
|
||||
<item quantity="other">%1$s jarda</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d metr</item>
|
||||
<item quantity="few">%1$d metry</item>
|
||||
<item quantity="many">%1$d metrów</item>
|
||||
<item quantity="other">%1$d metra</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Jeśli samochód się nie łączy, może odrzucać ten certyfikat. Zaimportuj własny w Ustawieniach, w sekcji Certyfikat.</string>
|
||||
<string name="cert_downloaded_footer">Samochód się nie łączy? Niektóre samochody od około 2020 roku odrzucają certyfikat aasdk, więc zaimportuj własny. Zmiany zadziałają przy następnym połączeniu.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Karta manewru na ekranie zegarów</string>
|
||||
<string name="car_cluster_video_note">Dla samochodu z drugim ekranem w zestawie wskaźników: Gearslip pokazuje tam następny manewr. Działa od następnego połączenia.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Nie połączono</string>
|
||||
<string name="status_idle_detail">Podłącz telefon do portu USB samochodu lub połącz się bezprzewodowo. Gearslip otworzy się sam, gdy samochód odpowie.</string>
|
||||
<string name="status_connecting">Łączenie</string>
|
||||
<string name="status_projecting">Wyświetlanie</string>
|
||||
<string name="status_projecting_detail">Przesyłanie obrazu do radia samochodowego.</string>
|
||||
<string name="status_disconnected">Rozłączono</string>
|
||||
<string name="status_disconnected_detail">Radio samochodowe nie jest już połączone.</string>
|
||||
<string name="places_title">Zapisane miejsca</string>
|
||||
<string name="places_sub">Powiedz „navigate home” w dowolnej aplikacji z mapami</string>
|
||||
<string name="places_intro">Powiedz „navigate home”, „take me to work” albo „navigate to” z własną nazwą, a Gearslip wyśle adres do aplikacji z mapami. Na razie polecenia głosowe działają tylko po angielsku.</string>
|
||||
<string name="places_home">Dom</string>
|
||||
<string name="places_work">Praca</string>
|
||||
<string name="places_other">Miejsce %1$d</string>
|
||||
<string name="places_name">Nazwa</string>
|
||||
<string name="places_address">Adres</string>
|
||||
<string name="places_here">Użyj mojej lokalizacji</string>
|
||||
<string name="places_finding">Szukam…</string>
|
||||
<string name="places_not_found">Gearslip nie znalazł Twojej lokalizacji. Sprawdź, czy lokalizacja jest włączona.</string>
|
||||
<string name="places_clear">Wyczyść</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,840 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Permite que o Gearslip repita no celular os toques feitos na tela do carro durante o espelhamento. O Gearslip não lê o conteúdo da tela.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Ajude outros motoristas.</string>
|
||||
<string name="guide_safety_title">Dirija com segurança.</string>
|
||||
<string name="guide_safety_body">O Gearslip não é oficial. Nenhuma montadora o criou ou verificou, e ele pode falhar sem aviso. Configure antes de sair e depois mantenha os olhos na estrada.</string>
|
||||
<string name="guide_mic_title">Microfone.</string>
|
||||
<string name="guide_mic_body">O Gearslip usa o microfone para enviar sua música ao carro e para ouvir você quando pede algo ou responde a uma mensagem. O que você diz fica no celular.</string>
|
||||
<string name="guide_location_title">Localização.</string>
|
||||
<string name="guide_location_body">O mapa do carro e as buscas por perto precisam dela. Sem ela, o painel não mostra o mapa.</string>
|
||||
<string name="guide_calendar_title">Agenda.</string>
|
||||
<string name="guide_calendar_body">A tela do carro mostra seu próximo evento. O Gearslip só lê a sua agenda.</string>
|
||||
<string name="guide_calls_title">Chamadas e contatos.</string>
|
||||
<string name="guide_calls_body">O carro mostra o nome de quem está ligando, e o app Telefone dele pode discar um número ou ligar para um contato seu. O Gearslip só os lê.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">Na primeira vez que você conectar o cabo, o Gearslip oferece parear o celular com o carro, para que as chamadas saiam pelos alto-falantes dele.</string>
|
||||
<string name="guide_notifications_title">Notificações.</string>
|
||||
<string name="guide_notifications_body">Enquanto está conectado, o Gearslip mostra uma notificação com um botão para parar. Sem esta permissão, o Android a esconde.</string>
|
||||
<string name="guide_messages_title">Mensagens.</string>
|
||||
<string name="guide_messages_body">O carro mostra suas mensagens, lê em voz alta e deixa você responder. Ative o Gearslip na lista que vai abrir.</string>
|
||||
<string name="guide_android_auto_title">Desative o Android Auto.</string>
|
||||
<string name="guide_android_auto_body">O Android Auto ainda está instalado e disputa com o Gearslip a conexão USB do carro. Desative ou desinstale na tela que vai abrir.</string>
|
||||
<string name="guide_default_title">Escolha o Gearslip.</string>
|
||||
<string name="guide_default_body">Se o celular perguntar qual app abrir quando você conectar ao carro, escolha o Gearslip e toque em Sempre.</string>
|
||||
<string name="guide_audio_title">Áudio do carro.</string>
|
||||
<string name="guide_audio_body">Escolha como a música chega ao carro. As chamadas vão pelo Bluetooth de qualquer forma.</string>
|
||||
<string name="guide_voice_title">Voz.</string>
|
||||
<string name="guide_voice_body">Peça rotas, música ou uma chamada por voz. O celular transforma a fala em texto sozinho, e nada do que você diz sai dele. São %1$d MB, baixados uma única vez. Use Wi-Fi se puder.</string>
|
||||
<string name="guide_audio_prompt_title">Sem pedido de áudio.</string>
|
||||
<string name="guide_audio_prompt_body">Rode isto em um computador com a depuração USB ativada, e o Android confia seu áudio ao Gearslip de vez. Sem isso, você toca em Iniciar uma vez a cada viagem.</string>
|
||||
<string name="guide_readable_title">Notificações legíveis.</string>
|
||||
<string name="guide_readable_body">Enquanto o áudio vai para o carro, o Android esconde o texto das notificações, como se você estivesse compartilhando a tela. Isto permite que o Gearslip as mostre durante a viagem e as esconda de novo depois.</string>
|
||||
<string name="guide_answer_title">Atender chamadas.</string>
|
||||
<string name="guide_answer_body">Defina o Gearslip como app de identificação de chamadas, e o carro poderá atender e recusar chamadas. Sem isso, as chamadas tocam mesmo assim, mas só o celular pode atendê-las.</string>
|
||||
<string name="guide_apps_title">Boa companhia.</string>
|
||||
<string name="guide_buttons_title">Seus botões.</string>
|
||||
<string name="guide_buttons_body">Escolha a aparência dos botões no carro. Mude quando quiser nas configurações do carro.</string>
|
||||
<string name="guide_open_notification_access">Abrir acesso a notificações</string>
|
||||
<string name="guide_open_android_auto">Abrir informações do app Android Auto</string>
|
||||
<string name="guide_download_voice">Baixar voz</string>
|
||||
<string name="guide_set_up">Configurar</string>
|
||||
<string name="guide_voice_downloading">Baixando, %1$d%%. Pode continuar, o download segue sozinho.</string>
|
||||
<string name="guide_voice_ready">A voz está pronta.</string>
|
||||
<string name="guide_voice_failed">O download não terminou. Toque em Baixar voz para tentar de novo.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Recomendado</string>
|
||||
<string name="guide_audio_bluetooth_body">Nenhum pedido a cada viagem, e a voz do seu app de mapas chega ao carro.</string>
|
||||
<string name="guide_audio_usb">Pelo Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Som mais limpo, sem atraso. A cada viagem, o Android pede para você compartilhar a tela, o que permite ao Gearslip enviar o som.</string>
|
||||
<string name="guide_optional">OPCIONAL</string>
|
||||
<string name="guide_already_allowed">Já permitido.</string>
|
||||
<string name="guide_scroll_more">Role para ver mais ↓</string>
|
||||
<string name="guide_allow">Permitir</string>
|
||||
<string name="guide_skip">Pular por enquanto</string>
|
||||
<string name="guide_open">Abrir</string>
|
||||
<string name="guide_next">Próximo</string>
|
||||
<string name="guide_done">Concluído</string>
|
||||
<string name="guide_back">Voltar</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Ative isto, e o Gearslip envia uma nota curta quando você o abre e depois de cada viagem. <b>A nota não contém nome, conta nem localização. Um número aleatório conta seu celular uma única vez, e o servidor não guarda nenhum endereço IP.</b> As notas mostram quais carros funcionam e onde os outros falham.</string>
|
||||
<string name="usage_send">Enviar notas de uso</string>
|
||||
<string name="usage_also_share">Compartilhar também</string>
|
||||
<string name="usage_share_android">Versão e sistema Android</string>
|
||||
<string name="usage_share_phone">Modelo do celular</string>
|
||||
<string name="usage_share_cars">Cada carro e se ele conectou</string>
|
||||
<string name="usage_share_drives">Cada viagem e como terminou</string>
|
||||
<string name="usage_what_sent">O que é enviado</string>
|
||||
<string name="usage_what_sent_sub">Cada campo, exatamente como sai</string>
|
||||
<string name="usage_hide_note">Ocultar a nota</string>
|
||||
<string name="usage_show_note">Mostrar a nota</string>
|
||||
<string name="usage_example">Exemplo, não enviado: uma viagem adiciona isto a “sessions”</string>
|
||||
<string name="usage_no_server">Esta versão não tem servidor de estatísticas, então não envia nada</string>
|
||||
<string name="usage_debug_build">Versão de depuração: as notas ficam separadas e nunca entram nas estatísticas</string>
|
||||
<string name="worked_title">O Gearslip funcionou no seu %1$s</string>
|
||||
<string name="worked_title_unknown">O Gearslip funcionou no seu carro</string>
|
||||
<string name="worked_body">Compartilhar para o próximo motorista saber que funciona? Sem nome, conta ou localização.</string>
|
||||
<string name="worked_not_now">Agora não</string>
|
||||
<string name="worked_share">Compartilhar</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">Estes apps gratuitos rodam na tela do carro do Gearslip. Cada botão abre a página do app no GitHub.</string>
|
||||
<string name="recommended_title">Apps recomendados</string>
|
||||
<string name="back">Voltar</string>
|
||||
<string name="recommended_installed">Instalado</string>
|
||||
<string name="recommended_maps">Mapas</string>
|
||||
<string name="recommended_music">Música</string>
|
||||
<string name="recommended_podcasts">Podcasts</string>
|
||||
<string name="recommended_other">Outros apps legais</string>
|
||||
<string name="recommended_about_organic">Mapas e rotas offline.</string>
|
||||
<string name="recommended_about_vela">Navegação passo a passo sem o Google.</string>
|
||||
<string name="recommended_about_ytm">Um player do YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">Podcasts, com capítulos e tudo.</string>
|
||||
<string name="recommended_about_rfsentinel">Detecta rastreadores e câmeras por perto.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Configurações</string>
|
||||
<string name="settings_whats_new">Novidades</string>
|
||||
<string name="settings_whats_new_sub">Leia as notas da versão %1$s no GitHub</string>
|
||||
<string name="settings_support">Apoie o Gearslip</string>
|
||||
<string name="settings_support_footer">O Gearslip é gratuito, sem anúncios e sem rastreamento. Seu apoio o mantém vivo.</string>
|
||||
<string name="settings_usage_notes">Notas de uso</string>
|
||||
<string name="settings_updates">Atualizações</string>
|
||||
<string name="settings_about">Sobre</string>
|
||||
<string name="settings_replay">Rever o guia de configuração</string>
|
||||
<string name="settings_replay_sub">Refazer os passos da primeira execução</string>
|
||||
<string name="settings_connection">Conexão</string>
|
||||
<string name="settings_use_in_car">Usar o Gearslip no carro</string>
|
||||
<string name="settings_use_in_car_on">O Gearslip responde quando você conecta a um carro</string>
|
||||
<string name="settings_use_in_car_off">Desativado: o Android Auto pode assumir a conexão</string>
|
||||
<string name="settings_apps">Apps</string>
|
||||
<string name="settings_certificate">Certificado</string>
|
||||
<string name="cert_next_connection">As mudanças valem na próxima conexão.</string>
|
||||
<string name="cert_checking">Verificando…</string>
|
||||
<string name="cert_valid_until">Válido até %1$s</string>
|
||||
<string name="cert_hide_details">Ocultar detalhes</string>
|
||||
<string name="cert_show_details">Mostrar detalhes</string>
|
||||
<string name="cert_subject">Titular</string>
|
||||
<string name="cert_issuer">Emissor</string>
|
||||
<string name="cert_downloading">Baixando…</string>
|
||||
<string name="cert_download">Baixar do aasdk</string>
|
||||
<string name="cert_downloaded">Certificado baixado.</string>
|
||||
<string name="cert_download_failed">Falha no download: %1$s</string>
|
||||
<string name="cert_download_sub">O certificado que o projeto de código aberto aasdk publica no GitHub. A maioria dos carros aceita, mas alguns de 2020 em diante recusam</string>
|
||||
<string name="cert_import">Importar meu próprio arquivo</string>
|
||||
<string name="cert_import_sub">Um arquivo .p12 com a chave privada. Use o seu quando o carro recusar o certificado aasdk</string>
|
||||
<string name="cert_remove_imported">Remover certificado importado</string>
|
||||
<string name="cert_remove_downloaded">Remover certificado baixado</string>
|
||||
<string name="cert_removed_imported">Certificado importado removido.</string>
|
||||
<string name="cert_removed_downloaded">Certificado baixado removido.</string>
|
||||
<string name="cert_imported">Certificado importado.</string>
|
||||
<string name="cert_import_failed">Não foi possível importar: senha errada, ou não é um arquivo PKCS#12 com chave privada.</string>
|
||||
<string name="cert_password_title">Senha do certificado</string>
|
||||
<string name="cert_password">Senha</string>
|
||||
<string name="cert_password_hint">Deixe em branco se o arquivo não tiver senha.</string>
|
||||
<string name="cert_import_button">Importar</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="cert_none">Nenhum carregado (autoassinado; os carros recusam)</string>
|
||||
<string name="cert_kind_imported">Importado: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Baixado: %1$s</string>
|
||||
<string name="cert_kind_adb">Enviado via adb: %1$s</string>
|
||||
<string name="cert_kind_self">Autoassinado: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Tela</string>
|
||||
<string name="car_group_nav_bar">Barra de navegação e player</string>
|
||||
<string name="car_group_sound">Som e voz</string>
|
||||
<string name="car_group_warnings">Avisos</string>
|
||||
<string name="car_group_more">Mais</string>
|
||||
<string name="choice_size_normal">Normal</string>
|
||||
<string name="choice_size_large">Grande</string>
|
||||
<string name="choice_size_xlarge">Muito grande</string>
|
||||
<string name="choice_theme_phone">Igual ao celular</string>
|
||||
<string name="choice_theme_light">Claro</string>
|
||||
<string name="choice_theme_dark">Escuro</string>
|
||||
<string name="choice_dark_black">Preto</string>
|
||||
<string name="choice_dark_black_colour">Preto e cor</string>
|
||||
<string name="choice_dark_deep_colour">Cor profunda</string>
|
||||
<string name="choice_dark_grey">Cinza</string>
|
||||
<string name="choice_auto">Automático</string>
|
||||
<string name="choice_night_day">Dia</string>
|
||||
<string name="choice_night_night">Noite</string>
|
||||
<string name="choice_units_imperial">Imperial</string>
|
||||
<string name="choice_units_metric">Métrico</string>
|
||||
<string name="choice_nav_show">Sempre mostrar</string>
|
||||
<string name="choice_nav_hide_navigating">Ocultar durante a navegação</string>
|
||||
<string name="choice_nav_hide">Ocultar até tocar</string>
|
||||
<string name="choice_open_home">Início</string>
|
||||
<string name="summary_player_left">Player à esquerda</string>
|
||||
<string name="summary_player_right">Player à direita</string>
|
||||
<string name="summary_usb_audio">Áudio pelo Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Áudio por Bluetooth</string>
|
||||
<string name="summary_directions_on">Instruções faladas ativadas</string>
|
||||
<string name="summary_directions_off">Instruções faladas desativadas</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth ativado</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth desativado</string>
|
||||
<string name="summary_location_on">Localização ativada</string>
|
||||
<string name="summary_location_off">Localização desativada</string>
|
||||
<string name="web_page">Página da web</string>
|
||||
<string name="summary_experimental_shown">Recursos experimentais visíveis</string>
|
||||
<string name="summary_experimental_hidden">Recursos experimentais ocultos</string>
|
||||
<string name="home_car">Carro</string>
|
||||
<string name="car_group_same">São as mesmas configurações de Configurações no carro. Uma mudança aqui aparece lá também.</string>
|
||||
<string name="car_open_on_connect">Abrir ao conectar</string>
|
||||
<string name="car_size">Tamanho</string>
|
||||
<string name="car_app_theme">Tema do app</string>
|
||||
<string name="car_dark_mode">Modo escuro</string>
|
||||
<string name="car_buttons">Botões</string>
|
||||
<string name="car_map_day_night">Mapa dia/noite</string>
|
||||
<string name="car_units">Unidades</string>
|
||||
<string name="car_nav_bar">Barra de navegação</string>
|
||||
<string name="car_player_nav_bar">Player na barra de navegação</string>
|
||||
<string name="car_skip_buttons">Botões de pular</string>
|
||||
<string name="car_song_title">Título da música</string>
|
||||
<string name="car_player_beside_map">Player ao lado do mapa</string>
|
||||
<string name="car_right">Direita</string>
|
||||
<string name="car_left">Esquerda</string>
|
||||
<string name="car_autoplay">Tocar ao conectar</string>
|
||||
<string name="car_more_lyrics">Mais fontes de letras</string>
|
||||
<string name="car_more_lyrics_note">Também consulta o BetterLyrics e o KuGou, que recebem o nome e o artista da música. Desativado, consulta só o LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Instruções faladas</string>
|
||||
<string name="car_spoken_directions_note">Desative se a voz do seu app de mapas já toca no carro.</string>
|
||||
<string name="car_phone_speech">Usar o reconhecimento de voz do celular</string>
|
||||
<string name="car_phone_speech_note">Na maioria dos celulares é o do Google, que pode enviar sua voz ao Google.</string>
|
||||
<string name="car_warn_bluetooth">Avisar quando o Bluetooth não estiver conectado</string>
|
||||
<string name="car_warn_location">Avisar quando a localização estiver desativada</string>
|
||||
<string name="car_experimental">Recursos experimentais</string>
|
||||
<string name="car_vehicle_data">Dados do veículo</string>
|
||||
<string name="car_vehicle_data_note">Adiciona um bloco Dados do veículo: uma leitura ao vivo dos sensores do carro. Os nomes dos campos são palpites até você ver um deles mudar.</string>
|
||||
<string name="car_adaptive_video">Qualidade de vídeo adaptável</string>
|
||||
<string name="car_adaptive_video_note">Reduz a qualidade da imagem quando o carro ou o Wi-Fi dele não acompanha, e aumenta de novo quando consegue. Vale na próxima conexão.</string>
|
||||
<string name="web_page_note">Mostrada pelo app Navegador no carro. Em branco, abre o DuckDuckGo. Vale na próxima conexão.</string>
|
||||
<string name="car_audio">Áudio do carro</string>
|
||||
<string name="car_audio_bt">Bluetooth (recomendado)</string>
|
||||
<string name="car_audio_bt_sub">Nenhum pedido a cada viagem, e a voz do seu app de mapas chega ao carro</string>
|
||||
<string name="car_audio_usb_sub">Som mais limpo, sem atraso. A cada viagem, o Android pede para você compartilhar a tela, o que permite ao Gearslip enviar o som</string>
|
||||
<string name="car_audio_usb_footer">Rode o comando da Ajuda uma vez, e o Android para de perguntar.</string>
|
||||
<string name="car_audio_bt_footer">As chamadas vão pelo Bluetooth de qualquer forma.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Tamanho da tela do carro</string>
|
||||
<string name="screen_use_cars">Usar o do carro</string>
|
||||
<string name="screen_preview_size">Tamanho da prévia</string>
|
||||
<string name="screen_preview_note">O tamanho em que a prévia do carro é desenhada, como 1920x720.</string>
|
||||
<string name="screen_car_screen">Tela do carro</string>
|
||||
<string name="screen_footer">O Gearslip usa o tamanho que o carro informa, a menos que você defina um.</string>
|
||||
<string name="screen_size">Tamanho da tela</string>
|
||||
<string name="screen_from_car">Do carro</string>
|
||||
<string name="screen_crop">Cortar as bordas</string>
|
||||
<string name="screen_crop_sub">Para um carro que corta o topo e a base, ou as laterais</string>
|
||||
<string name="screen_squash">Comprimir para caber</string>
|
||||
<string name="screen_squash_sub">Para um carro que achata a imagem inteira, deixando círculos com cara de ovais</string>
|
||||
<string name="screen_preview_title">Tamanho da prévia do carro</string>
|
||||
<string name="screen_last_car">Último carro</string>
|
||||
<string name="screen_last_car_sub">O tamanho do último carro a que você se conectou</string>
|
||||
<string name="screen_this_screen">Esta tela</string>
|
||||
<string name="screen_this_screen_sub">Preenche a tela deste aparelho, para uma central multimídia que roda o próprio Gearslip</string>
|
||||
<string name="screen_custom">Personalizado</string>
|
||||
<string name="screen_enter_size">Digite um tamanho</string>
|
||||
<string name="screen_change_custom">Mudar o tamanho personalizado</string>
|
||||
<string name="screen_width">Largura</string>
|
||||
<string name="screen_height">Altura</string>
|
||||
<string name="save">Salvar</string>
|
||||
|
||||
<string name="screen_car_size_note">Use isto se a imagem do carro parecer esticada. Digite o tamanho real da tela, como 1280x480. Vale na próxima vez que você conectar.</string>
|
||||
<string name="screen_set_by_you">%1$s, definido por você</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Como conectar</string>
|
||||
<string name="help_which_cars">Quais carros funcionam</string>
|
||||
<string name="help_full_list">Ver a lista completa</string>
|
||||
<string name="help_first_drive">Antes da primeira viagem</string>
|
||||
<string name="help_in_car">No carro</string>
|
||||
<string name="help_usb_audio">Áudio pelo Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Desative o Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Desative ou desinstale o app Android Auto, para que o carro converse com o Gearslip.</string>
|
||||
<string name="help_cert">Carregue um certificado.</string>
|
||||
<string name="help_cert_detail">Em Configurações, baixe o certificado aasdk, que a maioria dos carros aceita. Se o seu carro recusar, importe o seu.</string>
|
||||
<string name="help_mic">Permita o microfone.</string>
|
||||
<string name="help_mic_detail">Sem ele, o Android não deixa um app passar música para o carro. O Gearslip só escuta quando você toca no microfone ou aperta o botão de voz no volante.</string>
|
||||
<string name="help_plug_in">Conecte o cabo, ou conecte sem fio.</string>
|
||||
<string name="help_plug_in_detail">O Gearslip abre sozinho quando o carro conecta. Para usar sem fio, pareie o celular com o carro primeiro e depois toque em Conectar sem fio no Início.</string>
|
||||
<string name="help_pair">Pareie por Bluetooth para as chamadas.</string>
|
||||
<string name="help_pair_detail">As chamadas tocam pelo carro depois que o celular e o carro estão pareados. O Gearslip oferece o pareamento na primeira vez que você conecta o cabo.</string>
|
||||
<string name="help_answer">Atenda chamadas pela tela do carro.</string>
|
||||
<string name="help_answer_detail">Defina o Gearslip como app de identificação de chamadas e spam. Sem isso, as chamadas tocam mesmo assim, mas só o celular pode atendê-las.</string>
|
||||
<string name="help_prompt">Acabe com o pedido de áudio a cada viagem.</string>
|
||||
<string name="help_prompt_detail">Conecte o celular a um computador com a depuração USB ativada e rode isto uma vez. Sem isso, você toca em Iniciar toda vez que sai.</string>
|
||||
<string name="help_readable">Mantenha as notificações legíveis.</string>
|
||||
<string name="help_readable_detail">O Android esconde o texto das notificações enquanto compartilha áudio. Rode isto também, e o Gearslip libera o texto só enquanto a música vai para o carro.</string>
|
||||
<string name="help_which_cars_body">A maioria dos carros aceita o certificado aasdk. A partir de 2020, mais ou menos, alguns carros adotaram verificações de segurança mais novas que o recusam, e nesses carros o Gearslip precisa de um certificado seu.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Baixado. O Android pergunta antes de instalar.</string>
|
||||
<string name="update_install">Instalar</string>
|
||||
<string name="update_download_now">Baixe agora. O Android pergunta antes de instalar.</string>
|
||||
<string name="update_try_again">Tentar de novo</string>
|
||||
<string name="update_update">Atualizar</string>
|
||||
<string name="update_checking">Verificando…</string>
|
||||
<string name="update_latest">Você tem a versão mais recente</string>
|
||||
<string name="update_download">Baixar</string>
|
||||
<string name="update_check_now">Verificar agora</string>
|
||||
<string name="update_check_auto">Verificar automaticamente</string>
|
||||
<string name="update_once_a_day">Uma vez por dia</string>
|
||||
|
||||
<string name="update_unplug_first">Desconecte do carro para instalar. A instalação fecha o Gearslip.</string>
|
||||
<string name="update_card_title">O Gearslip %1$s saiu</string>
|
||||
<string name="update_downloading">Baixando… %1$d%%</string>
|
||||
<string name="update_debug_manual">Versões de depuração só verificam quando você toca em Verificar agora</string>
|
||||
<string name="update_checks_daily">O Gearslip verifica uma vez por dia</string>
|
||||
<string name="update_version_out">A versão %1$s saiu</string>
|
||||
<string name="update_version_ready">A versão %1$s está pronta para instalar</string>
|
||||
<string name="update_dialog_status">Versão %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Doe toda semana ou todo mês</string>
|
||||
<string name="about_bmac_sub" formatted="false">Doe uma vez ou todo mês, com cartão. O Buy Me a Coffee fica com 5%</string>
|
||||
<string name="about_give_once">Doar uma vez</string>
|
||||
<string name="about_any_amount">Qualquer valor, com cartão</string>
|
||||
<string name="about_monero_sub">Abra sua carteira ou copie o endereço</string>
|
||||
<string name="about_monero_title">Apoie com Monero</string>
|
||||
<string name="about_monero_body">Envie qualquer valor para este endereço pela sua carteira Monero.</string>
|
||||
<string name="about_open_wallet">Abrir na carteira</string>
|
||||
<string name="about_copy_address">Copiar endereço</string>
|
||||
<string name="close">Fechar</string>
|
||||
<string name="about_matrix">Entre no chat no Matrix</string>
|
||||
<string name="about_matrix_sub">Converse com outros motoristas sobre carros, apps e o Gearslip</string>
|
||||
<string name="about_translate">Ajude a traduzir o Gearslip</string>
|
||||
<string name="about_translate_sub">Corrija ou adicione seu idioma no Weblate. Algumas traduções começaram como traduções automáticas.</string>
|
||||
<string name="about_source">Código-fonte</string>
|
||||
<string name="about_source_sub">Leia ou altere o código do Gearslip sob a AGPL</string>
|
||||
<string name="about_voice_credits">Créditos de voz</string>
|
||||
|
||||
<string name="about_no_wallet">Nenhum app de carteira Monero encontrado. Copie o endereço.</string>
|
||||
<string name="about_monero_copied">Endereço Monero copiado</string>
|
||||
<string name="about_voice_credits_sub">Reconhecimento de voz pelo sherpa-onnx (Apache 2.0), com Kroko ASR da Banafo (CC BY-SA 4.0), e Parakeet e Canary da NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Configuração do certificado</string>
|
||||
<string name="certsetup_installed">Certificado instalado.</string>
|
||||
<string name="certsetup_continue">Continuar</string>
|
||||
<string name="certsetup_failed">Falha no download</string>
|
||||
<string name="certsetup_retry">Tentar de novo</string>
|
||||
<string name="certsetup_import_sub">Um arquivo .p12 com a chave privada. Use o seu quando o carro recusar o certificado aasdk.</string>
|
||||
<string name="certsetup_skip">Pular a configuração da conexão com o carro</string>
|
||||
|
||||
<string name="certsetup_loaded">Os carros só aceitam certificados emitidos pelo Google, e você tem um carregado.</string>
|
||||
<string name="certsetup_choose">Os carros só aceitam certificados emitidos pelo Google. O Gearslip não inclui nenhum, então escolha de onde vem o seu.</string>
|
||||
<string name="certsetup_aasdk_sub">O projeto de código aberto aasdk publica este certificado em github.com/opencardev/aasdk. A maioria dos carros aceita. Alguns de 2020 em diante recusam, e aí você precisa importar o seu.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Armazenamento do Gearslip</string>
|
||||
<string name="storage_measuring">Medindo…</string>
|
||||
<string name="storage_lyrics">Letras</string>
|
||||
<string name="storage_clear">Limpar</string>
|
||||
<string name="storage_voice">Modelos de voz</string>
|
||||
<string name="storage_remove">Remover</string>
|
||||
<string name="storage_logs">Logs antigos</string>
|
||||
<string name="storage_other">Outros arquivos temporários</string>
|
||||
<string name="storage_delete_all">Apagar todos os dados do Gearslip</string>
|
||||
|
||||
<string name="storage_total">Os dados do Gearslip ocupam %1$s do armazenamento do celular.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d música · %2$s. Letras apagadas são baixadas de novo quando você as abre.</item>
|
||||
<item quantity="many">%1$d músicas · %2$s. Letras apagadas são baixadas de novo quando você as abre.</item>
|
||||
<item quantity="other">%1$d músicas · %2$s. Letras apagadas são baixadas de novo quando você as abre.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Os comandos de voz precisam deles; baixe de novo em Configurações.</string>
|
||||
<string name="storage_logs_detail">%1$s. Logs de execuções anteriores; o log desta execução fica.</string>
|
||||
<string name="storage_other_detail">%1$s. Atualizações baixadas e outros arquivos que o Gearslip pode recriar.</string>
|
||||
<string name="storage_delete_armed">Toque em Apagar de novo para apagar configurações, certificados, modelos de voz e logs. O Gearslip fecha e começa da configuração na próxima vez.</string>
|
||||
<string name="storage_delete_sub">Configurações, certificados, modelos de voz, letras e logs.</string>
|
||||
<string name="storage_delete">Apagar</string>
|
||||
<string name="storage_delete_ellipsis">Apagar…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Que bom que a viagem funcionou</string>
|
||||
<string name="donate_liberapay">Apoie no Liberapay</string>
|
||||
<string name="donate_bmac">Me pague um café</string>
|
||||
<string name="donate_card">Doar uma vez com cartão</string>
|
||||
|
||||
<string name="donate_body">O Gearslip é gratuito, sem anúncios e sem rastreamento. Se ele te leva para a estrada, seu apoio o mantém vivo.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">O Gearslip precisa de acesso ao Bluetooth e ao Wi-Fi próximo para isso.</string>
|
||||
<string name="wireless_enable_permission">Ativar a permissão Dispositivos por perto</string>
|
||||
<string name="wireless_connect">Conectar sem fio</string>
|
||||
<string name="wireless_which_car">Qual carro?</string>
|
||||
<string name="wireless_none_paired">Seu celular ainda não está pareado com nada. Pareie com o carro primeiro.</string>
|
||||
<string name="wireless_not_listed">O carro não aparece? Pareie com o celular primeiro.</string>
|
||||
<string name="bluetooth_settings">Configurações de Bluetooth</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Voz: palavras ao vivo</string>
|
||||
<string name="voice_final_title">Voz: palavras finais</string>
|
||||
<string name="voice_none">Nenhum</string>
|
||||
<string name="voice_none_sub">Manter as palavras ao vivo. Mais rápido, menos preciso</string>
|
||||
|
||||
<string name="voice_live_body">O assistente de voz transforma a fala em texto neste celular, e nada do que você diz sai dele. Ele precisa de dois modelos, cerca de 270 MB no total, baixados uma única vez. Este escreve suas palavras enquanto você fala.</string>
|
||||
<string name="voice_final_body">Quando você para de falar, este modelo relê a frase inteira. Ele pega nomes e lugares que o modelo ao vivo perde.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Baixado. %1$s</string>
|
||||
<string name="voice_downloading">Baixando, %1$d%%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Ajuda</string>
|
||||
<string name="home_preview">Prévia</string>
|
||||
<string name="home_logs">Logs</string>
|
||||
<string name="home_disconnect">Desconectar</string>
|
||||
<string name="home_bt_title">O Bluetooth não está conectado</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · versão de depuração</string>
|
||||
<string name="home_bt_body">A música toca por Bluetooth, mas seu celular não está conectado ao carro. Conecte aqui ou pelo menu Bluetooth do carro.</string>
|
||||
<string name="home_voice_downloading">Baixando voz, %1$d%%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Tocar o áudio do carro por Bluetooth?</string>
|
||||
<string name="audio_never_ask">Nunca perguntar, usando um computador</string>
|
||||
<string name="audio_use_bt">Usar Bluetooth</string>
|
||||
<string name="audio_keep_asking">Continuar perguntando</string>
|
||||
<string name="audio_mic_title">O áudio do carro precisa da permissão do microfone</string>
|
||||
<string name="audio_open_settings">Abrir configurações do app</string>
|
||||
|
||||
<string name="audio_bt_body">O Android trata a música que o Gearslip envia ao carro como um compartilhamento de tela, então pergunta a cada viagem, por cabo ou sem fio. Por Bluetooth ele nunca pergunta. Você pode mudar isso depois em Configurações.</string>
|
||||
<string name="audio_keep_usb">Ou mantenha o som pelo Gearslip e acabe com a pergunta de vez: conecte o celular a um computador com a depuração USB ativada e rode isto uma vez.</string>
|
||||
<string name="audio_mic_body">O Android só deixa um app enviar o som de outros apps para o carro se ele tiver a permissão do microfone. Ela está desativada para o Gearslip, então a música toca no celular. Ative em Permissões, depois Microfone.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Fechar o teclado do carro</string>
|
||||
<string name="typing_label">Digite para a tela do carro</string>
|
||||
<string name="typing_search">Pesquisar na tela do carro</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Toque para copiar</string>
|
||||
<string name="command_copied">Comando copiado</string>
|
||||
<string name="whats_new_this_version">%1$s (esta versão)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Tema</string>
|
||||
<string name="carset_imperial">Imperial (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Métrico (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Tamanho da tela (próxima conexão)</string>
|
||||
<string name="carset_fit">Ajustar à tela</string>
|
||||
<string name="off">Desativado</string>
|
||||
<string name="on">Ativado</string>
|
||||
<string name="carset_lyrics_from">Letras de</string>
|
||||
<string name="carset_more_sources">Mais fontes</string>
|
||||
<string name="carset_hide">Ocultar</string>
|
||||
<string name="carset_show">Mostrar</string>
|
||||
<string name="carset_phone">Celular</string>
|
||||
<string name="carset_vehicle">Veículo</string>
|
||||
<string name="carset_video">Vídeo</string>
|
||||
<string name="carset_version">Versão</string>
|
||||
<string name="carset_build">Build</string>
|
||||
<string name="carset_whats_new_after">Novidades após atualizações</string>
|
||||
<string name="carset_whats_new_after_sub">Mostrar a lista de mudanças uma vez, na primeira conexão do carro após uma atualização.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (padrão) - toque para definir outra página</string>
|
||||
<string name="carset_web_note">Mostrada pelo app Navegador no launcher. Vale na próxima conexão.</string>
|
||||
<string name="carset_screen_fit">Ajuste da tela</string>
|
||||
<string name="carset_top">Topo</string>
|
||||
<string name="carset_bottom">Base</string>
|
||||
<string name="carset_save_vehicle">Salvar para este veículo</string>
|
||||
<string name="carset_no_profile">Nenhum perfil de veículo correspondente</string>
|
||||
|
||||
<string name="carset_audio_note">Pelo Gearslip envia o som do celular pela conexão com o carro, por cabo ou sem fio. O Android pede para compartilhar a tela a cada viagem, e alguns apps bloqueiam isso. O Bluetooth toca como o celular tocaria normalmente, e o Android nunca pergunta.</string>
|
||||
<string name="carset_directions_note">O Gearslip lê cada curva em voz alta pelos alto-falantes do carro, e a música abaixa enquanto ele fala. Desative se a voz do seu app de mapas já toca no carro.</string>
|
||||
<string name="carset_phone_speech_note">Escuta pelo serviço de voz do próprio celular em vez do Gearslip. Na maioria dos celulares é o do Google, que pode enviar sua voz ao Google. O GrapheneOS não tem um, então o Gearslip continua usando o seu.</string>
|
||||
<string name="carset_vehicle_data_note">Adiciona um bloco Dados do veículo: uma leitura bruta e ao vivo do canal de sensores da central multimídia. Os rótulos dos campos (velocidade, RPM, combustível...) vêm de outros projetos de engenharia reversa e não foram verificados neste carro. Trate como palpite, não como fato, até ver um deles mudar com seus próprios olhos.</string>
|
||||
<string name="carset_adaptive_note">Reduz a qualidade da imagem quando o carro, ou o Wi-Fi dele, não acompanha, e aumenta de novo quando consegue. Vale na próxima conexão.</string>
|
||||
<string name="carset_usable_height">Altura útil %1$dpx de 480</string>
|
||||
<string name="carset_saved_to">Salvo em %1$s</string>
|
||||
<string name="carset_save_failed">Não foi possível salvar</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Abrir no celular</string>
|
||||
<string name="media_now_playing">Tocando agora</string>
|
||||
<string name="media_up_next">A seguir</string>
|
||||
<string name="media_browse">Navegar</string>
|
||||
<string name="media_search_hint">Digite uma música, artista, álbum ou playlist.</string>
|
||||
<string name="media_looking_lyrics">Procurando a letra…</string>
|
||||
<string name="media_nothing_playing">Nada tocando</string>
|
||||
<string name="media_press_play">Aperte o play para continuar de onde parou, ou encontre algo em Navegar.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s não compartilha a biblioteca.</string>
|
||||
<string name="media_needs_access">O Gearslip ainda pode controlá-lo se tiver acesso às notificações no celular. Permita em Configurações > Notificações > Acesso a notificações.</string>
|
||||
<string name="media_not_running">%1$s não está aberto.</string>
|
||||
<string name="media_not_running_body">%1$s toca pelo player do Gearslip, mas só enquanto está aberto no celular. Abra uma vez e ele continua tocando aqui no carro.</string>
|
||||
<string name="media_open_app">Abrir %1$s</string>
|
||||
<string name="media_refused">%1$s não deixou o Gearslip navegar nele.</string>
|
||||
<string name="media_refused_body">Alguns apps de mídia só aceitam o host do próprio Google.</string>
|
||||
<string name="media_connecting">Conectando a %1$s…</string>
|
||||
<string name="media_search">Pesquisar em %1$s</string>
|
||||
<string name="media_search_no_list">%1$s não consegue listar resultados. Aperte pesquisar, e %1$s escolhe o que tocar.</string>
|
||||
<string name="media_search_unsupported">%1$s não permite pesquisar pelo carro.</string>
|
||||
<string name="media_searching">Pesquisando em %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s não encontrou nada para “%2$s”.</string>
|
||||
<string name="media_search_ignored">%1$s não tocou nada para “%2$s”.</string>
|
||||
<string name="media_searching_for">Pesquisando “%2$s” em %1$s…</string>
|
||||
<string name="media_nothing_playing_dot">Nada tocando.</string>
|
||||
<string name="media_no_lyrics">Nenhuma letra encontrada para esta faixa.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">Acima</string>
|
||||
<string name="media_loading">Carregando…</string>
|
||||
<string name="media_browse_start">Comece a música no app do celular. Tocar, pausar, pular e avançar funcionam daqui.</string>
|
||||
<string name="media_load_failed">Não foi possível carregar esta lista.</string>
|
||||
<string name="media_nothing_here">Nada aqui.</string>
|
||||
<string name="media_nothing_queued">Nada na fila.</string>
|
||||
<string name="media_play">Tocar</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Nada tocando - nem todo app conta ao Gearslip o que vem depois.</string>
|
||||
<string name="media_queue_empty_playing">Só o que já está tocando - nem todo app conta ao Gearslip o que vem depois.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Anterior</string>
|
||||
<string name="player_next">Próxima</string>
|
||||
<string name="player_repeat">Repetir</string>
|
||||
<string name="player_pause">Pausar</string>
|
||||
<string name="player_unlike">Descurtir</string>
|
||||
<string name="player_like">Curtir</string>
|
||||
<string name="player_shuffle_off">Aleatório desativado</string>
|
||||
<string name="player_shuffle_on">Aleatório ativado</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Escolher outro app</string>
|
||||
<string name="home_no_maps">Nenhum app de mapas encontrado.</string>
|
||||
<string name="home_no_maps_body">Instale um app de navegação compatível com o Android Auto, e ele vai abrir aqui sozinho.</string>
|
||||
<string name="home_map_stopped">Mapa parado</string>
|
||||
<string name="home_map_stopped_body">Ele fica fechado até você abrir de novo.</string>
|
||||
<string name="home_reopen_map">Reabrir o mapa</string>
|
||||
<string name="home_car_apps">Apps do carro</string>
|
||||
<string name="home_back_nav">Voltar para a barra de navegação</string>
|
||||
<string name="home_show_player">Mostrar o player</string>
|
||||
<string name="home_hide_lyrics">Ocultar letra</string>
|
||||
|
||||
<string name="home_starting">Iniciando %1$s…</string>
|
||||
<string name="home_starting_map">Iniciando o mapa…</string>
|
||||
<string name="home_failed">%1$s não quis iniciar.</string>
|
||||
<string name="home_failed_map">O app de mapas não quis iniciar.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Início</string>
|
||||
<string name="crumb_media">Mídia</string>
|
||||
<string name="crumb_apps">Apps</string>
|
||||
<string name="crumb_dashboard">Painel</string>
|
||||
<string name="crumb_weather">Clima</string>
|
||||
<string name="crumb_reply">Responder</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Navegador</string>
|
||||
<string name="app_screen_sharing">Espelhamento de tela</string>
|
||||
<string name="app_exit">Sair</string>
|
||||
<string name="navapps_unpin">Desafixar da barra de navegação</string>
|
||||
<string name="navapps_full">A barra de navegação está cheia (%1$d fixados)</string>
|
||||
<string name="navapps_pin">Fixar na barra de navegação</string>
|
||||
<string name="navapps_close">Fechar app</string>
|
||||
<string name="navapps_move_left">Mover para a esquerda</string>
|
||||
<string name="navapps_move_right">Mover para a direita</string>
|
||||
<string name="navapps_reset">Redefinir a ordem dos apps</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Dispensar</string>
|
||||
<string name="msg_read">Ler</string>
|
||||
<string name="msg_stop">Parar</string>
|
||||
<string name="msg_no">Não</string>
|
||||
<string name="msg_send_now">Enviar agora</string>
|
||||
<string name="msg_type">Digitar</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nova</item>
|
||||
<item quantity="many">%1$s · %2$d novas</item>
|
||||
<item quantity="other">%1$s · %2$d novas</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Responder a %1$s</string>
|
||||
<string name="msg_listening">Ouvindo</string>
|
||||
<string name="msg_sending_to">Enviando para %1$s</string>
|
||||
<string name="msg_sent_to">Enviada para %1$s</string>
|
||||
<string name="msg_reply_failed">Não foi possível responder</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">O pareamento não terminou. Pareie pelo menu Bluetooth do carro.</string>
|
||||
<string name="bt_pair_not_started">O pareamento não começou. Pareie pelo menu Bluetooth do carro.</string>
|
||||
<string name="bt_paired">Pareado. As chamadas agora tocam pelo carro.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Não mostrar de novo</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">A localização está desativada</string>
|
||||
<string name="overlay_pair_title">Parear com este carro?</string>
|
||||
<string name="overlay_pair">Parear</string>
|
||||
<string name="overlay_confirm_title">Confirme no celular</string>
|
||||
<string name="overlay_confirm_body">Confira o código no celular e toque em Parear. O carro também pode pedir.</string>
|
||||
|
||||
<string name="overlay_bt_body">A música toca por Bluetooth, mas seu celular não está conectado ao carro. Conecte pelo menu Bluetooth do carro ou do celular.</string>
|
||||
<string name="overlay_location_body">Os apps de mapas não conseguem mostrar onde você está nem dar rotas. Ative a localização nas configurações rápidas do celular quando estiver estacionado.</string>
|
||||
<string name="overlay_pair_body_music">Chamadas e música tocam pelos alto-falantes do carro depois que o celular e o carro estão pareados. O celular então pede para você confirmar um código.</string>
|
||||
<string name="overlay_pair_body">As chamadas tocam pelos alto-falantes do carro depois que o celular e o carro estão pareados. O celular então pede para você confirmar um código.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">Aguardando a localização.</string>
|
||||
<string name="weather_allow_location">Permita a localização para o Gearslip no celular para ver o clima.</string>
|
||||
<string name="weather_unreachable">Não foi possível acessar o serviço de clima.</string>
|
||||
<string name="weather_clear">Céu limpo</string>
|
||||
<string name="weather_mostly_clear">Predominantemente limpo</string>
|
||||
<string name="weather_partly_cloudy">Parcialmente nublado</string>
|
||||
<string name="weather_overcast">Encoberto</string>
|
||||
<string name="weather_fog">Neblina</string>
|
||||
<string name="weather_drizzle">Garoa</string>
|
||||
<string name="weather_rain">Chuva</string>
|
||||
<string name="weather_snow">Neve</string>
|
||||
<string name="weather_rain_showers">Pancadas de chuva</string>
|
||||
<string name="weather_snow_showers">Pancadas de neve</string>
|
||||
<string name="weather_thunderstorm">Tempestade</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Buscando o clima…</string>
|
||||
<string name="weather_next_hours">Próximas horas</string>
|
||||
<string name="weather_this_week">Esta semana</string>
|
||||
<string name="weather_today">Hoje</string>
|
||||
<string name="weather_credit">Dados do clima por Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Sensação de %1$s</string>
|
||||
<string name="weather_humidity">Umidade %1$d%%</string>
|
||||
<string name="weather_wind">Vento %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">Hoje</string>
|
||||
<string name="dash_getting_weather">Buscando o clima…</string>
|
||||
<string name="dash_coming_up">Em breve</string>
|
||||
<string name="dash_loading_calendar">Carregando a agenda…</string>
|
||||
<string name="dash_nothing_week">Nada na próxima semana.</string>
|
||||
<string name="dash_all_day">Dia inteiro</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Contatos</string>
|
||||
<string name="dialer_keypad">Teclado</string>
|
||||
<string name="dialer_ask">Perguntar no celular</string>
|
||||
<string name="dialer_enter_number">Digite um número</string>
|
||||
<string name="dialer_call">Ligar</string>
|
||||
<string name="dialer_no_contacts">Nenhum contato com número de telefone encontrado.</string>
|
||||
<string name="dialer_search">Pesquisar contatos</string>
|
||||
|
||||
<string name="dialer_needs_permission">O Gearslip precisa das permissões Telefone e Contatos para ligar daqui. Permita no celular quando estiver estacionado.</string>
|
||||
<string name="dialer_no_match">Nenhum contato corresponde a “%1$s”.</string>
|
||||
<string name="dialer_call_who">Ligar para %1$s?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Sair da tela cheia</string>
|
||||
<string name="carui_drive_safely">Dirija com segurança</string>
|
||||
<string name="carui_understand">Entendi</string>
|
||||
|
||||
<string name="carui_unofficial">O Gearslip é um software não oficial: não foi feito nem revisado por nenhuma montadora, e pode falhar sem aviso.</string>
|
||||
<string name="carui_never_interact">Nunca mexa nesta tela enquanto dirige. Defina a rota ou a música antes de sair e depois mantenha a atenção na estrada.</string>
|
||||
<string name="carui_this_app">Este app</string>
|
||||
<string name="carui_app_unsupported">%1$s ainda não funciona com o Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">Ele não abriu. Alguns apps só rodam no Android Auto do Google.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">Em chamada</string>
|
||||
<string name="navbar_end_call">Encerrar chamada</string>
|
||||
<string name="navbar_unmute">Ativar som</string>
|
||||
<string name="navbar_mute">Silenciar</string>
|
||||
<string name="navbar_close_panel">Fechar o painel lateral</string>
|
||||
<string name="navbar_dock_player">Mover o player para o lado do mapa</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Pesquise ou digite um endereço</string>
|
||||
<string name="web_forward">Avançar</string>
|
||||
<string name="web_reload">Recarregar</string>
|
||||
<string name="web_start_page">Página inicial</string>
|
||||
<string name="web_fullscreen">Tela cheia</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Notificações</string>
|
||||
<string name="notif_clear_all">Limpar tudo</string>
|
||||
<string name="notif_empty">Nada ainda. Novas notificações do celular aparecerão aqui.</string>
|
||||
<string name="notif_off">As notificações estão desativadas. Permita o Gearslip em Configurações > Notificações > Acesso a notificações no celular.</string>
|
||||
<string name="notif_replied">Respondida</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Marcar como lida</string>
|
||||
<string name="msg_messages">Mensagens</string>
|
||||
<string name="msg_none_recent">Nenhuma mensagem recente. Novas mensagens do %1$s aparecem aqui assim que chegam no celular.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d anterior</item>
|
||||
<item quantity="many">%1$d anteriores</item>
|
||||
<item quantity="other">%1$d anteriores</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Ocultar respostas rápidas</string>
|
||||
<string name="msg_quick">Resposta rápida</string>
|
||||
<string name="msg_you">Você: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Buscando uma rota…</string>
|
||||
<string name="nav_then">Depois</string>
|
||||
<string name="nav_stop">Parar a navegação</string>
|
||||
<string name="nav_finding_routes">Buscando rotas…</string>
|
||||
<string name="nav_lane_recommended">Faixa recomendada</string>
|
||||
<string name="nav_lane">Faixa</string>
|
||||
<string name="nav_arrive">Chegada %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Nada tocando ainda.</string>
|
||||
<string name="template_search">Pesquisar</string>
|
||||
<string name="template_start">Iniciar</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Chamada recebida</string>
|
||||
<string name="call_decline">Recusar</string>
|
||||
<string name="call_answer">Atender</string>
|
||||
<string name="call_ringing">Já está tocando alto - atenda aqui ou deixe tocar.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">O toque está desativado. Ative o Gearslip em Configurações > Acessibilidade no celular.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Mostrar após atualizações</string>
|
||||
<string name="whatsnew_got_it">Entendi</string>
|
||||
<string name="whatsnew_title">Novidades da versão %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">espaço</string>
|
||||
<string name="keyboard_hide">Ocultar teclado</string>
|
||||
<string name="keyboard_submit">Enviar</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Não funciona com o Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">App do carro</string>
|
||||
<string name="stage_close">Fechar %1$s</string>
|
||||
<string name="stage_close_app">Fechar o app</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Vire levemente à esquerda</string>
|
||||
<string name="dir_slight_left_onto">Vire levemente à esquerda na %1$s</string>
|
||||
<string name="dir_slight_right">Vire levemente à direita</string>
|
||||
<string name="dir_slight_right_onto">Vire levemente à direita na %1$s</string>
|
||||
<string name="dir_left">Vire à esquerda</string>
|
||||
<string name="dir_left_onto">Vire à esquerda na %1$s</string>
|
||||
<string name="dir_right">Vire à direita</string>
|
||||
<string name="dir_right_onto">Vire à direita na %1$s</string>
|
||||
<string name="dir_sharp_left">Vire acentuadamente à esquerda</string>
|
||||
<string name="dir_sharp_left_onto">Vire acentuadamente à esquerda na %1$s</string>
|
||||
<string name="dir_sharp_right">Vire acentuadamente à direita</string>
|
||||
<string name="dir_sharp_right_onto">Vire acentuadamente à direita na %1$s</string>
|
||||
<string name="dir_u_turn">Faça o retorno</string>
|
||||
<string name="dir_keep_left">Mantenha-se à esquerda</string>
|
||||
<string name="dir_keep_left_toward">Mantenha-se à esquerda em direção a %1$s</string>
|
||||
<string name="dir_keep_right">Mantenha-se à direita</string>
|
||||
<string name="dir_keep_right_toward">Mantenha-se à direita em direção a %1$s</string>
|
||||
<string name="dir_fork_left">Na bifurcação, mantenha-se à esquerda</string>
|
||||
<string name="dir_fork_left_toward">Na bifurcação, mantenha-se à esquerda em direção a %1$s</string>
|
||||
<string name="dir_fork_right">Na bifurcação, mantenha-se à direita</string>
|
||||
<string name="dir_fork_right_toward">Na bifurcação, mantenha-se à direita em direção a %1$s</string>
|
||||
<string name="dir_ramp_left">Pegue a rampa à esquerda</string>
|
||||
<string name="dir_ramp_left_onto">Pegue a rampa à esquerda para %1$s</string>
|
||||
<string name="dir_ramp_right">Pegue a rampa à direita</string>
|
||||
<string name="dir_ramp_right_onto">Pegue a rampa à direita para %1$s</string>
|
||||
<string name="dir_exit_left">Pegue a saída à esquerda</string>
|
||||
<string name="dir_exit_left_toward">Pegue a saída à esquerda em direção a %1$s</string>
|
||||
<string name="dir_exit_right">Pegue a saída à direita</string>
|
||||
<string name="dir_exit_right_toward">Pegue a saída à direita em direção a %1$s</string>
|
||||
<string name="dir_merge_left">Entre à esquerda</string>
|
||||
<string name="dir_merge_left_onto">Entre à esquerda na %1$s</string>
|
||||
<string name="dir_merge_right">Entre à direita</string>
|
||||
<string name="dir_merge_right_onto">Entre à direita na %1$s</string>
|
||||
<string name="dir_merge">Entre na via</string>
|
||||
<string name="dir_merge_onto">Entre na %1$s</string>
|
||||
<string name="dir_roundabout_through">Atravesse a rotatória</string>
|
||||
<string name="dir_roundabout_through_onto">Atravesse a rotatória para %1$s</string>
|
||||
<string name="dir_roundabout_enter">Entre na rotatória</string>
|
||||
<string name="dir_roundabout_exit">Saia da rotatória</string>
|
||||
<string name="dir_roundabout_exit_onto">Saia da rotatória na %1$s</string>
|
||||
<string name="dir_straight">Siga em frente</string>
|
||||
<string name="dir_straight_onto">Siga em frente na %1$s</string>
|
||||
<string name="dir_continue">Continue</string>
|
||||
<string name="dir_continue_onto">Continue na %1$s</string>
|
||||
<string name="dir_ferry">Pegue a balsa</string>
|
||||
<string name="dir_train">Pegue o trem</string>
|
||||
<string name="dir_destination">Seu destino está à frente</string>
|
||||
<string name="dir_destination_left">Seu destino está à esquerda</string>
|
||||
<string name="dir_destination_right">Seu destino está à direita</string>
|
||||
<string name="dir_roundabout_take">Na rotatória, pegue a %1$s saída</string>
|
||||
<string name="dir_roundabout_take_onto">Na rotatória, pegue a %1$s saída para %2$s</string>
|
||||
<string name="dir_ordinal_1">primeira</string>
|
||||
<string name="dir_ordinal_2">segunda</string>
|
||||
<string name="dir_ordinal_3">terceira</string>
|
||||
<string name="dir_ordinal_4">quarta</string>
|
||||
<string name="dir_ordinal_5">quinta</string>
|
||||
<string name="dir_ordinal_6">sexta</string>
|
||||
<string name="dir_ordinal_7">sétima</string>
|
||||
<string name="dir_ordinal_8">oitava</string>
|
||||
<string name="dir_in_distance">Em %1$s, %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s quilômetro</item>
|
||||
<item quantity="many">%1$s quilômetros</item>
|
||||
<item quantity="other">%1$s quilômetros</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s quilômetros</string>
|
||||
<string name="dir_quarter_mile">um quarto de milha</string>
|
||||
<string name="dir_half_mile">meia milha</string>
|
||||
<string name="dir_three_quarter_mile">três quartos de milha</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s milha</item>
|
||||
<item quantity="many">%1$s milhas</item>
|
||||
<item quantity="other">%1$s milhas</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s milhas</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s pé</item>
|
||||
<item quantity="many">%1$s pés</item>
|
||||
<item quantity="other">%1$s pés</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s jarda</item>
|
||||
<item quantity="many">%1$s jardas</item>
|
||||
<item quantity="other">%1$s jardas</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d metro</item>
|
||||
<item quantity="many">%1$d metros</item>
|
||||
<item quantity="other">%1$d metros</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Se o carro não conectar, ele pode estar recusando este certificado. Importe o seu em Configurações, em Certificado.</string>
|
||||
<string name="cert_downloaded_footer">O carro não conecta? Alguns carros de 2020 em diante recusam o certificado aasdk, então importe o seu. As mudanças valem na próxima conexão.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Cartão de curva no painel de instrumentos</string>
|
||||
<string name="car_cluster_video_note">Para um carro com uma segunda tela no painel de instrumentos: o Gearslip mostra a próxima curva nela. Vale na próxima conexão.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Não conectado</string>
|
||||
<string name="status_idle_detail">Conecte o cabo na porta USB do carro, ou conecte sem fio. O Gearslip abre sozinho quando o carro responde.</string>
|
||||
<string name="status_connecting">Conectando</string>
|
||||
<string name="status_projecting">Projetando</string>
|
||||
<string name="status_projecting_detail">Transmitindo para a central multimídia.</string>
|
||||
<string name="status_disconnected">Desconectado</string>
|
||||
<string name="status_disconnected_detail">A central multimídia não está mais conectada.</string>
|
||||
<string name="places_title">Lugares salvos</string>
|
||||
<string name="places_sub">Diga “navigate home” em qualquer app de mapas</string>
|
||||
<string name="places_intro">Diga “navigate home”, “take me to work” ou “navigate to” com um nome seu, e o Gearslip envia o endereço para o seu app de mapas. Por enquanto os comandos de voz são só em inglês.</string>
|
||||
<string name="places_home">Casa</string>
|
||||
<string name="places_work">Trabalho</string>
|
||||
<string name="places_other">Lugar %1$d</string>
|
||||
<string name="places_name">Nome</string>
|
||||
<string name="places_address">Endereço</string>
|
||||
<string name="places_here">Usar onde estou</string>
|
||||
<string name="places_finding">Procurando…</string>
|
||||
<string name="places_not_found">O Gearslip não encontrou onde você está. Veja se a localização está ligada.</string>
|
||||
<string name="places_clear">Limpar</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,848 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Позволяет Gearslip повторять на телефоне касания экрана автомобиля во время трансляции экрана. Gearslip не читает содержимое экрана.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Помогите другим водителям.</string>
|
||||
<string name="guide_safety_title">Будьте осторожны за рулём.</string>
|
||||
<string name="guide_safety_body">Gearslip неофициальный. Ни один автопроизводитель его не создавал и не проверял, и он может отказать без предупреждения. Настройте его до поездки, а потом смотрите на дорогу.</string>
|
||||
<string name="guide_mic_title">Микрофон.</string>
|
||||
<string name="guide_mic_body">Gearslip использует его, чтобы передавать вашу музыку в машину и слышать вас, когда вы о чём-то просите или отвечаете на сообщение. Всё, что вы говорите, остаётся на телефоне.</string>
|
||||
<string name="guide_location_title">Местоположение.</string>
|
||||
<string name="guide_location_body">Оно нужно карте в машине и поиску поблизости. Без него на панели не будет карты.</string>
|
||||
<string name="guide_calendar_title">Календарь.</string>
|
||||
<string name="guide_calendar_body">Экран машины показывает ваше ближайшее событие. Gearslip только читает календарь.</string>
|
||||
<string name="guide_calls_title">Звонки и контакты.</string>
|
||||
<string name="guide_calls_body">Машина показывает имя звонящего, а её приложение «Телефон» может набрать номер или позвонить вашему контакту. Gearslip только читает их.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">При первом подключении Gearslip предложит связать телефон с машиной, чтобы звонки шли через её динамики.</string>
|
||||
<string name="guide_notifications_title">Уведомления.</string>
|
||||
<string name="guide_notifications_body">Пока Gearslip подключён, он показывает уведомление с кнопкой остановки. Без этого разрешения Android его скрывает.</string>
|
||||
<string name="guide_messages_title">Сообщения.</string>
|
||||
<string name="guide_messages_body">Машина показывает ваши сообщения, читает их вслух и позволяет ответить. Включите Gearslip в открывшемся списке.</string>
|
||||
<string name="guide_android_auto_title">Отключите Android Auto.</string>
|
||||
<string name="guide_android_auto_body">Android Auto всё ещё установлен и спорит с Gearslip за USB-подключение машины. Отключите или удалите его на открывшемся экране.</string>
|
||||
<string name="guide_default_title">Выберите Gearslip.</string>
|
||||
<string name="guide_default_body">Если при подключении к машине телефон спросит, какое приложение открыть, выберите Gearslip и нажмите «Всегда».</string>
|
||||
<string name="guide_audio_title">Звук в машине.</string>
|
||||
<string name="guide_audio_body">Выберите, как музыка попадает в машину. Звонки в любом случае идут через Bluetooth.</string>
|
||||
<string name="guide_voice_title">Голос.</string>
|
||||
<string name="guide_voice_body">Попросите голосом маршрут, музыку или звонок. Телефон сам превращает речь в текст, и ничего из сказанного его не покидает. Нужно %1$d МБ, скачиваются один раз. По возможности используйте Wi-Fi.</string>
|
||||
<string name="guide_audio_prompt_title">Без запроса звука.</string>
|
||||
<string name="guide_audio_prompt_body">Выполните это на компьютере с включённой отладкой по USB, и Android навсегда доверит звук Gearslip. Иначе в каждой поездке вы один раз нажимаете «Начать».</string>
|
||||
<string name="guide_readable_title">Читаемые уведомления.</string>
|
||||
<string name="guide_readable_body">Пока звук идёт в машину, Android скрывает текст уведомлений, как будто вы транслируете экран. Это позволяет Gearslip показывать их в поездке и потом снова скрывать.</string>
|
||||
<string name="guide_answer_title">Ответ на звонки.</string>
|
||||
<string name="guide_answer_body">Сделайте Gearslip приложением для определения номера, и машина сможет принимать и отклонять звонки. Без этого звонки всё равно звучат, но ответить можно только на телефоне.</string>
|
||||
<string name="guide_apps_title">Хорошая компания.</string>
|
||||
<string name="guide_buttons_title">Ваши кнопки.</string>
|
||||
<string name="guide_buttons_body">Выберите вид кнопок в машине. Изменить его можно в любой момент в настройках машины.</string>
|
||||
<string name="guide_open_notification_access">Открыть доступ к уведомлениям</string>
|
||||
<string name="guide_open_android_auto">Открыть сведения о приложении Android Auto</string>
|
||||
<string name="guide_download_voice">Скачать голос</string>
|
||||
<string name="guide_set_up">Настроить</string>
|
||||
<string name="guide_voice_downloading">Загрузка, %1$d%%. Можно продолжать, загрузка идёт дальше.</string>
|
||||
<string name="guide_voice_ready">Голос готов.</string>
|
||||
<string name="guide_voice_failed">Загрузка не завершилась. Нажмите «Скачать голос», чтобы попробовать снова.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Рекомендуется</string>
|
||||
<string name="guide_audio_bluetooth_body">Никаких запросов в каждой поездке, а голос навигатора звучит в машине.</string>
|
||||
<string name="guide_audio_usb">Через Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Чище звук и никакой задержки. В каждой поездке Android просит разрешить трансляцию экрана, и это позволяет Gearslip передавать звук.</string>
|
||||
<string name="guide_optional">НЕОБЯЗАТЕЛЬНО</string>
|
||||
<string name="guide_already_allowed">Уже разрешено.</string>
|
||||
<string name="guide_scroll_more">Листайте дальше ↓</string>
|
||||
<string name="guide_allow">Разрешить</string>
|
||||
<string name="guide_skip">Пока пропустить</string>
|
||||
<string name="guide_open">Открыть</string>
|
||||
<string name="guide_next">Далее</string>
|
||||
<string name="guide_done">Готово</string>
|
||||
<string name="guide_back">Назад</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Включите это, и Gearslip будет отправлять короткую заметку при открытии и после каждой поездки. <b>В заметке нет имени, аккаунта или местоположения. Случайное число учитывает ваш телефон один раз, а сервер не хранит IP-адрес.</b> Заметки показывают, какие машины работают и где другие дают сбой.</string>
|
||||
<string name="usage_send">Отправлять заметки об использовании</string>
|
||||
<string name="usage_also_share">Также передавать</string>
|
||||
<string name="usage_share_android">Версию и систему Android</string>
|
||||
<string name="usage_share_phone">Модель телефона</string>
|
||||
<string name="usage_share_cars">Каждую машину и подключилась ли она</string>
|
||||
<string name="usage_share_drives">Каждую поездку и как она закончилась</string>
|
||||
<string name="usage_what_sent">Что отправляется</string>
|
||||
<string name="usage_what_sent_sub">Каждое поле, ровно в том виде, как уходит</string>
|
||||
<string name="usage_hide_note">Скрыть заметку</string>
|
||||
<string name="usage_show_note">Показать заметку</string>
|
||||
<string name="usage_example">Пример, не отправляется: одна поездка добавляет это в «sessions»</string>
|
||||
<string name="usage_no_server">У этой сборки нет сервера статистики, поэтому она ничего не отправляет</string>
|
||||
<string name="usage_debug_build">Отладочная сборка: заметки хранятся отдельно и никогда не учитываются в статистике</string>
|
||||
<string name="worked_title">Gearslip заработал с вашим %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip заработал с вашей машиной</string>
|
||||
<string name="worked_body">Поделиться, чтобы следующий водитель знал, что это работает? Без имени, аккаунта и местоположения.</string>
|
||||
<string name="worked_not_now">Не сейчас</string>
|
||||
<string name="worked_share">Поделиться</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">Эти бесплатные приложения работают на автомобильном экране Gearslip. Каждая кнопка открывает страницу приложения на GitHub.</string>
|
||||
<string name="recommended_title">Рекомендуемые приложения</string>
|
||||
<string name="back">Назад</string>
|
||||
<string name="recommended_installed">Установлено</string>
|
||||
<string name="recommended_maps">Карты</string>
|
||||
<string name="recommended_music">Музыка</string>
|
||||
<string name="recommended_podcasts">Подкасты</string>
|
||||
<string name="recommended_other">Другие классные приложения</string>
|
||||
<string name="recommended_about_organic">Офлайн-карты и маршруты.</string>
|
||||
<string name="recommended_about_vela">Пошаговая навигация без Google.</string>
|
||||
<string name="recommended_about_ytm">Плеер для YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">Подкасты, с главами и всем прочим.</string>
|
||||
<string name="recommended_about_rfsentinel">Находит трекеры и камеры поблизости.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Настройки</string>
|
||||
<string name="settings_whats_new">Что нового</string>
|
||||
<string name="settings_whats_new_sub">Читать заметки о выпуске %1$s на GitHub</string>
|
||||
<string name="settings_support">Поддержать Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip бесплатный, без рекламы и слежки. Ваша поддержка помогает ему жить.</string>
|
||||
<string name="settings_usage_notes">Заметки об использовании</string>
|
||||
<string name="settings_updates">Обновления</string>
|
||||
<string name="settings_about">О приложении</string>
|
||||
<string name="settings_replay">Пройти мастер настройки заново</string>
|
||||
<string name="settings_replay_sub">Повторить шаги первого запуска</string>
|
||||
<string name="settings_connection">Подключение</string>
|
||||
<string name="settings_use_in_car">Использовать Gearslip в машине</string>
|
||||
<string name="settings_use_in_car_on">Gearslip отвечает, когда вы подключаетесь к машине</string>
|
||||
<string name="settings_use_in_car_off">Выкл.: подключение может взять Android Auto</string>
|
||||
<string name="settings_apps">Приложения</string>
|
||||
<string name="settings_certificate">Сертификат</string>
|
||||
<string name="cert_next_connection">Изменения вступят в силу при следующем подключении.</string>
|
||||
<string name="cert_checking">Проверка…</string>
|
||||
<string name="cert_valid_until">Действителен до %1$s</string>
|
||||
<string name="cert_hide_details">Скрыть подробности</string>
|
||||
<string name="cert_show_details">Показать подробности</string>
|
||||
<string name="cert_subject">Субъект</string>
|
||||
<string name="cert_issuer">Издатель</string>
|
||||
<string name="cert_downloading">Загрузка…</string>
|
||||
<string name="cert_download">Скачать из aasdk</string>
|
||||
<string name="cert_downloaded">Сертификат скачан.</string>
|
||||
<string name="cert_download_failed">Не удалось скачать: %1$s</string>
|
||||
<string name="cert_download_sub">Сертификат, который открытый проект aasdk публикует на GitHub. Большинство машин его принимает, но некоторые примерно с 2020 года отклоняют</string>
|
||||
<string name="cert_import">Импортировать свой файл</string>
|
||||
<string name="cert_import_sub">Файл .p12 с закрытым ключом. Используйте свой, если машина отклоняет сертификат aasdk</string>
|
||||
<string name="cert_remove_imported">Удалить импортированный сертификат</string>
|
||||
<string name="cert_remove_downloaded">Удалить скачанный сертификат</string>
|
||||
<string name="cert_removed_imported">Импортированный сертификат удалён.</string>
|
||||
<string name="cert_removed_downloaded">Скачанный сертификат удалён.</string>
|
||||
<string name="cert_imported">Сертификат импортирован.</string>
|
||||
<string name="cert_import_failed">Не удалось импортировать: неверный пароль или это не файл PKCS#12 с закрытым ключом.</string>
|
||||
<string name="cert_password_title">Пароль сертификата</string>
|
||||
<string name="cert_password">Пароль</string>
|
||||
<string name="cert_password_hint">Оставьте пустым, если у файла нет пароля.</string>
|
||||
<string name="cert_import_button">Импортировать</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="cert_none">Не загружен (самоподписанный; машины его отклоняют)</string>
|
||||
<string name="cert_kind_imported">Импортирован: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Скачан: %1$s</string>
|
||||
<string name="cert_kind_adb">Загружен через adb: %1$s</string>
|
||||
<string name="cert_kind_self">Самоподписанный: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Экран</string>
|
||||
<string name="car_group_nav_bar">Панель навигации и плеер</string>
|
||||
<string name="car_group_sound">Звук и голос</string>
|
||||
<string name="car_group_warnings">Предупреждения</string>
|
||||
<string name="car_group_more">Ещё</string>
|
||||
<string name="choice_size_normal">Обычный</string>
|
||||
<string name="choice_size_large">Крупный</string>
|
||||
<string name="choice_size_xlarge">Очень крупный</string>
|
||||
<string name="choice_theme_phone">Как на телефоне</string>
|
||||
<string name="choice_theme_light">Светлая</string>
|
||||
<string name="choice_theme_dark">Тёмная</string>
|
||||
<string name="choice_dark_black">Чёрный</string>
|
||||
<string name="choice_dark_black_colour">Чёрный с цветом</string>
|
||||
<string name="choice_dark_deep_colour">Глубокий цвет</string>
|
||||
<string name="choice_dark_grey">Серый</string>
|
||||
<string name="choice_auto">Авто</string>
|
||||
<string name="choice_night_day">День</string>
|
||||
<string name="choice_night_night">Ночь</string>
|
||||
<string name="choice_units_imperial">Имперские</string>
|
||||
<string name="choice_units_metric">Метрические</string>
|
||||
<string name="choice_nav_show">Показывать всегда</string>
|
||||
<string name="choice_nav_hide_navigating">Скрывать во время навигации</string>
|
||||
<string name="choice_nav_hide">Скрывать до касания</string>
|
||||
<string name="choice_open_home">Главная</string>
|
||||
<string name="summary_player_left">Плеер слева</string>
|
||||
<string name="summary_player_right">Плеер справа</string>
|
||||
<string name="summary_usb_audio">Звук через Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Звук через Bluetooth</string>
|
||||
<string name="summary_directions_on">Голосовые подсказки вкл.</string>
|
||||
<string name="summary_directions_off">Голосовые подсказки выкл.</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth вкл.</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth выкл.</string>
|
||||
<string name="summary_location_on">Местоположение вкл.</string>
|
||||
<string name="summary_location_off">Местоположение выкл.</string>
|
||||
<string name="web_page">Веб-страница</string>
|
||||
<string name="summary_experimental_shown">Экспериментальные функции показаны</string>
|
||||
<string name="summary_experimental_hidden">Экспериментальные функции скрыты</string>
|
||||
<string name="home_car">Машина</string>
|
||||
<string name="car_group_same">Это те же настройки, что и «Настройки» в машине. Изменение здесь появится и там.</string>
|
||||
<string name="car_open_on_connect">Открывать при подключении</string>
|
||||
<string name="car_size">Размер</string>
|
||||
<string name="car_app_theme">Тема приложения</string>
|
||||
<string name="car_dark_mode">Тёмный режим</string>
|
||||
<string name="car_buttons">Кнопки</string>
|
||||
<string name="car_map_day_night">Карта день/ночь</string>
|
||||
<string name="car_units">Единицы</string>
|
||||
<string name="car_nav_bar">Панель навигации</string>
|
||||
<string name="car_player_nav_bar">Плеер на панели навигации</string>
|
||||
<string name="car_skip_buttons">Кнопки перемотки</string>
|
||||
<string name="car_song_title">Название песни</string>
|
||||
<string name="car_player_beside_map">Плеер рядом с картой</string>
|
||||
<string name="car_right">Справа</string>
|
||||
<string name="car_left">Слева</string>
|
||||
<string name="car_autoplay">Воспроизводить при подключении</string>
|
||||
<string name="car_more_lyrics">Больше источников текстов</string>
|
||||
<string name="car_more_lyrics_note">Также спрашивает BetterLyrics и KuGou, которые тогда получают название и исполнителя песни. Если выключено, спрашивает только LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Голосовые подсказки</string>
|
||||
<string name="car_spoken_directions_note">Выключите, если голос вашего навигатора уже звучит в машине.</string>
|
||||
<string name="car_phone_speech">Распознавание речи телефона</string>
|
||||
<string name="car_phone_speech_note">На большинстве телефонов это сервис Google, который может отправлять ваш голос в Google.</string>
|
||||
<string name="car_warn_bluetooth">Предупреждать, если Bluetooth не подключён</string>
|
||||
<string name="car_warn_location">Предупреждать, если местоположение выключено</string>
|
||||
<string name="car_experimental">Экспериментальные функции</string>
|
||||
<string name="car_vehicle_data">Данные автомобиля</string>
|
||||
<string name="car_vehicle_data_note">Добавляет плитку «Данные автомобиля»: живой вывод датчиков машины. Названия полей остаются догадками, пока вы не увидите, как одно из них меняется.</string>
|
||||
<string name="car_adaptive_video">Адаптивное качество видео</string>
|
||||
<string name="car_adaptive_video_note">Снижает качество картинки, когда машина или её Wi-Fi не успевают, и снова повышает, когда могут. Действует со следующего подключения.</string>
|
||||
<string name="web_page_note">Показывается приложением «Браузер» в машине. Если пусто, открывается DuckDuckGo. Действует со следующего подключения.</string>
|
||||
<string name="car_audio">Звук в машине</string>
|
||||
<string name="car_audio_bt">Bluetooth (рекомендуется)</string>
|
||||
<string name="car_audio_bt_sub">Никаких запросов в каждой поездке, а голос навигатора звучит в машине</string>
|
||||
<string name="car_audio_usb_sub">Чище звук и никакой задержки. В каждой поездке Android просит разрешить трансляцию экрана, и это позволяет Gearslip передавать звук</string>
|
||||
<string name="car_audio_usb_footer">Один раз выполните команду из Справки, и Android перестанет спрашивать.</string>
|
||||
<string name="car_audio_bt_footer">Звонки в любом случае идут через Bluetooth.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Размер экрана машины</string>
|
||||
<string name="screen_use_cars">Как у машины</string>
|
||||
<string name="screen_preview_size">Размер предпросмотра</string>
|
||||
<string name="screen_preview_note">Размер, в котором рисуется предпросмотр машины, например 1920x720.</string>
|
||||
<string name="screen_car_screen">Экран машины</string>
|
||||
<string name="screen_footer">Gearslip использует размер, который сообщает машина, если вы не зададите свой.</string>
|
||||
<string name="screen_size">Размер экрана</string>
|
||||
<string name="screen_from_car">От машины</string>
|
||||
<string name="screen_crop">Обрезать края</string>
|
||||
<string name="screen_crop_sub">Для машины, которая обрезает верх и низ или бока</string>
|
||||
<string name="screen_squash">Сжать по размеру</string>
|
||||
<string name="screen_squash_sub">Для машины, которая сплющивает всю картинку, так что круги выглядят овалами</string>
|
||||
<string name="screen_preview_title">Размер предпросмотра машины</string>
|
||||
<string name="screen_last_car">Последняя машина</string>
|
||||
<string name="screen_last_car_sub">Размер последней машины, к которой вы подключались</string>
|
||||
<string name="screen_this_screen">Этот экран</string>
|
||||
<string name="screen_this_screen_sub">Заполняет экран этого устройства, для магнитолы, на которой работает сам Gearslip</string>
|
||||
<string name="screen_custom">Свой</string>
|
||||
<string name="screen_enter_size">Введите размер</string>
|
||||
<string name="screen_change_custom">Изменить свой размер</string>
|
||||
<string name="screen_width">Ширина</string>
|
||||
<string name="screen_height">Высота</string>
|
||||
<string name="save">Сохранить</string>
|
||||
|
||||
<string name="screen_car_size_note">Используйте это, если картинка в машине выглядит растянутой. Введите реальный размер экрана, например 1280x480. Он применится при следующем подключении.</string>
|
||||
<string name="screen_set_by_you">%1$s, задано вами</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Как подключиться</string>
|
||||
<string name="help_which_cars">Какие машины работают</string>
|
||||
<string name="help_full_list">Весь список</string>
|
||||
<string name="help_first_drive">Перед первой поездкой</string>
|
||||
<string name="help_in_car">В машине</string>
|
||||
<string name="help_usb_audio">Звук через Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Отключите Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Отключите или удалите приложение Android Auto, чтобы машина общалась с Gearslip.</string>
|
||||
<string name="help_cert">Загрузите сертификат.</string>
|
||||
<string name="help_cert_detail">В Настройках скачайте сертификат aasdk, который принимает большинство машин. Если ваша машина его отклоняет, импортируйте свой.</string>
|
||||
<string name="help_mic">Разрешите микрофон.</string>
|
||||
<string name="help_mic_detail">Без него Android не даст приложению передавать музыку в машину. Gearslip слушает, только когда вы нажимаете микрофон или кнопку голоса на руле.</string>
|
||||
<string name="help_plug_in">Подключите кабель или соединитесь без проводов.</string>
|
||||
<string name="help_plug_in_detail">Gearslip открывается сам, когда машина подключается. Для беспроводного подключения сначала свяжите телефон с машиной, затем нажмите «Подключиться без проводов» на Главной.</string>
|
||||
<string name="help_pair">Свяжите по Bluetooth для звонков.</string>
|
||||
<string name="help_pair_detail">Звонки идут через машину, когда телефон и машина связаны. Gearslip предложит связать их при первом подключении.</string>
|
||||
<string name="help_answer">Отвечайте на звонки с экрана машины.</string>
|
||||
<string name="help_answer_detail">Сделайте Gearslip приложением для определения номера и защиты от спама. Без этого звонки всё равно звучат, но ответить можно только на телефоне.</string>
|
||||
<string name="help_prompt">Уберите запрос звука в каждой поездке.</string>
|
||||
<string name="help_prompt_detail">Подключите телефон к компьютеру с включённой отладкой по USB и один раз выполните это. Если пропустить, придётся нажимать «Начать» при каждом выезде.</string>
|
||||
<string name="help_readable">Сохраните уведомления читаемыми.</string>
|
||||
<string name="help_readable_detail">Android скрывает текст уведомлений, пока передаёт звук. Выполните и это, и Gearslip снимет ограничение только на время, пока музыка идёт в машину.</string>
|
||||
<string name="help_which_cars_body">Большинство машин принимает сертификат aasdk. Примерно с 2020 года в некоторых машинах появились новые проверки безопасности, которые его отклоняют, и в них Gearslip нужен ваш собственный сертификат.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Скачано. Android спросит перед установкой.</string>
|
||||
<string name="update_install">Установить</string>
|
||||
<string name="update_download_now">Скачайте сейчас. Android спросит перед установкой.</string>
|
||||
<string name="update_try_again">Попробовать снова</string>
|
||||
<string name="update_update">Обновить</string>
|
||||
<string name="update_checking">Проверка…</string>
|
||||
<string name="update_latest">У вас последняя версия</string>
|
||||
<string name="update_download">Скачать</string>
|
||||
<string name="update_check_now">Проверить сейчас</string>
|
||||
<string name="update_check_auto">Проверять автоматически</string>
|
||||
<string name="update_once_a_day">Раз в день</string>
|
||||
|
||||
<string name="update_unplug_first">Отключитесь от машины, чтобы установить. Установка закроет Gearslip.</string>
|
||||
<string name="update_card_title">Вышел Gearslip %1$s</string>
|
||||
<string name="update_downloading">Загрузка… %1$d%%</string>
|
||||
<string name="update_debug_manual">Отладочные сборки проверяют, только когда вы нажимаете «Проверить сейчас»</string>
|
||||
<string name="update_checks_daily">Gearslip проверяет раз в день</string>
|
||||
<string name="update_version_out">Вышла версия %1$s</string>
|
||||
<string name="update_version_ready">Версия %1$s готова к установке</string>
|
||||
<string name="update_dialog_status">Версия %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Поддерживать еженедельно или ежемесячно</string>
|
||||
<string name="about_bmac_sub" formatted="false">Разово или ежемесячно, картой. Buy Me a Coffee берёт 5%</string>
|
||||
<string name="about_give_once">Поддержать разово</string>
|
||||
<string name="about_any_amount">Любая сумма, картой</string>
|
||||
<string name="about_monero_sub">Откройте кошелёк или скопируйте адрес</string>
|
||||
<string name="about_monero_title">Поддержать через Monero</string>
|
||||
<string name="about_monero_body">Отправьте любую сумму на этот адрес из своего кошелька Monero.</string>
|
||||
<string name="about_open_wallet">Открыть в кошельке</string>
|
||||
<string name="about_copy_address">Скопировать адрес</string>
|
||||
<string name="close">Закрыть</string>
|
||||
<string name="about_matrix">Присоединиться к чату в Matrix</string>
|
||||
<string name="about_matrix_sub">Общайтесь с другими водителями о машинах, приложениях и Gearslip</string>
|
||||
<string name="about_translate">Помочь с переводом Gearslip</string>
|
||||
<string name="about_translate_sub">Исправьте или добавьте свой язык на Weblate. Некоторые переводы начинались как машинные.</string>
|
||||
<string name="about_source">Исходный код</string>
|
||||
<string name="about_source_sub">Читайте или меняйте код Gearslip по лицензии AGPL</string>
|
||||
<string name="about_voice_credits">Благодарности за голос</string>
|
||||
|
||||
<string name="about_no_wallet">Кошелёк Monero не найден. Скопируйте адрес.</string>
|
||||
<string name="about_monero_copied">Адрес Monero скопирован</string>
|
||||
<string name="about_voice_credits_sub">Распознавание речи: sherpa-onnx (Apache 2.0), с Kroko ASR от Banafo (CC BY-SA 4.0), а также Parakeet и Canary от NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Настройка сертификата</string>
|
||||
<string name="certsetup_installed">Сертификат установлен.</string>
|
||||
<string name="certsetup_continue">Продолжить</string>
|
||||
<string name="certsetup_failed">Не удалось скачать</string>
|
||||
<string name="certsetup_retry">Повторить</string>
|
||||
<string name="certsetup_import_sub">Файл .p12 с закрытым ключом. Используйте свой, если машина отклоняет сертификат aasdk.</string>
|
||||
<string name="certsetup_skip">Пропустить настройку подключения к машине</string>
|
||||
|
||||
<string name="certsetup_loaded">Машины принимают только сертификаты, выданные Google, и у вас один загружен.</string>
|
||||
<string name="certsetup_choose">Машины принимают только сертификаты, выданные Google. В Gearslip его нет, так что выберите, откуда взять свой.</string>
|
||||
<string name="certsetup_aasdk_sub">Открытый проект aasdk публикует этот сертификат на github.com/opencardev/aasdk. Большинство машин его принимает. Некоторые примерно с 2020 года отклоняют, и тогда нужно импортировать свой.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Хранилище Gearslip</string>
|
||||
<string name="storage_measuring">Подсчёт…</string>
|
||||
<string name="storage_lyrics">Тексты песен</string>
|
||||
<string name="storage_clear">Очистить</string>
|
||||
<string name="storage_voice">Голосовые модели</string>
|
||||
<string name="storage_remove">Удалить</string>
|
||||
<string name="storage_logs">Старые журналы</string>
|
||||
<string name="storage_other">Другие временные файлы</string>
|
||||
<string name="storage_delete_all">Удалить все данные Gearslip</string>
|
||||
|
||||
<string name="storage_total">Данные Gearslip занимают %1$s памяти телефона.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d песня · %2$s. Очищенные тексты скачаются снова, когда вы их откроете.</item>
|
||||
<item quantity="few">%1$d песни · %2$s. Очищенные тексты скачаются снова, когда вы их откроете.</item>
|
||||
<item quantity="many">%1$d песен · %2$s. Очищенные тексты скачаются снова, когда вы их откроете.</item>
|
||||
<item quantity="other">%1$d песни · %2$s. Очищенные тексты скачаются снова, когда вы их откроете.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Нужны для голосовых команд; скачайте их снова в Настройках.</string>
|
||||
<string name="storage_logs_detail">%1$s. Журналы прошлых запусков; журнал текущего запуска остаётся.</string>
|
||||
<string name="storage_other_detail">%1$s. Скачанные обновления и другие файлы, которые Gearslip может создать заново.</string>
|
||||
<string name="storage_delete_armed">Нажмите «Удалить» ещё раз, чтобы стереть настройки, сертификаты, голосовые модели и журналы. Gearslip закроется и в следующий раз начнёт с настройки.</string>
|
||||
<string name="storage_delete_sub">Настройки, сертификаты, голосовые модели, тексты и журналы.</string>
|
||||
<string name="storage_delete">Удалить</string>
|
||||
<string name="storage_delete_ellipsis">Удалить…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Рад, что поездка удалась</string>
|
||||
<string name="donate_liberapay">Поддержать на Liberapay</string>
|
||||
<string name="donate_bmac">Угостите меня кофе</string>
|
||||
<string name="donate_card">Поддержать разово картой</string>
|
||||
|
||||
<string name="donate_body">Gearslip бесплатный, без рекламы и слежки. Если он помогает вам в дороге, ваша поддержка помогает ему жить.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">Для этого Gearslip нужен доступ к Bluetooth и Wi-Fi поблизости.</string>
|
||||
<string name="wireless_enable_permission">Включить разрешение «Устройства поблизости»</string>
|
||||
<string name="wireless_connect">Подключиться без проводов</string>
|
||||
<string name="wireless_which_car">Какая машина?</string>
|
||||
<string name="wireless_none_paired">Телефон пока ни с чем не связан. Сначала свяжите его с машиной.</string>
|
||||
<string name="wireless_not_listed">Машины нет в списке? Сначала свяжите её с телефоном.</string>
|
||||
<string name="bluetooth_settings">Настройки Bluetooth</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Голос: слова на лету</string>
|
||||
<string name="voice_final_title">Голос: итоговые слова</string>
|
||||
<string name="voice_none">Нет</string>
|
||||
<string name="voice_none_sub">Оставить слова на лету. Быстрее всего, наименее точно</string>
|
||||
|
||||
<string name="voice_live_body">Голосовой помощник превращает речь в текст на этом телефоне, и ничего из сказанного его не покидает. Ему нужны две модели, вместе около 270 МБ, скачиваются один раз. Эта записывает ваши слова, пока вы говорите.</string>
|
||||
<string name="voice_final_body">Когда вы замолкаете, эта модель перечитывает всю фразу. Она ловит имена и места, которые упускает модель на лету.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d МБ</string>
|
||||
<string name="voice_downloaded">Скачано. %1$s</string>
|
||||
<string name="voice_downloading">Загрузка, %1$d%%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Справка</string>
|
||||
<string name="home_preview">Предпросмотр</string>
|
||||
<string name="home_logs">Журналы</string>
|
||||
<string name="home_disconnect">Отключиться</string>
|
||||
<string name="home_bt_title">Bluetooth не подключён</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · отладочная сборка</string>
|
||||
<string name="home_bt_body">Музыка играет через Bluetooth, но телефон не подключён к машине. Подключите его здесь или в меню Bluetooth машины.</string>
|
||||
<string name="home_voice_downloading">Загрузка голоса, %1$d%%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Выводить звук в машине через Bluetooth?</string>
|
||||
<string name="audio_never_ask">Не спрашивать никогда, с помощью компьютера</string>
|
||||
<string name="audio_use_bt">Использовать Bluetooth</string>
|
||||
<string name="audio_keep_asking">Продолжать спрашивать</string>
|
||||
<string name="audio_mic_title">Для звука в машине нужно разрешение на микрофон</string>
|
||||
<string name="audio_open_settings">Открыть настройки приложения</string>
|
||||
|
||||
<string name="audio_bt_body">Android считает музыку, которую Gearslip отправляет в машину, трансляцией экрана, поэтому спрашивает в каждой поездке, по кабелю или без проводов. Через Bluetooth он никогда не спрашивает. Это можно изменить позже в Настройках.</string>
|
||||
<string name="audio_keep_usb">Или оставьте звук через Gearslip и уберите вопрос навсегда: подключите телефон к компьютеру с включённой отладкой по USB и один раз выполните это.</string>
|
||||
<string name="audio_mic_body">Android позволяет приложению отправлять звук других приложений в машину, только если у него есть разрешение на микрофон. Для Gearslip оно выключено, поэтому музыка играет на телефоне. Включите его в разделе «Разрешения», затем «Микрофон».</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Закрыть клавиатуру машины</string>
|
||||
<string name="typing_label">Ввод для экрана машины</string>
|
||||
<string name="typing_search">Поиск на экране машины</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Нажмите, чтобы скопировать</string>
|
||||
<string name="command_copied">Команда скопирована</string>
|
||||
<string name="whats_new_this_version">%1$s (эта версия)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Тема</string>
|
||||
<string name="carset_imperial">Имперские (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Метрические (км, м, °C)</string>
|
||||
<string name="carset_screen_size">Размер экрана (следующее подключение)</string>
|
||||
<string name="carset_fit">Подогнать под экран</string>
|
||||
<string name="off">Выкл.</string>
|
||||
<string name="on">Вкл.</string>
|
||||
<string name="carset_lyrics_from">Тексты из</string>
|
||||
<string name="carset_more_sources">Больше источников</string>
|
||||
<string name="carset_hide">Скрыть</string>
|
||||
<string name="carset_show">Показать</string>
|
||||
<string name="carset_phone">Телефон</string>
|
||||
<string name="carset_vehicle">Автомобиль</string>
|
||||
<string name="carset_video">Видео</string>
|
||||
<string name="carset_version">Версия</string>
|
||||
<string name="carset_build">Сборка</string>
|
||||
<string name="carset_whats_new_after">Что нового после обновлений</string>
|
||||
<string name="carset_whats_new_after_sub">Показать список изменений один раз, при первом подключении машины после обновления.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (по умолчанию) - нажмите, чтобы задать другую страницу</string>
|
||||
<string name="carset_web_note">Показывается приложением «Браузер» в лаунчере. Действует со следующего подключения.</string>
|
||||
<string name="carset_screen_fit">Подгонка экрана</string>
|
||||
<string name="carset_top">Сверху</string>
|
||||
<string name="carset_bottom">Снизу</string>
|
||||
<string name="carset_save_vehicle">Сохранить для этого автомобиля</string>
|
||||
<string name="carset_no_profile">Подходящий профиль автомобиля не найден</string>
|
||||
|
||||
<string name="carset_audio_note">«Через Gearslip» передаёт звук телефона по подключению к машине, кабелем или без проводов. Android в каждой поездке просит разрешить трансляцию экрана, а некоторые приложения это блокируют. Bluetooth играет так, как телефон играл бы обычно, и Android ничего не спрашивает.</string>
|
||||
<string name="carset_directions_note">Gearslip зачитывает каждый поворот через динамики машины, а музыка в это время стихает. Выключите, если голос вашего навигатора уже звучит в машине.</string>
|
||||
<string name="carset_phone_speech_note">Слушает через речевой сервис телефона вместо сервиса Gearslip. На большинстве телефонов это сервис Google, который может отправлять ваш голос в Google. В GrapheneOS его нет, поэтому Gearslip продолжает использовать свой.</string>
|
||||
<string name="carset_vehicle_data_note">Добавляет плитку «Данные автомобиля»: сырой живой вывод канала датчиков магнитолы. Подписи полей (скорость, обороты, топливо...) взяты из других проектов по обратной разработке и не проверены на этой машине. Считайте их догадкой, а не фактом, пока сами не увидите, как одно из них меняется.</string>
|
||||
<string name="carset_adaptive_note">Снижает качество картинки, когда машина, или её Wi-Fi, не успевает, и снова повышает, когда может. Действует со следующего подключения.</string>
|
||||
<string name="carset_usable_height">Полезная высота %1$dpx из 480</string>
|
||||
<string name="carset_saved_to">Сохранено в %1$s</string>
|
||||
<string name="carset_save_failed">Не удалось сохранить</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Открыть на телефоне</string>
|
||||
<string name="media_now_playing">Сейчас играет</string>
|
||||
<string name="media_up_next">Далее</string>
|
||||
<string name="media_browse">Обзор</string>
|
||||
<string name="media_search_hint">Введите песню, исполнителя, альбом или плейлист.</string>
|
||||
<string name="media_looking_lyrics">Ищем текст…</string>
|
||||
<string name="media_nothing_playing">Ничего не играет</string>
|
||||
<string name="media_press_play">Нажмите воспроизведение, чтобы продолжить с того места, где остановились, или найдите что-нибудь в «Обзоре».</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s не открывает свою библиотеку.</string>
|
||||
<string name="media_needs_access">Gearslip всё равно может им управлять, если получит доступ к уведомлениям на телефоне. Разрешите его в Настройки > Уведомления > Доступ к уведомлениям.</string>
|
||||
<string name="media_not_running">%1$s не запущено.</string>
|
||||
<string name="media_not_running_body">%1$s играет через плеер Gearslip, но только пока открыто на телефоне. Откройте его один раз, и оно продолжит играть здесь, в машине.</string>
|
||||
<string name="media_open_app">Открыть %1$s</string>
|
||||
<string name="media_refused">%1$s не разрешило Gearslip просматривать его.</string>
|
||||
<string name="media_refused_body">Некоторые медиаприложения принимают только собственный хост Google.</string>
|
||||
<string name="media_connecting">Подключение к %1$s…</string>
|
||||
<string name="media_search">Поиск в %1$s</string>
|
||||
<string name="media_search_no_list">%1$s не умеет показывать результаты. Нажмите поиск, и %1$s само выберет, что играть.</string>
|
||||
<string name="media_search_unsupported">%1$s не поддерживает поиск из машины.</string>
|
||||
<string name="media_searching">Поиск в %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s ничего не нашло по запросу «%2$s».</string>
|
||||
<string name="media_search_ignored">%1$s ничего не включило по запросу «%2$s».</string>
|
||||
<string name="media_searching_for">Ищем «%2$s» в %1$s…</string>
|
||||
<string name="media_nothing_playing_dot">Ничего не играет.</string>
|
||||
<string name="media_no_lyrics">Текст для этого трека не найден.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">Наверх</string>
|
||||
<string name="media_loading">Загрузка…</string>
|
||||
<string name="media_browse_start">Включите музыку в приложении на телефоне. Воспроизведение, пауза, переключение и перемотка работают отсюда.</string>
|
||||
<string name="media_load_failed">Не удалось загрузить этот список.</string>
|
||||
<string name="media_nothing_here">Здесь пусто.</string>
|
||||
<string name="media_nothing_queued">Очередь пуста.</string>
|
||||
<string name="media_play">Играть</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Ничего не играет - не каждое приложение сообщает Gearslip, что будет дальше.</string>
|
||||
<string name="media_queue_empty_playing">Только то, что уже играет - не каждое приложение сообщает Gearslip, что будет дальше.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Предыдущий</string>
|
||||
<string name="player_next">Следующий</string>
|
||||
<string name="player_repeat">Повтор</string>
|
||||
<string name="player_pause">Пауза</string>
|
||||
<string name="player_unlike">Убрать лайк</string>
|
||||
<string name="player_like">Нравится</string>
|
||||
<string name="player_shuffle_off">Перемешивание выкл.</string>
|
||||
<string name="player_shuffle_on">Перемешивание вкл.</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Выбрать другое приложение</string>
|
||||
<string name="home_no_maps">Приложения с картами не найдены.</string>
|
||||
<string name="home_no_maps_body">Установите навигатор с поддержкой Android Auto, и он сам запустится здесь.</string>
|
||||
<string name="home_map_stopped">Карта остановлена</string>
|
||||
<string name="home_map_stopped_body">Она останется закрытой, пока вы не откроете её снова.</string>
|
||||
<string name="home_reopen_map">Открыть карту снова</string>
|
||||
<string name="home_car_apps">Автомобильные приложения</string>
|
||||
<string name="home_back_nav">Назад к панели навигации</string>
|
||||
<string name="home_show_player">Показать плеер</string>
|
||||
<string name="home_hide_lyrics">Скрыть текст</string>
|
||||
|
||||
<string name="home_starting">Запуск %1$s…</string>
|
||||
<string name="home_starting_map">Запуск карты…</string>
|
||||
<string name="home_failed">%1$s не запустилось.</string>
|
||||
<string name="home_failed_map">Приложение с картами не запустилось.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Главная</string>
|
||||
<string name="crumb_media">Медиа</string>
|
||||
<string name="crumb_apps">Приложения</string>
|
||||
<string name="crumb_dashboard">Панель</string>
|
||||
<string name="crumb_weather">Погода</string>
|
||||
<string name="crumb_reply">Ответ</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Браузер</string>
|
||||
<string name="app_screen_sharing">Трансляция экрана</string>
|
||||
<string name="app_exit">Выход</string>
|
||||
<string name="navapps_unpin">Открепить от панели навигации</string>
|
||||
<string name="navapps_full">Панель навигации заполнена (закреплено: %1$d)</string>
|
||||
<string name="navapps_pin">Закрепить на панели навигации</string>
|
||||
<string name="navapps_close">Закрыть приложение</string>
|
||||
<string name="navapps_move_left">Сдвинуть влево</string>
|
||||
<string name="navapps_move_right">Сдвинуть вправо</string>
|
||||
<string name="navapps_reset">Сбросить порядок приложений</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Закрыть</string>
|
||||
<string name="msg_read">Прочитать</string>
|
||||
<string name="msg_stop">Стоп</string>
|
||||
<string name="msg_no">Нет</string>
|
||||
<string name="msg_send_now">Отправить сейчас</string>
|
||||
<string name="msg_type">Набрать</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d новое</item>
|
||||
<item quantity="few">%1$s · %2$d новых</item>
|
||||
<item quantity="many">%1$s · %2$d новых</item>
|
||||
<item quantity="other">%1$s · %2$d новых</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Ответить: %1$s</string>
|
||||
<string name="msg_listening">Слушаю</string>
|
||||
<string name="msg_sending_to">Отправка: %1$s</string>
|
||||
<string name="msg_sent_to">Отправлено: %1$s</string>
|
||||
<string name="msg_reply_failed">Не удалось ответить</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">Связывание не завершилось. Свяжите через меню Bluetooth машины.</string>
|
||||
<string name="bt_pair_not_started">Связывание не началось. Свяжите через меню Bluetooth машины.</string>
|
||||
<string name="bt_paired">Связано. Теперь звонки идут через машину.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Больше не показывать</string>
|
||||
<string name="overlay_ok">ОК</string>
|
||||
<string name="overlay_location_off">Местоположение выключено</string>
|
||||
<string name="overlay_pair_title">Связать с этой машиной?</string>
|
||||
<string name="overlay_pair">Связать</string>
|
||||
<string name="overlay_confirm_title">Подтвердите на телефоне</string>
|
||||
<string name="overlay_confirm_body">Проверьте код на телефоне и нажмите «Связать». Машина тоже может спросить.</string>
|
||||
|
||||
<string name="overlay_bt_body">Музыка играет через Bluetooth, но телефон не подключён к машине. Подключите его в меню Bluetooth машины или телефона.</string>
|
||||
<string name="overlay_location_body">Приложения с картами не могут показать, где вы, и проложить маршрут. Включите местоположение в быстрых настройках телефона, когда припаркуетесь.</string>
|
||||
<string name="overlay_pair_body_music">Звонки и музыка идут через динамики машины, когда телефон и машина связаны. Затем телефон попросит подтвердить код.</string>
|
||||
<string name="overlay_pair_body">Звонки идут через динамики машины, когда телефон и машина связаны. Затем телефон попросит подтвердить код.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">Определяем местоположение.</string>
|
||||
<string name="weather_allow_location">Разрешите Gearslip доступ к местоположению на телефоне, чтобы видеть погоду.</string>
|
||||
<string name="weather_unreachable">Не удалось связаться с сервисом погоды.</string>
|
||||
<string name="weather_clear">Ясно</string>
|
||||
<string name="weather_mostly_clear">Преимущественно ясно</string>
|
||||
<string name="weather_partly_cloudy">Переменная облачность</string>
|
||||
<string name="weather_overcast">Пасмурно</string>
|
||||
<string name="weather_fog">Туман</string>
|
||||
<string name="weather_drizzle">Морось</string>
|
||||
<string name="weather_rain">Дождь</string>
|
||||
<string name="weather_snow">Снег</string>
|
||||
<string name="weather_rain_showers">Ливни</string>
|
||||
<string name="weather_snow_showers">Снегопады</string>
|
||||
<string name="weather_thunderstorm">Гроза</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Получаем погоду…</string>
|
||||
<string name="weather_next_hours">Ближайшие часы</string>
|
||||
<string name="weather_this_week">На этой неделе</string>
|
||||
<string name="weather_today">Сегодня</string>
|
||||
<string name="weather_credit">Данные о погоде: Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Ощущается как %1$s</string>
|
||||
<string name="weather_humidity">Влажность %1$d%%</string>
|
||||
<string name="weather_wind">Ветер %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">Сегодня</string>
|
||||
<string name="dash_getting_weather">Получаем погоду…</string>
|
||||
<string name="dash_coming_up">Скоро</string>
|
||||
<string name="dash_loading_calendar">Загрузка календаря…</string>
|
||||
<string name="dash_nothing_week">На ближайшей неделе ничего.</string>
|
||||
<string name="dash_all_day">Весь день</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Контакты</string>
|
||||
<string name="dialer_keypad">Клавиатура</string>
|
||||
<string name="dialer_ask">Спросить на телефоне</string>
|
||||
<string name="dialer_enter_number">Введите номер</string>
|
||||
<string name="dialer_call">Позвонить</string>
|
||||
<string name="dialer_no_contacts">Контакты с номером телефона не найдены.</string>
|
||||
<string name="dialer_search">Поиск контактов</string>
|
||||
|
||||
<string name="dialer_needs_permission">Gearslip нужны разрешения «Телефон» и «Контакты», чтобы звонить отсюда. Разрешите их на телефоне, когда припаркуетесь.</string>
|
||||
<string name="dialer_no_match">Нет контактов по запросу «%1$s».</string>
|
||||
<string name="dialer_call_who">Позвонить: %1$s?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Выйти из полноэкранного режима</string>
|
||||
<string name="carui_drive_safely">Будьте осторожны за рулём</string>
|
||||
<string name="carui_understand">Понятно</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip - неофициальная программа: её не создавал и не проверял ни один автопроизводитель, и она может отказать без предупреждения.</string>
|
||||
<string name="carui_never_interact">Никогда не трогайте этот экран за рулём. Задайте маршрут или песню до поездки, а потом следите за дорогой.</string>
|
||||
<string name="carui_this_app">Это приложение</string>
|
||||
<string name="carui_app_unsupported">%1$s пока не работает с Gearslip.</string>
|
||||
<string name="carui_app_unsupported_body">Оно не запустилось. Некоторые приложения работают только в Android Auto от Google.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">Идёт звонок</string>
|
||||
<string name="navbar_end_call">Завершить звонок</string>
|
||||
<string name="navbar_unmute">Включить микрофон</string>
|
||||
<string name="navbar_mute">Выключить микрофон</string>
|
||||
<string name="navbar_close_panel">Закрыть боковую панель</string>
|
||||
<string name="navbar_dock_player">Переместить плеер к карте</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Поиск или адрес</string>
|
||||
<string name="web_forward">Вперёд</string>
|
||||
<string name="web_reload">Обновить</string>
|
||||
<string name="web_start_page">Стартовая страница</string>
|
||||
<string name="web_fullscreen">Во весь экран</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Уведомления</string>
|
||||
<string name="notif_clear_all">Очистить все</string>
|
||||
<string name="notif_empty">Пока ничего. Новые уведомления с телефона появятся здесь.</string>
|
||||
<string name="notif_off">Уведомления выключены. Разрешите Gearslip в Настройки > Уведомления > Доступ к уведомлениям на телефоне.</string>
|
||||
<string name="notif_replied">Отвечено</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Отметить прочитанным</string>
|
||||
<string name="msg_messages">Сообщения</string>
|
||||
<string name="msg_none_recent">Нет недавних сообщений. Новые сообщения %1$s появятся здесь, как только придут на телефон.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">Ещё %1$d раньше</item>
|
||||
<item quantity="few">Ещё %1$d раньше</item>
|
||||
<item quantity="many">Ещё %1$d раньше</item>
|
||||
<item quantity="other">Ещё %1$d раньше</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Скрыть быстрые ответы</string>
|
||||
<string name="msg_quick">Быстрый ответ</string>
|
||||
<string name="msg_you">Вы: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Поиск маршрута…</string>
|
||||
<string name="nav_then">Затем</string>
|
||||
<string name="nav_stop">Завершить навигацию</string>
|
||||
<string name="nav_finding_routes">Поиск маршрутов…</string>
|
||||
<string name="nav_lane_recommended">Рекомендуемая полоса</string>
|
||||
<string name="nav_lane">Полоса</string>
|
||||
<string name="nav_arrive">Прибытие %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Пока ничего не играет.</string>
|
||||
<string name="template_search">Поиск</string>
|
||||
<string name="template_start">Начать</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Входящий звонок</string>
|
||||
<string name="call_decline">Отклонить</string>
|
||||
<string name="call_answer">Ответить</string>
|
||||
<string name="call_ringing">Уже звонит вслух - ответьте здесь или дайте позвонить.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">Касания выключены. Включите Gearslip в Настройки > Спецвозможности на телефоне.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Показывать после обновлений</string>
|
||||
<string name="whatsnew_got_it">Понятно</string>
|
||||
<string name="whatsnew_title">Что нового в версии %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">пробел</string>
|
||||
<string name="keyboard_hide">Скрыть клавиатуру</string>
|
||||
<string name="keyboard_submit">Готово</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Не работает с Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">Автоприложение</string>
|
||||
<string name="stage_close">Закрыть %1$s</string>
|
||||
<string name="stage_close_app">Закрыть приложение</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Плавно поверните налево</string>
|
||||
<string name="dir_slight_left_onto">Плавно поверните налево на %1$s</string>
|
||||
<string name="dir_slight_right">Плавно поверните направо</string>
|
||||
<string name="dir_slight_right_onto">Плавно поверните направо на %1$s</string>
|
||||
<string name="dir_left">Поверните налево</string>
|
||||
<string name="dir_left_onto">Поверните налево на %1$s</string>
|
||||
<string name="dir_right">Поверните направо</string>
|
||||
<string name="dir_right_onto">Поверните направо на %1$s</string>
|
||||
<string name="dir_sharp_left">Резко поверните налево</string>
|
||||
<string name="dir_sharp_left_onto">Резко поверните налево на %1$s</string>
|
||||
<string name="dir_sharp_right">Резко поверните направо</string>
|
||||
<string name="dir_sharp_right_onto">Резко поверните направо на %1$s</string>
|
||||
<string name="dir_u_turn">Развернитесь</string>
|
||||
<string name="dir_keep_left">Держитесь левее</string>
|
||||
<string name="dir_keep_left_toward">Держитесь левее в направлении %1$s</string>
|
||||
<string name="dir_keep_right">Держитесь правее</string>
|
||||
<string name="dir_keep_right_toward">Держитесь правее в направлении %1$s</string>
|
||||
<string name="dir_fork_left">На развилке держитесь левее</string>
|
||||
<string name="dir_fork_left_toward">На развилке держитесь левее в направлении %1$s</string>
|
||||
<string name="dir_fork_right">На развилке держитесь правее</string>
|
||||
<string name="dir_fork_right_toward">На развилке держитесь правее в направлении %1$s</string>
|
||||
<string name="dir_ramp_left">Въезжайте на съезд слева</string>
|
||||
<string name="dir_ramp_left_onto">Въезжайте на съезд слева на %1$s</string>
|
||||
<string name="dir_ramp_right">Въезжайте на съезд справа</string>
|
||||
<string name="dir_ramp_right_onto">Въезжайте на съезд справа на %1$s</string>
|
||||
<string name="dir_exit_left">Сверните на съезд слева</string>
|
||||
<string name="dir_exit_left_toward">Сверните на съезд слева в направлении %1$s</string>
|
||||
<string name="dir_exit_right">Сверните на съезд справа</string>
|
||||
<string name="dir_exit_right_toward">Сверните на съезд справа в направлении %1$s</string>
|
||||
<string name="dir_merge_left">Перестройтесь влево</string>
|
||||
<string name="dir_merge_left_onto">Перестройтесь влево на %1$s</string>
|
||||
<string name="dir_merge_right">Перестройтесь вправо</string>
|
||||
<string name="dir_merge_right_onto">Перестройтесь вправо на %1$s</string>
|
||||
<string name="dir_merge">Влейтесь в поток</string>
|
||||
<string name="dir_merge_onto">Влейтесь в поток на %1$s</string>
|
||||
<string name="dir_roundabout_through">Проезжайте через круговое движение</string>
|
||||
<string name="dir_roundabout_through_onto">Проезжайте через круговое движение на %1$s</string>
|
||||
<string name="dir_roundabout_enter">Въезжайте на круговое движение</string>
|
||||
<string name="dir_roundabout_exit">Съезжайте с кругового движения</string>
|
||||
<string name="dir_roundabout_exit_onto">Съезжайте с кругового движения на %1$s</string>
|
||||
<string name="dir_straight">Двигайтесь прямо</string>
|
||||
<string name="dir_straight_onto">Двигайтесь прямо на %1$s</string>
|
||||
<string name="dir_continue">Продолжайте движение</string>
|
||||
<string name="dir_continue_onto">Продолжайте движение по %1$s</string>
|
||||
<string name="dir_ferry">Сядьте на паром</string>
|
||||
<string name="dir_train">Сядьте на поезд</string>
|
||||
<string name="dir_destination">Пункт назначения впереди</string>
|
||||
<string name="dir_destination_left">Пункт назначения слева</string>
|
||||
<string name="dir_destination_right">Пункт назначения справа</string>
|
||||
<string name="dir_roundabout_take">На круговом движении выберите %1$s съезд</string>
|
||||
<string name="dir_roundabout_take_onto">На круговом движении выберите %1$s съезд на %2$s</string>
|
||||
<string name="dir_ordinal_1">первый</string>
|
||||
<string name="dir_ordinal_2">второй</string>
|
||||
<string name="dir_ordinal_3">третий</string>
|
||||
<string name="dir_ordinal_4">четвёртый</string>
|
||||
<string name="dir_ordinal_5">пятый</string>
|
||||
<string name="dir_ordinal_6">шестой</string>
|
||||
<string name="dir_ordinal_7">седьмой</string>
|
||||
<string name="dir_ordinal_8">восьмой</string>
|
||||
<string name="dir_in_distance">Через %1$s %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s километр</item>
|
||||
<item quantity="few">%1$s километра</item>
|
||||
<item quantity="many">%1$s километров</item>
|
||||
<item quantity="other">%1$s километра</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s километра</string>
|
||||
<string name="dir_quarter_mile">четверть мили</string>
|
||||
<string name="dir_half_mile">полмили</string>
|
||||
<string name="dir_three_quarter_mile">три четверти мили</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s милю</item>
|
||||
<item quantity="few">%1$s мили</item>
|
||||
<item quantity="many">%1$s миль</item>
|
||||
<item quantity="other">%1$s мили</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s мили</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s фут</item>
|
||||
<item quantity="few">%1$s фута</item>
|
||||
<item quantity="many">%1$s футов</item>
|
||||
<item quantity="other">%1$s фута</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s ярд</item>
|
||||
<item quantity="few">%1$s ярда</item>
|
||||
<item quantity="many">%1$s ярдов</item>
|
||||
<item quantity="other">%1$s ярда</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d метр</item>
|
||||
<item quantity="few">%1$d метра</item>
|
||||
<item quantity="many">%1$d метров</item>
|
||||
<item quantity="other">%1$d метра</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Если машина не подключается, возможно, она отклоняет этот сертификат. Импортируйте свой в Настройках, в разделе «Сертификат».</string>
|
||||
<string name="cert_downloaded_footer">Машина не подключается? Некоторые машины примерно с 2020 года отклоняют сертификат aasdk, так что импортируйте свой. Изменения вступят в силу при следующем подключении.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Карточка поворота на приборной панели</string>
|
||||
<string name="car_cluster_video_note">Для машины со вторым экраном в приборной панели: Gearslip показывает на нём следующий поворот. Действует со следующего подключения.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Не подключено</string>
|
||||
<string name="status_idle_detail">Подключите телефон к USB-порту машины или соединитесь без проводов. Gearslip откроется сам, когда машина ответит.</string>
|
||||
<string name="status_connecting">Подключение</string>
|
||||
<string name="status_projecting">Трансляция</string>
|
||||
<string name="status_projecting_detail">Изображение передаётся на магнитолу.</string>
|
||||
<string name="status_disconnected">Отключено</string>
|
||||
<string name="status_disconnected_detail">Магнитола больше не подключена.</string>
|
||||
<string name="places_title">Сохранённые места</string>
|
||||
<string name="places_sub">Скажите «navigate home» в любом приложении карт</string>
|
||||
<string name="places_intro">Скажите «navigate home», «take me to work» или «navigate to» со своим названием, и Gearslip отправит адрес в приложение карт. Пока голосовые команды работают только на английском.</string>
|
||||
<string name="places_home">Дом</string>
|
||||
<string name="places_work">Работа</string>
|
||||
<string name="places_other">Место %1$d</string>
|
||||
<string name="places_name">Название</string>
|
||||
<string name="places_address">Адрес</string>
|
||||
<string name="places_here">Где я сейчас</string>
|
||||
<string name="places_finding">Ищу…</string>
|
||||
<string name="places_not_found">Gearslip не нашёл, где вы. Проверьте, что геолокация включена.</string>
|
||||
<string name="places_clear">Очистить</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,832 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="touch_relay_description">Låter Gearslip upprepa tryck från bilens skärm på telefonen under skärmdelning. Gearslip läser inte skärmens innehåll.</string>
|
||||
|
||||
<!-- Setup guide -->
|
||||
<string name="guide_usage_title">Hjälp andra förare.</string>
|
||||
<string name="guide_safety_title">Kör säkert.</string>
|
||||
<string name="guide_safety_body">Gearslip är inte officiellt. Ingen biltillverkare har gjort eller granskat det, och det kan sluta fungera utan förvarning. Ställ in det innan du åker och håll sedan blicken på vägen.</string>
|
||||
<string name="guide_mic_title">Mikrofon.</string>
|
||||
<string name="guide_mic_body">Gearslip använder den för att skicka din musik till bilen och för att höra dig när du ber om något eller svarar på ett meddelande. Det du säger stannar på telefonen.</string>
|
||||
<string name="guide_location_title">Plats.</string>
|
||||
<string name="guide_location_body">Bilens karta och sökningar i närheten behöver den. Utan den visar instrumentpanelen ingen karta.</string>
|
||||
<string name="guide_calendar_title">Kalender.</string>
|
||||
<string name="guide_calendar_body">Bilens skärm visar din nästa händelse. Gearslip läser bara din kalender.</string>
|
||||
<string name="guide_calls_title">Samtal och kontakter.</string>
|
||||
<string name="guide_calls_body">Bilen visar namnet på den som ringer, och dess Telefon-app kan slå ett nummer eller ringa en av dina kontakter. Gearslip läser dem bara.</string>
|
||||
<string name="guide_bluetooth_title">Bluetooth.</string>
|
||||
<string name="guide_bluetooth_body">Första gången du ansluter erbjuder Gearslip att parkoppla telefonen med bilen, så att samtal hörs i dess högtalare.</string>
|
||||
<string name="guide_notifications_title">Aviseringar.</string>
|
||||
<string name="guide_notifications_body">Medan Gearslip är anslutet visar det en avisering med en knapp för att stoppa. Utan detta döljer Android den.</string>
|
||||
<string name="guide_messages_title">Meddelanden.</string>
|
||||
<string name="guide_messages_body">Bilen visar dina meddelanden, läser upp dem och låter dig svara. Slå på Gearslip i listan som öppnas.</string>
|
||||
<string name="guide_android_auto_title">Stäng av Android Auto.</string>
|
||||
<string name="guide_android_auto_body">Android Auto är fortfarande installerat och slåss med Gearslip om bilens USB-anslutning. Inaktivera eller avinstallera det på skärmen som öppnas.</string>
|
||||
<string name="guide_default_title">Välj Gearslip.</string>
|
||||
<string name="guide_default_body">Om telefonen frågar vilken app som ska öppnas när du ansluter till bilen, välj Gearslip och tryck på Alltid.</string>
|
||||
<string name="guide_audio_title">Bilens ljud.</string>
|
||||
<string name="guide_audio_body">Välj hur musiken når bilen. Samtal går via Bluetooth i båda fallen.</string>
|
||||
<string name="guide_voice_title">Röst.</string>
|
||||
<string name="guide_voice_body">Be om vägbeskrivning, musik eller ett samtal med rösten. Telefonen gör själv om tal till text, och inget du säger lämnar den. Det kräver %1$d MB, som laddas ner en gång. Använd wifi om du kan.</string>
|
||||
<string name="guide_audio_prompt_title">Ingen ljudfråga.</string>
|
||||
<string name="guide_audio_prompt_body">Kör detta från en dator med USB-felsökning på, så litar Android på Gearslip med ditt ljud för gott. Annars trycker du på Starta en gång varje resa.</string>
|
||||
<string name="guide_readable_title">Läsbara aviseringar.</string>
|
||||
<string name="guide_readable_body">Medan ljudet går till bilen döljer Android vad aviseringarna säger, som om du delade skärmen. Detta låter Gearslip visa dem under resan och dölja dem igen efteråt.</string>
|
||||
<string name="guide_answer_title">Svara på samtal.</string>
|
||||
<string name="guide_answer_body">Gör Gearslip till din app för nummerpresentation, så kan bilen svara på och avvisa samtal. Utan det ringer samtalen ändå, men bara telefonen kan svara.</string>
|
||||
<string name="guide_apps_title">Gott sällskap.</string>
|
||||
<string name="guide_buttons_title">Dina knappar.</string>
|
||||
<string name="guide_buttons_body">Välj hur bilens knappar ser ut. Ändra det när du vill i bilens inställningar.</string>
|
||||
<string name="guide_open_notification_access">Öppna aviseringsåtkomst</string>
|
||||
<string name="guide_open_android_auto">Öppna appinfo för Android Auto</string>
|
||||
<string name="guide_download_voice">Ladda ner röst</string>
|
||||
<string name="guide_set_up">Konfigurera</string>
|
||||
<string name="guide_voice_downloading">Laddar ner, %1$d %%. Du kan fortsätta, det pågår ändå.</string>
|
||||
<string name="guide_voice_ready">Rösten är klar.</string>
|
||||
<string name="guide_voice_failed">Nedladdningen blev inte klar. Tryck på Ladda ner röst för att försöka igen.</string>
|
||||
<string name="guide_audio_bluetooth">Bluetooth</string>
|
||||
<string name="guide_recommended">Rekommenderas</string>
|
||||
<string name="guide_audio_bluetooth_body">Ingen fråga varje resa, och kartappens röst når bilen.</string>
|
||||
<string name="guide_audio_usb">Via Gearslip</string>
|
||||
<string name="guide_audio_usb_body">Klarare ljud utan fördröjning. Varje resa ber Android dig dela skärmen, vilket låter Gearslip skicka ljudet.</string>
|
||||
<string name="guide_optional">VALFRITT</string>
|
||||
<string name="guide_already_allowed">Redan tillåtet.</string>
|
||||
<string name="guide_scroll_more">Rulla för mer ↓</string>
|
||||
<string name="guide_allow">Tillåt</string>
|
||||
<string name="guide_skip">Hoppa över</string>
|
||||
<string name="guide_open">Öppna</string>
|
||||
<string name="guide_next">Nästa</string>
|
||||
<string name="guide_done">Klar</string>
|
||||
<string name="guide_back">Tillbaka</string>
|
||||
|
||||
<!-- Usage notes -->
|
||||
<string name="usage_intro">Slå på detta, så skickar Gearslip en kort anteckning när du öppnar appen och efter varje resa. <b>Anteckningen innehåller inget namn, konto eller plats. Ett slumptal räknar din telefon en gång, och servern sparar ingen IP-adress.</b> Anteckningarna visar vilka bilar som fungerar och var de andra fallerar.</string>
|
||||
<string name="usage_send">Skicka användningsanteckningar</string>
|
||||
<string name="usage_also_share">Dela även</string>
|
||||
<string name="usage_share_android">Android-version och system</string>
|
||||
<string name="usage_share_phone">Telefonmodell</string>
|
||||
<string name="usage_share_cars">Varje bil och om den anslöt</string>
|
||||
<string name="usage_share_drives">Varje resa och hur den slutade</string>
|
||||
<string name="usage_what_sent">Vad som skickas</string>
|
||||
<string name="usage_what_sent_sub">Varje fält, exakt som det skickas</string>
|
||||
<string name="usage_hide_note">Dölj anteckningen</string>
|
||||
<string name="usage_show_note">Visa anteckningen</string>
|
||||
<string name="usage_example">Exempel, skickas inte: en resa lägger till detta i ”sessions”</string>
|
||||
<string name="usage_no_server">Den här versionen har ingen statistikserver, så den skickar ingenting</string>
|
||||
<string name="usage_debug_build">Felsökningsversion: anteckningarna hålls isär och räknas aldrig i statistiken</string>
|
||||
<string name="worked_title">Gearslip fungerade med din %1$s</string>
|
||||
<string name="worked_title_unknown">Gearslip fungerade med din bil</string>
|
||||
<string name="worked_body">Dela det så att nästa förare vet att det fungerar? Inget namn, konto eller plats.</string>
|
||||
<string name="worked_not_now">Inte nu</string>
|
||||
<string name="worked_share">Dela</string>
|
||||
|
||||
<!-- Recommended apps -->
|
||||
<string name="recommended_intro">De här gratisapparna körs på Gearslips bilskärm. Varje knapp öppnar appens sida på GitHub.</string>
|
||||
<string name="recommended_title">Rekommenderade appar</string>
|
||||
<string name="back">Tillbaka</string>
|
||||
<string name="recommended_installed">Installerad</string>
|
||||
<string name="recommended_maps">Kartor</string>
|
||||
<string name="recommended_music">Musik</string>
|
||||
<string name="recommended_podcasts">Poddar</string>
|
||||
<string name="recommended_other">Andra coola appar</string>
|
||||
<string name="recommended_about_organic">Kartor och vägvisning offline.</string>
|
||||
<string name="recommended_about_vela">Sväng-för-sväng-navigering utan Google.</string>
|
||||
<string name="recommended_about_ytm">En spelare för YouTube Music.</string>
|
||||
<string name="recommended_about_antennapod">Poddar, med kapitel och allt.</string>
|
||||
<string name="recommended_about_rfsentinel">Hittar spårare och kameror i närheten.</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="settings_title">Inställningar</string>
|
||||
<string name="settings_whats_new">Nyheter</string>
|
||||
<string name="settings_whats_new_sub">Läs versionsinformationen för %1$s på GitHub</string>
|
||||
<string name="settings_support">Stöd Gearslip</string>
|
||||
<string name="settings_support_footer">Gearslip är gratis, utan reklam och utan spårning. Ditt stöd håller det igång.</string>
|
||||
<string name="settings_usage_notes">Användningsanteckningar</string>
|
||||
<string name="settings_updates">Uppdateringar</string>
|
||||
<string name="settings_about">Om</string>
|
||||
<string name="settings_replay">Visa installationsguiden igen</string>
|
||||
<string name="settings_replay_sub">Gå igenom stegen från första start igen</string>
|
||||
<string name="settings_connection">Anslutning</string>
|
||||
<string name="settings_use_in_car">Använd Gearslip i bilen</string>
|
||||
<string name="settings_use_in_car_on">Gearslip svarar när du ansluter till en bil</string>
|
||||
<string name="settings_use_in_car_off">Av: Android Auto kan ta anslutningen i stället</string>
|
||||
<string name="settings_apps">Appar</string>
|
||||
<string name="settings_certificate">Certifikat</string>
|
||||
<string name="cert_next_connection">Ändringar gäller från nästa anslutning.</string>
|
||||
<string name="cert_checking">Kontrollerar…</string>
|
||||
<string name="cert_valid_until">Giltigt till %1$s</string>
|
||||
<string name="cert_hide_details">Dölj detaljer</string>
|
||||
<string name="cert_show_details">Visa detaljer</string>
|
||||
<string name="cert_subject">Subjekt</string>
|
||||
<string name="cert_issuer">Utfärdare</string>
|
||||
<string name="cert_downloading">Laddar ner…</string>
|
||||
<string name="cert_download">Ladda ner från aasdk</string>
|
||||
<string name="cert_downloaded">Certifikatet har laddats ner.</string>
|
||||
<string name="cert_download_failed">Nedladdningen misslyckades: %1$s</string>
|
||||
<string name="cert_download_sub">Certifikatet som projektet aasdk, med öppen källkod, publicerar på GitHub. De flesta bilar godtar det, men vissa från omkring 2020 och framåt avvisar det</string>
|
||||
<string name="cert_import">Importera min egen fil</string>
|
||||
<string name="cert_import_sub">En .p12-fil med sin privata nyckel. Använd din egen när bilen avvisar aasdk-certifikatet</string>
|
||||
<string name="cert_remove_imported">Ta bort importerat certifikat</string>
|
||||
<string name="cert_remove_downloaded">Ta bort nedladdat certifikat</string>
|
||||
<string name="cert_removed_imported">Det importerade certifikatet har tagits bort.</string>
|
||||
<string name="cert_removed_downloaded">Det nedladdade certifikatet har tagits bort.</string>
|
||||
<string name="cert_imported">Certifikatet har importerats.</string>
|
||||
<string name="cert_import_failed">Kunde inte importera: fel lösenord, eller inte en PKCS#12-fil med en privat nyckel.</string>
|
||||
<string name="cert_password_title">Certifikatets lösenord</string>
|
||||
<string name="cert_password">Lösenord</string>
|
||||
<string name="cert_password_hint">Lämna tomt om filen inte har något.</string>
|
||||
<string name="cert_import_button">Importera</string>
|
||||
|
||||
<!-- Certificate summary -->
|
||||
<string name="cancel">Avbryt</string>
|
||||
<string name="cert_none">Inget inläst (självsignerat; bilar avvisar det)</string>
|
||||
<string name="cert_kind_imported">Importerat: %1$s</string>
|
||||
<string name="cert_kind_downloaded">Nedladdat: %1$s</string>
|
||||
<string name="cert_kind_adb">Placerat via adb: %1$s</string>
|
||||
<string name="cert_kind_self">Självsignerat: %1$s</string>
|
||||
|
||||
<!-- Car settings on the phone -->
|
||||
<string name="car_group_screen">Skärm</string>
|
||||
<string name="car_group_nav_bar">Navigeringsfält och spelare</string>
|
||||
<string name="car_group_sound">Ljud och röst</string>
|
||||
<string name="car_group_warnings">Varningar</string>
|
||||
<string name="car_group_more">Mer</string>
|
||||
<string name="choice_size_normal">Normal</string>
|
||||
<string name="choice_size_large">Stor</string>
|
||||
<string name="choice_size_xlarge">Extra stor</string>
|
||||
<string name="choice_theme_phone">Som telefonen</string>
|
||||
<string name="choice_theme_light">Ljust</string>
|
||||
<string name="choice_theme_dark">Mörkt</string>
|
||||
<string name="choice_dark_black">Svart</string>
|
||||
<string name="choice_dark_black_colour">Svart och färg</string>
|
||||
<string name="choice_dark_deep_colour">Djup färg</string>
|
||||
<string name="choice_dark_grey">Grått</string>
|
||||
<string name="choice_auto">Auto</string>
|
||||
<string name="choice_night_day">Dag</string>
|
||||
<string name="choice_night_night">Natt</string>
|
||||
<string name="choice_units_imperial">Brittiska</string>
|
||||
<string name="choice_units_metric">Metriska</string>
|
||||
<string name="choice_nav_show">Visa alltid</string>
|
||||
<string name="choice_nav_hide_navigating">Dölj vid navigering</string>
|
||||
<string name="choice_nav_hide">Dölj tills du trycker</string>
|
||||
<string name="choice_open_home">Hem</string>
|
||||
<string name="summary_player_left">Spelaren till vänster</string>
|
||||
<string name="summary_player_right">Spelaren till höger</string>
|
||||
<string name="summary_usb_audio">Ljud via Gearslip</string>
|
||||
<string name="summary_bluetooth_audio">Bluetooth-ljud</string>
|
||||
<string name="summary_directions_on">Röstvägledning på</string>
|
||||
<string name="summary_directions_off">Röstvägledning av</string>
|
||||
<string name="summary_bluetooth_on">Bluetooth på</string>
|
||||
<string name="summary_bluetooth_off">Bluetooth av</string>
|
||||
<string name="summary_location_on">Plats på</string>
|
||||
<string name="summary_location_off">Plats av</string>
|
||||
<string name="web_page">Webbsida</string>
|
||||
<string name="summary_experimental_shown">Experimentella funktioner visas</string>
|
||||
<string name="summary_experimental_hidden">Experimentella funktioner dolda</string>
|
||||
<string name="home_car">Bil</string>
|
||||
<string name="car_group_same">Det här är samma inställningar som Inställningar i bilen. En ändring här syns där också.</string>
|
||||
<string name="car_open_on_connect">Öppna vid anslutning</string>
|
||||
<string name="car_size">Storlek</string>
|
||||
<string name="car_app_theme">Apptema</string>
|
||||
<string name="car_dark_mode">Mörkt läge</string>
|
||||
<string name="car_buttons">Knappar</string>
|
||||
<string name="car_map_day_night">Karta dag/natt</string>
|
||||
<string name="car_units">Enheter</string>
|
||||
<string name="car_nav_bar">Navigeringsfält</string>
|
||||
<string name="car_player_nav_bar">Spelare i navigeringsfältet</string>
|
||||
<string name="car_skip_buttons">Hoppaknappar</string>
|
||||
<string name="car_song_title">Låttitel</string>
|
||||
<string name="car_player_beside_map">Spelare bredvid kartan</string>
|
||||
<string name="car_right">Höger</string>
|
||||
<string name="car_left">Vänster</string>
|
||||
<string name="car_autoplay">Spela upp vid anslutning</string>
|
||||
<string name="car_more_lyrics">Fler källor för låttexter</string>
|
||||
<string name="car_more_lyrics_note">Frågar även BetterLyrics och KuGou, som då får låtens namn och artist. Av frågar bara LRCLIB.</string>
|
||||
<string name="car_spoken_directions">Röstvägledning</string>
|
||||
<string name="car_spoken_directions_note">Stäng av den om kartappens egen röst redan hörs i bilen.</string>
|
||||
<string name="car_phone_speech">Använd telefonens taligenkänning</string>
|
||||
<string name="car_phone_speech_note">På de flesta telefoner är det Googles, som kan skicka din röst till Google.</string>
|
||||
<string name="car_warn_bluetooth">Varna när Bluetooth inte är anslutet</string>
|
||||
<string name="car_warn_location">Varna när platsen är av</string>
|
||||
<string name="car_experimental">Experimentella funktioner</string>
|
||||
<string name="car_vehicle_data">Fordonsdata</string>
|
||||
<string name="car_vehicle_data_note">Lägger till en ruta Fordonsdata: en live-dump av bilens sensorer. Fältnamnen är gissningar tills du har sett ett av dem ändras.</string>
|
||||
<string name="car_adaptive_video">Anpassad videokvalitet</string>
|
||||
<string name="car_adaptive_video_note">Sänker bildkvaliteten när bilen eller dess wifi inte hänger med, och höjer den igen när det går. Gäller från nästa anslutning.</string>
|
||||
<string name="web_page_note">Visas av appen Webbläsare i bilen. Tomt öppnar DuckDuckGo. Gäller från nästa anslutning.</string>
|
||||
<string name="car_audio">Bilens ljud</string>
|
||||
<string name="car_audio_bt">Bluetooth (rekommenderas)</string>
|
||||
<string name="car_audio_bt_sub">Ingen fråga varje resa, och kartappens röst når bilen</string>
|
||||
<string name="car_audio_usb_sub">Klarare ljud utan fördröjning. Varje resa ber Android dig dela skärmen, vilket låter Gearslip skicka ljudet</string>
|
||||
<string name="car_audio_usb_footer">Kör kommandot i Hjälp en gång, så slutar Android fråga.</string>
|
||||
<string name="car_audio_bt_footer">Samtal går via Bluetooth i båda fallen.</string>
|
||||
|
||||
<!-- Car screen size -->
|
||||
<string name="screen_car_size">Bilskärmens storlek</string>
|
||||
<string name="screen_use_cars">Använd bilens</string>
|
||||
<string name="screen_preview_size">Förhandsvisningens storlek</string>
|
||||
<string name="screen_preview_note">Storleken som bilens förhandsvisning ritas i, till exempel 1920x720.</string>
|
||||
<string name="screen_car_screen">Bilskärm</string>
|
||||
<string name="screen_footer">Gearslip använder storleken bilen anger, om du inte ställer in en egen.</string>
|
||||
<string name="screen_size">Skärmstorlek</string>
|
||||
<string name="screen_from_car">Från bilen</string>
|
||||
<string name="screen_crop">Beskär kanterna</string>
|
||||
<string name="screen_crop_sub">För en bil som skär av över- och underkant, eller sidorna</string>
|
||||
<string name="screen_squash">Tryck ihop för att passa</string>
|
||||
<string name="screen_squash_sub">För en bil som trycker ihop hela bilden, så att cirklar ser ut som ovaler</string>
|
||||
<string name="screen_preview_title">Storlek på bilens förhandsvisning</string>
|
||||
<string name="screen_last_car">Senaste bilen</string>
|
||||
<string name="screen_last_car_sub">Storleken på den senaste bilen du anslöt till</string>
|
||||
<string name="screen_this_screen">Den här skärmen</string>
|
||||
<string name="screen_this_screen_sub">Fyller den här enhetens skärm, för en bilstereo som själv kör Gearslip</string>
|
||||
<string name="screen_custom">Anpassad</string>
|
||||
<string name="screen_enter_size">Ange en storlek</string>
|
||||
<string name="screen_change_custom">Ändra den anpassade storleken</string>
|
||||
<string name="screen_width">Bredd</string>
|
||||
<string name="screen_height">Höjd</string>
|
||||
<string name="save">Spara</string>
|
||||
|
||||
<string name="screen_car_size_note">Använd detta om bilden i bilen ser utdragen ut. Ange skärmens riktiga storlek, till exempel 1280x480. Den gäller nästa gång du ansluter.</string>
|
||||
<string name="screen_set_by_you">%1$s, inställd av dig</string>
|
||||
|
||||
<!-- Help -->
|
||||
<string name="help_title">Så ansluter du</string>
|
||||
<string name="help_which_cars">Vilka bilar fungerar</string>
|
||||
<string name="help_full_list">Se hela listan</string>
|
||||
<string name="help_first_drive">Före din första resa</string>
|
||||
<string name="help_in_car">I bilen</string>
|
||||
<string name="help_usb_audio">Ljud via Gearslip</string>
|
||||
|
||||
<string name="help_aa_off">Stäng av Android Auto.</string>
|
||||
<string name="help_aa_off_detail">Inaktivera eller avinstallera Android Auto-appen, så att bilen pratar med Gearslip i stället.</string>
|
||||
<string name="help_cert">Läs in ett certifikat.</string>
|
||||
<string name="help_cert_detail">Ladda ner aasdk-certifikatet i Inställningar, som de flesta bilar godtar. Om din bil avvisar det, importera ditt eget i stället.</string>
|
||||
<string name="help_mic">Tillåt mikrofonen.</string>
|
||||
<string name="help_mic_detail">Utan den låter Android ingen app skicka musik till bilen. Gearslip lyssnar bara när du trycker på mikrofonen eller på röstknappen på ratten.</string>
|
||||
<string name="help_plug_in">Anslut med kabel eller trådlöst.</string>
|
||||
<string name="help_plug_in_detail">Gearslip öppnas av sig självt när bilen ansluter. För trådlöst, parkoppla först telefonen med bilen och tryck sedan på Anslut trådlöst på Hem.</string>
|
||||
<string name="help_pair">Parkoppla via Bluetooth för samtal.</string>
|
||||
<string name="help_pair_detail">Samtal hörs i bilen när telefonen och bilen är parkopplade. Gearslip erbjuder parkoppling första gången du ansluter.</string>
|
||||
<string name="help_answer">Svara på samtal från bilens skärm.</string>
|
||||
<string name="help_answer_detail">Ställ in Gearslip som din app för nummerpresentation och skräpsamtal. Utan det ringer samtalen ändå, men bara telefonen kan svara.</string>
|
||||
<string name="help_prompt">Slipp ljudfrågan varje resa.</string>
|
||||
<string name="help_prompt_detail">Anslut telefonen till en dator med USB-felsökning på och kör detta en gång. Hoppa över det, så trycker du på Starta varje gång du åker.</string>
|
||||
<string name="help_readable">Håll aviseringarna läsbara.</string>
|
||||
<string name="help_readable_detail">Android döljer vad aviseringarna säger medan ljud delas. Kör detta också, så häver Gearslip det bara medan musiken går till bilen.</string>
|
||||
<string name="help_which_cars_body">De flesta bilar godtar aasdk-certifikatet. Från omkring 2020 lade vissa bilar till nyare säkerhetskontroller som avvisar det, och i de bilarna behöver Gearslip ett eget certifikat från dig.</string>
|
||||
|
||||
<!-- Updates -->
|
||||
<string name="update_downloaded_ask">Nedladdad. Android frågar innan installationen.</string>
|
||||
<string name="update_install">Installera</string>
|
||||
<string name="update_download_now">Ladda ner nu. Android frågar innan installationen.</string>
|
||||
<string name="update_try_again">Försök igen</string>
|
||||
<string name="update_update">Uppdatera</string>
|
||||
<string name="update_checking">Kontrollerar…</string>
|
||||
<string name="update_latest">Du har den senaste versionen</string>
|
||||
<string name="update_download">Ladda ner</string>
|
||||
<string name="update_check_now">Kontrollera nu</string>
|
||||
<string name="update_check_auto">Kontrollera automatiskt</string>
|
||||
<string name="update_once_a_day">En gång om dagen</string>
|
||||
|
||||
<string name="update_unplug_first">Koppla från bilen för att installera. Installationen stänger Gearslip.</string>
|
||||
<string name="update_card_title">Gearslip %1$s har släppts</string>
|
||||
<string name="update_downloading">Laddar ner… %1$d %%</string>
|
||||
<string name="update_debug_manual">Felsökningsversioner kontrollerar bara när du trycker på Kontrollera nu</string>
|
||||
<string name="update_checks_daily">Gearslip kontrollerar en gång om dagen</string>
|
||||
<string name="update_version_out">Version %1$s har släppts</string>
|
||||
<string name="update_version_ready">Version %1$s är klar att installera</string>
|
||||
<string name="update_dialog_status">Version %1$s. %2$s.</string>
|
||||
|
||||
<!-- About and support -->
|
||||
<string name="about_give_regularly">Ge varje vecka eller månad</string>
|
||||
<string name="about_bmac_sub" formatted="false">Ge en gång eller varje månad, med kort. Buy Me a Coffee behåller 5 %</string>
|
||||
<string name="about_give_once">Ge en gång</string>
|
||||
<string name="about_any_amount">Valfritt belopp, med kort</string>
|
||||
<string name="about_monero_sub">Öppna din plånbok eller kopiera adressen</string>
|
||||
<string name="about_monero_title">Stöd med Monero</string>
|
||||
<string name="about_monero_body">Skicka valfritt belopp till den här adressen från din Monero-plånbok.</string>
|
||||
<string name="about_open_wallet">Öppna i plånbok</string>
|
||||
<string name="about_copy_address">Kopiera adress</string>
|
||||
<string name="close">Stäng</string>
|
||||
<string name="about_matrix">Gå med i chatten på Matrix</string>
|
||||
<string name="about_matrix_sub">Prata med andra förare om bilar, appar och Gearslip</string>
|
||||
<string name="about_translate">Hjälp till att översätta Gearslip</string>
|
||||
<string name="about_translate_sub">Rätta eller lägg till ditt språk på Weblate. Vissa översättningar började som maskinöversättningar.</string>
|
||||
<string name="about_source">Källkod</string>
|
||||
<string name="about_source_sub">Läs eller ändra Gearslips kod under AGPL</string>
|
||||
<string name="about_voice_credits">Tack för rösten</string>
|
||||
|
||||
<string name="about_no_wallet">Ingen Monero-plånboksapp hittades. Kopiera adressen i stället.</string>
|
||||
<string name="about_monero_copied">Monero-adressen har kopierats</string>
|
||||
<string name="about_voice_credits_sub">Taligenkänning med sherpa-onnx (Apache 2.0), med Kroko ASR från Banafo (CC BY-SA 4.0), samt Parakeet och Canary från NVIDIA (CC BY 4.0)</string>
|
||||
|
||||
<!-- Certificate setup -->
|
||||
<string name="certsetup_title">Certifikatinställning</string>
|
||||
<string name="certsetup_installed">Certifikatet är installerat.</string>
|
||||
<string name="certsetup_continue">Fortsätt</string>
|
||||
<string name="certsetup_failed">Nedladdningen misslyckades</string>
|
||||
<string name="certsetup_retry">Försök igen</string>
|
||||
<string name="certsetup_import_sub">En .p12-fil med sin privata nyckel. Använd din egen när bilen avvisar aasdk-certifikatet.</string>
|
||||
<string name="certsetup_skip">Hoppa över inställning av bilanslutning</string>
|
||||
|
||||
<string name="certsetup_loaded">Bilar godtar bara certifikat utfärdade av Google, och du har ett inläst.</string>
|
||||
<string name="certsetup_choose">Bilar godtar bara certifikat utfärdade av Google. Gearslip har inget inbyggt, så välj var ditt kommer ifrån.</string>
|
||||
<string name="certsetup_aasdk_sub">Projektet aasdk, med öppen källkod, publicerar det här certifikatet på github.com/opencardev/aasdk. De flesta bilar godtar det. Vissa från omkring 2020 och framåt avvisar det, och då behöver du importera ditt eget.</string>
|
||||
|
||||
<!-- Storage -->
|
||||
<string name="storage_title">Gearslips lagring</string>
|
||||
<string name="storage_measuring">Mäter…</string>
|
||||
<string name="storage_lyrics">Låttexter</string>
|
||||
<string name="storage_clear">Rensa</string>
|
||||
<string name="storage_voice">Röstmodeller</string>
|
||||
<string name="storage_remove">Ta bort</string>
|
||||
<string name="storage_logs">Gamla loggar</string>
|
||||
<string name="storage_other">Andra tillfälliga filer</string>
|
||||
<string name="storage_delete_all">Radera all Gearslip-data</string>
|
||||
|
||||
<string name="storage_total">Gearslips data tar %1$s av telefonens lagring.</string>
|
||||
<plurals name="storage_lyrics_detail">
|
||||
<item quantity="one">%1$d låt · %2$s. Rensade låttexter laddas ner igen när du öppnar dem.</item>
|
||||
<item quantity="other">%1$d låtar · %2$s. Rensade låttexter laddas ner igen när du öppnar dem.</item>
|
||||
</plurals>
|
||||
<string name="storage_voice_detail">%1$s. Röstkommandon behöver dem; ladda ner dem igen i Inställningar.</string>
|
||||
<string name="storage_logs_detail">%1$s. Loggar från tidigare starter; den här startens logg finns kvar.</string>
|
||||
<string name="storage_other_detail">%1$s. Nedladdade uppdateringar och andra filer som Gearslip kan skapa igen.</string>
|
||||
<string name="storage_delete_armed">Tryck på Radera igen för att radera inställningar, certifikat, röstmodeller och loggar. Gearslip stängs och börjar om från inställningen nästa gång.</string>
|
||||
<string name="storage_delete_sub">Inställningar, certifikat, röstmodeller, låttexter och loggar.</string>
|
||||
<string name="storage_delete">Radera</string>
|
||||
<string name="storage_delete_ellipsis">Radera…</string>
|
||||
|
||||
<!-- Donate prompt -->
|
||||
<string name="donate_title">Kul att resan fungerade</string>
|
||||
<string name="donate_liberapay">Stöd på Liberapay</string>
|
||||
<string name="donate_bmac">Bjud mig på en kaffe</string>
|
||||
<string name="donate_card">Ge en gång med kort</string>
|
||||
|
||||
<string name="donate_body">Gearslip är gratis, utan reklam och utan spårning. Om det tar dig ut på vägen håller ditt stöd det igång.</string>
|
||||
|
||||
<!-- Wireless -->
|
||||
<string name="wireless_needs_permission">Gearslip behöver åtkomst till Bluetooth och wifi i närheten för detta.</string>
|
||||
<string name="wireless_enable_permission">Aktivera behörigheten Enheter i närheten</string>
|
||||
<string name="wireless_connect">Anslut trådlöst</string>
|
||||
<string name="wireless_which_car">Vilken bil?</string>
|
||||
<string name="wireless_none_paired">Telefonen är inte parkopplad med något än. Parkoppla den med bilen först.</string>
|
||||
<string name="wireless_not_listed">Finns inte bilen med? Parkoppla den med telefonen först.</string>
|
||||
<string name="bluetooth_settings">Bluetooth-inställningar</string>
|
||||
|
||||
<!-- Voice models -->
|
||||
<string name="voice_live_title">Röst: ord i realtid</string>
|
||||
<string name="voice_final_title">Röst: slutliga ord</string>
|
||||
<string name="voice_none">Ingen</string>
|
||||
<string name="voice_none_sub">Behåll orden i realtid. Snabbast, minst exakt</string>
|
||||
|
||||
<string name="voice_live_body">Röstassistenten gör om tal till text på den här telefonen, och inget du säger lämnar den. Den behöver två modeller, cirka 270 MB tillsammans, som laddas ner en gång. Den här skriver dina ord medan du pratar.</string>
|
||||
<string name="voice_final_body">När du slutar prata läser den här modellen hela frasen igen. Den fångar namn och platser som realtidsmodellen missar.</string>
|
||||
<string name="voice_model_size">%1$s · %2$d MB</string>
|
||||
<string name="voice_downloaded">Nedladdad. %1$s</string>
|
||||
<string name="voice_downloading">Laddar ner, %1$d %%</string>
|
||||
|
||||
<!-- Home -->
|
||||
<string name="home_help">Hjälp</string>
|
||||
<string name="home_preview">Förhandsvisning</string>
|
||||
<string name="home_logs">Loggar</string>
|
||||
<string name="home_disconnect">Koppla från</string>
|
||||
<string name="home_bt_title">Bluetooth är inte anslutet</string>
|
||||
|
||||
<string name="home_debug_build">%1$s · felsökningsversion</string>
|
||||
<string name="home_bt_body">Musiken spelas via Bluetooth, men telefonen är inte ansluten till bilen. Anslut den här eller från bilens Bluetooth-meny.</string>
|
||||
<string name="home_voice_downloading">Laddar ner röst, %1$d %%</string>
|
||||
|
||||
<!-- Car audio dialogs -->
|
||||
<string name="audio_bt_title">Spela bilens ljud via Bluetooth?</string>
|
||||
<string name="audio_never_ask">Fråga aldrig, med en dator</string>
|
||||
<string name="audio_use_bt">Använd Bluetooth</string>
|
||||
<string name="audio_keep_asking">Fortsätt fråga</string>
|
||||
<string name="audio_mic_title">Bilens ljud behöver mikrofonbehörigheten</string>
|
||||
<string name="audio_open_settings">Öppna appinställningar</string>
|
||||
|
||||
<string name="audio_bt_body">Android behandlar musiken som Gearslip skickar till bilen som skärmdelning, så den frågar varje resa, via kabel eller trådlöst. Via Bluetooth frågar den aldrig. Du kan ändra detta senare i Inställningar.</string>
|
||||
<string name="audio_keep_usb">Eller låt ljudet gå via Gearslip och slipp frågan för gott: anslut telefonen till en dator med USB-felsökning på och kör detta en gång.</string>
|
||||
<string name="audio_mic_body">Android låter en app skicka andra appars ljud till bilen bara om den har mikrofonbehörighet. Den är avstängd för Gearslip, så musiken spelas på telefonen. Slå på den under Behörigheter och sedan Mikrofon.</string>
|
||||
|
||||
<!-- Phone keyboard -->
|
||||
<string name="typing_close">Stäng bilens tangentbord</string>
|
||||
<string name="typing_label">Skriv för bilens skärm</string>
|
||||
<string name="typing_search">Sök på bilens skärm</string>
|
||||
|
||||
<!-- Copyable command -->
|
||||
<string name="command_tap_copy">Tryck för att kopiera</string>
|
||||
<string name="command_copied">Kommandot har kopierats</string>
|
||||
<string name="whats_new_this_version">%1$s (den här versionen)</string>
|
||||
|
||||
<!-- Car settings screen -->
|
||||
<string name="carset_theme">Tema</string>
|
||||
<string name="carset_imperial">Brittiska (mi, ft, °F)</string>
|
||||
<string name="carset_metric">Metriska (km, m, °C)</string>
|
||||
<string name="carset_screen_size">Skärmstorlek (nästa anslutning)</string>
|
||||
<string name="carset_fit">Anpassa till skärmen</string>
|
||||
<string name="off">Av</string>
|
||||
<string name="on">På</string>
|
||||
<string name="carset_lyrics_from">Låttexter från</string>
|
||||
<string name="carset_more_sources">Fler källor</string>
|
||||
<string name="carset_hide">Dölj</string>
|
||||
<string name="carset_show">Visa</string>
|
||||
<string name="carset_phone">Telefon</string>
|
||||
<string name="carset_vehicle">Fordon</string>
|
||||
<string name="carset_video">Video</string>
|
||||
<string name="carset_version">Version</string>
|
||||
<string name="carset_build">Bygge</string>
|
||||
<string name="carset_whats_new_after">Nyheter efter uppdateringar</string>
|
||||
<string name="carset_whats_new_after_sub">Visa listan över ändringar en gång, första gången bilen ansluter efter en uppdatering.</string>
|
||||
<string name="carset_web_default">DuckDuckGo (standard) - tryck för att välja en annan sida</string>
|
||||
<string name="carset_web_note">Visas av appen Webbläsare i startprogrammet. Gäller från nästa anslutning.</string>
|
||||
<string name="carset_screen_fit">Skärmanpassning</string>
|
||||
<string name="carset_top">Överst</string>
|
||||
<string name="carset_bottom">Nederst</string>
|
||||
<string name="carset_save_vehicle">Spara för det här fordonet</string>
|
||||
<string name="carset_no_profile">Ingen fordonsprofil matchade</string>
|
||||
|
||||
<string name="carset_audio_note">Via Gearslip skickar telefonens ljud över bilanslutningen, via kabel eller trådlöst. Android ber dig dela skärmen varje resa, och några appar blockerar det. Bluetooth spelar som telefonen normalt gör, och Android frågar aldrig.</string>
|
||||
<string name="carset_directions_note">Gearslip läser upp varje sväng i bilens högtalare, och musiken sänks medan den pratar. Stäng av den om kartappens egen röst redan hörs i bilen.</string>
|
||||
<string name="carset_phone_speech_note">Lyssnar via telefonens egen taltjänst i stället för Gearslips. På de flesta telefoner är det Googles, som kan skicka din röst till Google. GrapheneOS har ingen, så Gearslip fortsätter med sin egen.</string>
|
||||
<string name="carset_vehicle_data_note">Lägger till en ruta Fordonsdata: en rå live-dump av bilstereons sensorkanal. Fältetiketterna (hastighet, varvtal, bränsle...) kommer från andra reverse engineering-projekt och är inte kontrollerade mot den här bilen. Se dem som en gissning, inte ett faktum, tills du själv har sett en ändras.</string>
|
||||
<string name="carset_adaptive_note">Sänker bildkvaliteten när bilen, eller dess wifi, inte hänger med, och höjer den igen när det går. Gäller från nästa anslutning.</string>
|
||||
<string name="carset_usable_height">Användbar höjd %1$dpx av 480</string>
|
||||
<string name="carset_saved_to">Sparat i %1$s</string>
|
||||
<string name="carset_save_failed">Kunde inte spara</string>
|
||||
<string name="carset_px">%1$dpx</string>
|
||||
|
||||
<!-- Car media app -->
|
||||
<string name="media_open_on_phone">Öppna på telefonen</string>
|
||||
<string name="media_now_playing">Spelas nu</string>
|
||||
<string name="media_up_next">Härnäst</string>
|
||||
<string name="media_browse">Bläddra</string>
|
||||
<string name="media_search_hint">Skriv en låt, artist, album eller spellista.</string>
|
||||
<string name="media_looking_lyrics">Letar efter låttext…</string>
|
||||
<string name="media_nothing_playing">Inget spelas</string>
|
||||
<string name="media_press_play">Tryck på spela för att fortsätta där du slutade, eller hitta något i Bläddra.</string>
|
||||
|
||||
<string name="media_keeps_library">%1$s håller sitt bibliotek för sig själv.</string>
|
||||
<string name="media_needs_access">Gearslip kan ändå styra den när den har aviseringsåtkomst på telefonen. Tillåt det under Inställningar > Aviseringar > Aviseringsåtkomst.</string>
|
||||
<string name="media_not_running">%1$s körs inte.</string>
|
||||
<string name="media_not_running_body">%1$s spelas via Gearslips egen spelare, men bara medan appen är öppen på telefonen. Öppna den en gång så fortsätter den spela här i bilen.</string>
|
||||
<string name="media_open_app">Öppna %1$s</string>
|
||||
<string name="media_refused">%1$s lät inte Gearslip bläddra i den.</string>
|
||||
<string name="media_refused_body">Vissa medieappar godtar bara Googles egen värd.</string>
|
||||
<string name="media_connecting">Ansluter till %1$s…</string>
|
||||
<string name="media_search">Sök i %1$s</string>
|
||||
<string name="media_search_no_list">%1$s kan inte lista resultat. Tryck på sök, så väljer %1$s vad som spelas.</string>
|
||||
<string name="media_search_unsupported">%1$s kan inte söka från bilen.</string>
|
||||
<string name="media_searching">Söker i %1$s…</string>
|
||||
<string name="media_search_nothing">%1$s hittade inget för ”%2$s”.</string>
|
||||
<string name="media_search_ignored">%1$s spelade inget för ”%2$s”.</string>
|
||||
<string name="media_searching_for">Söker efter ”%2$s” i %1$s…</string>
|
||||
<string name="media_nothing_playing_dot">Inget spelas.</string>
|
||||
<string name="media_no_lyrics">Ingen låttext hittades för det här spåret.</string>
|
||||
|
||||
<!-- Car media browsing -->
|
||||
<string name="media_up">Upp</string>
|
||||
<string name="media_loading">Läser in…</string>
|
||||
<string name="media_browse_start">Starta musik i appen på telefonen. Spela, pausa, hoppa och spola fungerar härifrån.</string>
|
||||
<string name="media_load_failed">Kunde inte läsa in listan.</string>
|
||||
<string name="media_nothing_here">Inget här.</string>
|
||||
<string name="media_nothing_queued">Inget i kön.</string>
|
||||
<string name="media_play">Spela</string>
|
||||
|
||||
<string name="media_queue_empty_idle">Inget spelas - alla appar berättar inte för Gearslip vad som kommer härnäst.</string>
|
||||
<string name="media_queue_empty_playing">Bara det som redan spelas - alla appar berättar inte för Gearslip vad som kommer härnäst.</string>
|
||||
|
||||
<!-- Player controls -->
|
||||
<string name="player_previous">Föregående</string>
|
||||
<string name="player_next">Nästa</string>
|
||||
<string name="player_repeat">Upprepa</string>
|
||||
<string name="player_pause">Pausa</string>
|
||||
<string name="player_unlike">Ta bort gilla</string>
|
||||
<string name="player_like">Gilla</string>
|
||||
<string name="player_shuffle_off">Blanda av</string>
|
||||
<string name="player_shuffle_on">Blanda på</string>
|
||||
|
||||
<!-- Car home -->
|
||||
<string name="home_choose_app">Välj en annan app</string>
|
||||
<string name="home_no_maps">Inga kartappar hittades.</string>
|
||||
<string name="home_no_maps_body">Installera en navigeringsapp som stöder Android Auto, så startar den här av sig själv.</string>
|
||||
<string name="home_map_stopped">Kartan stoppad</string>
|
||||
<string name="home_map_stopped_body">Den förblir stängd tills du öppnar den igen.</string>
|
||||
<string name="home_reopen_map">Öppna kartan igen</string>
|
||||
<string name="home_car_apps">Bilappar</string>
|
||||
<string name="home_back_nav">Tillbaka till navigeringsfältet</string>
|
||||
<string name="home_show_player">Visa spelaren</string>
|
||||
<string name="home_hide_lyrics">Dölj låttext</string>
|
||||
|
||||
<string name="home_starting">Startar %1$s…</string>
|
||||
<string name="home_starting_map">Startar kartan…</string>
|
||||
<string name="home_failed">%1$s ville inte starta.</string>
|
||||
<string name="home_failed_map">Kartappen ville inte starta.</string>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<string name="crumb_home">Hem</string>
|
||||
<string name="crumb_media">Media</string>
|
||||
<string name="crumb_apps">Appar</string>
|
||||
<string name="crumb_dashboard">Instrumentpanel</string>
|
||||
<string name="crumb_weather">Väder</string>
|
||||
<string name="crumb_reply">Svara</string>
|
||||
|
||||
<!-- Built-in apps -->
|
||||
<string name="app_browser">Webbläsare</string>
|
||||
<string name="app_screen_sharing">Skärmdelning</string>
|
||||
<string name="app_exit">Avsluta</string>
|
||||
<string name="navapps_unpin">Lossa från navigeringsfältet</string>
|
||||
<string name="navapps_full">Navigeringsfältet är fullt (%1$d fästa)</string>
|
||||
<string name="navapps_pin">Fäst i navigeringsfältet</string>
|
||||
<string name="navapps_close">Stäng appen</string>
|
||||
<string name="navapps_move_left">Flytta åt vänster</string>
|
||||
<string name="navapps_move_right">Flytta åt höger</string>
|
||||
<string name="navapps_reset">Återställ appordningen</string>
|
||||
|
||||
<!-- Message popup -->
|
||||
<string name="msg_dismiss">Stäng</string>
|
||||
<string name="msg_read">Läs upp</string>
|
||||
<string name="msg_stop">Stoppa</string>
|
||||
<string name="msg_no">Nej</string>
|
||||
<string name="msg_send_now">Skicka nu</string>
|
||||
<string name="msg_type">Skriv</string>
|
||||
|
||||
<plurals name="msg_burst">
|
||||
<item quantity="one">%1$s · %2$d nytt</item>
|
||||
<item quantity="other">%1$s · %2$d nya</item>
|
||||
</plurals>
|
||||
<string name="msg_reply_to">Svara %1$s</string>
|
||||
<string name="msg_listening">Lyssnar</string>
|
||||
<string name="msg_sending_to">Skickar till %1$s</string>
|
||||
<string name="msg_sent_to">Skickat till %1$s</string>
|
||||
<string name="msg_reply_failed">Kunde inte svara</string>
|
||||
|
||||
<!-- Bluetooth pairing -->
|
||||
<string name="bt_pair_unfinished">Parkopplingen blev inte klar. Parkoppla från bilens Bluetooth-meny i stället.</string>
|
||||
<string name="bt_pair_not_started">Parkopplingen startade inte. Parkoppla från bilens Bluetooth-meny i stället.</string>
|
||||
<string name="bt_paired">Parkopplad. Samtal hörs nu i bilen.</string>
|
||||
|
||||
<!-- Car overlays -->
|
||||
<string name="overlay_dont_show">Visa inte igen</string>
|
||||
<string name="overlay_ok">OK</string>
|
||||
<string name="overlay_location_off">Platsen är av</string>
|
||||
<string name="overlay_pair_title">Parkoppla med den här bilen?</string>
|
||||
<string name="overlay_pair">Parkoppla</string>
|
||||
<string name="overlay_confirm_title">Bekräfta på telefonen</string>
|
||||
<string name="overlay_confirm_body">Kontrollera koden på telefonen och tryck på Parkoppla. Bilen kan också fråga.</string>
|
||||
|
||||
<string name="overlay_bt_body">Musiken spelas via Bluetooth, men telefonen är inte ansluten till bilen. Anslut den från bilens eller telefonens Bluetooth-meny.</string>
|
||||
<string name="overlay_location_body">Kartappar kan inte visa var du är eller ge vägbeskrivningar. Slå på platsen i telefonens snabbinställningar när du har parkerat.</string>
|
||||
<string name="overlay_pair_body_music">Samtal och musik hörs i bilens högtalare när telefonen och bilen är parkopplade. Telefonen ber dig sedan bekräfta en kod.</string>
|
||||
<string name="overlay_pair_body">Samtal hörs i bilens högtalare när telefonen och bilen är parkopplade. Telefonen ber dig sedan bekräfta en kod.</string>
|
||||
|
||||
<!-- Weather -->
|
||||
<string name="weather_waiting_fix">Väntar på platsbestämning.</string>
|
||||
<string name="weather_allow_location">Tillåt plats för Gearslip på telefonen för att se vädret.</string>
|
||||
<string name="weather_unreachable">Kunde inte nå vädertjänsten.</string>
|
||||
<string name="weather_clear">Klart</string>
|
||||
<string name="weather_mostly_clear">Mestadels klart</string>
|
||||
<string name="weather_partly_cloudy">Växlande molnighet</string>
|
||||
<string name="weather_overcast">Mulet</string>
|
||||
<string name="weather_fog">Dimma</string>
|
||||
<string name="weather_drizzle">Duggregn</string>
|
||||
<string name="weather_rain">Regn</string>
|
||||
<string name="weather_snow">Snö</string>
|
||||
<string name="weather_rain_showers">Regnskurar</string>
|
||||
<string name="weather_snow_showers">Snöbyar</string>
|
||||
<string name="weather_thunderstorm">Åska</string>
|
||||
|
||||
<!-- Weather screen -->
|
||||
<string name="weather_getting">Hämtar vädret…</string>
|
||||
<string name="weather_next_hours">Kommande timmar</string>
|
||||
<string name="weather_this_week">Den här veckan</string>
|
||||
<string name="weather_today">I dag</string>
|
||||
<string name="weather_credit">Väderdata från Open-Meteo.com (CC BY 4.0)</string>
|
||||
|
||||
<string name="weather_feels_like">Känns som %1$s</string>
|
||||
<string name="weather_humidity">Luftfuktighet %1$d %%</string>
|
||||
<string name="weather_wind">Vind %1$s</string>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<string name="dash_today">I dag</string>
|
||||
<string name="dash_getting_weather">Hämtar väder…</string>
|
||||
<string name="dash_coming_up">På gång</string>
|
||||
<string name="dash_loading_calendar">Läser in kalendern…</string>
|
||||
<string name="dash_nothing_week">Inget den kommande veckan.</string>
|
||||
<string name="dash_all_day">Heldag</string>
|
||||
|
||||
<!-- Phone dialer -->
|
||||
<string name="dialer_contacts">Kontakter</string>
|
||||
<string name="dialer_keypad">Knappsats</string>
|
||||
<string name="dialer_ask">Fråga på telefonen</string>
|
||||
<string name="dialer_enter_number">Ange ett nummer</string>
|
||||
<string name="dialer_call">Ring</string>
|
||||
<string name="dialer_no_contacts">Inga kontakter med telefonnummer hittades.</string>
|
||||
<string name="dialer_search">Sök kontakter</string>
|
||||
|
||||
<string name="dialer_needs_permission">Gearslip behöver behörigheterna Telefon och Kontakter för att ringa härifrån. Tillåt dem på telefonen när du har parkerat.</string>
|
||||
<string name="dialer_no_match">Inga kontakter matchar ”%1$s”.</string>
|
||||
<string name="dialer_call_who">Ringa %1$s?</string>
|
||||
|
||||
<!-- Car screen -->
|
||||
<string name="carui_exit_fullscreen">Avsluta helskärm</string>
|
||||
<string name="carui_drive_safely">Kör säkert</string>
|
||||
<string name="carui_understand">Jag förstår</string>
|
||||
|
||||
<string name="carui_unofficial">Gearslip är inofficiell programvara: den är inte gjord eller granskad av någon biltillverkare, och den kan sluta fungera utan förvarning.</string>
|
||||
<string name="carui_never_interact">Använd aldrig den här skärmen medan du kör. Ställ in rutten eller låten innan du åker och håll sedan uppmärksamheten på vägen.</string>
|
||||
<string name="carui_this_app">Den här appen</string>
|
||||
<string name="carui_app_unsupported">%1$s fungerar inte med Gearslip än.</string>
|
||||
<string name="carui_app_unsupported_body">Den startade inte. Vissa appar körs bara i Googles Android Auto.</string>
|
||||
|
||||
<!-- Nav bar -->
|
||||
<string name="navbar_on_call">I samtal</string>
|
||||
<string name="navbar_end_call">Avsluta samtal</string>
|
||||
<string name="navbar_unmute">Slå på ljudet</string>
|
||||
<string name="navbar_mute">Stäng av ljudet</string>
|
||||
<string name="navbar_close_panel">Stäng sidopanelen</string>
|
||||
<string name="navbar_dock_player">Flytta spelaren bredvid kartan</string>
|
||||
|
||||
<!-- Browser -->
|
||||
<string name="web_search">Sök eller skriv en adress</string>
|
||||
<string name="web_forward">Framåt</string>
|
||||
<string name="web_reload">Läs in igen</string>
|
||||
<string name="web_start_page">Startsida</string>
|
||||
<string name="web_fullscreen">Helskärm</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notif_title">Aviseringar</string>
|
||||
<string name="notif_clear_all">Rensa alla</string>
|
||||
<string name="notif_empty">Inget än. Nya aviseringar från telefonen visas här.</string>
|
||||
<string name="notif_off">Aviseringar är av. Tillåt Gearslip under Inställningar > Aviseringar > Aviseringsåtkomst på telefonen.</string>
|
||||
<string name="notif_replied">Besvarad</string>
|
||||
|
||||
<!-- Messages -->
|
||||
<string name="msg_mark_read">Markera som läst</string>
|
||||
<string name="msg_messages">Meddelanden</string>
|
||||
<string name="msg_none_recent">Inga nya meddelanden. Nya meddelanden från %1$s visas här när de kommer till telefonen.</string>
|
||||
<plurals name="msg_earlier">
|
||||
<item quantity="one">%1$d tidigare</item>
|
||||
<item quantity="other">%1$d tidigare</item>
|
||||
</plurals>
|
||||
<string name="msg_hide_quick">Dölj snabbsvar</string>
|
||||
<string name="msg_quick">Snabbsvar</string>
|
||||
<string name="msg_you">Du: </string>
|
||||
<string name="msg_sender">%1$s: </string>
|
||||
|
||||
<!-- Car app navigation -->
|
||||
<string name="nav_finding_route">Söker rutt…</string>
|
||||
<string name="nav_then">Sedan</string>
|
||||
<string name="nav_stop">Avsluta navigering</string>
|
||||
<string name="nav_finding_routes">Söker rutter…</string>
|
||||
<string name="nav_lane_recommended">Rekommenderat körfält</string>
|
||||
<string name="nav_lane">Körfält</string>
|
||||
<string name="nav_arrive">Ankomst %1$s</string>
|
||||
|
||||
<!-- Car app screens -->
|
||||
<string name="template_nothing_playing">Inget spelas än.</string>
|
||||
<string name="template_search">Sök</string>
|
||||
<string name="template_start">Starta</string>
|
||||
|
||||
<!-- Calls -->
|
||||
<string name="call_incoming">Inkommande samtal</string>
|
||||
<string name="call_decline">Avvisa</string>
|
||||
<string name="call_answer">Svara</string>
|
||||
<string name="call_ringing">Ringer redan högt - svara här, eller låt det ringa.</string>
|
||||
|
||||
<!-- Screen sharing -->
|
||||
<string name="phoneapps_touch_off">Tryck är av. Aktivera Gearslip under Inställningar > Tillgänglighet på telefonen.</string>
|
||||
|
||||
<!-- What's new in the car -->
|
||||
<string name="whatsnew_show_after">Visa efter uppdateringar</string>
|
||||
<string name="whatsnew_got_it">Uppfattat</string>
|
||||
<string name="whatsnew_title">Nyheter i version %1$s</string>
|
||||
|
||||
<!-- Car keyboard -->
|
||||
<string name="keyboard_space">mellanslag</string>
|
||||
<string name="keyboard_hide">Dölj tangentbordet</string>
|
||||
<string name="keyboard_submit">Skicka</string>
|
||||
|
||||
<!-- Launcher -->
|
||||
<string name="launcher_broken">Fungerar inte med Gearslip</string>
|
||||
|
||||
<!-- Car app stage -->
|
||||
<string name="stage_car_app">Bilapp</string>
|
||||
<string name="stage_close">Stäng %1$s</string>
|
||||
<string name="stage_close_app">Stäng appen</string>
|
||||
|
||||
<!-- Spoken directions -->
|
||||
<string name="dir_slight_left">Sväng svagt vänster</string>
|
||||
<string name="dir_slight_left_onto">Sväng svagt vänster in på %1$s</string>
|
||||
<string name="dir_slight_right">Sväng svagt höger</string>
|
||||
<string name="dir_slight_right_onto">Sväng svagt höger in på %1$s</string>
|
||||
<string name="dir_left">Sväng vänster</string>
|
||||
<string name="dir_left_onto">Sväng vänster in på %1$s</string>
|
||||
<string name="dir_right">Sväng höger</string>
|
||||
<string name="dir_right_onto">Sväng höger in på %1$s</string>
|
||||
<string name="dir_sharp_left">Sväng skarpt vänster</string>
|
||||
<string name="dir_sharp_left_onto">Sväng skarpt vänster in på %1$s</string>
|
||||
<string name="dir_sharp_right">Sväng skarpt höger</string>
|
||||
<string name="dir_sharp_right_onto">Sväng skarpt höger in på %1$s</string>
|
||||
<string name="dir_u_turn">Gör en U-sväng</string>
|
||||
<string name="dir_keep_left">Håll till vänster</string>
|
||||
<string name="dir_keep_left_toward">Håll till vänster mot %1$s</string>
|
||||
<string name="dir_keep_right">Håll till höger</string>
|
||||
<string name="dir_keep_right_toward">Håll till höger mot %1$s</string>
|
||||
<string name="dir_fork_left">Håll till vänster vid vägdelningen</string>
|
||||
<string name="dir_fork_left_toward">Håll till vänster vid vägdelningen mot %1$s</string>
|
||||
<string name="dir_fork_right">Håll till höger vid vägdelningen</string>
|
||||
<string name="dir_fork_right_toward">Håll till höger vid vägdelningen mot %1$s</string>
|
||||
<string name="dir_ramp_left">Ta påfarten till vänster</string>
|
||||
<string name="dir_ramp_left_onto">Ta påfarten till vänster in på %1$s</string>
|
||||
<string name="dir_ramp_right">Ta påfarten till höger</string>
|
||||
<string name="dir_ramp_right_onto">Ta påfarten till höger in på %1$s</string>
|
||||
<string name="dir_exit_left">Ta avfarten till vänster</string>
|
||||
<string name="dir_exit_left_toward">Ta avfarten till vänster mot %1$s</string>
|
||||
<string name="dir_exit_right">Ta avfarten till höger</string>
|
||||
<string name="dir_exit_right_toward">Ta avfarten till höger mot %1$s</string>
|
||||
<string name="dir_merge_left">Byt in till vänster</string>
|
||||
<string name="dir_merge_left_onto">Byt in till vänster på %1$s</string>
|
||||
<string name="dir_merge_right">Byt in till höger</string>
|
||||
<string name="dir_merge_right_onto">Byt in till höger på %1$s</string>
|
||||
<string name="dir_merge">Kör in i trafiken</string>
|
||||
<string name="dir_merge_onto">Kör in på %1$s</string>
|
||||
<string name="dir_roundabout_through">Kör genom rondellen</string>
|
||||
<string name="dir_roundabout_through_onto">Kör genom rondellen in på %1$s</string>
|
||||
<string name="dir_roundabout_enter">Kör in i rondellen</string>
|
||||
<string name="dir_roundabout_exit">Kör ut ur rondellen</string>
|
||||
<string name="dir_roundabout_exit_onto">Kör ut ur rondellen in på %1$s</string>
|
||||
<string name="dir_straight">Fortsätt rakt fram</string>
|
||||
<string name="dir_straight_onto">Fortsätt rakt fram in på %1$s</string>
|
||||
<string name="dir_continue">Fortsätt</string>
|
||||
<string name="dir_continue_onto">Fortsätt in på %1$s</string>
|
||||
<string name="dir_ferry">Ta färjan</string>
|
||||
<string name="dir_train">Ta tåget</string>
|
||||
<string name="dir_destination">Ditt mål ligger framför dig</string>
|
||||
<string name="dir_destination_left">Ditt mål ligger till vänster</string>
|
||||
<string name="dir_destination_right">Ditt mål ligger till höger</string>
|
||||
<string name="dir_roundabout_take">Ta den %1$s avfarten i rondellen</string>
|
||||
<string name="dir_roundabout_take_onto">Ta den %1$s avfarten i rondellen in på %2$s</string>
|
||||
<string name="dir_ordinal_1">första</string>
|
||||
<string name="dir_ordinal_2">andra</string>
|
||||
<string name="dir_ordinal_3">tredje</string>
|
||||
<string name="dir_ordinal_4">fjärde</string>
|
||||
<string name="dir_ordinal_5">femte</string>
|
||||
<string name="dir_ordinal_6">sjätte</string>
|
||||
<string name="dir_ordinal_7">sjunde</string>
|
||||
<string name="dir_ordinal_8">åttonde</string>
|
||||
<string name="dir_in_distance">Om %1$s, %2$s</string>
|
||||
<plurals name="dir_km">
|
||||
<item quantity="one">%1$s kilometer</item>
|
||||
<item quantity="other">%1$s kilometer</item>
|
||||
</plurals>
|
||||
<string name="dir_km_decimal">%1$s kilometer</string>
|
||||
<string name="dir_quarter_mile">en kvarts mile</string>
|
||||
<string name="dir_half_mile">en halv mile</string>
|
||||
<string name="dir_three_quarter_mile">tre kvarts mile</string>
|
||||
<plurals name="dir_miles">
|
||||
<item quantity="one">%1$s mile</item>
|
||||
<item quantity="other">%1$s miles</item>
|
||||
</plurals>
|
||||
<string name="dir_miles_decimal">%1$s miles</string>
|
||||
<plurals name="dir_feet">
|
||||
<item quantity="one">%1$s fot</item>
|
||||
<item quantity="other">%1$s fot</item>
|
||||
</plurals>
|
||||
<plurals name="dir_yards">
|
||||
<item quantity="one">%1$s yard</item>
|
||||
<item quantity="other">%1$s yards</item>
|
||||
</plurals>
|
||||
<plurals name="dir_meters">
|
||||
<item quantity="one">%1$d meter</item>
|
||||
<item quantity="other">%1$d meter</item>
|
||||
</plurals>
|
||||
|
||||
<string name="certsetup_installed_hint">Om bilen inte ansluter kanske den avvisar det här certifikatet. Importera ditt eget i Inställningar, under Certifikat.</string>
|
||||
<string name="cert_downloaded_footer">Ansluter inte bilen? Vissa bilar från omkring 2020 och framåt avvisar aasdk-certifikatet, så importera ditt eget i stället. Ändringar gäller från nästa anslutning.</string>
|
||||
|
||||
<!-- Cluster screen -->
|
||||
<string name="car_cluster_video">Svängkort på instrumentskärmen</string>
|
||||
<string name="car_cluster_video_note">För en bil med en andra skärm i instrumentklustret: Gearslip visar nästa sväng där. Gäller från nästa anslutning.</string>
|
||||
|
||||
<!-- Connection status on Home -->
|
||||
<string name="status_idle">Inte ansluten</string>
|
||||
<string name="status_idle_detail">Anslut till bilens USB-port, eller anslut trådlöst. Gearslip öppnas av sig självt när bilen svarar.</string>
|
||||
<string name="status_connecting">Ansluter</string>
|
||||
<string name="status_projecting">Projicerar</string>
|
||||
<string name="status_projecting_detail">Strömmar till bilstereon.</string>
|
||||
<string name="status_disconnected">Frånkopplad</string>
|
||||
<string name="status_disconnected_detail">Bilstereon är inte längre ansluten.</string>
|
||||
<string name="places_title">Sparade platser</string>
|
||||
<string name="places_sub">Säg ”navigate home” i valfri kartapp</string>
|
||||
<string name="places_intro">Säg ”navigate home”, ”take me to work” eller ”navigate to” med ett eget namn, så skickar Gearslip adressen till din kartapp. Röstkommandon fungerar bara på engelska än så länge.</string>
|
||||
<string name="places_home">Hem</string>
|
||||
<string name="places_work">Jobb</string>
|
||||
<string name="places_other">Plats %1$d</string>
|
||||
<string name="places_name">Namn</string>
|
||||
<string name="places_address">Adress</string>
|
||||
<string name="places_here">Använd min plats</string>
|
||||
<string name="places_finding">Söker…</string>
|
||||
<string name="places_not_found">Gearslip hittade inte var du är. Kontrollera att platstjänster är på.</string>
|
||||
<string name="places_clear">Rensa</string>
|
||||
</resources>
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user