Compare commits

...
49 Commits
Author SHA1 Message Date
darken 92dac661c4 fix(device): Keep a log line for rejected malformed addresses
Structurally broken addresses now fail the octet parse instead of
throwing, so the catch-all error log no longer fires for them. Log the
rejection at WARN with a redacted address so support logs still show it.

Fixes review finding F1.
2026-08-31 15:54:51 +02:00
darken 7712b18388 fix(monitor): Re-arm the teardown when a start request is short-circuited
In AUTOMATIC mode with nothing connected the session is torn down 15 seconds
after the state that said so, and only a new state emission cancels that. A
start request arriving while a session is live is acknowledged and returns
without touching the pending teardown, and the connection state that would
abort it lags the Bluetooth event that caused the start by roughly 0.75s. A
request landing in the tail of the window was therefore answered with
"keeping current session" and the session went down anyway, taking every
reaction with it — in one recording the popup reaction was down for 34
seconds spanning an entire lid cycle.

The short-circuit now bumps a start signal that the mode pipeline combines
in, so the bump cancels the pending inner flow through the existing
cancellation topology and arms a fresh window. The countdown also re-reads
the signal after its delay, which closes the case where the bump lands while
an expired countdown is already unwinding. A re-armed window runs 15 seconds
from the start request, so the total dwell can exceed 15 seconds: the request
is fresh evidence of activity.

The decision segment moves to a top-level internal function so it can be
driven directly in tests, following MonitorModeState and
buildMonitorModeState which are top-level for the same reason.
2026-08-31 15:54:51 +02:00
darken d7988189a0 fix(reaction): Stamp connect times from the ACL broadcast
The connection timestamp cache was only maintained while the connected-devices
flow had a subscriber. If the process died while a device stayed connected and
that device then disconnected and reconnected with nothing running, the
restarted collection found the old entry still keyed by a currently-connected
address, kept it through the prune, and reported the original connect time —
so the popup age check rejected an arbitrarily old connection.

The ACL broadcasts arrive at a manifest-registered receiver that wakes the
process regardless of any flow subscription, so the stamp is taken there
instead: connect stamps (keeping an existing one, which is the earlier and
therefore truer time), disconnect drops the entry. The flow keeps its own
stamp-on-first-sight and prune as a backstop for the force-stopped state and
missed broadcasts.

ACL_DISCONNECTED was already in the receiver's expected actions but was never
registered in the manifest. It does not start the monitor: a disconnect is not
a reason to start monitoring, and the start triggers here are deliberately
conservative.
2026-08-31 15:54:51 +02:00
darken ff5d0cedfa fix(reaction): Stop bonded-device queries from poisoning connect times
seenFirstAt on a connected device is meant to be the connect time, and the
connected-devices flow maintains that by pruning its cache to the currently
connected addresses on every emission. bondedDevices() wrote into the same
cache for every bonded device, connected or not, and never pruned.

AapAutoConnect queries bonded devices on every connected-devices emission,
the disconnect one included, so an entry pruned at disconnect was re-stamped
milliseconds later at disconnect time. The next reconnect then inherited the
previous disconnect as its connect time, and the popup reaction rejects a
connection older than 30 seconds — so any reconnect more than half a minute
after the previous disconnect silently lost its popup.

bondedDevices() is now a cache reader: a connected bonded device still
reports its true connect time, a non-connected one gets the current time,
which no caller reads. The connected-devices path becomes the cache's only
writer, which is the invariant its prune-and-stamp logic already assumed.
2026-08-31 15:54:51 +02:00
darken f82adeba42 fix(monitor): Persist AAP keys only when their content changed
AppleDeviceProfile is a data class whose key fields are ByteArrays, so the
generated equals() compares them by reference and the "did anything change"
guard was true on every key exchange. That meant a redundant profile write on
every connect and a "Persisted keys" line that said nothing about whether a
key had actually changed.

Compare content instead, per key, and name the key that changed in the log.
2026-08-31 15:54:51 +02:00
darken cc99ad59ab fix(device): Resolve identity keys against reversed address octets
On at least one vendor stack the address handed up by the BLE scan callback
and the identity key stored for a profile disagree on octet order: the key
resolves the address only when its octets are reversed. Identity resolution
then fails on every advertisement, so no frame is attributed to the profile
and the case popup, connection popup, encrypted 1% battery granularity and
session reconnection all go with it.

RPAChecker gains resolve(), which reports the order that resolved. The
standard-order attempt is unchanged and ungated, so no currently-resolving
device can start failing. The reversed attempt only runs when the reversed
form carries the resolvable-private-address type marker (top bits 01), which
skips roughly three quarters of the extra comparisons for generic random
addresses. verify() is now a thin wrapper over resolve().

Address parsing is validated explicitly: exactly six components, each in
0..255. A seven-component string used to be silently mis-sliced, and
"15A:..." truncated to a valid octet — both cases could resolve.

The failure log no longer prints the identity key. A malformed address or
key wrote an identity-tracking secret into exactly the debug logs users mail
to support; the event and its level stay, the key is reduced to its length
and the address goes through redactedForLogs().

The history lookup that recovers a device by key now skips candidates bound
to a different profile. A 24-bit forward collision could already select the
wrong history; attempting two orders roughly doubles that exposure.

Test vectors are synthetic and derived from the key already committed in
RPACheckerTest. Two of them are complementary: one has an RPA-shaped
reversed form that fails the hash (proving the comparison runs), the other
has a reversed form that resolves cryptographically but carries marker 00
(proving the gate runs).
2026-08-31 15:54:51 +02:00
Matthias Urhahn b66c756741 Merge pull request #695 from d4rken-org/worktree-case-charges-align
General: Tidy up the captions under the battery levels
2026-08-31 06:34:50 +02:00
darken fbbdb4e131 ui(overview): Give the last known caption more room above
At 4.dp the caption crowded the bottom edge of the battery surface it
describes. 8.dp separates the two without pushing it far enough to read as
its own block, which 12.dp (the gap used before the ANC selector) would.
Both pod cards carry the caption, so both move together.
2026-08-30 08:12:23 +02:00
darken 0bf6bf7b5f ui(overview): Center the case charges line in the battery card
The line sat at a fixed 36.dp start indent, which put its left edge under
the case percentage label (28.dp icon + 8.dp spacer). That follows the M3
"supporting text aligns with the item's text column" convention, but the
case row is not a list item, and the sentence runs nearly full width, so
the lone left gutter read as an accident rather than an alignment.

Centering on the capsule instead would have been the other candidate, but
the capsule is weight(1f) between the percentage label and the status-chip
FlowRow, so its x-offset and width are both runtime values that vary with
locale and font scale. No static padding reaches it; it would take
onGloballyPositioned or a custom Layout, which is not worth it for a
caption. Centering in the surface is stable and needs neither.
2026-08-30 08:12:15 +02:00
darken 865a609203 fix(ui): Keep the device detail sheet reachable while scrolled
The detail sheet was composed inside the device-info lazy item, so tapping the
battery runtime warning banner after that item had scrolled out of composition
did nothing visible while still setting the visibility flag, which made the
sheet pop up unprompted on scrolling back up. Build the detail items and compose
the sheet at screen scope instead; the card now only reports the tap.

Fixes review finding F1
2026-08-25 23:42:23 +02:00
darken 9f213cf499 ui(settings): Reframe battery health as estimated listening time
The figure compares observed listening drain against Apple's rated hours, so
it cannot separate a degraded cell from loud volume, cold weather or a hungry
codec. Runtime wording is true either way, "Battery Health" is not.

String keys are kept as they are so the translated locale files aren't
orphaned.
2026-08-25 23:42:23 +02:00
darken 1330c77ed8 feat(battery): Warn when a pod's listening time drops by half
The per-pod listening-time estimate was only visible behind the info icon on
the device settings header card. A pod that reaches half its rated listening
hours or less now raises a banner on the device settings screen, tapping it
opens the same detail sheet.

A displayed number needs less backing than one that raises a warning, so a
reading is only promoted into the banner when its slot has accumulated at
least 8 listening sessions across its qualifying rates and the newest of
those rates is at most 60 days old. The banner reads the already-gated state
field, so the per-profile battery estimate toggle suppresses it too.

The detail sheet's visibility moves out of DeviceInfoCard so both the info
icon and the banner can open it.
2026-08-25 23:42:23 +02:00
darken 2356726417 fix(overview): Call an empty case not enough on older models
AirPods Gen 1 and Gen 2 publish their case capacity as a lower bound, and
the adequacy check let that guard run before any zero-reading check. At a
0% case those two models rendered the definite "no charges left" text in
the neutral uncertainty colour while the screen reader hedged with "may
not be enough", contradicting the same node three ways.

The empty reading is now handled before the lower-bound guard. It sits
after the "enough" branch, which a zero reading can never satisfy, so it
cannot mask a positive claim. The lower-bound rule exists to avoid
underselling a case whose published capacity is only a floor, and an
empty case has nothing to undersell.

Fixes review finding F4
2026-08-25 22:33:42 +02:00
darken 03c09917da fix(overview): Stop denying a charge the reading still allows
The visible count came from the pessimistic end of the interval alone, so
an interval straddling a full charge said "Less than one more full charge"
while the colour stayed neutral and TalkBack said "may not be enough". An
uncertain interval now rounds to a single charge instead, and an
open-ended spec that has not reached one charge names no number at all.

Fixes review finding F2
2026-08-25 22:33:42 +02:00
darken 70830cf2ca fix(overview): Call a case exactly one charge short "not enough"
The reading is an interval that excludes its upper end, so an upper end
landing exactly on one full charge still leaves every reachable value
below one. AirPods Gen 4 at 10% and Pro 3 at 40% read neutral instead of
orange because of it.

Fixes review finding F1
2026-08-25 22:33:42 +02:00
darken 43140005f2 chore(overview): Sort the imports added for the case charges line 2026-08-25 22:33:42 +02:00
darken 59e2a16a39 test(overview): Cover the battery level states and the case charges line
Colour is not exposed through the semantics tree, and captureToImage() times
out under Robolectric, so the rendered result is pinned in two halves: which
tier each of the five slots reports, and which colour a tier resolves to per
theme mode. The theme test drives an in-app dark override on a light host,
which is the case a plain isSystemInDarkTheme() read would get wrong.

The case charges line is covered for presence, the three adequacy states, both
special renderings, and under RTL, a narrow card and a doubled font scale.
2026-08-25 22:33:42 +02:00
darken 11d6ddf3fa feat(overview): Say how many earbud charges the case still holds
The case percentage alone doesn't answer the question people actually have,
which is whether the case can top the earbuds up again. The card now says so in
words, below the case row.

The reading is treated as the interval it really is: a decile source at 20% on
a 4.0-charge case means 0.8 to 1.2 charges, which is not an answer, so the line
stays neutral instead of flipping colour as the reading bounces between
adjacent frames. Only an interval that lies wholly above or below one full
charge is coloured, which removes the need to remember anything between frames.

The line is stacked rather than inline: the case row already has four children
with only the capsule weighted, so a plural in front of it would collapse the
capsule at large font scales and in locales with long plural forms.
2026-08-25 22:33:42 +02:00
darken b10b42eede feat(monitor): Expose how coarse a case battery reading is
PodDevice.batteryCase merges an AAP notification, the encrypted advertisement
payload, the public advertisement nibble and the cache into one Float, and the
provenance is gone by the time anyone reads it. The public nibble only carries
deciles, so 20% there can mean anything up to 29%.

batteryCaseReading walks the same precedence and keeps the step size of the
source that won, leaving batteryCase untouched for every existing caller.
2026-08-25 22:33:42 +02:00
darken 5801b8ea56 feat(device): Record how many earbud charges a case holds
Derived per model as hoursWithCase / hoursListening - 1, with both figures read
under the same noise control condition Apple quotes them under, matching their
stated test method (drained AirPods recharged to 100% and playback resumed
until both the AirPods and the case were fully discharged).

Models whose published total reads "more than N hours" are marked as a lower
bound so nothing downstream can turn an unbounded figure into a confident
claim. AirPods Pro is deliberately left without a spec: its 4.5 h ANC listening
figure is verified but the matching with-case total is not, and an unsourced
number is worse than no line at all.

The spec gates on itself rather than on hasCase — twenty models set hasCase,
including Beats Solo Buds, whose case holds no battery.
2026-08-25 22:33:42 +02:00
darken b9461d3259 feat(a11y): Announce a low battery instead of only colouring it
Crossing the warn or critical threshold was expressed as a colour change only,
so it did not exist for a screen reader. Each battery slot on the overview card
now carries the level as a state description.
2026-08-25 22:33:42 +02:00
darken 7960144be6 ui(overview): Give a low battery a colour that means "low"
The warn band was drawn in colorScheme.tertiary, which is whatever the palette
seed produces: olive under the amber theme, where the healthy band (primary) is
burnt orange, so a warning read as decoration and healthy read as a warning.

The warn band now uses fixed light/dark tokens, and the percentage text is
tinted at warn and critical instead of the level living in the gauge alone.
Values are derived against the composited backgrounds the cards actually draw
on: the gauge Surface at 4dp tonal elevation, and the same surface at alpha 0.7
that a card without live data uses. Measuring against raw surface would have
admitted values below 3:1 on screen.

The tokens are resolved from the theme mode CapodTheme already computed, not
from isSystemInDarkTheme(), so an in-app dark override on a light system does
not pick the light tokens.
2026-08-25 22:33:42 +02:00
darken 0f95538f39 feat(battery): Add a shared battery tier for level colours
The overview duplicates the same 30%/15% thresholds in four places, with two
subtly different unknown checks: the capsule and the pod gauge accept any
percent >= 0f, so Float.POSITIVE_INFINITY renders as a full, healthy ring.
batteryTier() settles that on the finite check the other two already use.
2026-08-25 22:33:42 +02:00
darken fec512175b fix: Prevent crash in Amharic battery time formatting
The Amharic translation of battery_time_remaining_format_hm dropped the
conversion character from its second placeholder: "%1$dሰ %2$ደ" instead of
"%1$dሰ %2$dደ". Java's formatter reads "%2$" followed by "ደ" as an unknown
conversion and throws UnknownFormatConversionException.

formatBatteryDurationShort() takes that branch whenever an estimate has both
hours and minutes, so any duration between 1h1m and 23h59m threw. It is
reached from the dashboard pod cards, the home screen widget, and the
foreground service notification, which makes it a crash on the common path
rather than an edge case.

Found because the Play Store rejected the Amharic screenshot upload: the
widget configuration preview had rendered as a 1x1 image, the layoutlib
render having aborted on the same exception.

Corrected at source in Crowdin, and in CAPod's translation memory, which
still mapped the English source to the broken string at 100% match and would
have re-injected it on the next pre-translate run.
2026-08-25 21:52:33 +02:00
darken a427fcbf4e chore(screenshots): Commit only English Play Store screenshots
Only en-US screenshots are tracked from now on. The other 67 locales are
generated on demand and gitignored, which drops 35 PNGs and roughly 5 MB from
the repository and stops every screenshot refresh from churning binaries in
five languages nobody reviews.

Play Store still gets the full 68 locales. supply only uploads what is present
under fastlane/metadata/android/<locale>/images/phoneScreenshots/ and retains
whatever was last pushed for locales absent from an upload, so localization is
maintained by an occasional manual regen plus screenshots_only rather than by
every PR.

PlayStoreLocales.kt had been committed since February holding a truncated
four-locale batch slice, left behind when a run was killed before its EXIT trap
restored the file. The next run then copied that slice over its own backup and
faithfully restored the corruption, which is why the remnant survived six
months. It is replaced with a documented en-US placeholder, the generator no
longer emits an annotation nothing references, and the script now refuses to
start when a stale .bak is present instead of clobbering the only good copy.

The refresh runbook is corrected as well: it ended with a checkout that restores
tracked files from the index, which would have reverted the freshly rendered
English screenshots while the store received them, and its upload step is now
gated so a failed upload leaves them staged for a retry rather than committed as
though deployed.
2026-08-25 20:08:48 +02:00
darken c49cd62b2a chore: Rename support-investigator plugin entry to support 2026-08-25 20:08:48 +02:00
d4rken-org-releaser[bot] abf28f335c Release: 5.2.4-rc0 2026-08-22 04:42:29 +00:00
Matthias Urhahn 540e6d1dbc Merge pull request #690 from d4rken-org/crowdin-update-20260821
General: Update translations from Crowdin
2026-08-21 20:54:10 +02:00
darken 76e1128e93 General: Update app translations from Crowdin 2026-08-21 19:14:22 +02:00
darken cbf681ccfc General: Update fastlane translations from Crowdin 2026-08-21 18:56:35 +02:00
darken 30f17b4791 fix(ui): Drop the removed monitor mode setting from the auto connect hint
The auto connect description told users the toggle would set the monitor mode
setting to 'Always'. That setting was removed in 5.1.6 and the mode is derived
from profile state now, so the sentence pointed at a settings entry that no
longer exists. The behaviour it described is unchanged: enabling auto connect
still resolves to continuous monitoring.

Base string only, so Crowdin flags the existing translations as outdated.
2026-08-21 10:52:55 +02:00
Matthias Urhahn cd6aaaf165 Merge pull request #687 from d4rken-org/worktree-settings-plain-language
General: Explain the compatibility settings in plain language
2026-08-21 01:20:03 +02:00
darken 289dcd660e ui(settings): Explain compatibility options in plain language
The three compatibility toggles described their own mechanism ("Don't
delegate data filtering to the system", "broadcast instead of callback")
without saying which symptom they address, so a non-technical user had no
way to tell whether a row applied to them. Each summary now leads with the
symptom and keeps a plain description of what changes. Titles are
unchanged: they name the mechanism and are referenced verbatim in issue
replies.

The category description "Don't touch if everything works ;)" stopped
rendering in 63692595 when the res/xml preference screens were replaced by
Compose, since SettingsCategoryHeader only took a title. The string and its
~75 translations survived unused. Added an optional subtitle to the header
and wired it back up.

Also rewrote the extra-notification summary, which named the "Device
status" channel but never said where to find it.

Reported via a Play Store review (vi, 4 stars, 5.2.3-rc0).
2026-08-21 01:13:13 +02:00
Matthias Urhahn b07428f894 Merge pull request #686 from d4rken-org/worktree-anc-adaptive-misreport
Device: Fix duplicate listening mode commands and silent failures
2026-08-20 18:16:20 +02:00
darken dbd1d422c6 refactor(aap): Split the echo classifier out of this change
Everything that reinterprets what the device reported moves to its own branch
(anc-echo-classifier), leaving only changes that stand on their own.

The classifier addresses a fault that has never been observed being handled: it
reproduced on two of four sessions and none since it was written. It also adds a
failure mode that did not exist before, where a report misattributed to our own
write makes CAPod show a mode the device is not in. That is a poor trade to carry
into main on the strength of tests alone, so it waits until it can be seen working
against a live fault.

What remains does not depend on the misreport:

- the verification deadline was 1000ms while the device answers in 833-1008ms, so
  a healthy reply could land just after the timer and trigger a bogus divergence
  plus a redundant re-send; this was captured live
- a listening mode request the device did not confirm produced no feedback at all
  for any mode except Off, which was a gap in the event plumbing rather than a
  timing artifact
- a mode outside the device's listening mode cycle was rendered as an ordinary
  selectable button whenever it happened to be the current mode
- an Off report arriving while a different mode was requested could teach the Allow
  Off inference, persisting "Off is permitted" into the device profile

Also drops effectiveAncMode, which only had an effect while the classifier was
present.
2026-08-20 12:38:30 +02:00
darken 0b83e86c5e fix(aap): Correct an overstated safety claim and a test that proved nothing
Review follow-ups on the previous commit.

Its claim that "nothing is learned or persisted" was wrong. BatteryEstimator
buckets drain samples by the current listening mode and force-persists the
accumulated window whenever that mode changes, so a misattributed mode can write
a drain rate to disk under the wrong bucket, and the corrective report does not
remove it. This is not new: the misreport being fixed here already mis-buckets in
the same way, and more often, since the device claims OFF while the pods play
Adaptive. Classifying corrects the common case and only gets it wrong on the rarer
misattribution. The design stands, the claim does not.

Two other claims were also too strong. The recorded echo is the first report after
the verification was installed, which is not exactly the wire write, so write
contention above the latency boundary can still inflate a fast refusal. And a
superseded write is not left alone entirely: classification is skipped, but it
still falls through to the ordinary retry path.

The supersession regression test asserted nothing: it fed a fresh ADAPTIVE report
in before its only assertion, overwriting either outcome, so it passed whether or
not the guard existed. It now asserts on the state left by the delayed echo, and
fails with the guard removed.

Also drops an unused import and restores an indent lost when the remap argument
was removed.
2026-08-20 00:06:03 +02:00
darken b396ce457d fix(aap): Attribute a listening mode echo before drawing conclusions from it
Extends the previous commit's classifier so it only judges evidence it can
actually attribute to our own write.

The classifier judged whatever mode happened to be current when the deadline
fired, and never looked at timing, despite the refusal-versus-misreport
distinction resting on it. A delayed answer to an earlier write, or a mode change
made on the pods themselves mid-request, could be taken for the answer to the
outstanding write.

Now the first report after a write is recorded with its latency, and that
recorded frame is what gets classified. A write is left alone entirely when it
was superseded by another listening mode write or by a stem press, since its
echoes can no longer be attributed. An answer arriving faster than 500ms is a
refusal, never a change: captures put refusals at 25-267ms and real changes at
815-1010ms. A re-send restamps its own send time and drops the previous attempt's
echo, so a retry is never judged on stale evidence. Latency is measured with the
monotonic clock, so a wall clock correction cannot turn a fast refusal into an
apparent change.

A misattribution is still possible, because AAP reports carry no correlation id
and a change made from iOS or another paired phone is invisible here. That is why
nothing is learned or persisted from this: the worst case is one wrong reading
that the device's next report corrects.

An earlier version of this work also carried a session-scoped remap, so that a
stem-initiated switch could be read correctly after our own write had proven the
device mislabels a value (issue #594). It is not included. The same
unattributability that bounds the classifier to a single wrong reading would have
let one misattribution rewrite every later report in the session, and a wrongly
resolved Off could persist AllowOffOption into the device profile, outliving the
session that produced it. Stem-initiated switches on an affected session are
therefore still not shown correctly.

Refs #594
2026-08-19 23:52:52 +02:00
darken 734e15c94d fix(aap): Tell a listening mode refusal apart from an unusable echo
Follow-up to the previous commit, which left the wrong half of this in place.
Distrusting the report only while the request was outstanding meant that four
seconds later the rejection cleared the pending mode, the bogus value came back,
and the user was shown an error for a mode change that had actually worked.

The two cases have different signatures, both readable from state the engine
already holds:

- A refusal echoes the mode the device is staying in, quickly. Captured Off
  refusals answer in 25-267ms with the previous mode.
- The Pro 3 misreport answers with a third mode, neither the one requested nor
  the one it was in, at normal change latency (815-1010ms).

So an echo that is neither the requested nor the previous mode is treated as an
unusable report rather than a refusal: no re-send of a write that already took
effect, no rejection, no error, and the requested mode is recorded as current.
Refusals still work, which is what the Off rejection message and the Allow Off
learning depend on.

This is deliberately engine-local. Seeding the cycle mask and Allow Off belief
from the device profile into the session would have encoded the rule directly,
but it inverts the current engine-to-profile data flow and creates a belief that
no device report can ever correct, since AirPods never report 0x1A or 0x34.
librepods keeps the same knowledge in its service layer and preferences, not in
its protocol manager.

The fault is per-session rather than per-request: across four sessions today the
pods either misreported every Adaptive write or none of them. The heuristic is
covered by unit tests but has not yet been observed handling a live bad session.
2026-08-19 20:33:32 +02:00
darken edf84248ab fix(anc): Don't show a phantom Off mode when AirPods misreport the listening mode
AirPods Pro 3 can answer a listening mode write with 0x0D 0x01 (Off) while
audibly switching to the requested mode. Seen on firmware
81.2675000075000000.6503, intermittently, and not reproducible on demand.

CAPod took that report at face value: it surfaced an Off button that isn't even
in the device's listening mode cycle, selected it, and said nothing about the
request not having been confirmed.

- visibleAncModes no longer re-admits a mode purely because it is the current
  one. That escape clause was what conjured the extra button.
- effectiveAncMode keeps showing the requested mode while our own request is
  outstanding and the device reports a mode it should not be able to reach.
- A rejected listening mode request now surfaces a message for every mode, not
  only Off. Other modes were dropped silently.
- The Allow Off inference ignores an Off report that arrived while a different
  mode was pending, so a single glitch cannot permanently persist "Off is
  allowed" into the device profile. An unsolicited Off still trains it, which
  is what keeps the option discoverable after it is enabled elsewhere.
- Verification deadline moved from a hardcoded 1000ms to 2000ms, and a matching
  device report now settles the verification when it arrives. Measured reply
  latency is 833-1008ms, so the old deadline sat inside the device's normal
  spread and could fire a bogus divergence plus a redundant re-send.

Settling verification on arrival is limited to SetAncMode deliberately. Every
other verified command is optimistically written into state when it is queued,
so its predicate is satisfied immediately and only the device's contradicting
echo makes it fail. Settling those early would swallow the rejection.

Note that AirPods never report AllowOffOption (0x34) or ListeningModeCycle
(0x1A), so which modes are permitted is always inferred, never device truth.
2026-08-19 19:09:20 +02:00
Matthias Urhahn 83dfdb9219 Merge pull request #682 from d4rken-org/feat/focus-duck-fallback
Reaction: Lower conversation volume even on phones that block it
2026-08-19 09:05:16 +02:00
darken 70b9c2981e fix(upgrade): Align the androidx.hilt declarations with the resolved 1.2.0
addWorkerManager() needed androidx.hilt 1.2.0 for its KSP compiler, which left
addDagger() asking for 1.0.0 while conflict resolution silently handed it 1.2.0,
and hilt-compiler declared twice at two different versions.

The version moves to Versions.AndroidX.Hilt.core, addDagger() stays the single
place that registers hilt-common and the hilt-compiler, and addWorkerManager()
declares only hilt-work. Declared and resolved versions now match, so the
dependency report no longer shows a 1.0.0 -> 1.2.0 substitution.

No dependency graph change: 1.2.0 is what already resolved.
2026-08-18 17:55:19 +02:00
darken 971fcbd34c fix(upgrade): Add a persistent acknowledgement safety net for Play purchases
Play auto-refunds (and revokes) purchases not acknowledged within 3 days.
The in-process ack machinery covers every case where the process lives
long enough; what it cannot cover is a process death around the Play
sheet (aggressive OEM task killers) followed by the user not reopening
the app before the deadline.

Add a gplay-only WorkManager safety net:
- PurchaseAckWorker: self-completing sweep via a new bounded
  BillingManager.ensureAllAcknowledged() that refreshes and acknowledges
  in the same coroutine (the reactive ack collector is async, so a worker
  cannot prove its acks happened through it). Retries with exponential
  backoff until the purchase's refund deadline, then gives up visibly.
- PurchaseAckScheduler: two unique work identities. A launch watch
  (REPLACE, armed and awaited before launchBillingFlow with a 30min delay
  so it cannot complete while the user is still in the sheet) and a
  discovered-purchase rescue (KEEP, 1min delay, armed directly from an
  ack pass that finds unacknowledged purchases, pre-attempt). Separate
  identities so a new purchase flow can never displace a pending rescue.
  Both triggers are fail-open: a broken WorkManager never blocks a
  purchase or an ack. WorkManager resolves via Provider at first arm so
  eager Application-time construction of the billing stack cannot
  trigger WorkManager's on-demand initialization prematurely.
- Nothing cancels the work from the foreground path: an ack pass can see
  zero unacked purchases while the sheet is still open, so the worker
  completes itself after its own reconciliation instead.

The ack pass now runs under a mutex (the worker sweep and the reactive
collector would otherwise race the token bookkeeping) and reports
per-outcome counts for the sweep result mapping.

This is a port of d4rken-org/sdmaid-se#2685; the ported sources are
byte-identical to the donor apart from the package rename.

CAPod had no explicit WorkManager wiring at all (work-runtime only
arrived transitively through Glance), so this also adds it:
- addWorkerManager() pinning androidx.work 2.7.1, the version already
  resolved via Glance, plus androidx.hilt:hilt-work and its KSP
  compiler. work-runtime-ktx is required at 2.7.1: CoroutineWorker,
  Operation.await, OneTimeWorkRequestBuilder and workDataOf all still
  live in the ktx artifact at that version. androidx.hilt moves 1.0.0 ->
  1.2.0 (by conflict resolution) because 1.0.0's hilt-compiler ships no
  KSP SymbolProcessorProvider, so @HiltWorker would generate nothing.
- WorkManagerModule providing the singleton WorkManager.
- App implements Configuration.Provider with the injected
  HiltWorkerFactory. WorkManager 2.7.1 still declares that interface as
  getWorkManagerConfiguration(), not the later property form.
- The manifest removes androidx.work's startup initializer so the
  on-demand configuration is the one that takes effect.

FOSS stays untouched behaviour-wise: all new billing types live in
src/gplay, workers need no manifest entry, and the worker factory
resolves the worker only in gplay variants.
2026-08-18 17:55:19 +02:00
Matthias Urhahn cebed0a60d Merge pull request #683 from d4rken-org/worktree-anc-selector-redesign
General: Redesign the noise control mode selector
2026-08-18 15:18:38 +02:00
darken 8817da309f ui(anc): Redesign the listening mode selector
Replaces the outlined segmented row with a filled track carrying a single
sliding thumb. The old control stacked three signals for one state (container
fill, bold, underline) in an outline treatment nothing else in the card uses,
which is what made it read as bolted on.

The track now sits at the same tonal step as the battery panel above it and
reuses the card's 16dp/12dp radii, so selection is carried by thumb position
and fill alone. Only the active mode is named and its slot expands to fit,
which keeps long translations off the width budget entirely.

Slot widths, label reveal and content tint are all derived from one clamped
fractional position, so geometry and content stay in lockstep. The thumb's
leading edge is that position in collapsed-slot units rather than a sum of
the animating widths, which would overshoot the target and crawl back.

Also fixes an invisible pending state: the in-flight branch set
activeContainerColor to secondaryContainer, which is exactly what
SegmentedButtonDefaults already used for the selected segment, so a requested
mode was indistinguishable from a confirmed one. An unconfirmed request now
draws a hollow pulsing thumb and tints the mode the pods are still in.

Transparency moves off Icons.TwoTone.Hearing, which is already the "In ear"
status chip and rendered identically two rows below it in the same card.
2026-08-18 15:04:50 +02:00
darken 27cc0e8d00 test(reaction): Cover the audio-focus duck fallback
Pins the outcome mapping of duckMusicVolume (a higher read-back stays a skip,
an unmoved one is a refusal), the focus request lifecycle (idempotent grant,
retry after denial, abandon only what is held, permanent loss clears the state
but a transient one does not), and the reaction side: focus is requested only
for an ignored write, released at teardown, re-requested by a keep-alive after
a permanent loss, and a late-landing volume write is restored.

The negative assertions are load-bearing: the MediaControl mock is relaxed, so
an accidental focus request on a healthy duck would pass silently.
2026-08-18 12:37:33 +02:00
darken cecd3088be feat(reaction): Duck via audio focus when the volume write is ignored
ColorOS 16 accepts setStreamVolume from a backgrounded app and leaves the
level where it was, so the conversation reaction ducked nothing. duckMusicVolume
now classifies the outcome (Ducked / Unchanged / Skipped) instead of collapsing
everything into a nullable duck, and a level that came back *higher* stays a
skip: that is the user raising the volume between the two reads, not a refusal.

On Unchanged the reaction requests AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK and lets
the framework attenuate the other player. Teardown abandons the focus and, if
the volume ended up below the pre-duck level anyway, restores it (guard for a
device that applies the write asynchronously). The focus request is built by an
injected factory because AudioFocusRequest.Builder is an unmocked stub in plain
JVM unit tests.
2026-08-18 12:36:30 +02:00
Matthias Urhahn 412ea369f1 Merge pull request #681 from d4rken-org/worktree-ca-duck-noop-diag
Reaction: Retry conversation volume lowering when the system ignores it
2026-08-17 18:40:57 +02:00
darken 22ca3a2a46 fix(bluetooth): Fix bogus scan-gap delays in debug logs
lastScanAt was read and written inside the log lambdas, which only run while
a logger is attached (log() checks Logging.hasReceivers first). In a release
build with recording off, the bookkeeping therefore never happened, so the
first delay of every debug recording reported the time since the *previous*
recording ended. Two logs from a support case opened with delay=878453ms and
delay=359983ms, which read as 14 and 6 minutes of suppressed scanning but
were just the gap between recordings.

The bookkeeping moves out of the lambdas, and the clock changes from
currentTimeMillis to elapsedRealtime so a wall-clock correction cannot
fabricate a gap either. That also puts the delay in the same boot-clock
domain as ScanResult.timestampNanos.
2026-08-17 13:00:02 +02:00
darken 92feea8a10 fix(reaction): Don't report a volume duck the system ignored
On ColorOS 16, setStreamVolume from a backgrounded app is accepted without
an exception and leaves the volume untouched: a debug log from an OPPO
PME110 shows "duckMusicVolume(100%): 40 -> 40 (requested 0)" while the same
call succeeded in the foreground.

duckMusicVolume returned a VolumeDuck for that, so ConversationReaction
recorded an Active session, armed the stale backstop, and later "restored" a
level that was never left. It now returns null when the read-back shows no
decrease, which routes the caller into its existing duck-no-op path: nothing
armed, nothing to restore, and a repeat START retries the duck instead of
treating the dead session as a keep-alive.

The predicate is applied >= prior rather than == prior. A route that
quantizes the target back up to the starting index attenuated nothing
either, and a volume that came back higher (user raised it between the two
reads) must not produce a duck whose restore would undo their change. The
WARN says "volume did not decrease" rather than blaming the ROM, since the
read-back alone cannot distinguish those three causes.
2026-08-17 13:00:02 +02:00
270 changed files with 5288 additions and 918 deletions
+16 -5
View File
@@ -32,13 +32,15 @@ ScreenshotContent.kt (mock data + composables)
|------|---------|
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables (7 exist; `HomescreenWidgetContent` has an IDE preview only and is **not** in the Play Store pipeline) |
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreScreenshots.kt` | `@PreviewTest` functions (currently: `DashboardLight`, `DashboardDark`, `CasePopUp`, `DeviceProfiles`, `AddProfile`, `DeviceSettingsReactions`, `WidgetConfiguration`) |
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreLocales.kt` | Multi-preview annotations (auto-generated by batch script) |
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreLocales.kt` | Multi-preview annotations. The committed content is an en-US placeholder, not meaningful data — `generate_screenshots.sh` rewrites it per batch and restores it from a `.bak` on exit. A run killed hard leaves that `.bak` behind, so the script now refuses to start until it is restored by hand |
| `fastlane/generate_screenshots.sh` | Batched generation; locale list (`ALL_LOCALES`) and `BATCH_SIZE` are defined inside the script |
| `fastlane/copy_screenshots.sh` | Copies rendered PNGs into fastlane structure |
## Commit policy
Only the 6 smoke locales (en-US, de-DE, ja-JP, ar, zh-CN, pt-BR) have `phoneScreenshots/*.png` checked into the repo. Non-smoke locales are excluded by `.gitignore`. This mirrors permission-pilot and keeps repo size small (~7 MB vs ~67 MB for the full 68 locales).
Only `en-US` has `phoneScreenshots/*.png` checked into the repo — 7 PNGs, ~1 MB tracked. Every other locale is excluded by `.gitignore`.
`--smoke` still *renders* 6 locales (en-US, de-DE, ja-JP, ar, zh-CN, pt-BR), but only en-US is committed. The other five cover LTR, RTL and CJK layout so a render that breaks on non-Latin script fails during generation, and the resulting PNGs sit in the working tree for manual inspection. Nothing compares them against a baseline, so this is render coverage plus eyeballing, not regression checking.
Play Store's `supply` only uploads what's present in `fastlane/metadata/android/<locale>/images/phoneScreenshots/`. For locales not in the upload, Play Store retains whatever was last pushed. So full localization on Play Store is maintained by an **occasional manual** full regen + `:screenshots_only` upload — not by every PR.
@@ -90,11 +92,20 @@ Periodic, manual operation — not per-PR:
```bash
./fastlane/generate_screenshots.sh # full, ~30 min, 476 PNGs (68 locales x 7)
./fastlane/copy_screenshots.sh --clean
bundle exec fastlane screenshots_only # uploads all 68 locales to Play Store
git checkout -- fastlane/metadata/android/ # discard non-smoke changes (gitignored anyway)
git add fastlane/metadata/android/en-US/images/phoneScreenshots/
if bundle exec fastlane screenshots_only; then
git checkout -- fastlane/metadata/android/ &&
git commit --only -m "chore(screenshots): Refresh Play Store screenshots" -- \
fastlane/metadata/android/en-US/images/phoneScreenshots/
else
git checkout -- fastlane/metadata/android/
echo "Upload failed; the refreshed en-US screenshots remain staged for retry."
fi
```
The `.gitignore` rule keeps non-smoke output unstaged automatically, so only the smoke locales' refreshed PNGs would show up as modifications and can be committed.
The `git add` has to happen before the upload. The final `git checkout` restores every tracked file under that path **from the index**, so staging the refreshed English set is precisely what makes it survive the checkout — skip the `git add` and the checkout silently reverts the refresh while the store still receives the new images.
Restoring is the checkout's job otherwise: `screenshots_only` runs `remove_unsupported_languages.sh`, which deletes 9 tracked locale directories (es-AR, sc-IT, sq-AL, uz, kmr-TR, ur-IN, zu, si-LK, nb) from the working tree before uploading — 35 tracked files, a subset of the 309 tracked non-screenshot metadata files under that path, all of them put back by the checkout. It does **not** touch the regenerated non-English PNGs: those are untracked and ignored, so they stay on disk and never show up in `git status`. Because the checkout discards any uncommitted metadata text edits too, run this refresh only with an otherwise-clean metadata tree. The final commit is path-limited on purpose, so an unrelated staged change can't ride along, and it is gated on `screenshots_only` succeeding rather than merely sequenced after it: if the upload fails, the refreshed English files stay staged for a retry instead of being committed as though they were deployed. The deleted locale directories are restored on either path.
## Technical Notes
+1 -1
View File
@@ -12,7 +12,7 @@
"jvm-tools@claude-code-cafe": true,
"frontend-design@claude-plugins-official": true,
"kotlin-lsp@claude-plugins-official": true,
"support-investigator@claude-code-cafe": true,
"support@claude-code-cafe": true,
"google-play@claude-code-cafe": true,
"devtools@claude-code-cafe": true
}
+1 -6
View File
@@ -17,16 +17,11 @@
.kotlin
# Screenshot test reference images (ephemeral, regenerated on demand)
app/src/screenshotTest*/reference/
# Play Store phone screenshots: commit only the 6 smoke locales (mirrors permission-pilot).
# Play Store phone screenshots: commit only en-US.
# Full localization is uploaded by occasional manual regen + screenshots_only;
# Play Store retains previously-uploaded screenshots for locales not pushed.
fastlane/metadata/android/*/images/phoneScreenshots/*.png
!fastlane/metadata/android/en-US/images/phoneScreenshots/*.png
!fastlane/metadata/android/de-DE/images/phoneScreenshots/*.png
!fastlane/metadata/android/ja-JP/images/phoneScreenshots/*.png
!fastlane/metadata/android/ar/images/phoneScreenshots/*.png
!fastlane/metadata/android/zh-CN/images/phoneScreenshots/*.png
!fastlane/metadata/android/pt-BR/images/phoneScreenshots/*.png
.codex
protocol-research/
_site/
+1 -1
View File
@@ -1 +1 @@
5.2.3-rc0 50203000
5.2.4-rc0 50204000
+1
View File
@@ -189,6 +189,7 @@ dependencies {
addCompose()
addGlance()
addWorkerManager()
addDataStore()
addNavigation3()
addSerialization()
+1 -1
View File
@@ -14,7 +14,7 @@
<string name="settings_upgrade_status_description">حالة دعمك.</string>
<string name="upgrade_screen_why_title">مزايا الترقية</string>
<string name="upgrade_screen_how_title">كيفية المساعدة</string>
<string name="upgrade_screen_how_body">كُن راعيًا وساهم في تطوير المشروع! اضغط على الزرّ أدناه لتفعيل جميع الميزات الإضافية وفتح ملفّي الشخصي على GitHub Sponsors.</string>
<string name="upgrade_screen_how_body">كُن راعيًا وادعم تطوير المشروع! اضغط على الزرّ أدناه لتفعيل جميع المزايا الإضافية وفتح ملفّي الشخصي على GitHub Sponsors.</string>
<string name="upgrade_screen_status_free_title">النسخة المجّانية</string>
<string name="upgrade_screen_status_free_body">أنت تستخدم النسخة المجّانية من كابود. يمكنك فتح ميزات إضافية من خلال دعم التطوير.</string>
<string name="upgrade_screen_status_free_action">الاطّلاع على خيارات الترقية</string>
@@ -23,6 +23,7 @@ import eu.darken.capod.common.upgrade.core.billing.Sku
import eu.darken.capod.common.upgrade.core.billing.SkuDetails
import eu.darken.capod.common.upgrade.core.billing.UserCanceledBillingException
import eu.darken.capod.common.upgrade.core.billing.client.redacted
import eu.darken.capod.common.upgrade.core.billing.work.PurchaseAckScheduler
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
@@ -58,6 +59,7 @@ class UpgradeRepoGplay @Inject constructor(
private val billingManager: BillingManager,
private val billingCache: BillingCache,
private val curriculumVitae: CurriculumVitae,
private val ackScheduler: PurchaseAckScheduler,
) : UpgradeRepo {
override val storeSite: String = STORE_SITE
@@ -137,6 +139,7 @@ class UpgradeRepoGplay @Inject constructor(
.onEach { failedAt -> recordProUnconfirmed(failedAt) }
.setupCommonEventHandlers(TAG) { "connectionFailureRecorder" }
.launchIn(scope)
}
// Settledness travels WITH the ownership data (Info.isSettled), never on a parallel flow —
@@ -255,6 +258,18 @@ class UpgradeRepoGplay @Inject constructor(
return
}
try {
// Persistent ack safety net, launch trigger: armed and AWAITED before the Play sheet
// can open, so the WorkManager DB transaction lands even if the process dies around
// the sheet — the exact window behind Play's unacknowledged-purchase auto-refunds.
// Failure to arm never blocks the purchase; the foreground ack path still exists.
try {
ackScheduler.armForBillingFlowLaunch()
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log(TAG, WARN) { "Failed to arm ack safety net for launch: ${e.asLog()}" }
}
// Bounded, like every other Play path (refresh, restore, SKU query, ack). useConnection
// waits for a healthy connection indefinitely, so a Play outage between rendering the
// offers and this tap would park the launch forever — with launchBusySku still held,
@@ -16,6 +16,7 @@ import eu.darken.capod.common.upgrade.core.billing.client.BillingConnection
import eu.darken.capod.common.upgrade.core.billing.client.BillingConnectionProvider
import eu.darken.capod.common.upgrade.core.billing.client.isPurchased
import eu.darken.capod.common.upgrade.core.billing.client.redacted
import eu.darken.capod.common.upgrade.core.billing.work.PurchaseAckScheduler
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@@ -25,6 +26,8 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import javax.inject.Inject
import javax.inject.Singleton
@@ -33,6 +36,7 @@ import javax.inject.Singleton
class BillingManager @Inject constructor(
@AppScope private val scope: CoroutineScope,
connectionProvider: BillingConnectionProvider,
private val ackScheduler: PurchaseAckScheduler,
) {
// Fresh Play data plus its provenance: a query result covers owned products of the queried
@@ -174,6 +178,11 @@ class BillingManager @Inject constructor(
}
}
// Serializes acknowledgement work between the reactive ack collector and explicit
// ensureAllAcknowledged() sweeps (PurchaseAckWorker): both paths mutate the token bookkeeping
// sets and both must never double-drive the same purchase's inline retry sequence.
private val ackMutex = Mutex()
// Re-drives the ack pass WITHOUT a new purchases emission: `purchases` is distinctUntilChanged,
// so a refresh returning a byte-identical (still unacknowledged) list is deduped and could never
// retry a failed ack -- the pipeline starved until Play sent something different. Declared ahead
@@ -256,13 +265,13 @@ class BillingManager @Inject constructor(
// below. The immutable Purchase snapshot keeps reporting isAcknowledged=false until a fresh Play
// query supersedes it, so the ack re-fires every emission until then; re-acking is a documented
// no-op on Play's side, whereas skipping a needed ack gets the purchase auto-refunded after 3
// days -- so the ack stays unconditional and this set only quiets the log spam. Single
// sequential collector (the ack pass below), no locking needed.
// days -- so the ack stays unconditional and this set only quiets the log spam. Confined by
// ackMutex (the collector's pass and explicit sweeps both run under it).
private val loggedAckTokens = mutableSetOf<String>()
// Tokens whose PERMANENT ack failure was already reported. Play will keep rejecting these
// (developer error, item not owned, unsupported feature), so the bug report fires once per token
// instead of once per pass. Same single-collector confinement as loggedAckTokens.
// instead of once per pass. Same ackMutex confinement as loggedAckTokens.
private val reportedAckFailures = mutableSetOf<String>()
// At most one reschedule timer in flight: repeated failures must not stack timers.
@@ -296,10 +305,13 @@ class BillingManager @Inject constructor(
// .isAcknowledged, whose immutable snapshot stays false until a fresh Play query.
private enum class AckOutcome { SUCCESS, TRANSIENT, PERMANENT }
// Aggregate outcome of one ack pass; ensureAllAcknowledged() maps it to a sweep result.
data class AckPassOutcome(val transient: Int, val permanent: Int)
// One acknowledgement pass over the canonical purchase list. Never throws except cancellation:
// transient failures schedule a re-drive, permanent ones are reported and left to organic fresh
// -data signals.
private suspend fun runAckPass(purchases: Collection<Purchase>) {
private suspend fun runAckPass(purchases: Collection<Purchase>): AckPassOutcome = ackMutex.withLock {
val needAck = purchases.filter {
// The canonical list carries pending payments too. Play rejects acknowledging one
// PERMANENTLY, so an unfiltered pass would fire a bug report for every pending purchase,
@@ -317,7 +329,23 @@ class BillingManager @Inject constructor(
needsAck
}
if (needAck.isNotEmpty()) {
// Arm the persistent safety net BEFORE attempting anything, and AWAIT the enqueue (the
// scheduler bounds it): the inline retries below can span minutes, and a process death
// inside them must not strand the purchase until Play's 3-day auto-refund. A deferred
// signal (channel + collector) would reintroduce exactly that window. Fail-open: the
// net is an extra layer, never a reason to skip the acks themselves.
try {
ackScheduler.armForUnackedPurchases(needAck.maxOf { it.purchaseTime } + ACK_SAFETY_NET_DEADLINE_MS)
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log(TAG, WARN) { "Failed to arm ack safety net: ${e.asLog()}" }
}
}
var transientFailures = 0
var permanentFailures = 0
for (purchase in needAck) {
// First ack of a token is INFO; idempotent repeats drop to DEBUG. This never gates the
@@ -381,6 +409,7 @@ class BillingManager @Inject constructor(
}
if (outcome == AckOutcome.TRANSIENT) transientFailures++
if (outcome == AckOutcome.PERMANENT) permanentFailures++
if (abortPass) break
}
@@ -391,6 +420,41 @@ class BillingManager @Inject constructor(
}
scheduleAckRetry()
}
AckPassOutcome(transient = transientFailures, permanent = permanentFailures)
}
// Outcome of an explicit safety-net sweep, see ensureAllAcknowledged().
enum class AckSweepResult { COMPLETE, RETRY, PERMANENT_FAILURE }
/**
* One self-contained acknowledgement sweep for the persistent safety net (PurchaseAckWorker):
* refresh from Play, then acknowledge everything unacknowledged IN THIS COROUTINE. The reactive
* ack collector consumes purchase state asynchronously, so a caller that needs proof the acks
* actually happened before it reports success (a worker deciding success vs retry) cannot rely
* on it. Never throws except cancellation.
*/
suspend fun ensureAllAcknowledged(): AckSweepResult {
log(TAG) { "ensureAllAcknowledged()" }
val fresh = try {
useConnection { refreshPurchases() }
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
log(TAG, WARN) { "ensureAllAcknowledged(): refresh failed: ${e.asLog()}" }
return AckSweepResult.RETRY
}
// Same bookkeeping every other refresh exit owes: grace episode clock + dead-binder teardown.
processReconciliation(fresh)
val outcome = runAckPass(fresh.purchases)
return when {
// An incomplete refresh may be hiding an unacknowledged purchase of the failed type,
// and a transient ack failure is retriable by definition.
outcome.transient > 0 || !fresh.isComplete -> AckSweepResult.RETRY
// Play will keep rejecting these no matter how often the worker comes back.
outcome.permanent > 0 -> AckSweepResult.PERMANENT_FAILURE
else -> AckSweepResult.COMPLETE
}
}
// A purchase Play will keep rejecting: report it once per token, then stay quiet. The pass still
@@ -586,6 +650,10 @@ class BillingManager @Inject constructor(
BillingResponseCode.ITEM_NOT_OWNED,
)
// Play auto-refunds purchases not acknowledged within 3 days; every safety-net deadline
// derives from this.
const val ACK_SAFETY_NET_DEADLINE_MS = 3 * 24 * 60 * 60 * 1000L
private const val INITIAL_REFRESH_TIMEOUT_MS = 30_000L
private const val MAX_BACKOFF_MS = 300_000L
@@ -0,0 +1,114 @@
package eu.darken.capod.common.upgrade.core.billing.work
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.await
import androidx.work.workDataOf
import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.upgrade.core.billing.BillingManager
import kotlinx.coroutines.withTimeoutOrNull
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
/**
* Arms the [PurchaseAckWorker] safety net. Two triggers:
* - a billing flow is about to launch (armed and awaited BEFORE the Play sheet, so the WorkManager
* DB transaction lands even if the process dies around the sheet),
* - an ack pass discovered unacknowledged purchases (called directly, pre-attempt, from
* BillingManager's runAckPass).
*/
@Singleton
class PurchaseAckScheduler @Inject constructor(
// Resolved on the first arm, not at construction: fleet App classes eagerly inject the billing
// stack during Application field injection, and resolving WorkManager there can trigger its
// on-demand initialization before the Application's worker factory field is set.
private val workManager: Provider<WorkManager>,
) {
// A genuinely new flow refreshes the watch window: REPLACE the previous LAUNCH watch. The
// worker sweeps ALL unacknowledged purchases, so replacing an older watch loses nothing — and a
// pending rescue for an already-discovered purchase has its own identity, so starting another
// purchase can never displace it. The long delay keeps the worker out of the window where the
// user may still be in the Play sheet.
suspend fun armForBillingFlowLaunch() = arm(
name = WORK_NAME_LAUNCH,
policy = ExistingWorkPolicy.REPLACE,
expiresAt = System.currentTimeMillis() + BillingManager.ACK_SAFETY_NET_DEADLINE_MS,
initialDelayMs = LAUNCH_DELAY_MS,
)
// Any pending rescue already covers every unacknowledged purchase: KEEP it. Once completed
// work exists, KEEP inserts a fresh request. Short delay — the purchase already EXISTS (unlike
// the launch trigger), possibly for days, so waiting 30min could waste real deadline time.
// Accepted edge of KEEP, within the rescue lane only: a pending request keeps its original
// (possibly earlier) expiry; a newer purchase with a later deadline is only re-covered once a
// later pass re-arms after the old work completed. Bounded residual, only reachable via
// out-of-band purchases.
suspend fun armForUnackedPurchases(expiresAt: Long) = arm(
name = WORK_NAME_RESCUE,
policy = ExistingWorkPolicy.KEEP,
expiresAt = expiresAt,
initialDelayMs = DISCOVERY_DELAY_MS,
)
private suspend fun arm(
name: String,
policy: ExistingWorkPolicy,
expiresAt: Long,
initialDelayMs: Long,
) {
if (expiresAt <= System.currentTimeMillis()) {
// Play has already voided (or is about to void) such a purchase; a sweep can't help.
log(TAG, WARN) { "arm($policy): deadline $expiresAt already passed, not scheduling" }
return
}
val request = OneTimeWorkRequestBuilder<PurchaseAckWorker>().apply {
setConstraints(
Constraints.Builder().apply {
setRequiredNetworkType(NetworkType.CONNECTED)
}.build()
)
// Launch trigger: the worker must not run while the user may still be in the Play
// sheet — an immediate sweep would find nothing unacknowledged, report success, and
// complete the net before the purchase it exists for even happened.
setInitialDelay(initialDelayMs, TimeUnit.MILLISECONDS)
setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY_MS, TimeUnit.MILLISECONDS)
setInputData(workDataOf(PurchaseAckWorker.KEY_EXPIRES_AT to expiresAt))
}.build()
// Await the enqueue: the caller arms this because the process may die at any moment — a
// fire-and-forget enqueue could be lost with it. Cancellable and BOUNDED: every caller
// needs a durable enqueue without an unbounded stall — a WorkManager that never settles
// must become an exception (handled fail-open by every caller) instead of a hang (which on
// the launch lane would park the purchase and its busy guard forever).
val operation = workManager.get().enqueueUniqueWork(name, policy, request)
withTimeoutOrNull(ENQUEUE_TIMEOUT_MS) { operation.await() }
?: throw IllegalStateException("WorkManager enqueue did not settle within ${ENQUEUE_TIMEOUT_MS}ms")
log(TAG) { "arm($policy): safety net armed, expiresAt=$expiresAt" }
}
companion object {
// WorkManager persists these names AND the worker's class name in its DB across app
// updates: keep all of them stable while old work may exist (hence the version suffix for
// future changes). Separate identities per trigger: the launch watch's REPLACE must not be
// able to displace a pending rescue for a purchase that already exists.
private val WORK_NAME_LAUNCH = "${BuildConfigWrap.APPLICATION_ID}.gplay.purchase-ack.launch.v1"
private val WORK_NAME_RESCUE = "${BuildConfigWrap.APPLICATION_ID}.gplay.purchase-ack.rescue.v1"
private const val LAUNCH_DELAY_MS = 30 * 60 * 1000L
private const val DISCOVERY_DELAY_MS = 60 * 1000L
private const val BACKOFF_DELAY_MS = 30 * 60 * 1000L
private const val ENQUEUE_TIMEOUT_MS = 10 * 1000L
val TAG: String = logTag("Upgrade", "Gplay", "Billing", "AckScheduler")
}
}
@@ -0,0 +1,86 @@
package eu.darken.capod.common.upgrade.core.billing.work
import android.content.Context
import androidx.hilt.work.HiltWorker
import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker.Result
import androidx.work.WorkerParameters
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
import eu.darken.capod.common.debug.logging.log
import eu.darken.capod.common.debug.logging.logTag
import eu.darken.capod.common.upgrade.core.billing.BillingManager
import kotlinx.coroutines.withTimeoutOrNull
/**
* Persistent acknowledgement safety net, armed by [PurchaseAckScheduler].
*
* Play auto-refunds (and revokes) any purchase not acknowledged within 3 days. The in-process ack
* machinery in [BillingManager] handles every case where the process lives long enough — this
* worker covers the case it can't: the process dies around the Play purchase sheet (OEM task
* killers) and the user doesn't reopen the app before the deadline. Play voids such purchases and
* revokes the entitlement, so the user loses what they signed up for.
*
* Self-completing by design: nothing cancels this work from the foreground ack path (an ack pass
* can legitimately see zero unacknowledged purchases while the Play sheet is still open, which
* must not tear down the net). The redundant sweep after a successful foreground ack is one
* purchase query.
*/
@HiltWorker
class PurchaseAckWorker @AssistedInject constructor(
@Assisted private val context: Context,
@Assisted private val params: WorkerParameters,
private val billingManager: BillingManager,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val expiresAt = inputData.getLong(KEY_EXPIRES_AT, 0L)
log(TAG) { "doWork(): attempt=$runAttemptCount, expiresAt=$expiresAt" }
if (!isWorthSweeping(System.currentTimeMillis(), expiresAt)) {
// Past Play's refund deadline (or malformed input): retrying can't achieve anything.
// failure() is deliberate over success() — it is visible in WorkManager diagnostics,
// and a completed state lets a later KEEP enqueue insert fresh work.
log(TAG, WARN) { "doWork(): deadline passed, giving up" }
return Result.failure()
}
// Bounded well below WorkManager's 10-minute execution limit, but generous enough for the
// connection wait plus the per-purchase inline retries. A sweep that ran out of time is a
// transient outcome, not a verdict. External cancellation propagates out of doWork — it
// must never be converted into success.
val sweep = withTimeoutOrNull(SWEEP_TIMEOUT_MS) {
billingManager.ensureAllAcknowledged()
}
log(TAG, INFO) { "doWork(): sweep=$sweep" }
return mapSweep(sweep, System.currentTimeMillis(), expiresAt)
}
companion object {
// Persisted in WorkManager's request data — keep the key stable while old work may exist.
const val KEY_EXPIRES_AT = "purchase.ack.expiresAt"
private const val SWEEP_TIMEOUT_MS = 4 * 60 * 1000L
// Pure so the retry/expiry decision is unit-testable without a WorkManager test harness.
internal fun isWorthSweeping(now: Long, expiresAt: Long): Boolean =
expiresAt > 0L && now < expiresAt
internal fun mapSweep(
sweep: BillingManager.AckSweepResult?,
now: Long,
expiresAt: Long,
): Result = when (sweep) {
BillingManager.AckSweepResult.COMPLETE -> Result.success()
BillingManager.AckSweepResult.PERMANENT_FAILURE -> Result.failure()
// RETRY or timeout (null): keep trying until the deadline. WorkManager's exponential
// backoff caps at 5h, so the 3-day window still yields many attempts.
else -> if (now < expiresAt) Result.retry() else Result.failure()
}
val TAG: String = logTag("Upgrade", "Gplay", "Billing", "AckWorker")
}
}
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Dit is \'n aparte aankoop — dit kanselleer of vergoed nie jou intekening nie. Gebruik dieselfde Google-rekening.</string>
<string name="upgrade_screen_sub_still_renewing_title">Intekening steeds aktief</string>
<string name="upgrade_screen_sub_still_renewing_message">Jou intekening is steeds gestel om te hernu. Kanselleer dit eers in Google Play, en wissel dan na die eenmalige aankoop. Pas gekanselleer? Gee Google Play \'n oomblik en probeer weer.</string>
<string name="upgrade_screen_purchase_check_failed_message">Kon nie jou aankope by Google Play nagaan nie. Probeer weer oor \'n oomblik om \'n dubbele aankoop te vermy.</string>
<string name="upgrade_screen_pending_card_title">Betaling hangende</string>
<string name="upgrade_screen_pending_card_body">Google Play verwerk steeds jou betaling. Pro word outomaties ontsluit sodra die betaling deurgaan. Sommige betaalmetodes kan \'n rukkie neem. Jy hoef verder niks te doen nie.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play het \'n aankoop met \'n hangende betaling gevind en verwerk dit steeds. Pro word outomaties ontsluit sodra die betaling deurgaan.</string>
<string name="upgrade_screen_grace_title">Jou aankoop word bevestig</string>
<string name="upgrade_screen_grace_body_short">Google Play het jou aankoop nog nie bevestig nie. Pro is steeds aktief — geen aksie nodig nie.</string>
<string name="upgrade_screen_grace_body">Google Play het jou aankoop al \'n rukkie nie bevestig nie. Pro is steeds aktief. Maak seker jy is aanlyn en aangemeld met die Google-rekening wat vir die aankoop gebruik is, en probeer dan herstel.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ይህ የተለየ ግዢ ነው — ምዝገባውን አይሰርዝም ወይም አይመልስም። ተመሳሳይ የGoogle መለያ ይጠቀሙ።</string>
<string name="upgrade_screen_sub_still_renewing_title">ምዝገባ አሁነም ንቁ ነው</string>
<string name="upgrade_screen_sub_still_renewing_message">ምዝገባዎ አሁንም ለማደስ ተዘጋጅቷል። መጀመሪያ በGoogle Play ውስጥ ይሰርዙት፣ ከዚያ ወደ አንድ ጊዜ ግዢ ይቀይሩ። አሁን ገና ሰርዘውታል? ለGoogle Play ትንሽ ጊዜ ይስጡ እና እንደገና ይሞክሩ።</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play ውስጥ የእርስዎን ግዢ ማረጋገጥ አልተቻልም። ተደገመ ግዢ ለመቆጠብ ውሌት በኋላ ሞክሩት።</string>
<string name="upgrade_screen_pending_card_title">ክፍያ በሂደት ላይ</string>
<string name="upgrade_screen_pending_card_body">Google Play ክፍያዎን ሥራ ላይ ቀጥሏል። ክፍያው ከተጠናቀቀ በኋላ Pro ራሱ ይከፈታል። አንዳንድ ክፍያ ዘዴ ጊዜ ሊወስዱ ይችላሉ። ሌላ ምንም ሊያደርጉት የሚገቡ ነገር የለም።</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ሥራ ያለበት ክፍያ ያለበት ግዢ አገኘ እና ሥራ ላይ ይገኛል። ክፍያው ከተጠናቀቀ በኋላ Pro ራሱ ይከፈታል።</string>
<string name="upgrade_screen_grace_title">ግዢዎን በማረጋገጥ ላይ</string>
<string name="upgrade_screen_grace_body_short">Google Play ግዢዎን እስካሁን አላረጋገጠም። Pro አሁንም ንቁ ነው — ምንም እርምጃ አያስፈልግም።</string>
<string name="upgrade_screen_grace_body">Google Play ለተወሰነ ጊዜ ግዢዎን አላረጋገጠም። Pro አሁንም ንቁ ነው። መስመር ላይ መሆንዎን እና ለግዢው ጥቅም ላይ በዋለው የGoogle መለያ መግባትዎን ያረጋግጡ፣ ከዚያ ወደነበረበት ለመመለስ ይሞክሩ።</string>
+52 -48
View File
@@ -1,78 +1,82 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="upgrades_gplay_unavailable_error">خدمات جوجل بلاي غير متوفّرة.</string>
<string name="upgrades_no_purchases_found_check_account">لم يتمّ العثور على أيّ مشتريات. هل تستخدم الحساب الصحيح؟</string>
<string name="upgrades_gplay_billing_error_label">خطأ في جوجل بلاي</string>
<string name="upgrades_gplay_billing_error_description">حدث خطأ في سوق جوجل بلاي. يُرجى المحاولة لاحقًا أو إعادة تشغيل هاتفك.\n\nالخطأ: %s</string>
<string name="upgrades_gplay_billing_result_error_label">خطأ في فوترة جوجل بلاي</string>
<string name="upgrades_gplay_billing_result_error_description">حدث خطأ أثناء طلب تفاصيل عملية الاشتراء من سوق جوجل بلاي. يُرجى مسح ذاكرة التخزين المؤقّت لسوق جوجل بلاي وإعادة تشغيل هاتفك.\n\nالخطأ %s</string>
<string name="upgrades_gplay_already_owned_label">هل اشتريت بالفعل؟</string>
<string name="upgrades_gplay_already_owned_description">أفادت سوق جوجل بلاي بأنّك تمتلك هذا التحديث بالفعل، ولكن تعذّر استعادته. تأكّد من استخدام حساب جوجل الذي اشتريت به التطبيق. قد تستغرق مزامنة سوق بلاي بعض الوقت — حاول إعادة تشغيل الجهاز، أو مسح ذاكرة التخزين المؤقّت لسوق جوجل بلاي، أو الانتظار ببساطة.</string>
<string name="upgrades_no_purchases_found_check_account">لم يُعثَر على أيّ مشتريات. هل تستخدم الحساب الصحيح؟</string>
<string name="upgrades_gplay_billing_error_label">خطأٌ في جوجل بلاي</string>
<string name="upgrades_gplay_billing_error_description">حدث خطأٌ في جوجل بلاي. يُرجى المحاولة لاحقًا أو إعادة تشغيل هاتفك.\n\nالخطأ: %s</string>
<string name="upgrades_gplay_billing_result_error_label">خطأٌ في فوترة جوجل بلاي</string>
<string name="upgrades_gplay_billing_result_error_description">حدث خطأٌ في أثناء طلب تفاصيل عملية الاشتراء من جوجل بلاي. يُرجى مسح ذاكرة التخزين المؤقّت لجوجل بلاي وإعادة تشغيل هاتفك.\n\nالخطأ %s</string>
<string name="upgrades_gplay_already_owned_label">هل اشتريت فعلًا؟</string>
<string name="upgrades_gplay_already_owned_description">أفاد جوجل بلاي بأنّك تمتلك هذه الترقية فعلًا، ولكن تعذّر استعادته. تأكّد من استخدام حساب جوجل الذي اشتريت به الترقية. قد تستغرق مزامنة سوق بلاي بعض الوقت — حاول إعادة تشغيل الجهاز، أو مسح ذاكرة التخزين المؤقّت لجوجل بلاي، أو الانتظار ببساطة.</string>
<string name="upgrade_restore_action">استعادة المشتريات</string>
<string name="upgrade_badge_label">النسخة الاحترافية</string>
<!-- %1$s is the full localized brand ("CAPod Pro", composed from the app name + postfix) — translate the sentence around it, keep the placeholder. -->
<string name="upgrade_screen_title_template">احصل على %1$s</string>
<string name="upgrade_preamble">تمّ تطوير كابود بواسطة شخص واحد. يتيح التحديث ميزات إضافية ويساعد في استمرار عمل التطبيق.</string>
<string name="upgrade_screen_options_description">نفس الميزات، بتسعير مختلف. يشمل الاشتراك فترة تجريبية مجّانية ويمكن إلغاؤه في أيّ وقت.</string>
<string name="upgrade_screen_options_description_no_trial">نفس الميزات، بتسعير مختلف. يمكن إلغاء الاشتراك في أي وقت.</string>
<string name="upgrade_screen_subscription_trial_action">بدء الفترة التجريبية المجّانية</string>
<string name="upgrade_preamble">تمّ تطوير كابود بواسطة شخصٍ واحد. تتيح الترقية مزايا إضافية وتساعد في استمرار عمل التطبيق.</string>
<string name="upgrade_screen_options_description">نفس المزايا، بتسعيرٍ مختلف. يشمل الاشتراك مدّةً تجريبيةً مجّانيةً ويمكن إلغاؤه في أيّ وقت.</string>
<string name="upgrade_screen_options_description_no_trial">نفس المزايا، بتسعيرٍ مختلف. يمكن إلغاء الاشتراك في أيّ وقت.</string>
<string name="upgrade_screen_subscription_trial_action">بدء المدّة التجريبية المجّانية</string>
<string name="upgrade_screen_subscription_action">الاشتراك سنويًّا</string>
<string name="upgrade_screen_subscription_action_hint">%s/سنويًّا</string>
<string name="upgrade_screen_iap_action">الاشتراء لمرّة واحدة</string>
<string name="upgrade_screen_iap_action_hint">اشتراء لمرّة واحدة: %s</string>
<string name="upgrade_screen_iap_action">الاشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_iap_action_hint">اشتراء لمرّةٍ واحدة: %s</string>
<string name="upgrade_screen_restore_purchase_action">استعادة المشتريات</string>
<string name="upgrade_screen_restore_purchase_message">لم يتمّ العثور على أيّ مشتريات. هل تستخدم الحساب الصحيح؟</string>
<string name="upgrade_screen_restore_purchase_message">لم يُعثَر على أيّ مشتريات. هل تستخدم الحساب الصحيح؟</string>
<string name="upgrade_screen_restore_troubleshooting_msg">إذا اشتريت مؤخّرًا، فقد يستغرق الأمر بعض الوقت حتى تتمّ مزامنة حسابك على جوجل بلاي.</string>
<string name="upgrade_screen_restore_sync_patience_hint">حاول مرة أخرى بعد بضع دقائق إذا لم يظهر طلبك.</string>
<string name="upgrade_screen_restore_multiaccount_hint">تأكّد من تسجيل دخولك باستخدام حساب جوجل نفسه المستخدم في عملية الاشتراء.</string>
<string name="upgrade_screen_restore_webinstall_hint">قد يؤدّي تعدّد الحسابات إلى إرباك سوق جوجل بلاي. لذا، فإنّ تثبيت التطبيق من خلال موقع جوجل بلاي الإلكتروني يجبر التطبيق على الارتباط بحساب مُحدَّد.</string>
<string name="upgrade_screen_restore_banner_title">هل اشتريت النسخة الاحترافية بالفعل؟</string>
<string name="upgrade_screen_restore_sync_patience_hint">حاول مرّةً أخرى بعد بضع دقائق إذا لم يظهر طلبك.</string>
<string name="upgrade_screen_restore_multiaccount_hint">تأكّد من تسجيل دخولك باستخدام حساب جوجل نفسه المُستخدَم في عملية الاشتراء.</string>
<string name="upgrade_screen_restore_webinstall_hint">قد يؤدّي تعدّد الحسابات إلى إرباك جوجل بلاي. لذا، فإنّ تثبيت التطبيق عبر موقع جوجل بلاي الإلكتروني يجبر التطبيق على الارتباط بحسابٍ مُحدَّد.</string>
<string name="upgrade_screen_restore_banner_title">هل اشتريت النسخة الاحترافية فعلًا؟</string>
<string name="upgrade_screen_restore_banner_body">يبدو أنّك رقّيت إلى النسخة الاحترافية على هذا الجهاز سابقًا. استعِدْ عملية الاشتراء لفتحه مرّةً أخرى.</string>
<string name="upgrade_screen_restore_success_message">تمّت استعادة عملية الاشتراء.</string>
<string name="upgrade_screen_restore_status_title">هل تبدو الحالة خاطئة؟</string>
<string name="upgrade_screen_restore_status_body">إذا كنت تمتلك ترقية غير معروضة هنا، فاستعِدْ عملية الاشتراء الخاصّة بك.</string>
<string name="upgrade_screen_restore_status_body">إذا كنت تمتلك ترقيةً غير معروضةٍ هنا، فاستعِدْ عملية الاشتراء الخاصّة بك.</string>
<string name="upgrade_screen_owned_hero_title">لديك النسخة الاحترافية من كابود!</string>
<string name="upgrade_screen_owned_hero_sub_body">تمّ فتحها باشتراكك. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_hero_iap_body">تمّ فتحها من خلال عملية اشتراء لمرّة واحدة. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_hero_iap_body">تمّ فتحها باختيارك خيار الاشتراء لمرّةٍ واحدة. شكرًا لدعمك لكابود!</string>
<string name="upgrade_screen_owned_sub_title">اشتراك سنوي</string>
<string name="upgrade_screen_owned_sub_renewing_body">اشتراكك فعّال ومُعَدّ للتجديد. تتمّ إدارة الفواتير بواسطة جوجل بلاي.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">اشتراكك فعّال، لكنّه غير مُعَدّ للتجديد. ستظلّ النسخة الاحترافية متاحةً حتّى نهاية الفترة الحالية.</string>
<string name="upgrade_screen_owned_iap_title">اشتراء لمرّة واحدة</string>
<string name="upgrade_screen_owned_sub_renewing_body">اشتراكك فعّالٌ ومُعَدٌّ للتجديد. تتمّ إدارة الفواتير بواسطة جوجل بلاي.</string>
<string name="upgrade_screen_owned_sub_not_renewing_body">اشتراكك فعّالٌ، لكنّه غير مُعَدٍّ للتجديد. ستظلّ النسخة الاحترافية متاحةً حتّى نهاية المدّة الحالية.</string>
<string name="upgrade_screen_owned_iap_title">اشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_owned_iap_body">ملكك للأبد. لا تجديدات، ولا تاريخ انتهاء.</string>
<string name="upgrade_screen_owned_both_renewing_warning">أنت تملك عملية الاشتراء لمرّة واحدة، لكنّ اشتراكك لا يزال مُعَدًّا للتجديد التلقائي. ألغِه من سوق جوجل بلاي لتجنّب الدفع مرّتين.</string>
<string name="upgrade_screen_owned_both_renewing_warning">أنت تملك عملية الاشتراء لمرّةٍ واحدة، لكنّ اشتراكك لا يزال مُعَدًّا للتجديد التلقائي. ألغِه من جوجل بلاي لتجنّب الدفع مرّتين.</string>
<string name="upgrade_screen_manage_subscription_action">إدارة الاشتراك</string>
<string name="upgrade_screen_iap_offer_title">التبديل إلى الشراء لمرة واحدة</string>
<string name="upgrade_screen_iap_offer_title">التبديل إلى الاشتراء لمرّةٍ واحدة</string>
<string name="upgrade_screen_owned_iap_locked_note">يصبح متاحًا بمجرد إلغاء ضبط اشتراكك على التجديد. ألغه في Google Play للتبديل.</string>
<string name="upgrade_screen_owned_iap_purchase_note">هذا شراء منفصل — لا يؤدي إلى إلغاء اشتراكك أو استرداد قيمته. استخدم نفس حساب Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">الاشتراك لا يزال نشطًا</string>
<string name="upgrade_screen_sub_still_renewing_message">اشتراكك لا يزال مضبوطًا على التجديد. ألغه في Google Play أولًا، ثم بدّل إلى الشراء لمرة واحدة. هل ألغيته للتو؟ امنح Google Play بعض الوقت وحاول مرة أخرى.</string>
<string name="upgrade_screen_grace_title">جارٍ تأكيد عملية الشراء</string>
<string name="upgrade_screen_grace_body_short">لم يؤكّد Google Play عملية الشراء الخاصة بك بعد. لا يزال Pro نشطًا — لا حاجة لاتخاذ أي إجراء.</string>
<string name="upgrade_screen_grace_body">لم يؤكّد Google Play عملية الشراء الخاصة بك منذ فترة. لا يزال Pro نشطًا. تأكد من اتصالك بالإنترنت وتسجيل دخولك بحساب Google المستخدم في عملية الشراء، ثم حاول استعادة الشراء.</string>
<string name="upgrade_screen_owned_iap_purchase_note">هذا شراءٌ منفصل — لا يؤدّي إلى إلغاء اشتراكك أو استرداد قيمته. استخدم حساب جوجل نفسه.</string>
<string name="upgrade_screen_sub_still_renewing_title">الاشتراك لا يزال فعّالًا</string>
<string name="upgrade_screen_sub_still_renewing_message">لا يزال اشتراكك مُعَدًّا للتجديد التلقائي. ألغِه من جوجل بلاي أوّلًا، ثمّ بدّل إلى خيار الاشتراء لمرّةٍ واحدة. هل ألغيت للتوّ؟ انتظر قليلًا ثمّ حاول مرّةً أخرى من جوجل بلاي.</string>
<string name="upgrade_screen_purchase_check_failed_message">تعذّرت مراجعة مشترياتك في جوجل بلاي. لتجنّب عملية اشتراءٍ مُكرَّرة، حاول مرّةً أخرى بعد قليل.</string>
<string name="upgrade_screen_pending_card_title">الدفع قيد الانتظار</string>
<string name="upgrade_screen_pending_card_body">لا تزال جوجل بلاي تعالج عملية الدفع. سيتم فتح الإصدار الاحترافي تلقائيًا فور إتمام الدفع. قد تستغرق بعض طرق الدفع بعض الوقت. لا حاجة لأي إجراء إضافي من جانبك.</string>
<string name="upgrade_screen_pending_dialog_message">عثرت جوجل بلاي على عملية شراء بدفعٍ معلّق ولا تزال تعالجها. سيتم فتح الإصدار الاحترافي تلقائيًا فور إتمام الدفع.</string>
<string name="upgrade_screen_grace_title">يجري تأكيد عملية الاشتراء</string>
<string name="upgrade_screen_grace_body_short">لم يؤكّد جوجل بلاي عملية الاشتراء بعد. لا تزَل النسخة الاحترافية فعّالةً، فلا داعي لاتّخاذ أيّ إجراء.</string>
<string name="upgrade_screen_grace_body">لم يؤكّد جوجل بلاي عملية الاشتراء منذ مدّة. لا تزَل النسخة الاحترافية فعّالةً. تأكّد من اتّصالك بالإنترنت وتسجيل دخولك باستخدام حساب جوجل المُستخدَم في عملية الاشتراء، ثمّ حاول استعادة حسابك.</string>
<!-- Offercard layout -->
<string name="upgrade_screen_offers_title">خيارات الترقية</string>
<string name="upgrade_screen_offers_or">أو</string>
<string name="upgrade_screen_offers_body">نفس الميزات، ولكن نماذج تسعير مختلفة.</string>
<string name="upgrade_screen_offers_body">نفس المزايا، ولكنّ نماذج تسعيرٍ مختلفة.</string>
<string name="upgrade_screen_offers_unavailable_title">الأسعار غير متوفّرة</string>
<string name="upgrade_screen_offers_unavailable_message">لم تعرض سوق جوجل بلاي الأسعار حاليًّا. تحقّق من اتّصالك بالإنترنت وحاول مرّة أخرى بعد قليل.</string>
<string name="upgrade_screen_offers_unavailable_message">لم يعرض جوجل بلاي الأسعار حاليًّا. راجع اتّصالك بالإنترنت وحاول مرّةً أخرى بعد قليل.</string>
<string name="upgrade_screen_subscription_offer_title">الاشتراك السنوي</string>
<string name="upgrade_screen_subscription_offer_body">يتمّ التجديد سنويًّا بعد انتهاء الفترة التجريبية المجّانية. يمكنك الإلغاء في أيّ وقت من خلال سوق جوجل بلاي.</string>
<string name="upgrade_screen_subscription_offer_body_no_trial">يتم التجديد سنويًّا. يمكنك الإلغاء في أيّ وقت من خلال سوق جوجل بلاي.</string>
<string name="upgrade_screen_iap_offer_body">دفعة واحدة، بدون تجديدات.</string>
<string name="upgrade_screen_subscription_offer_body">يتمّ التجديد سنويًّا بعد انتهاء المدّة التجريبية المجّانية. يمكنك الإلغاء في أيّ وقتٍ عبر جوجل بلاي.</string>
<string name="upgrade_screen_subscription_offer_body_no_trial">يتمّ التجديد سنويًّا. يمكنك الإلغاء في أيّ وقتٍ عبر جوجل بلاي.</string>
<string name="upgrade_screen_iap_offer_body">دُفعةً واحدة، دون تجديدات.</string>
<string name="upgrade_screen_benefits_title">مزايا الترقية</string>
<string name="upgrade_screen_restore_body">تطلب عملية الاستعادة من سوق جوجل بلاي إعادة التحقّق من مشتريات هذا التطبيق للحساب الحالي. ولا تبدأ عملية اشتراء جديدة.</string>
<string name="upgrade_screen_restore_checked_message">تحقَّق كابود من جوجل بلاي، ولكن لم يتمّ تأكيد أيّ عملية اشتراء للنسخة الاحترافية للحساب الذي يستخدمه جوجل بلاي لهذا التطبيق.</string>
<string name="upgrade_screen_restore_contact_hint">هل ما زلت تواجه مشكلة؟ تواصل مع الدعم من خلال البريد الإلكتروني الذي استخدمته لإتمام عملية الشراء، وضمّن رقم طلبك من سوق جوجل بلاي.</string>
<string name="upgrade_screen_contact_support_action">التواصُل مع الدعم</string>
<string name="upgrade_screen_restore_body">تطلب عملية الاستعادة من جوجل بلاي إعادة مراجعة مشتريات هذا التطبيق للحساب الحالي. ولا تبدأ عملية اشتراءٍ جديدة.</string>
<string name="upgrade_screen_restore_checked_message">راجَع كابود جوجل بلاي، ولكن لم يتمّ تأكيد أيّ عملية اشتراء النسخة الاحترافية للحساب الذي يستخدمه جوجل بلاي لهذا التطبيق.</string>
<string name="upgrade_screen_restore_contact_hint">هل ما زلت تواجه مشكلةً؟ تواصَل مع الدعم عبر البريد الإلكتروني الذي استخدمته لإتمام عملية الاشتراء، وضمّن رقم طلبك من جوجل بلاي.</string>
<string name="upgrade_screen_contact_support_action">تواصَل مع الدعم</string>
<string name="settings_upgrade_status_description">حالة الترقية والاشتراء الخاصّة بك.</string>
<string name="upgrades_gplay_unavailable_error_description">لا يمكن لكابود الاتّصال بسوق جوجل بلاي. هل سوق جوجل بلاي مُثبَّتة ومُحدَّثة؟ هل حساب جوجل الخاصّ بك مُسجَّل الدخول؟ حاول مسح ذاكرة التخزين المؤقّت لتطبيق جوجل بلاي وإعادة تشغيل جهازك.</string>
<string name="upgrades_gplay_unavailable_error_description">لا يمكن لكابود الاتّصال بجوجل بلاي. هل جوجل بلاي مُثبَّتٌ ومُحدَّث؟ هل حساب جوجل الخاصّ بك مُسجَّل الدخول؟ حاول مسح ذاكرة التخزين المؤقّت لتطبيق جوجل بلاي وإعادة تشغيل جهازك.</string>
<!-- Toast shown when the "Google Play" button of an error dialog can't open Google Play's app info, e.g. Google Play is missing or blocked on this device. "Google Play" is a brand name, keep it untranslated. -->
<string name="upgrades_gplay_not_installed_message">تعذر فتح Google Play. قد يكون التطبيق غير موجود أو معطل أو مقيد على هذا الجهاز.</string>
<string name="upgrades_gplay_internal_error_title">خطأ في جوجل بلاي</string>
<string name="upgrades_gplay_internal_error_description">حدث خطأ داخلي في سوق جوجل بلاي. يُرجى تجربة ما يلي:\n\n• أعِد تشغيل جهازك\n• امسح ذاكرة التخزين المؤقّت لسوق جوجل بلاي\n• حاول مرّة أخرى لاحقًا</string>
<string name="upgrades_gplay_network_error_title">خطأ في الاتّصال</string>
<string name="upgrades_gplay_network_error_description">تعذّر الاتصال بسوق جوجل بلاي. يُرجى التحقّق من اتّصالك بالإنترنت والمحاولة مرّة أخرى.</string>
<string name="upgrades_gplay_not_installed_message">تعذّر فتح جوجل بلاي. قد يكون غير موجودٍ أو مُعطَّلٍ أو مُقيَّدٍ على هذا الجهاز.</string>
<string name="upgrades_gplay_internal_error_title">خطأٌ في جوجل بلاي</string>
<string name="upgrades_gplay_internal_error_description">حدث خطأٌ داخليٌّ في جوجل بلاي. يُرجى تجربة ما يلي:\n\n• أعِدْ تشغيل جهازك\n• امسح ذاكرة التخزين المؤقّت لجوجل بلاي\n• حاول مرّةً أخرى لاحقًا</string>
<string name="upgrades_gplay_network_error_title">خطأٌ في الاتّصال</string>
<string name="upgrades_gplay_network_error_description">تعذّر الاتصال بجوجل بلاي. يُرجى التحقّق من اتّصالك بالإنترنت والمحاولة مرّةً أخرى.</string>
<string name="upgrades_gplay_offer_unavailable_title">العرض غير متوفّر</string>
<string name="upgrades_gplay_offer_unavailable_description">لا تقدّم سوق جوجل بلاي حاليًّا خيار الترقية هذا. يُرجى المحاولة لاحقًا أو مراجعة سوق جوجل بلاي.</string>
<string name="upgrade_screen_restore_inconclusive_message">لم يكتمل التحقّق من سوق جوجل بلاي في الوقت المُحدَّد، لذا لا تزال حالة عملية الاشتراء غير معروفة. لم يُطرأ أيّ تغيير على حسابك.</string>
<string name="upgrades_gplay_offer_unavailable_description">لا يقدّم جوجل بلاي حاليًّا خيار الترقية هذا. يُرجى المحاولة لاحقًا أو مراجعة سوق جوجل بلاي.</string>
<string name="upgrade_screen_restore_inconclusive_message">لم تكتمل مراجعة جوجل بلاي في الوقت المُحدَّد، لذا لا تزَل حالة عملية الاشتراء غير معروفة. لم يُطرأ أيّ تغيير على حسابك.</string>
</resources>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Bu ayrı bir satınalmadır — abunəliyinizi ləğv etmir və ya geri ödəmir. Eyni Google hesabından istifadə edin.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abunəlik hələ də aktivdir</string>
<string name="upgrade_screen_sub_still_renewing_message">Abunəliyiniz hələ də yenilənməyə təyin edilib. Əvvəlcə onu Google Play-də ləğv edin, sonra birdəfəlik satınalmaya keçin. Bu yaxınlarda ləğv etmisiniz? Google Play-ə bir az vaxt verin və yenidən cəhd edin.</string>
<string name="upgrade_screen_purchase_check_failed_message">Satın almalarını Google Play-də yoxlamaq olmadı. İki dəfə satın almamaq üçün bir az sonra yenidən cəhd et.</string>
<string name="upgrade_screen_pending_card_title">Ödəniş gözləyir</string>
<string name="upgrade_screen_pending_card_body">Google Play hələ də ödənişini emal etir. Ödəniş dəvam etdikdən sonra Pro avtomatik olaraq açılacaq. Bəzi ödəniş üsulları bir müddət çəkə bilər. Başqa heç bir şey etmən lazım deyil.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play, gözləmə ödənişi olan bir satın almağı tapdı və hələ də onu emal etir. Ödəniş dəvam etdikdən sonra Pro avtomatik olaraq açılacaq.</string>
<string name="upgrade_screen_grace_title">Satınalmanız təsdiqlənir</string>
<string name="upgrade_screen_grace_body_short">Google Play hələ satınalmanızı təsdiqləməyib. Pro hələ də aktivdir — heç bir əməliyyat lazım deyil.</string>
<string name="upgrade_screen_grace_body">Google Play bir müddətdir satınalmanızı təsdiqləməyib. Pro hələ də aktivdir. Onlayn olduğunuzdan və satınalma üçün istifadə edilən Google hesabı ilə daxil olduğunuzdan əmin olun, sonra bərpa etməyi sınayın.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Това е отделна покупка — тя не отменя и не възстановява средства по абонамента ви. Използвайте същия акаунт в Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Абонаментът все още е активен</string>
<string name="upgrade_screen_sub_still_renewing_message">Абонаментът ви все още е настроен да се подновява. Първо го отменете в Google Play, а след това преминете към еднократната покупка. Току-що отменихте? Изчакайте малко Google Play и опитайте отново.</string>
<string name="upgrade_screen_purchase_check_failed_message">Не можахме да проверим покупките ти в Google Play. За да избегнеш двойна покупка, опитай отново след малко.</string>
<string name="upgrade_screen_pending_card_title">Плащане в изчакване</string>
<string name="upgrade_screen_pending_card_body">Google Play все още обработва плащането ти. Pro се отключва автоматично, щом плащането бъде извършено. При някои методи на плащане това може да отнеме известно време. Не е нужно да правиш нищо друго.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play откри покупка с изчакващо плащане и все още я обработва. Pro се отключва автоматично, щом плащането бъде извършено.</string>
<string name="upgrade_screen_grace_title">Потвърждаване на покупката ви</string>
<string name="upgrade_screen_grace_body_short">Google Play все още не е потвърдил покупката ви. Pro все още е активен — не са необходими действия.</string>
<string name="upgrade_screen_grace_body">Google Play не е потвърдил покупката ви от известно време. Pro все още е активен. Уверете се, че сте онлайн и влезли с акаунта в Google, използван за покупката, след което опитайте да възстановите.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">এটি একটি আলাদা ক্রয় — এটি আপনার সাবস্ক্রিপশন বাতিল বা ফেরত দেয় না। একই Google অ্যাকাউন্ট ব্যবহার করুন।</string>
<string name="upgrade_screen_sub_still_renewing_title">সাবস্ক্রিপশন এখনও সক্রিয়</string>
<string name="upgrade_screen_sub_still_renewing_message">আপনার সাবস্ক্রিপশন এখনও নবায়ন হওয়ার জন্য সেট করা আছে। প্রথমে Google Play-তে এটি বাতিল করুন, তারপর এককালীন ক্রয়ে পরিবর্তন করুন। এইমাত্র বাতিল করেছেন? Google Play-কে কিছুটা সময় দিন এবং আবার চেষ্টা করুন।</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play-এর সাথে আপনার ক্রয় যাচাই করা যায়নি। দ্বিগুণ ক্রয় এড়াতে, কিছুক্ষণ পর আবার চেষ্টা করুন।</string>
<string name="upgrade_screen_pending_card_title">পেমেন্ট মুলতুবি আছে</string>
<string name="upgrade_screen_pending_card_body">Google Play এখনো আপনার পেমেন্ট প্রক্রিয়া করছে। পেমেন্ট সম্পন্ন হওয়ার সাথে সাথেই Pro স্বয়ংক্রিয়ভাবে আনলক হবে। কিছু পেমেন্ট পদ্ধতিতে কিছুটা সময় লাগতে পারে। আপনাকে আর কিছু করতে হবে না।</string>
<string name="upgrade_screen_pending_dialog_message">Google Play একটি মুলতুবি পেমেন্টসহ ক্রয় খুঁজে পেয়েছে এবং সেটি এখনো প্রক্রিয়া করছে। পেমেন্ট সম্পন্ন হওয়ার সাথে সাথেই Pro স্বয়ংক্রিয়ভাবে আনলক হবে।</string>
<string name="upgrade_screen_grace_title">আপনার কেনাকাটা নিশ্চিত করা হচ্ছে</string>
<string name="upgrade_screen_grace_body_short">Google Play এখনও আপনার কেনাকাটা নিশ্চিত করেনি। Pro এখনও সক্রিয় আছে — কোনো পদক্ষেপের প্রয়োজন নেই।</string>
<string name="upgrade_screen_grace_body">Google Play অনেকক্ষণ ধরে আপনার কেনাকাটা নিশ্চিত করেনি। Pro এখনও সক্রিয় আছে। আপনি অনলাইনে আছেন এবং কেনাকাটার জন্য ব্যবহৃত Google অ্যাকাউন্ট দিয়ে সাইন ইন করা আছেন কিনা তা নিশ্চিত করুন, তারপর পুনরুদ্ধার করার চেষ্টা করুন।</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Aquesta és una compra separada: no cancel·la ni reemborsa la vostra subscripció. Feu servir el mateix compte de Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">La subscripció encara està activa</string>
<string name="upgrade_screen_sub_still_renewing_message">La vostra subscripció encara està configurada per renovar-se. Cancel·leu-la primer al Google Play i després canvieu a la compra única. Acabeu de cancel·lar-la? Doneu un moment al Google Play i torneu-ho a provar.</string>
<string name="upgrade_screen_purchase_check_failed_message">No s\'han pogut comprovar les teves compres amb Google Play. Per evitar una compra duplicada, torna-ho a provar d\'aquí a un moment.</string>
<string name="upgrade_screen_pending_card_title">Pagament pendent</string>
<string name="upgrade_screen_pending_card_body">Google Play encara està processant el teu pagament. La versió Pro es desbloqueja automàticament un cop es completi el pagament. Alguns mètodes de pagament poden trigar una estona. No cal que facis res més.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ha trobat una compra amb un pagament pendent i encara l\'està processant. La versió Pro es desbloqueja automàticament un cop es completi el pagament.</string>
<string name="upgrade_screen_grace_title">S\'està confirmant la vostra compra</string>
<string name="upgrade_screen_grace_body_short">El Google Play encara no ha confirmat la vostra compra. El Pro segueix actiu, no cal fer res.</string>
<string name="upgrade_screen_grace_body">Fa temps que el Google Play no confirma la vostra compra. El Pro segueix actiu. Assegureu-vos que teniu connexió i heu iniciat sessió amb el compte de Google que vau fer servir per a la compra, i després proveu de restaurar-la.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Toto je samostatný nákup — nezruší ani nevrátí peníze za vaše předplatné. Použijte stejný účet Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Předplatné je stále aktivní</string>
<string name="upgrade_screen_sub_still_renewing_message">Vaše předplatné je stále nastaveno na obnovování. Nejprve ho zrušte v Google Play a poté přejděte na jednorázový nákup. Právě jste ho zrušili? Dejte Google Play chvilku a zkuste to znovu.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nepodařilo se ověřit tvé nákupy ve službě Google Play. Aby nedošlo k dvojímu nákupu, zkus to za chvíli znovu.</string>
<string name="upgrade_screen_pending_card_title">Platba se zpracovává</string>
<string name="upgrade_screen_pending_card_body">Google Play stále zpracovává tvou platbu. Verze Pro se odemkne automaticky, jakmile bude platba dokončena. U některých platebních metod to může chvíli trvat. Nemusíš dělat nic dalšího.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play našel nákup s čekající platbou a stále ji zpracovává. Verze Pro se odemkne automaticky, jakmile bude platba dokončena.</string>
<string name="upgrade_screen_grace_title">Potvrzování vašeho nákupu</string>
<string name="upgrade_screen_grace_body_short">Google Play zatím nepotvrdil váš nákup. Pro je stále aktivní — není potřeba nic dělat.</string>
<string name="upgrade_screen_grace_body">Google Play už delší dobu nepotvrdil váš nákup. Pro je stále aktivní. Ujistěte se, že jste online a přihlášeni k účtu Google použitému při nákupu, a poté zkuste nákup obnovit.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Dette er et separat køb — det opsiger eller refunderer ikke dit abonnement. Brug den samme Google-konto.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonnement stadig aktivt</string>
<string name="upgrade_screen_sub_still_renewing_message">Dit abonnement er stadig indstillet til at blive fornyet. Opsig det først i Google Play, og skift derefter til engangskøbet. Lige opsagt? Giv Google Play et øjeblik, og prøv igen.</string>
<string name="upgrade_screen_purchase_check_failed_message">Kunne ikke tjekke dine køb med Google Play. For at undgå et dobbeltkøb kan du prøve igen om et øjeblik.</string>
<string name="upgrade_screen_pending_card_title">Betaling afventer</string>
<string name="upgrade_screen_pending_card_body">Google Play behandler stadig din betaling. Pro låses automatisk op, når betalingen er gennemført. Nogle betalingsmetoder kan tage lidt tid. Du behøver ikke gøre andet.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play fandt et køb med en afventende betaling og er stadig ved at behandle det. Pro låses automatisk op, når betalingen er gennemført.</string>
<string name="upgrade_screen_grace_title">Bekræfter dit køb</string>
<string name="upgrade_screen_grace_body_short">Google Play har endnu ikke bekræftet dit køb. Pro er stadig aktivt — ingen handling nødvendig.</string>
<string name="upgrade_screen_grace_body">Google Play har ikke bekræftet dit køb i et stykke tid. Pro er stadig aktivt. Sørg for, at du er online og logget ind med den Google-konto, der blev brugt til købet, og prøv derefter at gendanne.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Dies ist ein separater Kauf — er kündigt oder erstattet dein Abonnement nicht. Verwende dasselbe Google-Konto.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonnement noch aktiv</string>
<string name="upgrade_screen_sub_still_renewing_message">Dein Abonnement ist weiterhin so eingestellt, dass es sich verlängert. Kündige es zuerst in Google Play und wechsle erst dann zum Einmalkauf. Gerade gekündigt? Gib Google Play einen Moment Zeit und versuche es erneut.</string>
<string name="upgrade_screen_purchase_check_failed_message">Deine Käufe konnten nicht bei Google Play überprüft werden. Um einen doppelten Kauf zu vermeiden, versuch es gleich noch einmal.</string>
<string name="upgrade_screen_pending_card_title">Zahlung ausstehend</string>
<string name="upgrade_screen_pending_card_body">Google Play verarbeitet deine Zahlung noch. Pro wird automatisch freigeschaltet, sobald die Zahlung durch ist. Bei manchen Zahlungsmethoden kann das etwas dauern. Du musst nichts weiter tun.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play hat einen Kauf mit ausstehender Zahlung gefunden und verarbeitet ihn noch. Pro wird automatisch freigeschaltet, sobald die Zahlung durch ist.</string>
<string name="upgrade_screen_grace_title">Dein Kauf wird bestätigt</string>
<string name="upgrade_screen_grace_body_short">Google Play hat deinen Kauf noch nicht bestätigt. Pro ist weiterhin aktiv — du musst nichts tun.</string>
<string name="upgrade_screen_grace_body">Google Play hat deinen Kauf seit einer Weile nicht bestätigt. Pro ist weiterhin aktiv. Stelle sicher, dass du online und mit dem Google-Konto angemeldet bist, das für den Kauf verwendet wurde, und versuche dann die Wiederherstellung.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Αυτή είναι μια ξεχωριστή αγορά — δεν ακυρώνει ούτε επιστρέφει τα χρήματα της συνδρομής σας. Χρησιμοποιήστε τον ίδιο λογαριασμό Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Η συνδρομή εξακολουθεί να είναι ενεργή</string>
<string name="upgrade_screen_sub_still_renewing_message">Η συνδρομή σας εξακολουθεί να έχει οριστεί να ανανεωθεί. Ακυρώστε την πρώτα στο Google Play και έπειτα μεταβείτε στην εφάπαξ αγορά. Μόλις ακυρώσατε; Δώστε λίγο χρόνο στο Google Play και δοκιμάστε ξανά.</string>
<string name="upgrade_screen_purchase_check_failed_message">Δεν ήταν δυνατός ο έλεγχος των αγορών σου μέσω του Google Play. Για να αποφύγεις διπλή αγορά, δοκίμασε ξανά σε λίγο.</string>
<string name="upgrade_screen_pending_card_title">Εκκρεμή πληρωμή</string>
<string name="upgrade_screen_pending_card_body">Το Google Play επεξεργάζεται ακόμη την πληρωμή σας. Η έκδοση Pro θα ξεκλειδωθεί αυτόματα μόλις ολοκληρωθεί η πληρωμή. Ορισμένοι τρόποι πληρωμής ενδέχεται να χρειαστούν λίγο χρόνο. Δεν χρειάζεται να κάνετε κάτι άλλο.</string>
<string name="upgrade_screen_pending_dialog_message">Το Google Play εντόπισε μια αγορά με εκκρεμή πληρωμή και εξακολουθεί να την επεξεργάζεται. Η έκδοση Pro θα ξεκλειδωθεί αυτόματα μόλις ολοκληρωθεί η πληρωμή.</string>
<string name="upgrade_screen_grace_title">Επιβεβαίωση της αγοράς σας</string>
<string name="upgrade_screen_grace_body_short">Το Google Play δεν έχει επιβεβαιώσει ακόμα την αγορά σας. Το Pro παραμένει ενεργό — δεν απαιτείται καμία ενέργεια.</string>
<string name="upgrade_screen_grace_body">Το Google Play δεν έχει επιβεβαιώσει την αγορά σας εδώ και καιρό. Το Pro παραμένει ενεργό. Βεβαιωθείτε ότι είστε συνδεδεμένοι στο διαδίκτυο και συνδεδεμένοι με τον λογαριασμό Google που χρησιμοποιήσατε για την αγορά, και μετά δοκιμάστε να την επαναφέρετε.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Esta es una compra separada — no cancela ni reembolsa tu suscripción. Usá la misma cuenta de Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Suscripción todavía activa</string>
<string name="upgrade_screen_sub_still_renewing_message">Tu suscripción todavía está configurada para renovarse. Cancelala primero en Google Play y después cambiá a la compra única. ¿La acabás de cancelar? Dale un momento a Google Play y probá de nuevo.</string>
<string name="upgrade_screen_purchase_check_failed_message">No se pudieron verificar tus compras con Google Play. Para evitar una compra duplicada, intentá de nuevo en un momento.</string>
<string name="upgrade_screen_pending_card_title">Pago pendiente</string>
<string name="upgrade_screen_pending_card_body">Google Play sigue procesando tu pago. Pro se desbloquea automáticamente una vez que el pago se haya procesado. Algunos métodos de pago pueden tomar un tiempo. No hay nada más que necesites hacer.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play encontró una compra con un pago pendiente y sigue procesándola. Pro se desbloquea automáticamente una vez que el pago se haya procesado.</string>
<string name="upgrade_screen_grace_title">Confirmando tu compra</string>
<string name="upgrade_screen_grace_body_short">Google Play todavía no confirmó tu compra. Pro sigue activo — no es necesario hacer nada.</string>
<string name="upgrade_screen_grace_body">Google Play no confirmó tu compra desde hace un tiempo. Pro sigue activo. Asegurate de estar conectado a internet y de haber iniciado sesión con la cuenta de Google que usaste para la compra, y después probá restaurar.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Esta es una compra independiente; no cancela ni reembolsa tu suscripción. Usa la misma cuenta de Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Suscripción todavía activa</string>
<string name="upgrade_screen_sub_still_renewing_message">Tu suscripción todavía está configurada para renovarse. Cancélala primero en Google Play y luego cambia a la compra única. ¿Acabas de cancelarla? Dale un momento a Google Play e inténtalo de nuevo.</string>
<string name="upgrade_screen_purchase_check_failed_message">No se pudieron verificar tus compras con Google Play. Para evitar una compra duplicada, intenta de nuevo en un momento.</string>
<string name="upgrade_screen_pending_card_title">Pago pendiente</string>
<string name="upgrade_screen_pending_card_body">Google Play todavía está procesando tu pago. Pro se desbloqueará automáticamente cuando el pago se procese. Algunos métodos de pago pueden tardar un tiempo. No hay nada más que debas hacer.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play encontró una compra con pago pendiente y todavía la está procesando. Pro se desbloqueará automáticamente cuando el pago se procese.</string>
<string name="upgrade_screen_grace_title">Confirmando tu compra</string>
<string name="upgrade_screen_grace_body_short">Google Play todavía no ha confirmado tu compra. Pro sigue activo; no necesitas hacer nada.</string>
<string name="upgrade_screen_grace_body">Google Play no ha confirmado tu compra desde hace un tiempo. Pro sigue activo. Asegúrate de estar conectado a internet y de haber iniciado sesión con la cuenta de Google que usaste para la compra, y luego intenta restaurarla.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Esta es una compra independiente: no cancela ni reembolsa tu suscripción. Usa la misma cuenta de Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">La suscripción sigue activa</string>
<string name="upgrade_screen_sub_still_renewing_message">Tu suscripción todavía está configurada para renovarse. Cancélala primero en Google Play y luego cambia a la compra única. ¿Acabas de cancelarla? Dale un momento a Google Play e inténtalo de nuevo.</string>
<string name="upgrade_screen_purchase_check_failed_message">No se pudieron verificar tus compras con Google Play. Para evitar una compra duplicada, intenta de nuevo en un momento.</string>
<string name="upgrade_screen_pending_card_title">Pago pendiente</string>
<string name="upgrade_screen_pending_card_body">Google Play sigue procesando tu pago. Pro se desbloqueará automáticamente una vez que el pago se procese. Algunos métodos de pago pueden tardar un tiempo. No hay nada más que necesites hacer.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play encontró una compra con un pago pendiente y sigue procesándola. Pro se desbloqueará automáticamente una vez que el pago se procese.</string>
<string name="upgrade_screen_grace_title">Confirmando tu compra</string>
<string name="upgrade_screen_grace_body_short">Google Play todavía no ha confirmado tu compra. Pro sigue activo; no es necesario hacer nada.</string>
<string name="upgrade_screen_grace_body">Google Play no ha confirmado tu compra desde hace tiempo. Pro sigue activo. Asegúrate de estar conectado a internet y de haber iniciado sesión con la cuenta de Google usada para la compra, y prueba a restaurarla.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">See on eraldi ost — see ei tühista ega tagasta sinu tellimust. Kasuta sama Google\'i kontot.</string>
<string name="upgrade_screen_sub_still_renewing_title">Tellimus on endiselt kasutusel</string>
<string name="upgrade_screen_sub_still_renewing_message">Tellimus uueneb ikka veel ise. Tühista see kõigepealt Google Plays ja lülitu seejärel ühekordsele ostule. Just tühistasid? Anna Google Playle hetk aega ja proovi uuesti.</string>
<string name="upgrade_screen_purchase_check_failed_message">Ei saanud Google Play\'ga oma oste kontrollida. Kahekordse ostu vältimiseks proovi uuesti hetke pärast.</string>
<string name="upgrade_screen_pending_card_title">Makse ootel</string>
<string name="upgrade_screen_pending_card_body">Google Play töötleb ikka sinu makset. Pro avab automaatselt, kui makse läbi läheb. Mõned makseviisid võivad aega võtta. Pole vaja midagi muud teha.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play leidis ostu ootel maksega ja töötleb seda ikka. Pro avab automaatselt, kui makse läbi läheb.</string>
<string name="upgrade_screen_grace_title">Ostu kinnitamine</string>
<string name="upgrade_screen_grace_body_short">Google Play pole ostu veel kinnitanud. Pro on endiselt kasutatav — midagi ette võtta pole vaja.</string>
<string name="upgrade_screen_grace_body">Google Play pole ostu juba mõnda aega kinnitanud. Pro on endiselt kasutatav. Veendu, et oled võrgus ja sisse logitud Google\'i kontoga, millega ostsid, ning proovi seejärel taastada.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Beste erosketa bat da — ez du zure harpidetza bertan behera uzten edo itzultzen. Erabili Google kontu bera.</string>
<string name="upgrade_screen_sub_still_renewing_title">Harpidetza oraindik aktibo</string>
<string name="upgrade_screen_sub_still_renewing_message">Zure harpidetza oraindik berritzeko ezarrita dago. Utzi lehenengo Google Playn, eta gero aldatu aldi bateko erosketara. Duela gutxi utzi duzu? Eman Google Play-ri unetxo bat eta saiatu berriro.</string>
<string name="upgrade_screen_purchase_check_failed_message">Ezin izan dira zure erosketak Google Play-rekin egiaztatu. Erosketa bikoitza saihesteko, saiatu berriro pixka bat geroago.</string>
<string name="upgrade_screen_pending_card_title">Ordainketa zain</string>
<string name="upgrade_screen_pending_card_body">Google Play zure ordainketa prozesatzen ari da oraindik. Pro automatikoki desblokeatuko da ordainketa burutzen denean. Ordainketa-metodo batzuek denbora har dezakete. Ez duzu ezer gehiago egin behar.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play-k ordainketa zain duen erosketa bat aurkitu du eta prozesatzen ari da oraindik. Pro automatikoki desblokeatuko da ordainketa burutzen denean.</string>
<string name="upgrade_screen_grace_title">Zure erosketa berresten</string>
<string name="upgrade_screen_grace_body_short">Google Playk oraindik ez du zure erosketa berretsi. Pro aktibo dago oraindik — ez da ekintzarik behar.</string>
<string name="upgrade_screen_grace_body">Google Playk denbora luzez ez du zure erosketa berretsi. Pro aktibo dago oraindik. Ziurtatu konektatuta zaudela eta erosketa egiteko erabilitako Google kontuarekin saioa hasita duzula, eta gero saiatu leheneratzen.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">این یک خرید جداگانه است — اشتراک شما را لغو یا بازپرداخت نمی‌کند. از همان حساب گوگل استفاده کنید.</string>
<string name="upgrade_screen_sub_still_renewing_title">اشتراک همچنان فعال است</string>
<string name="upgrade_screen_sub_still_renewing_message">اشتراک شما همچنان برای تمدید تنظیم شده است. ابتدا آن را در گوگل پلی لغو کنید، سپس به خرید یک‌باره تغییر دهید. تازه لغو کرده‌اید؟ کمی به گوگل پلی زمان بدهید و دوباره امتحان کنید.</string>
<string name="upgrade_screen_purchase_check_failed_message">بررسی خریدهای شما با گوگل پلی ممکن نشد. برای جلوگیری از خرید دوباره، کمی بعد دوباره امتحان کنید.</string>
<string name="upgrade_screen_pending_card_title">پرداخت در انتظار</string>
<string name="upgrade_screen_pending_card_body">گوگل پلی هنوز در حال پردازش پرداخت شماست. نسخه Pro به‌محض تکمیل پرداخت به‌طور خودکار فعال می‌شود. برخی روش‌های پرداخت ممکن است زمان‌بر باشند. کار دیگری لازم نیست انجام دهید.</string>
<string name="upgrade_screen_pending_dialog_message">گوگل پلی خریدی با پرداخت در انتظار پیدا کرده و هنوز در حال پردازش آن است. نسخه Pro به‌محض تکمیل پرداخت به‌طور خودکار فعال می‌شود.</string>
<string name="upgrade_screen_grace_title">در حال تأیید خرید شما</string>
<string name="upgrade_screen_grace_body_short">گوگل پلی هنوز خرید شما را تأیید نکرده است. Pro همچنان فعال است — نیازی به اقدام نیست.</string>
<string name="upgrade_screen_grace_body">گوگل پلی مدتی است خرید شما را تأیید نکرده است. Pro همچنان فعال است. مطمئن شوید که آنلاین هستید و با همان حساب گوگلی که خرید را با آن انجام داده‌اید وارد شده‌اید، سپس بازیابی را امتحان کنید.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Tämä on erillinen ostos — se ei peruuta eikä hyvitä tilaustasi. Käytä samaa Google-tiliä.</string>
<string name="upgrade_screen_sub_still_renewing_title">Tilaus on yhä aktiivinen</string>
<string name="upgrade_screen_sub_still_renewing_message">Tilauksesi on yhä asetettu uusiutumaan. Peruuta se ensin Google Playssa ja vaihda sitten kertaostokseen. Peruitko juuri? Anna Google Playlle hetki aikaa ja yritä uudelleen.</string>
<string name="upgrade_screen_purchase_check_failed_message">Ostoksiasi ei voitu tarkistaa Google Playsta. Vältä kaksinkertainen ostos yrittämällä uudelleen hetken kuluttua.</string>
<string name="upgrade_screen_pending_card_title">Maksu käsittelyssä</string>
<string name="upgrade_screen_pending_card_body">Google Play käsittelee maksuasi vielä. Pro-versio avautuu automaattisesti, kun maksu on suoritettu. Joidenkin maksutapojen käsittely voi kestää hetken. Sinun ei tarvitse tehdä muuta.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play löysi ostoksen, jonka maksu on vielä käsittelyssä. Pro-versio avautuu automaattisesti, kun maksu on suoritettu.</string>
<string name="upgrade_screen_grace_title">Vahvistetaan ostostasi</string>
<string name="upgrade_screen_grace_body_short">Google Play ei ole vielä vahvistanut ostostasi. Pro on yhä aktiivinen — mitään ei tarvitse tehdä.</string>
<string name="upgrade_screen_grace_body">Google Play ei ole vahvistanut ostostasi hetkeen. Pro on yhä aktiivinen. Varmista, että olet verkossa ja kirjautuneena sillä Google-tilillä, jolla teit ostoksen, ja yritä sitten palauttaa se.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ito ay hiwalay na pagbili — hindi nito kinakansela o nire-refund ang iyong subscription. Gamitin ang parehong Google account.</string>
<string name="upgrade_screen_sub_still_renewing_title">Aktibo pa rin ang subscription</string>
<string name="upgrade_screen_sub_still_renewing_message">Nakatakda pa ring mag-renew ang iyong subscription. Kanselahin muna ito sa Google Play, pagkatapos ay lumipat sa one-time na pagbili. Bagong-kansela lang? Bigyan ng oras ang Google Play at subukan muli.</string>
<string name="upgrade_screen_purchase_check_failed_message">Hindi nacheck ang iyong mga pagbili sa Google Play. Upang maiwasan ang doble na pagbili, subukan ulit sa loob ng ilang sandali.</string>
<string name="upgrade_screen_pending_card_title">Naghihintay na pagbabayad</string>
<string name="upgrade_screen_pending_card_body">Ang Google Play ay patuloy na nagpoproseso ng iyong pagbabayad. Ang Pro ay mag-unlock nang awtomatiko kapag napasa ang pagbabayad. Ang ilang paraan ng pagbabayad ay maaaring tumagal ng ilang oras. Walang ibang kailangan mong gawin.</string>
<string name="upgrade_screen_pending_dialog_message">Ang Google Play ay nahanap ang isang pagbili na may naghihintay na pagbabayad at patuloy na ginagawa itong proseso. Ang Pro ay mag-unlock nang awtomatiko kapag napasa ang pagbabayad.</string>
<string name="upgrade_screen_grace_title">Kinukumpirma ang iyong pagbili</string>
<string name="upgrade_screen_grace_body_short">Hindi pa nakukumpirma ng Google Play ang iyong pagbili. Aktibo pa rin ang Pro — walang kailangang gawin.</string>
<string name="upgrade_screen_grace_body">Matagal nang hindi nakukumpirma ng Google Play ang iyong pagbili. Aktibo pa rin ang Pro. Siguraduhing online ka at naka-sign in sa Google account na ginamit sa pagbili, pagkatapos ay subukang i-restore.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Il sagit dun achat distinct — il nannule ni ne rembourse votre abonnement. Utilisez le même compte Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonnement toujours actif</string>
<string name="upgrade_screen_sub_still_renewing_message">Votre abonnement est toujours configuré pour se renouveler. Annulez-le dabord dans Google Play, puis passez à lachat unique. Vous venez de lannuler ? Laissez un peu de temps à Google Play et réessayez.</string>
<string name="upgrade_screen_purchase_check_failed_message">Impossible de vérifier tes achats avec Google Play. Pour éviter un achat en double, réessaie dans un instant.</string>
<string name="upgrade_screen_pending_card_title">Paiement en attente</string>
<string name="upgrade_screen_pending_card_body">Google Play traite toujours ton paiement. Pro se déverrouille automatiquement une fois le paiement effectué. Certains modes de paiement peuvent prendre du temps. Tu n\'as rien d\'autre à faire.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play a trouvé un achat avec un paiement en attente et le traite actuellement. Pro se déverrouille automatiquement une fois le paiement effectué.</string>
<string name="upgrade_screen_grace_title">Confirmation de votre achat</string>
<string name="upgrade_screen_grace_body_short">Google Play na pas encore confirmé votre achat. Pro reste actif — aucune action requise.</string>
<string name="upgrade_screen_grace_body">Google Play na pas confirmé votre achat depuis un moment. Pro reste actif. Assurez-vous d’être connecté à Internet et au compte Google utilisé pour lachat, puis essayez de restaurer.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Esta é unha compra separada — non cancela nin reembolsa a túa subscrición. Usa a mesma conta de Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">A subscrición aínda está activa</string>
<string name="upgrade_screen_sub_still_renewing_message">A túa subscrición aínda está configurada para renovarse. Cancélaa primeiro en Google Play e despois cambia á compra única. Acabas de cancelala? Dálle un momento a Google Play e téntao de novo.</string>
<string name="upgrade_screen_purchase_check_failed_message">Non se puideron comprobar as túas compras con Google Play. Para evitar unha compra duplicada, téntao de novo nun momento.</string>
<string name="upgrade_screen_pending_card_title">Pagamento pendente</string>
<string name="upgrade_screen_pending_card_body">Google Play aínda está a procesar o teu pagamento. O Pro desbloquéase automaticamente en canto se complete o pagamento. Algúns métodos de pagamento poden tardar un tempo. Non tes que facer nada máis.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play atopou unha compra cun pagamento pendente e aínda o está a procesar. O Pro desbloquéase automaticamente en canto se complete o pagamento.</string>
<string name="upgrade_screen_grace_title">Confirmando a túa compra</string>
<string name="upgrade_screen_grace_body_short">Google Play aínda non confirmou a túa compra. Pro segue activo — non se require ningunha acción.</string>
<string name="upgrade_screen_grace_body">Google Play leva un tempo sen confirmar a túa compra. Pro segue activo. Asegúrate de estar conectado e con sesión iniciada coa conta de Google usada para a compra, e despois proba a restaurala.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">यह एक अलग खरीद है — यह आपकी सदस्यता को रद्द या रिफंड नहीं करता। उसी Google खाते का उपयोग करें।</string>
<string name="upgrade_screen_sub_still_renewing_title">सदस्यता अभी भी सक्रिय है</string>
<string name="upgrade_screen_sub_still_renewing_message">आपकी सदस्यता अभी भी नवीनीकरण के लिए सेट है। पहले इसे Google Play में रद्द करें, फिर एकमुश्त खरीद पर स्विच करें। अभी-अभी रद्द किया? Google Play को थोड़ा समय दें और फिर से कोशिश करें।</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play के साथ आपकी खरीद की जांच नहीं की जा सकी। दोहरी खरीद से बचने के लिए, थोड़ी देर में फिर से कोशिश करें।</string>
<string name="upgrade_screen_pending_card_title">भुगतान लंबित है</string>
<string name="upgrade_screen_pending_card_body">Google Play अभी भी आपके भुगतान को संसाधित कर रहा है। भुगतान पूरा होने पर Pro स्वचालित रूप से अनलॉक हो जाता है। कुछ भुगतान विधियों को कुछ समय लग सकता है। आपको कुछ और करने की आवश्यकता नहीं है।</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ने एक लंबित भुगतान के साथ एक खरीद पाई और अभी भी इसे संसाधित कर रहा है। भुगतान पूरा होने पर Pro स्वचालित रूप से अनलॉक हो जाता है।</string>
<string name="upgrade_screen_grace_title">आपकी खरीदारी की पुष्टि की जा रही है</string>
<string name="upgrade_screen_grace_body_short">Google Play ने अभी तक आपकी खरीदारी की पुष्टि नहीं की है। Pro अभी भी सक्रिय है — कोई कार्रवाई जरूरी नहीं।</string>
<string name="upgrade_screen_grace_body">Google Play ने काफ़ी समय से आपकी खरीदारी की पुष्टि नहीं की है। Pro अभी भी सक्रिय है। सुनिश्चित करें कि आप ऑनलाइन हैं और उसी Google खाते से साइन इन हैं जिससे खरीदारी की गई थी, फिर पुनर्स्थापित करने का प्रयास करें।</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ovo je zasebna kupnja — ona ne otkazuje niti vraća novac za vašu pretplatu. Koristite isti Google račun.</string>
<string name="upgrade_screen_sub_still_renewing_title">Pretplata je i dalje aktivna</string>
<string name="upgrade_screen_sub_still_renewing_message">Vaša pretplata je i dalje postavljena za obnavljanje. Prvo je otkažite u Google Playu, a zatim prijeđite na jednokratnu kupnju. Upravo ste otkazali? Dajte Google Playu trenutak i pokušajte ponovno.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nije moguće provjeriti tvoje kupnje putem Google Playa. Kako bi se izbjegla dvostruka kupnja, pokušaj ponovno za trenutak.</string>
<string name="upgrade_screen_pending_card_title">Plaćanje na čekanju</string>
<string name="upgrade_screen_pending_card_body">Google Play još uvijek obrađuje tvoje plaćanje. Pro se automatski otključava čim plaćanje bude izvršeno. Neki načini plaćanja mogu potrajati. Ne moraš poduzeti ništa drugo.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play je pronašao kupnju s plaćanjem na čekanju i još je uvijek obrađuje. Pro se automatski otključava čim plaćanje bude izvršeno.</string>
<string name="upgrade_screen_grace_title">Potvrđivanje vaše kupnje</string>
<string name="upgrade_screen_grace_body_short">Google Play još nije potvrdio vašu kupnju. Pro je i dalje aktivan — nije potrebna nikakva radnja.</string>
<string name="upgrade_screen_grace_body">Google Play već neko vrijeme nije potvrdio vašu kupnju. Pro je i dalje aktivan. Provjerite jeste li povezani s internetom i prijavljeni s Google računom koji ste koristili za kupnju, a zatim pokušajte vratiti kupnju.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ez egy külön vásárlás nem mondja le és nem téríti vissza az előfizetésedet. Használd ugyanazt a Google-fiókot.</string>
<string name="upgrade_screen_sub_still_renewing_title">Az előfizetés még aktív</string>
<string name="upgrade_screen_sub_still_renewing_message">Az előfizetésed még mindig megújulásra van állítva. Először mondd le a Google Playben, majd válts át az egyszeri vásárlásra. Most mondtad le? Adj egy kis időt a Google Playnek, és próbáld újra.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nem sikerült ellenőrizni a vásárlásaidat a Google Play-en. A kettős vásárlás elkerülése érdekében próbálkozz újra egy pillanat múlva.</string>
<string name="upgrade_screen_pending_card_title">Fizetés függőben</string>
<string name="upgrade_screen_pending_card_body">A Google Play még feldolgozza a fizetésedet. A Pro automatikusan feloldódik, amikor a fizetés feldolgozódik. Egyes fizetési módok feldolgozása több ideig is tarthat. Nem kell tenned semmi mást.</string>
<string name="upgrade_screen_pending_dialog_message">A Google Play egy függőben lévő vásárlást talált és feldolgozza. A Pro automatikusan feloldódik, amikor a fizetés feldolgozódik.</string>
<string name="upgrade_screen_grace_title">A vásárlásod megerősítése</string>
<string name="upgrade_screen_grace_body_short">A Google Play még nem erősítette meg a vásárlásodat. A Pro továbbra is aktív nincs teendő.</string>
<string name="upgrade_screen_grace_body">A Google Play egy ideje nem erősítette meg a vásárlásodat. A Pro továbbra is aktív. Győződj meg róla, hogy online vagy, és be vagy jelentkezve a vásárláshoz használt Google-fiókkal, majd próbáld meg visszaállítani.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Սա առանձին գնում է․ այն չի չեղարկում կամ վերադարձնում ձեր բաժանորդագրության գումարը: Օգտագործեք նույն Google հաշիվը:</string>
<string name="upgrade_screen_sub_still_renewing_title">Բաժանորդագրությունը դեռ ակտիվ է</string>
<string name="upgrade_screen_sub_still_renewing_message">Ձեր բաժանորդագրությունը դեռ նորոգվելու է: Նախ չեղարկեք այն Google Play-ում, ապա անցեք միանգամյա գնմանը: Հենց նոր չեղարկե՞լ եք: Տվեք Google Play-ին մի փոքր ժամանակ և կրկին փորձեք:</string>
<string name="upgrade_screen_purchase_check_failed_message">Չկարողացա ստուգել քո գնումները Google Play-ի հետ: Կրկնակի գնումից խուսափելու համար փորձիր մի պահից հետո:</string>
<string name="upgrade_screen_pending_card_title">Սպասվող վճարում</string>
<string name="upgrade_screen_pending_card_body">Google Play-ը դեռ մշակում է քո վճարումը: Pro-ն ինքնաբերաբար բացվում է, երբ վճարումը հաստատվում է: Որոշ վճարման մեթոդներ կարող են շատ ժամանակ տևել: Այլ ոչինչ չի պետք անել:</string>
<string name="upgrade_screen_pending_dialog_message">Google Play-ը հայտնաբերել է գնում, որը ունի սպասվող վճարում, և դեռ մշակում է այն: Pro-ն ինքնաբերաբար բացվում է, երբ վճարումը հաստատվում է:</string>
<string name="upgrade_screen_grace_title">Հաստատում ենք ձեր գնումը</string>
<string name="upgrade_screen_grace_body_short">Google Play-ը դեռ չի հաստատել ձեր գնումը: Pro-ն դեռ ակտիվ է․ գործողություն պահանջվող չէ:</string>
<string name="upgrade_screen_grace_body">Google Play-ը որոշ ժամանակ է, ինչ չի հաստատել ձեր գնումը: Pro-ն դեռ ակտիվ է: Համոզվեք, որ առցանց եք և մուտք գործած այն Google հաշվով, որով կատարվել է գնումը, ապա փորձեք վերականգնել:</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ini adalah pembelian terpisah — tidak membatalkan atau mengembalikan dana langganan Anda. Gunakan akun Google yang sama.</string>
<string name="upgrade_screen_sub_still_renewing_title">Langganan masih aktif</string>
<string name="upgrade_screen_sub_still_renewing_message">Langganan Anda masih diatur untuk diperpanjang. Batalkan dulu di Google Play, lalu beralih ke sekali bayar. Baru saja membatalkan? Beri waktu sebentar untuk Google Play lalu coba lagi.</string>
<string name="upgrade_screen_purchase_check_failed_message">Tidak dapat memeriksa pembelian kamu dengan Google Play. Untuk menghindari pembelian ganda, coba lagi sesaat lagi.</string>
<string name="upgrade_screen_pending_card_title">Pembayaran tertunda</string>
<string name="upgrade_screen_pending_card_body">Google Play masih memproses pembayaran kamu. Pro akan terbuka otomatis setelah pembayaran berhasil. Beberapa metode pembayaran mungkin membutuhkan waktu. Kamu tidak perlu melakukan apa pun lagi.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play menemukan pembelian dengan pembayaran tertunda dan masih memprosesnya. Pro akan terbuka otomatis setelah pembayaran berhasil.</string>
<string name="upgrade_screen_grace_title">Mengonfirmasi pembelian Anda</string>
<string name="upgrade_screen_grace_body_short">Google Play belum mengonfirmasi pembelian Anda. Pro masih aktif — tidak perlu tindakan apa pun.</string>
<string name="upgrade_screen_grace_body">Google Play belum mengonfirmasi pembelian Anda untuk beberapa waktu. Pro masih aktif. Pastikan Anda daring dan masuk dengan akun Google yang digunakan untuk pembelian, lalu coba pulihkan.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Þessi eru sérstök kaup — þau segja ekki upp áskrift þínni né endurgreiða hana. Notaðu sama Google reikning.</string>
<string name="upgrade_screen_sub_still_renewing_title">Áskrift enn virk</string>
<string name="upgrade_screen_sub_still_renewing_message">Áskrift þín er enn stillt á að endurnýjast. Segðu henni fyrst upp í Google Play og skiptu svo yfir í einskiptiskaup. Nýlega sagt upp? Gefðu Google Play smástund og reyndu aftur.</string>
<string name="upgrade_screen_purchase_check_failed_message">Gekk ekki að athuga kaup þín hjá Google Play. Til að forðast tvöfaldan kaup skaltu reyna aftur eftir augnablik.</string>
<string name="upgrade_screen_pending_card_title">Greiðsla í gangi</string>
<string name="upgrade_screen_pending_card_body">Google Play er enn að vinna úr greiðslu þinni. Pro opnast sjálfkrafa þegar greiðslan fer í gegn. Sumar greiðsluleiðir geta tekið tíma. Þú þarft ekki að gera neitt annað.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play fann kaup með greiðslu á bið og er enn að vinna úr henni. Pro opnast sjálfkrafa þegar greiðslan fer í gegn.</string>
<string name="upgrade_screen_grace_title">Staðfesti kaupin þín</string>
<string name="upgrade_screen_grace_body_short">Google Play hefur ekki staðfest kaupin þín ennþá. Pro er áfram virkt — engra aðgerða er þörf.</string>
<string name="upgrade_screen_grace_body">Google Play hefur ekki staðfest kaupin þín um nokkurt skeið. Pro er áfram virkt. Gakktu úr skugga um að þú sért nettengd(ur) og skráð(ur) inn með Google reikningnum sem notaður var við kaupin, og reyndu svo að endurheimta.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Si tratta di un acquisto separato: non annulla né rimborsa il tuo abbonamento. Usa lo stesso account Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abbonamento ancora attivo</string>
<string name="upgrade_screen_sub_still_renewing_message">Il tuo abbonamento è ancora impostato per il rinnovo. Annullalo prima su Google Play, poi passa allacquisto una tantum. Hai appena annullato? Dai un momento a Google Play e riprova.</string>
<string name="upgrade_screen_purchase_check_failed_message">Impossibile verificare i tuoi acquisti con Google Play. Per evitare un acquisto doppio, riprova tra un momento.</string>
<string name="upgrade_screen_pending_card_title">Pagamento in sospeso</string>
<string name="upgrade_screen_pending_card_body">Google Play sta ancora elaborando il tuo pagamento. Pro si sblocca automaticamente una volta completato il pagamento. Alcuni metodi di pagamento possono richiedere del tempo. Non devi fare nient\'altro.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ha trovato un acquisto con un pagamento in sospeso e lo sta ancora elaborando. Pro si sblocca automaticamente una volta completato il pagamento.</string>
<string name="upgrade_screen_grace_title">Conferma del tuo acquisto in corso</string>
<string name="upgrade_screen_grace_body_short">Google Play non ha ancora confermato il tuo acquisto. Pro è ancora attivo: nessuna azione necessaria.</string>
<string name="upgrade_screen_grace_body">Google Play non ha confermato il tuo acquisto da un po di tempo. Pro è ancora attivo. Assicurati di essere online e di aver effettuato laccesso con laccount Google utilizzato per lacquisto, poi prova a ripristinare.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">זוהי רכישה נפרדת — היא אינה מבטלת או מזכה את המינוי שלך. השתמש באותו חשבון Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">המינוי עדיין פעיל</string>
<string name="upgrade_screen_sub_still_renewing_message">המינוי שלך עדיין מוגדר להתחדש. בטל אותו קודם ב-Google Play, ולאחר מכן עבור לרכישה החד-פעמית. בטלת הרגע? תן ל-Google Play רגע ונסה שוב.</string>
<string name="upgrade_screen_purchase_check_failed_message">לא ניתן היה לבדוק את הרכישות שלך מול Google Play. כדי למנוע רכישה כפולה, נסה שוב בעוד רגע.</string>
<string name="upgrade_screen_pending_card_title">התשלום ממתין</string>
<string name="upgrade_screen_pending_card_body">Google Play עדיין מעבד את התשלום שלך. גרסת Pro תיפתח אוטומטית ברגע שהתשלום יאושר. חלק מאמצעי התשלום עשויים לקחת זמן. אין צורך לעשות דבר נוסף.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play מצא רכישה עם תשלום ממתין ועדיין מעבד אותה. גרסת Pro תיפתח אוטומטית ברגע שהתשלום יאושר.</string>
<string name="upgrade_screen_grace_title">מאשר את הרכישה שלך</string>
<string name="upgrade_screen_grace_body_short">Google Play עדיין לא אישר את הרכישה שלך. Pro עדיין פעיל — אין צורך בפעולה.</string>
<string name="upgrade_screen_grace_body">Google Play לא אישר את הרכישה שלך כבר זמן מה. Pro עדיין פעיל. ודא שאתה מחובר לאינטרנט ומחובר עם חשבון ה-Google שבו בוצעה הרכישה, ולאחר מכן נסה לשחזר.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">これは別の購入です。サブスクリプションのキャンセルや返金は行われません。同じGoogleアカウントを使用してください。</string>
<string name="upgrade_screen_sub_still_renewing_title">サブスクリプションはまだ有効です</string>
<string name="upgrade_screen_sub_still_renewing_message">サブスクリプションはまだ更新するように設定されています。まずGoogle Playで解約してから、1回限りの購入に切り替えてください。解約したばかりの場合は、Google Playの反映を少々待ってからもう一度お試しください。</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Playでの購入を確認できませんでした。二重購入を避けるため、しばらく待ってからもう一度試してください。</string>
<string name="upgrade_screen_pending_card_title">支払い待機中</string>
<string name="upgrade_screen_pending_card_body">Google Playがお支払いを処理しています。支払いが完了するとProが自動的にロック解除されます。支払い方法によっては時間がかかる場合があります。何もしなくても大丈夫です。</string>
<string name="upgrade_screen_pending_dialog_message">Google Playが支払い待機中の購入を検出し、処理中です。支払いが完了するとProが自動的にロック解除されます。</string>
<string name="upgrade_screen_grace_title">購入を確認中</string>
<string name="upgrade_screen_grace_body_short">Google Playがまだ購入を確認していません。Proは引き続き有効です。特に操作は必要ありません。</string>
<string name="upgrade_screen_grace_body">Google Playがしばらく購入を確認していません。Proは引き続き有効です。オンラインになっていること、購入時に使用したGoogleアカウントでサインインしていることを確認してから、復元をお試しください。</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ეს ცალკე შესყიდვაა — ის არ აუქმებს და არ აბრუნებს თანხას თქვენს გამოწერაზე. გამოიყენეთ იგივე Google ანგარიში.</string>
<string name="upgrade_screen_sub_still_renewing_title">გამოწერა კვლავ აქტიურია</string>
<string name="upgrade_screen_sub_still_renewing_message">თქვენი გამოწერა კვლავ დაყენებულია განახლებაზე. ჯერ გააუქმეთ ის Google Play-ში, შემდეგ გადადით ერთჯერად შესყიდვაზე. ახლახან გააუქმეთ? მიეცით Google Play-ს დრო და სცადეთ ხელახლა.</string>
<string name="upgrade_screen_purchase_check_failed_message">ვერ მოხერხდა შენი შესყიდვების შემოწმება Google Play-სთან. ორმაგი შესყიდვის თავიდან ასაცილებლად, სცადე ისევ ცოტა ხანში.</string>
<string name="upgrade_screen_pending_card_title">გადახდა მოლოდინშია</string>
<string name="upgrade_screen_pending_card_body">Google Play ჯერ კიდევ ამუშავებს შენს გადახდას. Pro ავტომატურად გაიხსნება, როგორც კი გადახდა დასრულდება. ზოგიერთ გადახდის მეთოდს გარკვეული დრო სჭირდება. სხვა არაფრის გაკეთება არ გჭირდება.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play-მ იპოვა შესყიდვა მოლოდინში მყოფი გადახდით და ჯერ კიდევ ამუშავებს მას. Pro ავტომატურად გაიხსნება, როგორც კი გადახდა დასრულდება.</string>
<string name="upgrade_screen_grace_title">თქვენი შესყიდვის დადასტურება</string>
<string name="upgrade_screen_grace_body_short">Google Play-მ ჯერ არ დაადასტურა თქვენი შესყიდვა. Pro კვლავ აქტიურია — არანაირი მოქმედება არ არის საჭირო.</string>
<string name="upgrade_screen_grace_body">Google Play-მ დიდი ხანია არ დაუდასტურებია თქვენი შესყიდვა. Pro კვლავ აქტიურია. დარწმუნდით, რომ ხართ ონლაინ და შესული ხართ იმ Google ანგარიშით, რომლითაც შესყიდვა განახორციელეთ, შემდეგ სცადეთ აღდგენა.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">នេះជាការទិញផ្សេងដាច់ដោយឡែក — វាមិនលុបចោល ឬបង្វិលប្រាក់ការជាវរបស់អ្នកទេ។ សូមប្រើគណនី Google ដូចគ្នា។</string>
<string name="upgrade_screen_sub_still_renewing_title">ការជាវនៅតែសកម្ម</string>
<string name="upgrade_screen_sub_still_renewing_message">ការជាវរបស់អ្នកនៅតែត្រូវបានកំណត់ឲ្យបន្ត។ សូមលុបចោលវានៅក្នុង Google Play ជាមុនសិន រួចប្ដូរទៅការទិញតែម្ដង។ ទើបតែលុបចោលមែនទេ? សូមរង់ចាំ Google Play បន្តិច រួចសាកល្បងម្ដងទៀត។</string>
<string name="upgrade_screen_purchase_check_failed_message">មិនអាចពិនិត្យការទិញរបស់អ្នកជាមួយ Google Play បានទេ។ ដើម្បីជៀសវាងការទិញស្ទួន សូមព្យាយាមម្តងទៀតបន្តិចទៀត។</string>
<string name="upgrade_screen_pending_card_title">ការទូទាត់កំពុងរង់ចាំ</string>
<string name="upgrade_screen_pending_card_body">Google Play នៅតែកំពុងដំណើរការការទូទាត់របស់អ្នក។ Pro នឹងបើកដោយស្វ័យប្រវត្តិនៅពេលការទូទាត់ជោគជ័យ។ វិធីទូទាត់ខ្លះអាចចំណាយពេលបន្តិច។ អ្នកមិនចាំបាច់ធ្វើអ្វីបន្ថែមទៀតទេ។</string>
<string name="upgrade_screen_pending_dialog_message">Google Play បានរកឃើញការទិញមួយដែលការទូទាត់កំពុងរង់ចាំ ហើយកំពុងដំណើរការវានៅឡើយ។ Pro នឹងបើកដោយស្វ័យប្រវត្តិនៅពេលការទូទាត់ជោគជ័យ។</string>
<string name="upgrade_screen_grace_title">កំពុងបញ្ជាក់ការទិញរបស់អ្នក</string>
<string name="upgrade_screen_grace_body_short">Google Play មិនទាន់បានបញ្ជាក់ការទិញរបស់អ្នកនៅឡើយទេ។ Pro នៅតែសកម្ម — មិនចាំបាច់ធ្វើសកម្មភាពអ្វីទេ។</string>
<string name="upgrade_screen_grace_body">Google Play មិនទាន់បានបញ្ជាក់ការទិញរបស់អ្នកជាយូរមកហើយ។ Pro នៅតែសកម្ម។ សូមប្រាកដថាអ្នកកំពុងអនឡាញ និងបានចូលគណនីជាមួយគណនី Google ដែលបានប្រើសម្រាប់ការទិញ រួចសាកល្បងស្ដារឡើងវិញ។</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ev kirîneke cuda ye — ew aboneya te betal nake û pereyê wê venagerîne. Heman hesabê Google bi kar bîne.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abone hîn jî çalak e</string>
<string name="upgrade_screen_sub_still_renewing_message">Aboneya te hîn jî ji bo nûkirinê hatiye mêhengkirin. Berî her tiştî wê li Google Play betal bike, paşê derbasî kirîna carekî bibe. Heke te niha betal kir? Demekê bide Google Play û dîsa biceribîne.</string>
<string name="upgrade_screen_purchase_check_failed_message">Kiriyan we bi Google Play-ê kontrol nekarî. Ji bo neyik kirîya duyemîn, piştî demekî kurt cardin bipirsînin.</string>
<string name="upgrade_screen_pending_card_title">Drav bitmîne</string>
<string name="upgrade_screen_pending_card_body">Google Play hengî dravê hûn didê prosès dike. Pro bixweber veşêne gava drav der tê. Hin rêbazên dravê dibe ku demek dirêj bigrin. Tu tiştekî din pêwîst nabe.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play krînekê bi dravê bitmîyî dît û hengî prosês dike. Pro bixweber veşêne gava drav der tê.</string>
<string name="upgrade_screen_grace_title">Kirîna te tê pejirandin</string>
<string name="upgrade_screen_grace_body_short">Google Play hê kirîna te napejirîne. Pro hîn jî çalak e — ne hewce ye tu tiştekî bikî.</string>
<string name="upgrade_screen_grace_body">Google Play demek e kirîna te napejirîne. Pro hîn jî çalak e. Piştrast bike ku tu girêdayî înternetê yî û bi hesabê Google yê ku pê kirî têkevî, paşê kirînê vegerîne.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ಇದು ಪ್ರತ್ಯೇಕ ಖರೀದಿಯಾಗಿದೆ — ಇದು ನಿಮ್ಮ ಚಂದಾದಾರಿಕೆಯನ್ನು ರದ್ದುಗೊಳಿಸುವುದಿಲ್ಲ ಅಥವಾ ಮರುಪಾವತಿಸುವುದಿಲ್ಲ. ಅದೇ Google ಖಾತೆಯನ್ನು ಬಳಸಿ.</string>
<string name="upgrade_screen_sub_still_renewing_title">ಚಂದಾದಾರಿಕೆ ಇನ್ನೂ ಸಕ್ರಿಯವಾಗಿದೆ</string>
<string name="upgrade_screen_sub_still_renewing_message">ನಿಮ್ಮ ಚಂದಾದಾರಿಕೆ ಇನ್ನೂ ನವೀಕರಿಸಲು ಹೊಂದಿಸಲಾಗಿದೆ. ಮೊದಲು Google Play ನಲ್ಲಿ ಅದನ್ನು ರದ್ದುಗೊಳಿಸಿ, ನಂತರ ಒಂದು ಬಾರಿಯ ಖರೀದಿಗೆ ಬದಲಾಯಿಸಿ. ಈಗಷ್ಟೇ ರದ್ದುಗೊಳಿಸಿದ್ದೀರಾ? Google Play ಗೆ ಸ್ವಲ್ಪ ಸಮಯ ನೀಡಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play ಜೊತೆ ನಿಮ್ಮ ಖರೀದಿಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಎರಡು ಬಾರಿ ಖರೀದಿಸುವುದನ್ನು ತಪ್ಪಿಸಲು, ಸ್ವಲ್ಪ ಸಮಯದ ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.</string>
<string name="upgrade_screen_pending_card_title">ಪಾವತಿ ಬಾಕಿ ಇದೆ</string>
<string name="upgrade_screen_pending_card_body">Google Play ಇನ್ನೂ ನಿಮ್ಮ ಪಾವತಿಯನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುತ್ತಿದೆ. ಪಾವತಿ ಪೂರ್ಣಗೊಂಡ ತಕ್ಷಣ Pro ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅನ್‌ಲಾಕ್ ಆಗುತ್ತದೆ. ಕೆಲವು ಪಾವತಿ ವಿಧಾನಗಳಿಗೆ ಸ್ವಲ್ಪ ಸಮಯ ತಗುಲಬಹುದು. ನೀವು ಬೇರೇನೂ ಮಾಡಬೇಕಿಲ್ಲ.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ಬಾಕಿ ಪಾವತಿಯೊಂದಿಗೆ ಖರೀದಿಯನ್ನು ಕಂಡುಕೊಂಡಿದೆ ಮತ್ತು ಇನ್ನೂ ಅದನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುತ್ತಿದೆ. ಪಾವತಿ ಪೂರ್ಣಗೊಂಡ ತಕ್ಷಣ Pro ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅನ್‌ಲಾಕ್ ಆಗುತ್ತದೆ.</string>
<string name="upgrade_screen_grace_title">ನಿಮ್ಮ ಖರೀದಿಯನ್ನು ದೃಢೀಕರಿಸಲಾಗುತ್ತಿದೆ</string>
<string name="upgrade_screen_grace_body_short">Google Play ಇನ್ನೂ ನಿಮ್ಮ ಖರೀದಿಯನ್ನು ದೃಢೀಕರಿಸಿಲ್ಲ. Pro ಇನ್ನೂ ಸಕ್ರಿಯವಾಗಿದೆ — ಯಾವುದೇ ಕ್ರಮದ ಅಗತ್ಯವಿಲ್ಲ.</string>
<string name="upgrade_screen_grace_body">Google Play ಸ್ವಲ್ಪ ಸಮಯದಿಂದ ನಿಮ್ಮ ಖರೀದಿಯನ್ನು ದೃಢೀಕರಿಸಿಲ್ಲ. Pro ಇನ್ನೂ ಸಕ್ರಿಯವಾಗಿದೆ. ನೀವು ಆನ್‌ಲೈನ್‌ನಲ್ಲಿದ್ದೀರಿ ಮತ್ತು ಖರೀದಿಗೆ ಬಳಸಿದ Google ಖಾತೆಯೊಂದಿಗೆ ಸೈನ್ ಇನ್ ಆಗಿರುವುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಂಡು, ನಂತರ ಮರುಸ್ಥಾಪಿಸಲು ಪ್ರಯತ್ನಿಸಿ.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">이는 별도의 구매이며 기존 구독을 취소하거나 환불하지 않습니다. 동일한 Google 계정을 사용하세요.</string>
<string name="upgrade_screen_sub_still_renewing_title">구독이 아직 활성 상태입니다</string>
<string name="upgrade_screen_sub_still_renewing_message">구독이 아직 갱신되도록 설정되어 있습니다. 먼저 Google Play에서 구독을 취소한 다음 일회성 구매로 전환하세요. 방금 취소하셨나요? Google Play가 처리할 시간을 조금 준 다음 다시 시도해 보세요.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play에서 구매 내역을 확인할 수 없었어요. 중복 구매를 피하려면 잠시 후 다시 시도해 보세요.</string>
<string name="upgrade_screen_pending_card_title">결제 대기 중</string>
<string name="upgrade_screen_pending_card_body">Google Play가 여전히 결제를 처리 중이에요. 결제가 완료되면 Pro가 활성화돼요. 일부 결제 수단은 시간이 걸릴 수 있어요. 다른 작업은 필요 없어요.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play에서 대기 중인 결제가 있는 구매를 발견했고 여전히 처리 중이에요. 결제가 완료되면 Pro가 활성화돼요.</string>
<string name="upgrade_screen_grace_title">구매 확인 중</string>
<string name="upgrade_screen_grace_body_short">Google Play에서 아직 구매를 확인하지 않았습니다. Pro는 계속 활성 상태이며 별도 조치가 필요하지 않습니다.</string>
<string name="upgrade_screen_grace_body">Google Play에서 한동안 구매를 확인하지 않았습니다. Pro는 계속 활성 상태입니다. 온라인 상태인지, 구매에 사용한 Google 계정으로 로그인되어 있는지 확인한 다음 복원을 시도해 보세요.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Бул өзүнчө сатып алуу — ал жазылууңузду жокко чыгарбайт же акчаны кайтарбайт. Ошол эле Google каттоо эсебин колдонуңуз.</string>
<string name="upgrade_screen_sub_still_renewing_title">Жазылуу дагы эле активдүү</string>
<string name="upgrade_screen_sub_still_renewing_message">Жазылууңуз дагы эле жаңылануучу кылып койулган. Адегенде аны Google Play\'де жокко чыгарыңыз, андан кийин бир жолку сатып алууга которулуңуз. Жаңы эле жокко чыгардыңызбы? Google Play\'ге бир аз убакыт бериңиз жана кайра аракет кылыңыз.</string>
<string name="upgrade_screen_purchase_check_failed_message">Сатып алууларыңызды Google Play менен текшере алган жокпуз. Кайра сатып алуудан алдын алуу үчүн, бир аздан кийин кайра аракет кылыңыз.</string>
<string name="upgrade_screen_pending_card_title">Төлөм күтүлүүдө</string>
<string name="upgrade_screen_pending_card_body">Google Play төлөмүңүздү дагы эле иштетип жатат. Төлөм өткөндөн кийин Pro автоматтык түрдө ачылат. Кээ бир төлөм ыкмалары бир аз убакытты алышы мүмкүн. Сизге башка эч нерсе кылуунун кереги жок.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play күтүлүп жаткан төлөмү бар сатып алууну тапты жана аны дагы эле иштетип жатат. Төлөм өткөндөн кийин Pro автоматтык түрдө ачылат.</string>
<string name="upgrade_screen_grace_title">Сатып алууңуз ырасталууда</string>
<string name="upgrade_screen_grace_body_short">Google Play сатып алууңузду азырынча ырастаган жок. Pro дагы эле активдүү — эч нерсе кылуунун кереги жок.</string>
<string name="upgrade_screen_grace_body">Google Play сатып алууңузду бир далай убакыттан бери ырастаган жок. Pro дагы эле активдүү. Онлайн экениңизди жана сатып алуу үчүн колдонулган Google каттоо эсеби менен кирип турганыңызды текшериңиз, андан кийин калыбына келтирип көрүңүз.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ນີ້ແມ່ນການຊື້ແຍກຕ່າງໜາກ — ມັນບໍ່ໄດ້ຍົກເລີກ ຫືລ ຄືນເງິນການສະໝັກສະມາຊິກຂອງທ່ານ. ໃຊ້ບັນຊີ Google ດຽວກັນ.</string>
<string name="upgrade_screen_sub_still_renewing_title">ການສະໝັກສະມາຊິກຍັງໃຊ້ງານຍູ່</string>
<string name="upgrade_screen_sub_still_renewing_message">ການສະໝັກສະມາຊິກຂອງທ່ານຍັງຕັ້ງໄວ້ໃຫ້ຕໍ່ອາຍຸຍູ່. ຍົກເລີກມັນໃນ Google Play ກ່ອນ, ແລ້ວຈຶ່ງປ່ຍນໄປໃຊ້ການຊື້ຄັ້ງດຽວ. ໜາກໍ່ຍົກເລີກບໍ? ໃຫ້ເວລາ Google Play ຊັກໝ້ອຍແລ້ວລອງໃໝ່.</string>
<string name="upgrade_screen_purchase_check_failed_message">ບໍ່ສາມາດກວດສອບການຊື້ຂອງທ່ານກັບ Google Play ໄດ້. ເພື່ອຫຼີກລ້ຽງການຊື້ຊ້ຳ, ກະລຸນາລອງໃໝ່ອີກຄັ້ງໃນອີກໄລຍະໜຶ່ງ.</string>
<string name="upgrade_screen_pending_card_title">ການຊຳລະເງິນລໍຖ້າດຳເນີນການ</string>
<string name="upgrade_screen_pending_card_body">Google Play ຍັງກຳລັງດຳເນີນການຊຳລະເງິນຂອງທ່ານຢູ່. Pro ຈະປົດລັອກໂດຍອັດຕະໂນມັດທັນທີທີ່ການຊຳລະເງິນສຳເລັດ. ບາງວິທີການຊຳລະເງິນອາດໃຊ້ເວລາໜ້ອຍໜຶ່ງ. ທ່ານບໍ່ຈຳເປັນຕ້ອງເຮັດຫຍັງເພີ່ມເຕີມອີກ.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ພົບການຊື້ທີ່ມີການຊຳລະເງິນລໍຖ້າດຳເນີນການ ແລະ ຍັງກຳລັງດຳເນີນການຢູ່. Pro ຈະປົດລັອກໂດຍອັດຕະໂນມັດທັນທີທີ່ການຊຳລະເງິນສຳເລັດ.</string>
<string name="upgrade_screen_grace_title">ກຳລັງຍືນຍັນການຊື້ຂອງທ່ານ</string>
<string name="upgrade_screen_grace_body_short">Google Play ຍັງບໍ່ໄດ້ຍືນຍັນການຊື້ຂອງທ່ານ. Pro ຍັງໃຊ້ງານໄດ້ຍູ່ — ບໍ່ຈຳເປັນຕ້ອງເຮັດໜັຍັງ.</string>
<string name="upgrade_screen_grace_body">Google Play ຍັງບໍ່ໄດ້ຍືນຍັນການຊື້ຂອງທ່ານມາໄລຍະໝຶ່ງແລ້ວ. Pro ຍັງໃຊ້ງານໄດ້ຍູ່. ກະລຸນາກວດສອບໃຫ້ແນ່ໃຈວ່າທ່ານອອນລາຍ ແລະ ເຂົ້າສູ່ລະບົບດ້ວຍບັນຊີ Google ດຽວກັນກັບທີ່ໃຊ້ຊື້, ແລ້ວລອງກູ້ຄືນ.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Tai atskiras pirkimas — jis neatšaukia ir negrąžina pinigų už jūsų prenumeratą. Naudokite tą pačią „Google“ paskyrą.</string>
<string name="upgrade_screen_sub_still_renewing_title">Prenumerata vis dar aktyvi</string>
<string name="upgrade_screen_sub_still_renewing_message">Jūsų prenumerata vis dar bus atnaujinta. Pirmiausia atšaukite ją „Google Play“, tada pereikite prie vienkartinio pirkimo. Ką tik atšaukėte? Palaukite kiek laiko, kol „Google Play“ apdoros, ir bandykite dar kartą.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nepavyko patikrinti jūsų pirkinių naudojant Google Play. Norėdami išvengti dvigubo pirkimo, bandykite dar kartą po kurio laiko.</string>
<string name="upgrade_screen_pending_card_title">Laukiamas mokėjimas</string>
<string name="upgrade_screen_pending_card_body">Google Play vis dar apdoroja jūsų mokėjimą. Pro automatiškai aktyvuojama, kai mokėjimas bus atliktas. Kai kurie mokėjimo būdai gali užtrukti. Jums nereikia daryti nieko daugiau.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play rado pirkimą su laukiamu mokėjimu ir vis dar jį apdoroja. Pro automatiškai aktyvuojama, kai mokėjimas bus atliktas.</string>
<string name="upgrade_screen_grace_title">Patvirtinamas jūsų pirkimas</string>
<string name="upgrade_screen_grace_body_short">„Google Play“ dar nepatvirtino jūsų pirkimo. „Pro“ vis dar aktyvus — jokių veiksmų atlikti nereikia.</string>
<string name="upgrade_screen_grace_body">„Google Play“ jau kurį laiką nepatvirtina jūsų pirkimo. „Pro“ vis dar aktyvus. Įsitikinkite, kad esate prisijungę prie interneto ir naudojate „Google“ paskyrą, kuria atlikote pirkimą, tuomet pabandykite atkurti.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Šis ir atsevišķis pirkums — tas neatceļ un neatmaksā tavu abonementu. Izmanto to pašu Google kontu.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonements joprojām aktīvs</string>
<string name="upgrade_screen_sub_still_renewing_message">Tavs abonements joprojām ir iestatīts atjaunoties. Vispirms atceļ to Google Play, tad pāriet uz vienreizējo pirkumu. Tikko atcēli? Dod Google Play brīdi un mēģini vēlreiz.</string>
<string name="upgrade_screen_purchase_check_failed_message">Neizdevās pārbaudīt tavus pirkumus ar Google Play. Lai izvairītos no dubulta pirkuma, mēģini vēlreiz pēc brīža.</string>
<string name="upgrade_screen_pending_card_title">Gaidīts maksājums</string>
<string name="upgrade_screen_pending_card_body">Google Play joprojām apstrādā tavu maksājumu. Pro atbloķēsies automātiski, kad maksājums tiks apstrādāts. Dažas maksājuma metodes var aizņemt ilgāku laiku. Tev nav ko vairāk jādara.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play atrada pirkumu ar gaidītu maksājumu un joprojām to apstrādā. Pro atbloķēsies automātiski, kad maksājums tiks apstrādāts.</string>
<string name="upgrade_screen_grace_title">Notiek pirkuma apstiprināšana</string>
<string name="upgrade_screen_grace_body_short">Google Play vēl nav apstiprinājis tavu pirkumu. Pro joprojām ir aktīvs — nekas nav jādara.</string>
<string name="upgrade_screen_grace_body">Google Play jau kādu laiku nav apstiprinājis tavu pirkumu. Pro joprojām ir aktīvs. Pārliecinies, ka esi tiešsaistē un pieteicies ar Google kontu, kas izmantots pirkumam, tad mēģini atjaunot pirkumu.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ова е одделна куповка — не ја откажува ниту ти ги враќа парите за претплатата. Користи го истиот Google профил.</string>
<string name="upgrade_screen_sub_still_renewing_title">Претплатата сеуште е активна</string>
<string name="upgrade_screen_sub_still_renewing_message">Твојата претплата сеуште е поставена да се обновува. Прво откажи ја во Google Play, па потоа премини на еднократната куповка. Штотуку ја откажа? Дај и на Google Play малку време и обиди се повторно.</string>
<string name="upgrade_screen_purchase_check_failed_message">Не можев да ги проверам вашите куповини со Google Play. За да избегнете двојна куповина, пробајте повторно наскоро.</string>
<string name="upgrade_screen_pending_card_title">Плаќање во очекување</string>
<string name="upgrade_screen_pending_card_body">Google Play сè уште го обработува вашето плаќање. Pro се отклучува автоматски откако плаќањето ќе мине. Некои начини на плаќање можат да потрајат. Нема ништо друго што треба да направите.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play пронајде куповина со плаќање во очекување и сè уште го обработува. Pro се отклучува автоматски откако плаќањето ќе мине.</string>
<string name="upgrade_screen_grace_title">Потврдување на твојата куповка</string>
<string name="upgrade_screen_grace_body_short">Google Play сè уште не ја потврди твојата куповка. Pro е сè уште активен — не е потребно дејство.</string>
<string name="upgrade_screen_grace_body">Google Play веќе подолго време не ја потврдува твојата куповка. Pro е сè уште активен. Провери дали си на интернет и најавен/а со Google профилот користен за куповката, па обиди се да ја вратиш.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ഇത് ഒരു പ്രത്യേക വാങ്ങലാണ് — ഇത് നിങ്ങളുടെ സബ്സ്ക്രിപ്ഷൻ റദ്ദാക്കുകയോ റീഫണ്ട് ചെയ്യുകയോ ചെയ്യില്ല. അതേ Google അക്കൗണ്ട് ഉപയോഗിക്കുക.</string>
<string name="upgrade_screen_sub_still_renewing_title">സബ്സ്ക്രിപ്ഷൻ ഇപ്പോഴും സജീവമാണ്</string>
<string name="upgrade_screen_sub_still_renewing_message">നിങ്ങളുടെ സബ്സ്ക്രിപ്ഷൻ ഇപ്പോഴും പുതുക്കാൻ സജ്ജീകരിച്ചിരിക്കുന്നു. ആദ്യം Google Play-യിൽ അത് റദ്ദാക്കുക, എന്നിട്ട് ഒറ്റത്തവണ വാങ്ങലിലേക്ക് മാറുക. ഇപ്പോൾ റദ്ദാക്കിയോ? Google Play-ക്ക് അൽപ്പം സമയം നൽകി വീണ്ടും ശ്രമിക്കുക.</string>
<string name="upgrade_screen_purchase_check_failed_message">നിങ്ങളുടെ വാങ്ങലുകൾ Google Play വഴി പരിശോധിക്കാനായില്ല. ഇരട്ട വാങ്ങൽ ഒഴിവാക്കാൻ, അൽപ്പസമയം കഴിഞ്ഞ് വീണ്ടും ശ്രമിക്കുക.</string>
<string name="upgrade_screen_pending_card_title">പേയ്‌മെന്റ് തീർപ്പുകൽപ്പിക്കാത്തതാണ്</string>
<string name="upgrade_screen_pending_card_body">Google Play നിങ്ങളുടെ പേയ്‌മെന്റ് ഇപ്പോഴും പ്രോസസ്സ് ചെയ്തുകൊണ്ടിരിക്കുകയാണ്. പേയ്‌മെന്റ് പൂർത്തിയാകുമ്പോൾ Pro സ്വയമേവ അൺലോക്ക് ആകും. ചില പേയ്‌മെന്റ് രീതികൾക്ക് കുറച്ച് സമയമെടുത്തേക്കാം. നിങ്ങൾ വേറെ ഒന്നും ചെയ്യേണ്ടതില്ല.</string>
<string name="upgrade_screen_pending_dialog_message">തീർപ്പുകൽപ്പിക്കാത്ത പേയ്‌മെന്റുള്ള ഒരു വാങ്ങൽ Google Play കണ്ടെത്തി, അത് ഇപ്പോഴും പ്രോസസ്സ് ചെയ്തുകൊണ്ടിരിക്കുകയാണ്. പേയ്‌മെന്റ് പൂർത്തിയാകുമ്പോൾ Pro സ്വയമേവ അൺലോക്ക് ആകും.</string>
<string name="upgrade_screen_grace_title">നിങ്ങളുടെ വാങ്ങൽ സ്ഥിരീകരിക്കുന്നു</string>
<string name="upgrade_screen_grace_body_short">Google Play ഇതുവരെ നിങ്ങളുടെ വാങ്ങൽ സ്ഥിരീകരിച്ചിട്ടില്ല. Pro ഇപ്പോഴും സജീവമാണ് — ഒരു നടപടിയും ആവശ്യമില്ല.</string>
<string name="upgrade_screen_grace_body">കുറച്ച് സമയമായി Google Play നിങ്ങളുടെ വാങ്ങൽ സ്ഥിരീകരിച്ചിട്ടില്ല. Pro ഇപ്പോഴും സജീവമാണ്. നിങ്ങൾ ഓൺലൈനിലാണെന്നും വാങ്ങലിനായി ഉപയോഗിച്ച
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Энэ бол тусдаа худалдан авалт бөгөөд таны захиалгыг цуцлахгүй, мөнгийг буцаан олгохгүй. Ижил Google бүртгэлийг ашиглана уу.</string>
<string name="upgrade_screen_sub_still_renewing_title">Захиалга хэвээр идэвхтэй байна</string>
<string name="upgrade_screen_sub_still_renewing_message">Таны захиалга шинэчлэгдэхээр тохируулагдсан хэвээр байна. Эхлээд Google Play дээр цуцалж, дараа нь нэг удаагийн худалдан авалт руу шилжинэ үү. Дөнгөж сая цуцалсан уу? Google Play-д бага зэрэг хугацаа өгөөд дахин оролдоно уу.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play-тэй худалдан авалтыг чинь шалгаж чадсангүй. Давхар худалдан авалт хийхээс сэргийлж, түр хугацааны дараа дахин оролдоно уу.</string>
<string name="upgrade_screen_pending_card_title">Төлбөр хүлээгдэж байна</string>
<string name="upgrade_screen_pending_card_body">Google Play таны төлбөрийг одоо ч боловсруулж байна. Төлбөр биелмэгц Pro автоматаар нээгдэнэ. Зарим төлбөрийн аргууд удаан хугацаа шаардаж болно. Танд өөр юу ч хийх шаардлагагүй.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play хүлээгдэж буй төлбөртэй худалдан авалт олж, одоо ч боловсруулж байна. Төлбөр биелмэгц Pro автоматаар нээгдэнэ.</string>
<string name="upgrade_screen_grace_title">Таны худалдан авалтыг баталгаажуулж байна</string>
<string name="upgrade_screen_grace_body_short">Google Play таны худалдан авалтыг одоохондоо баталгаажуулаагүй байна. Pro хэвээр идэвхтэй байгаа тул юу ч хийх шаардлагагүй.</string>
<string name="upgrade_screen_grace_body">Google Play таны худалдан авалтыг удаан хугацаанд баталгаажуулаагүй байна. Pro хэвээр идэвхтэй байна. Та онлайн байгаа эсэх, худалдан авалт хийхдээ ашигласан Google бүртгэлээр нэвтэрсэн эсэхээ шалгаад дараа нь сэргээхийг оролдоно уу.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ही एक वेगळी खरेदी आहे — ती तुमचे सदस्यत्व रद्द किंवा परत करत नाही. तेच Google खाते वापरा.</string>
<string name="upgrade_screen_sub_still_renewing_title">सदस्यत्व अजूनही सक्रिय आहे</string>
<string name="upgrade_screen_sub_still_renewing_message">तुमचे सदस्यत्व अजूनही नूतनीकरणासाठी सेट केलेले आहे. आधी ते Google Play मध्ये रद्द करा, नंतर एकवेळच्या खरेदीवर स्विच करा. नुकतेच रद्द केले? Google Play ला थोडा वेळ द्या आणि पुन्हा प्रयत्न करा.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play सोबत तुमच्या खरेदी तपासता आल्या नाहीत. दुहेरी खरेदी टाळण्यासाठी, थोड्या वेळाने पुन्हा प्रयत्न करा.</string>
<string name="upgrade_screen_pending_card_title">पेमेंट प्रलंबित आहे</string>
<string name="upgrade_screen_pending_card_body">Google Play अजूनही तुमचे पेमेंट प्रक्रिया करत आहे. पेमेंट पूर्ण होताच Pro आपोआप अनलॉक होईल. काही पेमेंट पद्धतींना थोडा वेळ लागू शकतो. तुम्हाला आणखी काही करण्याची गरज नाही.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ला प्रलंबित पेमेंट असलेली खरेदी सापडली असून ती अजूनही प्रक्रिया करत आहे. पेमेंट पूर्ण होताच Pro आपोआप अनलॉक होईल.</string>
<string name="upgrade_screen_grace_title">तुमची खरेदी निश्चित केली जात आहे</string>
<string name="upgrade_screen_grace_body_short">Google Play ने अद्याप तुमची खरेदी निश्चित केलेली नाही. Pro अजूनही सक्रिय आहे — कोणतीही कारवाई आवश्यक नाही.</string>
<string name="upgrade_screen_grace_body">Google Play ने बर्‍याच वेळेपासून तुमची खरेदी निश्चित केलेली नाही. Pro अजूनही सक्रिय आहे. तुम्ही ओनलाइन आहात आणि खरेदीसाठी वापरलेल्या Google खात्याने साइन इन केले आहे याची खात्री करा, नंतर पुनर्संचयित करण्याचा प्रयत्न करा.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ini ialah pembelian berasingan — ia tidak membatalkan atau memulangkan wang langganan anda. Gunakan akaun Google yang sama.</string>
<string name="upgrade_screen_sub_still_renewing_title">Langganan masih aktif</string>
<string name="upgrade_screen_sub_still_renewing_message">Langganan anda masih ditetapkan untuk diperbaharui. Batalkannya di Google Play terlebih dahulu, kemudian tukar kepada pembelian sekali sahaja. Baru sahaja membatalkan? Beri Google Play sedikit masa dan cuba lagi.</string>
<string name="upgrade_screen_purchase_check_failed_message">Tidak dapat menyemak pembelian anda dengan Google Play. Untuk mengelakkan pembelian berganda, cuba lagi sebentar lagi.</string>
<string name="upgrade_screen_pending_card_title">Pembayaran belum selesai</string>
<string name="upgrade_screen_pending_card_body">Google Play masih memproses pembayaran anda. Pro akan dibuka kunci secara automatik sebaik sahaja pembayaran berjaya. Sesetengah kaedah pembayaran mungkin mengambil masa. Tiada apa-apa lagi yang perlu anda lakukan.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play menemui pembelian dengan pembayaran belum selesai dan masih memprosesnya. Pro akan dibuka kunci secara automatik sebaik sahaja pembayaran berjaya.</string>
<string name="upgrade_screen_grace_title">Mengesahkan pembelian anda</string>
<string name="upgrade_screen_grace_body_short">Google Play belum mengesahkan pembelian anda lagi. Pro masih aktif — tiada tindakan diperlukan.</string>
<string name="upgrade_screen_grace_body">Google Play belum mengesahkan pembelian anda buat seketika. Pro masih aktif. Pastikan anda dalam talian dan log masuk dengan akaun Google yang digunakan untuk pembelian, kemudian cuba pulihkan.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ဤသည်မှာ သီးခြားဝယ်ယူမှုတစ်ခုဖြစ်သည် — ၎င်းသည် သင့်စာရင်းသွင်းမှုကို မပယ်ဖျက်ပါ၊ ငွေပြန်အမ်းမှုလည်း မပြုလုပ်ပါ။ တူညီသော Google အကောင့်ကို အသုံးပြုပါ။</string>
<string name="upgrade_screen_sub_still_renewing_title">စာရင်းသွင်းမှု ဆက်လက်အသက်ဝင်နေဆဲ</string>
<string name="upgrade_screen_sub_still_renewing_message">သင့်စာရင်းသွင်းမှုသည် ပြန်လည်သက်တမ်းတိုးရန် ဆက်လက်သတ်မှတ်ထားဆဲဖြစ်သည်။ ဦးစွာ Google Play တွင် ၎င်းကို ပယ်ဖျက်ပြီးမှ တစ်ကြိမ်တည်း ဝယ်ယူမှုသို့ ပြောင်းလဲပါ။ ယခုပဲ ပယ်ဖျက်လိုက်ပါသလား။ Google Play ကို အချိန်အနည်းငယ်ပေးပြီး ထပ်စမ်းကြည့်ပါ။</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play နှင့် မင်း၏ ဝယ်ယူမှုများကို စစ်ဆေးမရနိုင်ပါ။ နှစ်ကြိမ်ဝယ်ယူမိမှုကို ရှောင်ရှားရန် ခဏနေမှ ထပ်စမ်းကြည့်ပါ။</string>
<string name="upgrade_screen_pending_card_title">ငွေပေးချေမှု စောင့်ဆိုင်းနေသည်</string>
<string name="upgrade_screen_pending_card_body">Google Play က မင်း၏ ငွေပေးချေမှုကို ဆက်လက်စစ်ဆေးနေဆဲဖြစ်သည်။ ငွေပေးချေမှု အောင်မြင်ပြီးလျှင် Pro ကို အလိုအလျောက် ဖွင့်ပေးမည်။ ငွေပေးချေမှု နည်းလမ်းအချို့သည် အချိန်အနည်းငယ် ကြာနိုင်ပါသည်။ မင်း ဘာမှလုပ်ဆောင်ရန် မလိုအပ်ပါ။</string>
<string name="upgrade_screen_pending_dialog_message">Google Play က ငွေပေးချေမှု စောင့်ဆိုင်းနေသော ဝယ်ယူမှုတစ်ခုကို တွေ့ရှိပြီး ဆက်လက်စစ်ဆေးနေဆဲဖြစ်သည်။ ငွေပေးချေမှု အောင်မြင်ပြီးလျှင် Pro ကို အလိုအလျောက် ဖွင့်ပေးမည်။</string>
<string name="upgrade_screen_grace_title">သင့်ဝယ်ယူမှုကို အတည်ပြုနေသည်</string>
<string name="upgrade_screen_grace_body_short">Google Play က သင့်ဝယ်ယူမှုကို အတည်မပြုရသေးပါ။ Pro မှာ ဆက်လက်အသက်ဝင်နေဆဲဖြစ်သည် — မည်သည့်အရာမှ လုပ်ဆောင်ရန် မလိုအပ်ပါ။</string>
<string name="upgrade_screen_grace_body">Google Play သည် သင့်ဝယ်ယူမှုကို အတန်ကြာအောင် အတည်မပြုနိုင်သေးပါ။ Pro မှာ ဆက်လက်အသက်ဝင်နေဆဲဖြစ်သည်။ အွန်လိုင်းနှင့် ချိတ်ဆက်ထားပြီး ဝယ်ယူမှုတွင် အသုံးပြုခဲ့သည့် Google အကောင့်ဖြင့် လက်မှတ်ရေးထိုးဝင်ထားကြောင်း သေချာစေပြီးမှ ပြန်လည်ရယူရန် စမ်းကြည့်ပါ။</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Dette er et separat kjøp — det kansellerer eller refunderer ikke abonnementet ditt. Bruk samme Google-konto.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonnementet er fortsatt aktivt</string>
<string name="upgrade_screen_sub_still_renewing_message">Abonnementet ditt er fortsatt satt til å fornyes. Kanseller det i Google Play først, og bytt deretter til engangskjøpet. Nettopp kansellert? Gi Google Play litt tid og prøv igjen.</string>
<string name="upgrade_screen_purchase_check_failed_message">Kunne ikke sjekke kjøpene dine med Google Play. For å unngå et dobbelt kjøp, prøv igjen om en stund.</string>
<string name="upgrade_screen_pending_card_title">Ventende betaling</string>
<string name="upgrade_screen_pending_card_body">Google Play behandler fortsatt betalingen din. Pro låses opp automatisk når betalingen er gjennomført. Noen betalingsmåter kan ta en stund. Det er ingenting annet du trenger å gjøre.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play fant et kjøp med en ventende betaling og behandler det fortsatt. Pro låses opp automatisk når betalingen er gjennomført.</string>
<string name="upgrade_screen_grace_title">Bekrefter kjøpet ditt</string>
<string name="upgrade_screen_grace_body_short">Google Play har ikke bekreftet kjøpet ditt ennå. Pro er fortsatt aktivt — ingen handling nødvendig.</string>
<string name="upgrade_screen_grace_body">Google Play har ikke bekreftet kjøpet ditt på en stund. Pro er fortsatt aktivt. Sørg for at du er tilkoblet internett og logget inn med Google-kontoen som ble brukt til kjøpet, og prøv deretter å gjenopprette.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">यो एउटा छुट्टै खरिद हो — यसले तपाईंको सदस्यता रद्द वा फिर्ता गर्दैन। उही Google खाता प्रयोग गर्नुहोस्।</string>
<string name="upgrade_screen_sub_still_renewing_title">सदस्यता अझै सक्रिय छ</string>
<string name="upgrade_screen_sub_still_renewing_message">तपाईंको सदस्यता अझै नवीकरण हुने गरी सेट गरिएको छ। पहिले Google Play मा यसलाई रद्द गर्नुहोस्, त्यसपछि एक-पटक खरिदमा स्विच गर्नुहोस्। भर्खरै रद्द गर्नुभयो? Google Play लाई केही समय दिनुहोस् र फेरि प्रयास गर्नुहोस्।</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play सँग तपाईंको खरिदहरू जाँच गर्न सकिएन। दोहोरो खरिद हुनबाट जोगिन, केही क्षणमा फेरि प्रयास गर्नुहोस्।</string>
<string name="upgrade_screen_pending_card_title">भुक्तानी पर्खाइमा</string>
<string name="upgrade_screen_pending_card_body">Google Play अझै तपाईंको भुक्तानी प्रक्रिया गरिरहेको छ। भुक्तानी पूरा भएपछि Pro स्वतः अनलक हुन्छ। केही भुक्तानी विधिहरूले समय लिन सक्छन्। तपाईंले अरू केही गर्नु पर्दैन।</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ले पर्खाइमा रहेको भुक्तानी सहितको खरिद फेला पार्यो र अझै यसलाई प्रक्रिया गरिरहेको छ। भुक्तानी पूरा भएपछि Pro स्वतः अनलक हुन्छ।</string>
<string name="upgrade_screen_grace_title">तपाईंको खरिद पुष्टि गर्दै</string>
<string name="upgrade_screen_grace_body_short">Google Play ले तपाईंको खरिद अझै पुष्टि गरेको छैन। Pro अझै सक्रिय छ — कुनै कार्य आवश्यक छैन।</string>
<string name="upgrade_screen_grace_body">Google Play ले तपाईंको खरिद धेरै समयदेखि पुष्टि गरेको छैन। Pro अझै सक्रिय छ। तपाईं अनलाइन हुनुहुन्छ र खरिदको लागि प्रयोग गरिएको Google खातामा साइन इन गर्नुभएको सुनिश्चित गर्नुहोस्, त्यसपछि पुनर्स्थापना गर्ने प्रयास गर्नुहोस्।</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Dit is een aparte aankoop - je abonnement wordt hiermee niet geannuleerd en het aankoopbedrag wordt niet terugbetaald. Gebruik hetzelfde Google-account.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonnement nog steeds actief</string>
<string name="upgrade_screen_sub_still_renewing_message">Je abonnement wordt automatisch verlengd. Annuleer het eerst in Google Play en schakel daarna over naar de eenmalige aankoop. Net geannuleerd? Geef Google Play even de tijd en probeer het opnieuw.</string>
<string name="upgrade_screen_purchase_check_failed_message">Je aankopen konden niet worden geverifieerd met Google Play. Om een dubbele aankoop te voorkomen, probeer het over een moment opnieuw.</string>
<string name="upgrade_screen_pending_card_title">Betaling in behandeling</string>
<string name="upgrade_screen_pending_card_body">Google Play verwerkt je betaling nog steeds. Pro wordt automatisch ontgrendeld zodra de betaling is voltooid. Sommige betaalmethoden kunnen even duren. Je hoeft verder niets te doen.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play heeft een aankoop gevonden waarvoor nog een betaling in behandeling is en deze wordt nog verwerkt. Pro wordt automatisch ontgrendeld zodra de betaling is voltooid.</string>
<string name="upgrade_screen_grace_title">Je aankoop wordt bevestigd</string>
<string name="upgrade_screen_grace_body_short">Google Play heeft je aankoop nog niet bevestigd. Pro is nog steeds actief — geen actie nodig.</string>
<string name="upgrade_screen_grace_body">Google Play heeft je aankoop al een tijdje niet bevestigd. Pro is nog steeds actief. Zorg ervoor dat je online bent en bent ingelogd met het Google-account dat je voor de aankoop hebt gebruikt, en probeer het dan opnieuw te herstellen.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">To osobny zakup — nie anuluje ani nie zwraca kosztów Twojej subskrypcji. Użyj tego samego konta Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Subskrypcja nadal aktywna</string>
<string name="upgrade_screen_sub_still_renewing_message">Twoja subskrypcja nadal jest ustawiona na odnawianie. Najpierw anuluj ją w Sklepie Google Play, a następnie przejdź na jednorazowy zakup. Właśnie anulowałeś? Daj Sklepowi Google Play chwilę i spróbuj ponownie.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nie udało się sprawdzić twoich zakupów w Sklepie Google Play. Aby uniknąć podwójnego zakupu, spróbuj ponownie za chwilę.</string>
<string name="upgrade_screen_pending_card_title">Oczekiwanie na płatność</string>
<string name="upgrade_screen_pending_card_body">Sklep Google Play nadal przetwarza twoją płatność. Wersja Pro odblokuje się automatycznie po zaksięgowaniu płatności. Niektóre metody płatności mogą wymagać trochę czasu. Nie musisz nic więcej robić.</string>
<string name="upgrade_screen_pending_dialog_message">Sklep Google Play znalazł zakup z oczekującą płatnością i nadal go przetwarza. Wersja Pro odblokowuje się automatycznie po zaksięgowaniu płatności.</string>
<string name="upgrade_screen_grace_title">Potwierdzanie twojego zakupu</string>
<string name="upgrade_screen_grace_body_short">Sklep Google Play nie potwierdził jeszcze twojego zakupu. Pro nadal jest aktywne — nie musisz nic robić.</string>
<string name="upgrade_screen_grace_body">Sklep Google Play od jakiegoś czasu nie potwierdził twojego zakupu. Pro nadal jest aktywne. Upewnij się, że jesteś online i zalogowany na konto Google użyte do zakupu, a następnie spróbuj przywrócić zakup.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Esta é uma compra separada — ela não cancela nem reembolsa sua assinatura. Use a mesma conta Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Assinatura ainda ativa</string>
<string name="upgrade_screen_sub_still_renewing_message">Sua assinatura ainda está configurada para renovar. Cancele-a primeiro no Google Play e depois mude para a compra única. Acabou de cancelar? Dê um tempo ao Google Play e tente novamente.</string>
<string name="upgrade_screen_purchase_check_failed_message">Não foi possível verificar suas compras com Google Play. Para evitar uma compra duplicada, tente novamente em um momento.</string>
<string name="upgrade_screen_pending_card_title">Pagamento pendente</string>
<string name="upgrade_screen_pending_card_body">Google Play ainda está processando seu pagamento. Pro é desbloqueado automaticamente assim que o pagamento é processado. Alguns métodos de pagamento podem levar um tempo. Não há nada mais que você precise fazer.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play encontrou uma compra com um pagamento pendente e ainda está processando. Pro é desbloqueado automaticamente assim que o pagamento é processado.</string>
<string name="upgrade_screen_grace_title">Confirmando sua compra</string>
<string name="upgrade_screen_grace_body_short">O Google Play ainda não confirmou sua compra. O Pro continua ativo — nenhuma ação necessária.</string>
<string name="upgrade_screen_grace_body">O Google Play não confirma sua compra há um tempo. O Pro continua ativo. Verifique se você está online e conectado com a conta Google usada na compra, depois tente restaurar.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Esta é uma compra separada — não cancela nem reembolsa a tua subscrição. Utiliza a mesma conta Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Subscrição ainda ativa</string>
<string name="upgrade_screen_sub_still_renewing_message">A tua subscrição ainda está configurada para renovar. Cancela-a primeiro no Google Play e depois muda para a compra única. Acabaste de cancelar? Dá um momento ao Google Play e tenta novamente.</string>
<string name="upgrade_screen_purchase_check_failed_message">Não foi possível verificar as tuas compras com o Google Play. Para evitar uma compra duplicada, tenta novamente daqui a pouco.</string>
<string name="upgrade_screen_pending_card_title">Pagamento pendente</string>
<string name="upgrade_screen_pending_card_body">O Google Play ainda está a processar o teu pagamento. O Pro é desbloqueado automaticamente assim que o pagamento for concluído. Alguns métodos de pagamento podem demorar algum tempo. Não precisas de fazer mais nada.</string>
<string name="upgrade_screen_pending_dialog_message">O Google Play encontrou uma compra com um pagamento pendente e ainda está a processá-lo. O Pro é desbloqueado automaticamente assim que o pagamento for concluído.</string>
<string name="upgrade_screen_grace_title">A confirmar a tua compra</string>
<string name="upgrade_screen_grace_body_short">O Google Play ainda não confirmou a tua compra. O Pro continua ativo — não é necessária qualquer ação.</string>
<string name="upgrade_screen_grace_body">O Google Play não confirma a tua compra há algum tempo. O Pro continua ativo. Certifica-te de que estás online e com sessão iniciada com a conta Google utilizada na compra e, em seguida, tenta restaurar.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Quai è ina cumpra separada — ella n\'annulescha e n\'rimborsa betg tes abunament. Utilisescha il medem conto Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abunament anc activ</string>
<string name="upgrade_screen_sub_still_renewing_message">Tes abunament è anc configurà per sa renovar. Annullescha el en Google Play l\'emprim, e midescha lura a la cumpra unica. Già annullà? Dai in mument a Google Play ed emprova anc ina giada.</string>
<string name="upgrade_screen_purchase_check_failed_message">Impussibel da controllar tias cumpras cun Google Play. Per evitar ina cumpra dubla, emprova anc ina giada en in mument.</string>
<string name="upgrade_screen_pending_card_title">Pagament pendent</string>
<string name="upgrade_screen_pending_card_body">Google Play elavura anc tes pagament. Pro vegn debloccà automaticamain uschespert ch\'il pagament è cumplettà. Tschertas metodas da pagament pon prender in pau da temp. Ti n\'has nagut auter da far.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play ha chattà ina cumpra cun in pagament pendent e l\'elavura anc adina. Pro vegn debloccà automaticamain uschespert ch\'il pagament è cumplettà.</string>
<string name="upgrade_screen_grace_title">Confirmaziun da tia cumpra</string>
<string name="upgrade_screen_grace_body_short">Google Play n\'ha anc betg confirmà tia cumpra. Pro resta activ — nagina acziun necessaria.</string>
<string name="upgrade_screen_grace_body">Google Play n\'ha betg confirmà tia cumpra dapi in tschert temp. Pro resta activ. Controllescha che ti sajas online e annunzià cun il conto Google duvrà per la cumpra, ed emprova lura da restaurar.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Aceasta este o achiziție separată — nu anulează și nu rambursează abonamentul tău. Folosește același cont Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonamentul este încă activ</string>
<string name="upgrade_screen_sub_still_renewing_message">Abonamentul tău este încă setat să se reînnoiască. Anulează-l mai întâi din Google Play, apoi treci la achiziția unică. Tocmai l-ai anulat? Acordă-i lui Google Play un moment și încearcă din nou.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nu am putut verifica achizițiile tale cu Google Play. Pentru a evita o achiziție dublă, încearcă din nou peste ceva timp.</string>
<string name="upgrade_screen_pending_card_title">Plată în așteptare</string>
<string name="upgrade_screen_pending_card_body">Google Play încă procesează plata ta. Pro se deblochează automat după ce plata este procesată. Unele metode de plată pot dura ceva vreme. Nu trebuie să faci nimic altceva.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play a găsit o achiziție cu o plată în așteptare și o procesează încă. Pro se deblochează automat după ce plata este procesată.</string>
<string name="upgrade_screen_grace_title">Se confirmă achiziția ta</string>
<string name="upgrade_screen_grace_body_short">Google Play nu ți-a confirmat încă achiziția. Pro este încă activ — nu este nevoie de nicio acțiune.</string>
<string name="upgrade_screen_grace_body">Google Play nu a confirmat achiziția ta de ceva vreme. Pro este încă activ. Asigură-te că ești conectat la internet și autentificat cu contul Google folosit pentru achiziție, apoi încearcă să restaurezi.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Это отдельная покупка — она не отменяет и не возвращает средства за подписку. Используйте тот же аккаунт Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Подписка всё ещё активна</string>
<string name="upgrade_screen_sub_still_renewing_message">Ваша подписка всё ещё будет продлена. Сначала отмените её в Google Play, а затем перейдите на разовую покупку. Только что отменили? Дайте Google Play немного времени и попробуйте снова.</string>
<string name="upgrade_screen_purchase_check_failed_message">Не удалось проверить твои покупки в Google Play. Чтобы избежать повторной покупки, попробуй ещё раз через некоторое время.</string>
<string name="upgrade_screen_pending_card_title">Платёж в обработке</string>
<string name="upgrade_screen_pending_card_body">Google Play всё ещё обрабатывает Вашй платёж. Pro разблокируется автоматически, как только платёж будет проведён. Некоторые способы оплаты могут занимать больше времени. Вам больше ничего не нужно делать.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play обнаружил покупку с ожидающим платёжом и всё ещё обрабатывает её. Pro разблокируется автоматически, как только платёж будет проведён.</string>
<string name="upgrade_screen_grace_title">Подтверждение покупки</string>
<string name="upgrade_screen_grace_body_short">Google Play ещё не подтвердил вашу покупку. Pro всё ещё активен — никаких действий не требуется.</string>
<string name="upgrade_screen_grace_body">Google Play уже некоторое время не подтверждает вашу покупку. Pro всё ещё активен. Убедитесь, что вы в сети и вошли в тот аккаунт Google, с которого была совершена покупка, а затем попробуйте восстановить покупку.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Custu est unu cùmperu separadu — non cantzellat nen rimbursat s\'abbonamentu tuo. Impreu su matessi contu Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">S\'abbonamentu est galu atívu</string>
<string name="upgrade_screen_sub_still_renewing_message">S\'abbonamentu tuo est galu programadu pro si rennovare. Cantzella·lu innantis in Google Play, e a pustis passa a su cùmperu de una borta. Cantzelladu como como? Dae unu momentu a Google Play e torra a proare.</string>
<string name="upgrade_screen_purchase_check_failed_message">No est istadu possìbile controllare is comporas tuas cun Google Play. Pro evitare una compora dòpia, torra a proare pagu tempus a pustis.</string>
<string name="upgrade_screen_pending_card_title">Pagamentu in atesa</string>
<string name="upgrade_screen_pending_card_body">Google Play est ancora elaborende su pagamentu tuo. Pro s\'isblocat automaticamente cando su pagamentu est cumpletadu. Calicunu metodu de pagamentu podet pigare tempus. No b\'at àteru de fàghere.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play at agatadu una compora cun unu pagamentu in atesa e ddu est ancora elaborende. Pro s\'isblocat automaticamente cando su pagamentu est cumpletadu.</string>
<string name="upgrade_screen_grace_title">Cunfirmande su cùmperu tuo</string>
<string name="upgrade_screen_grace_body_short">Google Play no at ancora cunfirmadu su cùmperu tuo. Pro est galu atívu — no b\'at bisòngiu de fàghere nudda.</string>
<string name="upgrade_screen_grace_body">Google Play no at cunfirmadu su cùmperu tuo dae unu pagu de tempus. Pro est galu atívu. Assegura·ti de èssere in lìnia e de àere fatu s\'aciesu cun su contu Google impreadu pro su cùmperu, e a pustis proa a ripristinare.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">මෙය වෙනම මිලදී ගැනීමකි — එය ඔබේ දායකත්වය අවලංගු කිරීමක් හෝ මුදල් ආපසු ගෙවීමක් සිදු නොකරයි. එකම Google ගිණුම භාවිත කරන්න.</string>
<string name="upgrade_screen_sub_still_renewing_title">දායකත්වය තවමත් සක්‍රියයි</string>
<string name="upgrade_screen_sub_still_renewing_message">ඔබේ දායකත්වය තවමත් අළුත් කිරීමට සකසා ඇත. පළමුව Google Play හි එය අවලංගු කර, පසුව එක් වරක් මිලදී ගැනීමට මාරු වන්න. දැන් අවලංගු කළාද? Google Play ට මොහොතක් ලබා දී නැවත උත්සාහ කරන්න.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play සමඟ ඔබගේ මිලදී ගැනීම් පරීක්ෂා කළ නොහැකි විය. දෙවරක් මිලදී ගැනීමක් වළක්වා ගැනීමට, ටික වේලාවකින් නැවත උත්සාහ කරන්න.</string>
<string name="upgrade_screen_pending_card_title">ගෙවීම අපේක්ෂිතයි</string>
<string name="upgrade_screen_pending_card_body">Google Play තවමත් ඔබගේ ගෙවීම සකසමින් සිටී. ගෙවීම සම්පූර්ණ වූ පසු Pro ස්වයංක්‍රීයව අගුළු හැරෙනු ඇත. සමහර ගෙවීම් ක්‍රම වලට ටික වේලාවක් ගත විය හැක. ඔබ කළ යුතු වෙන කිසිවක් නැත.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play අපේක්ෂිත ගෙවීමක් සහිත මිලදී ගැනීමක් සොයාගෙන ඇති අතර, එය තවමත් සකසමින් සිටී. ගෙවීම සම්පූර්ණ වූ පසු Pro ස්වයංක්‍රීයව අගුළු හැරෙනු ඇත.</string>
<string name="upgrade_screen_grace_title">ඔබේ මිලදී ගැනීම තහවුරු කරමින්</string>
<string name="upgrade_screen_grace_body_short">Google Play තවම ඔබේ මිලදී ගැනීම තහවුරු කර නැත. Pro තවමත් සක්‍රියයි — කිසිදු ක්‍රියාමාර්ගයක් අවශ්‍ය නොවේ.</string>
<string name="upgrade_screen_grace_body">Google Play ටික කලක් තිස්සේ ඔබේ මිලදී ගැනීම තහවුරු කර නැත. Pro තවමත් සක්‍රියයි. ඔබ අන්තර්ජාලයට සම්බන්ධව සිටින බවත්, මිලදී ගැනීම සඳහා භාවිත කළ Google ගිණුමෙන් පුරන්ව ඇති බවත් තහවුරු කරගෙන, පසුව ප්‍රතිසාධනය කිරීමට උත්සාහ කරන්න.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Toto je samostatný nákup — nezruší ani nevráti platbu za vaše predplatné. Použite rovnaký účet Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Predplatné je stále aktívne</string>
<string name="upgrade_screen_sub_still_renewing_message">Vaše predplatné je stále nastavené na obnovenie. Najprv ho zrušte v Google Play a potom prejdite na jednorazový nákup. Práve ste ho zrušili? Dajte Google Play chvíľu čas a skúste to znova.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nepodarilo sa skontrolovať tvoje nákupy v Obchode Play. Aby sa zabránilo dvojitému nákupu, skús to znova o chvíľu.</string>
<string name="upgrade_screen_pending_card_title">Čakajúca platba</string>
<string name="upgrade_screen_pending_card_body">Obchod Play stále spracúva tvoju platbu. Pro sa odomkne automaticky, keď platba prejde. Niektoré spôsoby platby môžu trvať dlhšie. Nemusíš robiť nič ďalšie.</string>
<string name="upgrade_screen_pending_dialog_message">Obchod Play našiel nákup s čakajúcou platbou a stále ho spracúva. Pro sa odomkne automaticky, keď platba prejde.</string>
<string name="upgrade_screen_grace_title">Potvrdzujeme váš nákup</string>
<string name="upgrade_screen_grace_body_short">Google Play zatiaľ nepotvrdil váš nákup. Pro je stále aktívne — nie je potrebná žiadna akcia.</string>
<string name="upgrade_screen_grace_body">Google Play už dlhšie nepotvrdil váš nákup. Pro je stále aktívne. Uistite sa, že ste online a prihlásení s účtom Google, ktorý ste použili pri nákupe, a potom skúste nákup obnoviť.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">To je ločen nakup — ne prekliče ali povrne stroškov vaše naročnine. Uporabite isti Google račun.</string>
<string name="upgrade_screen_sub_still_renewing_title">Naročnina je še vedno aktivna</string>
<string name="upgrade_screen_sub_still_renewing_message">Vaša naročnina je še vedno nastavljena na samodejno podaljšanje. Najprej jo prekličite v Google Play, nato preklopite na enkratni nakup. Ste jo pravkar preklicali? Počakajte trenutek, da se Google Play posodobi, in poskusite znova.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nakupov ni bilo mogoče preveriti pri Google Play. Da se izogneš dvojnemu nakupu, poskusi znova čez trenutek.</string>
<string name="upgrade_screen_pending_card_title">Plačilo v obdelavi</string>
<string name="upgrade_screen_pending_card_body">Google Play še obdeluje tvoje plačilo. Pro se samodejno odklene, ko bo plačilo izvedeno. Nekateri načini plačila lahko trajajo dlje. Ni ti treba storiti ničesar drugega.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play je našel nakup s plačilom v obdelavi in ga še vedno obdeluje. Pro se samodejno odklene, ko bo plačilo izvedeno.</string>
<string name="upgrade_screen_grace_title">Potrjevanje vašega nakupa</string>
<string name="upgrade_screen_grace_body_short">Google Play še ni potrdil vašega nakupa. Pro je še vedno aktiven — ni potreben noben ukrep.</string>
<string name="upgrade_screen_grace_body">Google Play že nekaj časa ni potrdil vašega nakupa. Pro je še vedno aktiven. Prepričajte se, da ste povezani v splet in prijavljeni z Google računom, uporabljenim za nakup, nato poskusite obnoviti nakup.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Kjo është një blerje e veçantë — nuk e anulon dhe nuk e rimburson abonimin tuaj. Përdorni të njëjtën llogari Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonimi ende aktiv</string>
<string name="upgrade_screen_sub_still_renewing_message">Abonimi juaj është ende i caktuar për t\'u rinovuar. Anulojeni fillimisht në Google Play, pastaj kaloni në blerjen një herë. Sapo e keni anuluar? Jepini Google Play pak kohë dhe provoni përsëri.</string>
<string name="upgrade_screen_purchase_check_failed_message">Nuk u arrit të kontrolloheshin blerjet e tua me Google Play. Për të shmangur një blerje të dyfishtë, provo përsëri pas pak.</string>
<string name="upgrade_screen_pending_card_title">Pagesa në pritje</string>
<string name="upgrade_screen_pending_card_body">Google Play po e përpunon ende pagesën tënde. Pro do të shkyçet automatikisht sapo pagesa të kalojë. Disa metoda pagese mund të kërkojnë kohë. Nuk ka gjë tjetër që duhet të bësh.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play gjeti një blerje me pagesë në pritje dhe po e përpunon ende. Pro do të shkyçet automatikisht sapo pagesa të kalojë.</string>
<string name="upgrade_screen_grace_title">Duke konfirmuar blerjen tuaj</string>
<string name="upgrade_screen_grace_body_short">Google Play ende nuk e ka konfirmuar blerjen tuaj. Pro është ende aktiv — nuk kërkohet asnjë veprim.</string>
<string name="upgrade_screen_grace_body">Google Play nuk e ka konfirmuar blerjen tuaj prej njëfarë kohe. Pro është ende aktiv. Sigurohuni që jeni online dhe të kyçur me llogarinë Google të përdorur për blerjen, pastaj provoni ta rikthyeni.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Ово је посебна куповина — не отказује нити рефундира вашу претплату. Користите исти Google налог.</string>
<string name="upgrade_screen_sub_still_renewing_title">Претплата је и даље активна</string>
<string name="upgrade_screen_sub_still_renewing_message">Ваша претплата је и даље подешена да се обнавља. Прво је откажите у Google Play-у, а затим пређите на једнократну куповину. Управо сте је отказали? Сачекајте тренутак да Google Play обради промену и покушајте поново.</string>
<string name="upgrade_screen_purchase_check_failed_message">Не могу да проверим твоју куповину са Google Play. Покушај поново за трен да избегнеш двоструку куповину.</string>
<string name="upgrade_screen_pending_card_title">Плаћање је на чекању</string>
<string name="upgrade_screen_pending_card_body">Google Play још обрађује твоје плаћање. Pro се откључава аутоматски чим плаћање буде обрађено. Неке методе плаћања могу требати више времена. Нема ничега другог што требаш да урадиш.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play је нашао куповину са плаћањем на чекању и још је обрађује. Pro се откључава аутоматски чим плаћање буде обрађено.</string>
<string name="upgrade_screen_grace_title">Потврђивање ваше куповине</string>
<string name="upgrade_screen_grace_body_short">Google Play још увек није потврдио вашу куповину. Pro је и даље активан — није потребна никаква радња.</string>
<string name="upgrade_screen_grace_body">Google Play већ извесно време није потврдио вашу куповину. Pro је и даље активан. Проверите да ли сте на мрежи и пријављени на Google налог који сте користили за куповину, а затим покушајте да обновите куповину.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Detta är ett separat köp — det avbryter eller återbetalar inte din prenumeration. Använd samma Google-konto.</string>
<string name="upgrade_screen_sub_still_renewing_title">Prenumerationen är fortfarande aktiv</string>
<string name="upgrade_screen_sub_still_renewing_message">Din prenumeration är fortfarande inställd på att förnyas. Avbryt den i Google Play först och byt sedan till engångsköpet. Precis avbrutit? Ge Google Play en stund och försök igen.</string>
<string name="upgrade_screen_purchase_check_failed_message">Det gick inte att kontrollera dina köp med Google Play. Försök igen om en liten stund för att undvika ett dubbelt köp.</string>
<string name="upgrade_screen_pending_card_title">Betalning väntar</string>
<string name="upgrade_screen_pending_card_body">Google Play behandlar fortfarande din betalning. Pro låses upp automatiskt när betalningen har gått igenom. Vissa betalningsmetoder kan ta ett tag. Du behöver inte göra något mer.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play hittade ett köp med en väntande betalning och behandlar det fortfarande. Pro låses upp automatiskt när betalningen har gått igenom.</string>
<string name="upgrade_screen_grace_title">Bekräftar ditt köp</string>
<string name="upgrade_screen_grace_body_short">Google Play har inte bekräftat ditt köp än. Pro är fortfarande aktivt — ingen åtgärd behövs.</string>
<string name="upgrade_screen_grace_body">Google Play har inte bekräftat ditt köp på ett tag. Pro är fortfarande aktivt. Kontrollera att du är online och inloggad med det Google-konto som användes för köpet, och försök sedan återställa.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Huu ni ununuzi tofauti — hauighairi wala kurejesha fedha za usajili wako. Tumia akaunti ile ile ya Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Usajili bado unatumika</string>
<string name="upgrade_screen_sub_still_renewing_message">Usajili wako bado umewekwa kujiongeza upya. Ughairi kwanza katika Google Play, kisha ubadilishe hadi ununuzi wa mara moja. Umeghairi hivi punde? Mpe Google Play muda kidogo kisha ujaribu tena.</string>
<string name="upgrade_screen_purchase_check_failed_message">Imeshindwa kukagua manunuzi yako na Google Play. Ili kuepuka manunuzi mara mbili, jaribu tena baada ya muda.</string>
<string name="upgrade_screen_pending_card_title">Malipo yanasubiri</string>
<string name="upgrade_screen_pending_card_body">Google Play bado inashughulikia malipo yako. Pro itafunguliwa kiotomatiki mara malipo yatakapokamilika. Njia baadhi za malipo zinaweza kuchukua muda. Hakuna kingine unachohitaji kufanya.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play imepata ununuzi wenye malipo yanayosubiri na bado inayashughulikia. Pro itafunguliwa kiotomatiki mara malipo yatakapokamilika.</string>
<string name="upgrade_screen_grace_title">Inathibitisha ununuzi wako</string>
<string name="upgrade_screen_grace_body_short">Google Play bado haijathibitisha ununuzi wako. Pro bado inatumika — hakuna hatua inayohitajika.</string>
<string name="upgrade_screen_grace_body">Google Play haijathibitisha ununuzi wako kwa muda. Pro bado inatumika. Hakikisha uko mtandaoni na umeingia na akaunti ya Google iliyotumika kununua, kisha jaribu kurejesha.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">இது ஒரு தனி கொள்முதல் — இது உங்கள் சந்தாவை ரத்து செய்யாது அல்லது பணத்தைத் திரும்பச் செலுத்தாது. அதே Google கணக்கைப் பயன்படுத்தவும்.</string>
<string name="upgrade_screen_sub_still_renewing_title">சந்தா இன்னும் செயலில் உள்ளது</string>
<string name="upgrade_screen_sub_still_renewing_message">உங்கள் சந்தா இன்னும் புதுப்பிக்க அமைக்கப்பட்டுள்ளது. முதலில் Google Play-இல் அதை ரத்து செய்யவும், பின்னர் ஒரு முறை வாங்குதலுக்கு மாறவும். இப்போதுதான் ரத்து செய்தீர்களா? Google Play-க்கு சிறிது நேரம் கொடுத்து மீண்டும் முயற்சிக்கவும்.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play உடன் உங்கள் வாங்கல்களை சரிபார்க்க முடியவில்லை. இரட்டை வாங்கலைத் தவிர்க்க, சிறிது நேரத்தில் மீண்டும் முயற்சி செய்யவும்.</string>
<string name="upgrade_screen_pending_card_title">பணம் செலுத்தல் நிலுவை</string>
<string name="upgrade_screen_pending_card_body">Google Play உங்கள் பணம் செலுத்தலை இன்னும் செயல்படுத்திக் கொண்டுள்ளது. பணம் செலுத்தல் முடிந்ததும் Pro தானாகவே திறக்கப்படும். சில பணம் செலுத்தல் வகைகளுக்குச் சிறிது நேரம் ஆகலாம். வேறு எதுவும் செய்ய வேண்டியதில்லை.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play நிலுவையான பணம் செலுத்தல் உடன் ஒரு வாங்கலைக் கண்டுபிடித்துள்ளது மற்றும் அதைத் தொடர்ந்து செயல்படுத்திக் கொண்டுள்ளது. பணம் செலுத்தல் முடிந்ததும் Pro தானாகவே திறக்கப்படும்.</string>
<string name="upgrade_screen_grace_title">உங்கள் கொள்முதலை உறுதிப்படுத்துகிறது</string>
<string name="upgrade_screen_grace_body_short">Google Play இன்னும் உங்கள் கொள்முதலை உறுதிப்படுத்தவில்லை. Pro இன்னும் செயலில் உள்ளது — எந்த நடவடிக்கையும் தேவையில்லை.</string>
<string name="upgrade_screen_grace_body">Google Play சிறிது காலமாக உங்கள் கொள்முதலை உறுதிப்படுத்தவில்லை. Pro இன்னும் செயலில் உள்ளது. நீங்கள் ஆன்லைனில் இருப்பதையும், கொள்முதலுக்குப் பயன்படுத்திய Google கணக்கில் உள்நுழைந்திருப்பதையும் உறுதிப்படுத்திக் கொண்டு, பின்னர் மீட்டெடுக்க முயற்சிக்கவும்.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">ఇది ఒక ప్రత్యేక కొనుగోలు — ఇది మీ సబ్‌స్క్రిప్షన్‌ను రద్దు చేయదు లేదా రీఫండ్ చేయదు. అదే Google ఖాతాను ఉపయోగించండి.</string>
<string name="upgrade_screen_sub_still_renewing_title">సబ్‌స్క్రిప్షన్ ఇప్పటికీ యాక్టివ్‌గా ఉంది</string>
<string name="upgrade_screen_sub_still_renewing_message">మీ సబ్‌స్క్రిప్షన్ ఇప్పటికీ రెన్యూ అయ్యేలా సెట్ చేయబడి ఉంది. ముందుగా Google Playలో దాన్ని రద్దు చేసి, తర్వాత ఒకేసారి కొనుగోలుకు మారండి. ఇప్పుడే రద్దు చేశారా? Google Playకి కొంత సమయం ఇచ్చి మళ్ళీ ప్రయత్నించండి.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Playతో నీ కొనుగోళ్లను తనిఖీ చేయలేకపోయాం. రెండుసార్లు కొనుగోలు కాకుండా ఉండేందుకు, కాసేపటి తర్వాత మళ్లీ ప్రయత్నించు.</string>
<string name="upgrade_screen_pending_card_title">చెల్లింపు పెండింగ్‌లో ఉంది</string>
<string name="upgrade_screen_pending_card_body">Google Play ఇంకా నీ చెల్లింపును ప్రాసెస్ చేస్తోంది. చెల్లింపు పూర్తయిన వెంటనే Pro స్వయంచాలకంగా అన్‌లాక్ అవుతుంది. కొన్ని చెల్లింపు పద్ధతులకు కొంత సమయం పట్టవచ్చు. నువ్వు చేయాల్సింది ఇంకేమీ లేదు.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play పెండింగ్‌లో ఉన్న చెల్లింపుతో ఒక కొనుగోలును కనుగొంది, దాన్ని ఇంకా ప్రాసెస్ చేస్తోంది. చెల్లింపు పూర్తయిన వెంటనే Pro స్వయంచాలకంగా అన్‌లాక్ అవుతుంది.</string>
<string name="upgrade_screen_grace_title">మీ కొనుగోలును నిర్ధారిస్తోంది</string>
<string name="upgrade_screen_grace_body_short">Google Play ఇంకా మీ కొనుగోలును నిర్ధారించలేదు. Pro ఇప్పటికీ యాక్టివ్‌గా ఉంది — ఎలాంటి చర్య అవసరం లేదు.</string>
<string name="upgrade_screen_grace_body">కొంత సమయంగా Google Play మీ కొనుగోలును నిర్ధారించలేదు. Pro ఇప్పటికీ యాక్టివ్‌గా ఉంది. మీరు ఆన్‌లైన్‌లో ఉన్నారని మరియు కొనుగోలు కోసం ఉపయోగించిన Google ఖాతాతో సైన్ ఇన్ అయ్యి ఉన్నారని నిర్ధారించుకుని, తర్వాత పునరుద్ధరించడానికి ప్రయత్నించండి.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">นี่เป็นการซื้อแยกต่างหาก — จะไม่ยกเลิกหรือคืนเงินการสมัครสมาชิกของคุณ ใช้บัญชี Google เดียวกัน</string>
<string name="upgrade_screen_sub_still_renewing_title">การสมัครสมาชิกยังคงใช้งานอยู่</string>
<string name="upgrade_screen_sub_still_renewing_message">การสมัครสมาชิกของคุณยังคงตั้งค่าให้ต่ออายุ ให้ยกเลิกใน Google Play ก่อน แล้วจึงเปลี่ยนไปใช้การซื้อครั้งเดียว เพิ่งยกเลิกใช่ไหม รอสักครู่ให้ Google Play อัปเดตแล้วลองอีกครั้ง</string>
<string name="upgrade_screen_purchase_check_failed_message">ไม่สามารถตรวจสอบการซื้อของคุณกับ Google Play ได้ เพื่อหลีกเลี่ยงการซื้อซ้ำ โปรดลองอีกครั้งในอีกสักครู่</string>
<string name="upgrade_screen_pending_card_title">การชำระเงินอยู่ระหว่างดำเนินการ</string>
<string name="upgrade_screen_pending_card_body">Google Play กำลังดำเนินการชำระเงินของคุณ Pro จะปลดล็อกโดยอัตโนมัติเมื่อการชำระเงินเสร็จสมบูรณ์ วิธีการชำระเงินบางอย่างอาจใช้เวลาสักพัก คุณไม่จำเป็นต้องทำอะไรเพิ่มเติม</string>
<string name="upgrade_screen_pending_dialog_message">Google Play พบการซื้อที่มีการชำระเงินอยู่ระหว่างดำเนินการและกำลังดำเนินการอยู่ Pro จะปลดล็อกโดยอัตโนมัติเมื่อการชำระเงินเสร็จสมบูรณ์</string>
<string name="upgrade_screen_grace_title">กำลังยืนยันการซื้อของคุณ</string>
<string name="upgrade_screen_grace_body_short">Google Play ยังไม่ได้ยืนยันการซื้อของคุณ Pro ยังคงใช้งานได้อยู่ — ไม่ต้องดำเนินการใด ๆ</string>
<string name="upgrade_screen_grace_body">Google Play ยังไม่ได้ยืนยันการซื้อของคุณมาสักระยะแล้ว Pro ยังคงใช้งานได้อยู่ ตรวจสอบให้แน่ใจว่าคุณออนไลน์อยู่และลงชื่อเข้าใช้ด้วยบัญชี Google ที่ใช้ซื้อ จากนั้นลองกู้คืน</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Bu ayrı bir satın almadır — aboneliğini iptal etmez veya geri ödemez. Aynı Google hesabını kullan.</string>
<string name="upgrade_screen_sub_still_renewing_title">Abonelik hâlâ aktif</string>
<string name="upgrade_screen_sub_still_renewing_message">Aboneliğin hâlâ yenilenecek şekilde ayarlı. Önce Google Play\'de iptal et, ardından tek seferlik satın alıma geç. Az önce mi iptal ettin? Google Play\'e biraz zaman tanı ve tekrar dene.</string>
<string name="upgrade_screen_purchase_check_failed_message">Satın alımların Google Play ile kontrol edilemedi. Çift satın alımı önlemek için birazdan tekrar dene.</string>
<string name="upgrade_screen_pending_card_title">Ödeme bekleniyor</string>
<string name="upgrade_screen_pending_card_body">Google Play ödemeni hâlâ işliyor. Ödeme tamamlandığında Pro otomatik olarak açılır. Bazı ödeme yöntemleri biraz zaman alabilir. Başka bir şey yapmana gerek yok.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play, ödemesi bekleyen bir satın alma buldu ve hâlâ işliyor. Ödeme tamamlandığında Pro otomatik olarak açılır.</string>
<string name="upgrade_screen_grace_title">Satın alman onaylanıyor</string>
<string name="upgrade_screen_grace_body_short">Google Play satın almanı henüz onaylamadı. Pro hâlâ aktif — herhangi bir işlem yapmana gerek yok.</string>
<string name="upgrade_screen_grace_body">Google Play satın almanı bir süredir onaylamadı. Pro hâlâ aktif. Çevrimiçi olduğundan ve satın alma için kullanılan Google hesabıyla oturum açtığından emin ol, ardından geri yüklemeyi dene.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Це окрема покупка — вона не скасовує підписку та не повертає за неї кошти. Використовуйте той самий обліковий запис Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Підписка досі активна</string>
<string name="upgrade_screen_sub_still_renewing_message">Ваша підписка досі налаштована на автоматичне продовження. Спочатку скасуйте її в Google Play, а потім перейдіть на одноразову покупку. Щойно скасували? Дайте Google Play трохи часу та спробуйте ще раз.</string>
<string name="upgrade_screen_purchase_check_failed_message">Не вдалося перевірити твої покупки в Google Play. Щоб уникнути подвійної покупки, спробуй ще раз через деякий час.</string>
<string name="upgrade_screen_pending_card_title">Платіж в обробці</string>
<string name="upgrade_screen_pending_card_body">Google Play все ще обробляє твій платіж. Pro розблокується автоматично, як тільки платіж буде завершений. Деякі способи оплати можуть займати довгий час. Більше нічого робити не потрібно.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play знайшов покупку з платежем в обробці і все ще її обробляє. Pro розблокується автоматично, як тільки платіж буде завершений.</string>
<string name="upgrade_screen_grace_title">Підтвердження вашої покупки</string>
<string name="upgrade_screen_grace_body_short">Google Play ще не підтвердив вашу покупку. Pro все ще активний — жодних дій не потрібно.</string>
<string name="upgrade_screen_grace_body">Google Play вже деякий час не підтверджує вашу покупку. Pro все ще активний. Переконайтеся, що ви в мережі та ввійшли в обліковий запис Google, який використовували для покупки, а потім спробуйте відновити.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">یہ ایک الگ خریداری ہے — یہ آپ کے سبسکرپشن کو منسوخ یا ریفنڈ نہیں کرتی۔ وہی گوگل اکاؤنٹ استعمال کریں۔</string>
<string name="upgrade_screen_sub_still_renewing_title">سبسکرپشن اب بھی فعال ہے</string>
<string name="upgrade_screen_sub_still_renewing_message">آپ کا سبسکرپشن اب بھی تجدید ہونے کے لیے طے ہے۔ پہلے اسے گوگل پلے میں منسوخ کریں، پھر ایک بار کی خریداری پر منتقل ہوں۔ ابھی منسوخ کیا؟ گوگل پلے کو تھوڑا وقت دیں اور دوبارہ کوشش کریں۔</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play کے ساتھ آپ کی خریداریوں کی جانچ نہیں ہو سکی۔ دوبارہ خریداری سے بچنے کے لیے، براہ کرم تھوڑی دیر میں دوبارہ کوشش کریں۔</string>
<string name="upgrade_screen_pending_card_title">ادائیگی زیر التوا ہے</string>
<string name="upgrade_screen_pending_card_body">Google Play ابھی بھی آپ کی ادائیگی پر کارروائی کر رہا ہے۔ ادائیگی مکمل ہوتے ہی Pro خود بخود ان لاک ہو جائے گا۔ کچھ ادائیگی کے طریقوں میں کچھ وقت لگ سکتا ہے۔ آپ کو مزید کچھ کرنے کی ضرورت نہیں ہے۔</string>
<string name="upgrade_screen_pending_dialog_message">Google Play کو ایک ایسی خریداری ملی ہے جس کی ادائیگی زیر التوا ہے اور وہ ابھی بھی اس پر کارروائی کر رہا ہے۔ ادائیگی مکمل ہوتے ہی Pro خود بخود ان لاک ہو جائے گا۔</string>
<string name="upgrade_screen_grace_title">آپ کی خریداری کی تصدیق ہو رہی ہے</string>
<string name="upgrade_screen_grace_body_short">گوگل پلے نے ابھی تک آپ کی خریداری کی تصدیق نہیں کی۔ Pro اب بھی فعال ہے — کسی اقدام کی ضرورت نہیں۔</string>
<string name="upgrade_screen_grace_body">گوگل پلے نے کافی عرصے سے آپ کی خریداری کی تصدیق نہیں کی۔ Pro اب بھی فعال ہے۔ یقینی بنائیں کہ آپ آن لائن ہیں اور وہی گوگل اکاؤنٹ سے سائن ان ہیں جو خریداری کے لیے استعمال ہوا تھا، پھر بحال کرنے کی کوشش کریں۔</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Bu alohida xarid — u obunangizni bekor qilmaydi yoki pulini qaytarmaydi. Bir xil Google hisobidan foydalaning.</string>
<string name="upgrade_screen_sub_still_renewing_title">Obuna hali ham faol</string>
<string name="upgrade_screen_sub_still_renewing_message">Obunangiz hali ham yangilanishga sozlangan. Avval uni Google Play\'da bekor qiling, so\'ngra bir martalik xaridga o\'ting. Hozirgina bekor qildingizmi? Google Play\'ga biroz vaqt bering va qayta urinib ko\'ring.</string>
<string name="upgrade_screen_purchase_check_failed_message">Google Play orqali xaridlaringizni tekshirib bo\'lmadi. Ikki marta xarid qilib qo\'ymaslik uchun bir ozdan so\'ng qayta urinib ko\'ring.</string>
<string name="upgrade_screen_pending_card_title">To\'lov kutilmoqda</string>
<string name="upgrade_screen_pending_card_body">Google Play to\'lovingizni hali ham qayta ishlamoqda. To\'lov amalga oshgach, Pro avtomatik ravishda faollashadi. Ba\'zi to\'lov usullari biroz vaqt olishi mumkin. Sizdan boshqa hech narsa talab qilinmaydi.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play to\'lovi kutilayotgan bir xaridni topdi va uni hali ham qayta ishlamoqda. To\'lov amalga oshgach, Pro avtomatik ravishda faollashadi.</string>
<string name="upgrade_screen_grace_title">Xaridingiz tasdiqlanmoqda</string>
<string name="upgrade_screen_grace_body_short">Google Play hali xaridingizni tasdiqlamadi. Pro hali ham faol — hech qanday amal talab qilinmaydi.</string>
<string name="upgrade_screen_grace_body">Google Play xaridingizni ancha vaqtdan beri tasdiqlamadi. Pro hali ham faol. Onlayn ekanligingizga va xarid uchun ishlatilgan Google hisobi bilan tizimga kirganingizga ishonch hosil qiling, so\'ngra tiklashga urinib ko\'ring.</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Đây là một giao dịch mua riêng biệt — không hủy hoặc hoàn tiền gói đăng ký của bạn. Hãy sử dụng cùng tài khoản Google.</string>
<string name="upgrade_screen_sub_still_renewing_title">Gói đăng ký vẫn đang hoạt động</string>
<string name="upgrade_screen_sub_still_renewing_message">Gói đăng ký của bạn vẫn được đặt để gia hạn. Hãy hủy gói đăng ký trong Google Play trước, sau đó chuyển sang mua một lần. Vừa mới hủy? Hãy chờ Google Play một chút rồi thử lại.</string>
<string name="upgrade_screen_purchase_check_failed_message">Không thể kiểm tra giao dịch mua của bạn với Google Play. Để tránh mua trùng lặp, hãy thử lại sau ít phút.</string>
<string name="upgrade_screen_pending_card_title">Thanh toán đang chờ xử lý</string>
<string name="upgrade_screen_pending_card_body">Google Play vẫn đang xử lý khoản thanh toán của bạn. Pro sẽ tự động mở khóa ngay khi thanh toán hoàn tất. Một số phương thức thanh toán có thể mất một chút thời gian. Bạn không cần làm gì thêm.</string>
<string name="upgrade_screen_pending_dialog_message">Google Play tìm thấy một giao dịch mua có thanh toán đang chờ xử lý và vẫn đang xử lý. Pro sẽ tự động mở khóa ngay khi thanh toán hoàn tất.</string>
<string name="upgrade_screen_grace_title">Đang xác nhận giao dịch mua của bạn</string>
<string name="upgrade_screen_grace_body_short">Google Play chưa xác nhận giao dịch mua của bạn. Pro vẫn đang hoạt động — không cần thực hiện gì thêm.</string>
<string name="upgrade_screen_grace_body">Google Play đã một thời gian chưa xác nhận giao dịch mua của bạn. Pro vẫn đang hoạt động. Hãy đảm bảo bạn đang trực tuyến và đã đăng nhập bằng tài khoản Google đã dùng để mua, sau đó thử khôi phục.</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">这是一笔独立的购买——不会取消或退款您的订阅。请使用相同的 Google 账号。</string>
<string name="upgrade_screen_sub_still_renewing_title">订阅仍然有效</string>
<string name="upgrade_screen_sub_still_renewing_message">您的订阅仍设置为自动续订。请先在 Google Play 中取消订阅,再切换为一次性购买。刚取消?请稍等片刻,让 Google Play 同步后再试。</string>
<string name="upgrade_screen_purchase_check_failed_message">无法与 Google Play 验证你的购买。为了避免重复购买,请稍后再试。</string>
<string name="upgrade_screen_pending_card_title">付款待处理</string>
<string name="upgrade_screen_pending_card_body">Google Play 还在处理你的付款。付款成功后,Pro 会自动解锁。某些付款方式可能需要一段时间。你无需执行任何其他操作。</string>
<string name="upgrade_screen_pending_dialog_message">Google Play 找到了一笔待处理付款的购买,正在处理中。付款成功后,Pro 会自动解锁。</string>
<string name="upgrade_screen_grace_title">正在确认您的购买</string>
<string name="upgrade_screen_grace_body_short">Google Play 尚未确认您的购买。Pro 功能仍然有效——无需任何操作。</string>
<string name="upgrade_screen_grace_body">Google Play 已有一段时间未确认您的购买。Pro 功能仍然有效。请确保您已联网,并使用购买时的 Google 账号登录,然后尝试恢复购买。</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">這是一項獨立的購買——不會取消或退還你的訂閱款項。請使用同一個 Google 帳戶。</string>
<string name="upgrade_screen_sub_still_renewing_title">訂閱仍然有效</string>
<string name="upgrade_screen_sub_still_renewing_message">你的訂閱仍設定為自動續訂。請先在 Google Play 取消訂閱,然後再切換至一次性購買。剛剛才取消?請給 Google Play 一點時間,然後再試一次。</string>
<string name="upgrade_screen_purchase_check_failed_message">無法使用 Google Play 檢查你的購買。為避免重複購買,請稍後再試。</string>
<string name="upgrade_screen_pending_card_title">待處理付款</string>
<string name="upgrade_screen_pending_card_body">Google Play 正在處理你的付款。付款完成後,Pro 版本會自動解鎖。某些付款方式可能需要時間。無需做任何其他事。</string>
<string name="upgrade_screen_pending_dialog_message">Google Play 發現有待處理付款的購買,正在處理中。付款完成後,Pro 版本會自動解鎖。</string>
<string name="upgrade_screen_grace_title">正在確認你的購買</string>
<string name="upgrade_screen_grace_body_short">Google Play 尚未確認你的購買。Pro 仍然有效——無需任何操作。</string>
<string name="upgrade_screen_grace_body">Google Play 已有一段時間未有確認你的購買。Pro 仍然有效。請確保你已連接網絡,並使用購買時所用的 Google 帳戶登入,然後嘗試還原。</string>
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">這是一項獨立的購買,不會取消或退還您的訂閱費用。請使用相同的 Google 帳戶。</string>
<string name="upgrade_screen_sub_still_renewing_title">訂閱仍在生效中</string>
<string name="upgrade_screen_sub_still_renewing_message">您的訂閱仍設定為自動續訂。請先在 Google Play 中取消訂閱,再切換至一次性購買。剛取消嗎?請稍候片刻讓 Google Play 更新後再試一次。</string>
<string name="upgrade_screen_purchase_check_failed_message">無法向 Google Play 檢查你的購買。為了避免重複購買,請稍後再試。</string>
<string name="upgrade_screen_pending_card_title">付款待處理</string>
<string name="upgrade_screen_pending_card_body">Google Play 正在處理你的付款。一旦付款完成,專業版將自動解鎖。某些付款方式可能需要一段時間。你無需進行任何其他操作。</string>
<string name="upgrade_screen_pending_dialog_message">Google Play 找到待付款的購買項目,並正在處理中。一旦付款完成,專業版將自動解鎖。</string>
<string name="upgrade_screen_grace_title">正在確認您的購買</string>
<string name="upgrade_screen_grace_body_short">Google Play 尚未確認您的購買。Pro 仍處於啟用狀態 — 無需採取任何動作。</string>
<string name="upgrade_screen_grace_body">Google Play 已有一段時間尚未確認您的購買。Pro 仍處於啟用狀態。請確認您已連上網路,並登入購買時所用的 Google 帳戶,然後嘗試還原購買項目。</string>
+4
View File
@@ -46,6 +46,10 @@
<string name="upgrade_screen_owned_iap_purchase_note">Lokhu kuwukuthenga okuhlukile — akukhanseli noma akubuyiseli imali yokubhalisa kwakho. Sebenzisa i-akhawunti ye-Google efanayo.</string>
<string name="upgrade_screen_sub_still_renewing_title">Ukubhalisa kusasebenza</string>
<string name="upgrade_screen_sub_still_renewing_message">Ukubhalisa kwakho kusahlelelwe ukuvuselelwa. Kuqale ukhansele ku-Google Play, bese ushintshela ekuthengeni okwenziwa kanye. Usanda kukhansela? Nika i-Google Play isikhashana bese uzama futhi.</string>
<string name="upgrade_screen_purchase_check_failed_message">Ayikwazanga ukuhlola okuthengile ku-Google Play. Ukugwema ukuthenga kabili, zama futhi ngemuva kwesikhashana.</string>
<string name="upgrade_screen_pending_card_title">Inkokhelo ilindile</string>
<string name="upgrade_screen_pending_card_body">I-Google Play isalicubungula ikhokhelo lakho. I-Pro ivulwa ngokuzenzakalela uma inkokhelo isiphumelele. Ezinye izindlela zokukhokha zingathatha isikhathi. Akukho okunye okudingeka ukukwenze.</string>
<string name="upgrade_screen_pending_dialog_message">I-Google Play ithole ukuthenga okunenkokhelo elindile futhi isayicubungula. I-Pro ivulwa ngokuzenzakalela uma inkokhelo isiphumelele.</string>
<string name="upgrade_screen_grace_title">Kuqinisekiswa ukuthenga kwakho</string>
<string name="upgrade_screen_grace_body_short">I-Google Play ayikaqinisekisi ukuthenga kwakho. I-Pro isasebenza — akudingeki sinyathelo.</string>
<string name="upgrade_screen_grace_body">I-Google Play ayikaqinisekisi ukuthenga kwakho isikhathi eside. I-Pro isasebenza. Qinisekisa ukuthi ukuxhunyiwe ku-inthanethi futhi ungenile nge-akhawunti ye-Google eyasetshenziswa ekutheneni, bese uzama ukubuyisela.</string>
+14
View File
@@ -86,6 +86,7 @@
android:label="Service trigger">
<intent-filter>
<action android:name="android.bluetooth.device.action.ACL_CONNECTED" />
<action android:name="android.bluetooth.device.action.ACL_DISCONNECTED" />
</intent-filter>
</receiver>
@@ -132,6 +133,19 @@
</intent-filter>
</activity>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<!-- Debug stuff-->
<activity
android:name=".common.debug.recording.ui.RecorderActivity"
+21 -1
View File
@@ -1,7 +1,10 @@
package eu.darken.capod
import android.app.Application
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
import eu.darken.capod.common.BuildConfigWrap
import eu.darken.capod.common.coroutine.AppScope
import eu.darken.capod.common.debug.autoreport.AutomaticBugReporter
import eu.darken.capod.common.debug.logging.LogCatLogger
@@ -34,8 +37,9 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltAndroidApp
open class App : Application() {
open class App : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
@Inject lateinit var autoReporting: AutomaticBugReporter
@Inject lateinit var deviceMonitor: DeviceMonitor
@Inject lateinit var widgetManager: WidgetManager
@@ -96,6 +100,22 @@ open class App : Application() {
.launchIn(appScope)
}
// WorkManager 2.7.1 (see Dependencies.addWorkerManager) still declares Configuration.Provider
// as getWorkManagerConfiguration(); the `workManagerConfiguration` property form only exists
// from 2.9.0 onwards.
override fun getWorkManagerConfiguration(): Configuration = Configuration.Builder()
.setMinimumLoggingLevel(
when {
BuildConfigWrap.DEBUG -> android.util.Log.VERBOSE
BuildConfigWrap.BUILD_TYPE == BuildConfigWrap.BuildType.DEV -> android.util.Log.DEBUG
BuildConfigWrap.BUILD_TYPE == BuildConfigWrap.BuildType.BETA -> android.util.Log.INFO
BuildConfigWrap.BUILD_TYPE == BuildConfigWrap.BuildType.RELEASE -> android.util.Log.WARN
else -> android.util.Log.VERBOSE
}
)
.setWorkerFactory(workerFactory)
.build()
companion object {
internal val TAG = logTag("CAP")
}
@@ -1,5 +1,7 @@
package eu.darken.capod.common
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.media.AudioPlaybackConfiguration
import android.os.Build
@@ -25,6 +27,7 @@ class MediaControl @Inject constructor(
private val audioManager: AudioManager,
private val timeSource: TimeSource,
@AudioCallbackHandler private val audioCallbackHandler: Handler,
private val duckFocusRequestFactory: DuckFocusRequestFactory,
) {
/**
* Set when [sendPause] dispatches a pause we expect to take effect, cleared when [sendPlay]
@@ -47,6 +50,11 @@ class MediaControl @Inject constructor(
*/
private val dispatchLock = Mutex()
/** The granted ducking focus request, or `null` when we don't hold focus. Guarded by `this`. */
private var duckFocusRequest: AudioFocusRequest? = null
private val duckFocusListener = AudioManager.OnAudioFocusChangeListener { change -> onDuckFocusChanged(change) }
private val playbackCallback = object : AudioManager.AudioPlaybackCallback() {
override fun onPlaybackConfigChanged(configs: List<AudioPlaybackConfiguration>) {
// The edge is derived from this delivery's own snapshot, not a live isMusicActive read:
@@ -263,23 +271,25 @@ class MediaControl @Inject constructor(
fun currentMusicVolume(): Int = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
/**
* Lowers STREAM_MUSIC volume by [reductionPercent] (relative to the current level) and returns
* the prior + the volume actually applied, so the caller can later restore it and detect whether
* the user changed the volume in the meantime.
* Lowers STREAM_MUSIC volume by [reductionPercent] (relative to the current level) and classifies
* what actually happened, so the caller can restore the prior level later, fall back to audio
* focus, or do nothing at all.
*
* Returns `null` (no-op) when nothing is playing, the device has fixed volume, or the computed
* target wouldn't actually lower the volume. No [AudioManager.FLAG_SHOW_UI] — this fires on a
* frequent push event and the volume panel flashing would be noisy. The applied target is read
* back from the system because Bluetooth absolute-volume routes can quantize the requested value.
* Returns [DuckOutcome.Skipped] when nothing is playing, the device has fixed volume, the computed
* target wouldn't actually lower the volume, the level came back higher, or the write was denied.
* Returns [DuckOutcome.Unchanged] when the write was accepted but the level did not drop. No
* [AudioManager.FLAG_SHOW_UI] — this fires on a frequent push event and the volume panel
* flashing would be noisy. The applied target is read back from the system because Bluetooth
* absolute-volume routes can quantize the requested value.
*/
fun duckMusicVolume(reductionPercent: Int): VolumeDuck? {
fun duckMusicVolume(reductionPercent: Int): DuckOutcome {
if (!audioManager.isMusicActive) {
log(TAG, INFO) { "duckMusicVolume: nothing playing, skipping" }
return null
return DuckOutcome.Skipped
}
if (audioManager.isVolumeFixed) {
log(TAG, INFO) { "duckMusicVolume: device has fixed volume, skipping" }
return null
return DuckOutcome.Skipped
}
val percent = reductionPercent.coerceIn(0, 100)
val max = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
@@ -292,17 +302,45 @@ class MediaControl @Inject constructor(
val target = (prior * (100 - percent) / 100).coerceIn(min, max)
if (target >= prior) {
log(TAG, INFO) { "duckMusicVolume: target $target >= current $prior, skipping" }
return null
return DuckOutcome.Skipped
}
return try {
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, target, 0)
val applied = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
log(TAG, INFO) { "duckMusicVolume($percent%): $prior -> $applied (requested $target)" }
VolumeDuck(priorVolume = prior, appliedVolume = applied)
when {
applied > prior -> {
// The level came back HIGHER than we found it: the user raised the volume between
// the two reads. That is not the ROM refusing the write, so it must not read as
// one — a fallback here would fire on a device whose volume writes work fine.
log(TAG, WARN) {
"duckMusicVolume($percent%): volume increased, $prior -> $applied " +
"(requested $target, min=$min, max=$max)"
}
DuckOutcome.Skipped
}
applied == prior -> {
// No attenuation happened even though the write was accepted: ColorOS 16 does
// this while the app is in the background (no exception, volume untouched), and
// a route may quantize the target back up to where it started. Reporting a duck
// would have the caller track a session that never attenuated anything, and
// later "restore" a level it never left.
log(TAG, WARN) {
"duckMusicVolume($percent%): volume did not decrease, $prior -> $applied " +
"(requested $target, min=$min, max=$max)"
}
DuckOutcome.Unchanged(priorVolume = prior)
}
else -> {
log(TAG, INFO) { "duckMusicVolume($percent%): $prior -> $applied (requested $target)" }
DuckOutcome.Ducked(priorVolume = prior, appliedVolume = applied)
}
}
} catch (e: SecurityException) {
// setStreamVolume throws under Do-Not-Disturb without notification policy access.
log(TAG, WARN) { "duckMusicVolume: setStreamVolume denied: ${e.message}" }
null
DuckOutcome.Skipped
}
}
@@ -317,11 +355,73 @@ class MediaControl @Inject constructor(
}
}
/** Snapshot of a volume duck so the caller can restore the prior level and detect user changes. */
data class VolumeDuck(
val priorVolume: Int,
val appliedVolume: Int,
)
/**
* Requests transient ducking audio focus so the framework attenuates the other player for us.
* Fallback for devices where the volume write is accepted but ignored ([DuckOutcome.Unchanged]).
*
* Idempotent — returns `true` when focus is held, whether this call obtained it or an earlier one
* did. A grant only describes the instant of the request, so the held state is dropped again when
* the system takes focus away permanently.
*/
@Synchronized
fun requestDuckFocus(): Boolean {
if (duckFocusRequest != null) {
log(TAG, INFO) { "requestDuckFocus(): already held" }
return true
}
val request = duckFocusRequestFactory.create(duckFocusListener)
val result = audioManager.requestAudioFocus(request)
return if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
duckFocusRequest = request
log(TAG, INFO) { "requestDuckFocus(): granted" }
true
} else {
log(TAG, INFO) { "requestDuckFocus(): denied ($result)" }
false
}
}
/** Releases the ducking focus taken by [requestDuckFocus]. No-op when we don't hold it. */
@Synchronized
fun abandonDuckFocus() {
val request = duckFocusRequest ?: return
duckFocusRequest = null
audioManager.abandonAudioFocusRequest(request)
log(TAG, INFO) { "abandonDuckFocus(): focus released" }
}
val isDuckFocusHeld: Boolean
@Synchronized get() = duckFocusRequest != null
@Synchronized
private fun onDuckFocusChanged(change: Int) {
// Only a PERMANENT loss ends our request. AUDIOFOCUS_LOSS_TRANSIENT leaves it in place, so
// clearing on that would forget a request the system still tracks and let the next
// abandon/re-request pair fight the other app over a temporary interruption.
if (change != AudioManager.AUDIOFOCUS_LOSS) return
log(TAG, INFO) { "Duck focus permanently lost" }
duckFocusRequest = null
}
/** What a [duckMusicVolume] call actually achieved. */
sealed interface DuckOutcome {
/** The level dropped: [priorVolume] is what to restore, [appliedVolume] what landed. */
data class Ducked(val priorVolume: Int, val appliedVolume: Int) : DuckOutcome
/** The write was accepted but the level did not move — the read-back proves no attenuation. */
data class Unchanged(val priorVolume: Int) : DuckOutcome
/** Nothing was attempted, or the result is nothing for the caller to act on. */
data object Skipped : DuckOutcome
}
/**
* Test seam for building the ducking focus request: [AudioFocusRequest.Builder] and
* [AudioAttributes.Builder] are unmocked stubs that throw in this module's plain JVM unit tests.
*/
fun interface DuckFocusRequestFactory {
fun create(listener: AudioManager.OnAudioFocusChangeListener): AudioFocusRequest
}
companion object {
private val TAG = logTag("MediaControl")
@@ -79,11 +79,22 @@ class BleScanner @Inject constructor(
}
val callback = object : ScanCallback() {
var lastScanAt = timeSource.currentTimeMillis()
// Updated outside the log lambdas below: those only run while a logger is attached, so
// folding the bookkeeping into them made the first delay of a debug recording measure
// the time since the *previous* recording ended instead of the actual callback gap.
// Monotonic clock, so a wall-clock correction can't fabricate a gap either.
var lastScanAt = timeSource.elapsedRealtime()
private fun takeDelay(): Long {
val now = timeSource.elapsedRealtime()
val delay = now - lastScanAt
lastScanAt = now
return delay
}
override fun onScanResult(callbackType: Int, result: ScanResult) {
val delay = takeDelay()
log(TAG, VERBOSE) {
val delay = timeSource.currentTimeMillis() - lastScanAt
lastScanAt = timeSource.currentTimeMillis()
"onScanResult(delay=${delay}ms, callbackType=$callbackType, ${result.logSummary()})"
}
@@ -91,11 +102,8 @@ class BleScanner @Inject constructor(
}
override fun onBatchScanResults(results: MutableList<ScanResult>) {
log(TAG, VERBOSE) {
val delay = timeSource.currentTimeMillis() - lastScanAt
lastScanAt = timeSource.currentTimeMillis()
"onBatchScanResults(delay=${delay}ms, ${results.logSummary()})"
}
val delay = takeDelay()
log(TAG, VERBOSE) { "onBatchScanResults(delay=${delay}ms, ${results.logSummary()})" }
trySend(filterResults(results))
}
@@ -237,6 +237,26 @@ class BluetoothManager2 @Inject constructor(
private val seenDevicesLock = Mutex()
private val seenDevicesCache = mutableMapOf<String, Instant>()
/**
* Stamps the connect time from the ACL broadcast, which reaches a manifest-registered receiver
* whether or not [connectedDevices] is being collected. An existing stamp wins: the earlier one
* is the real connect time.
*/
fun markDeviceConnected(address: BluetoothAddress) {
appScope.launch {
seenDevicesLock.withLock {
if (seenDevicesCache.containsKey(address)) return@withLock
seenDevicesCache[address] = timeSource.now()
}
}
}
fun markDeviceDisconnected(address: BluetoothAddress) {
appScope.launch {
seenDevicesLock.withLock { seenDevicesCache.remove(address) }
}
}
val connectedDevices: Flow<List<BluetoothDevice2>> = isBluetoothEnabled
.flatMapLatest { enabled ->
if (enabled) monitorProfile(BluetoothProfile.HEADSET)
@@ -311,12 +331,10 @@ class BluetoothManager2 @Inject constructor(
address = device.address,
name = device.name,
internal = device,
// Read-only: a bonded device is not necessarily connected, and writing here would
// re-stamp entries that [connectedDevices] just pruned.
seenFirstAt = seenDevicesLock.withLock {
seenDevicesCache[device.address] ?: run {
val now = timeSource.now()
seenDevicesCache[device.address] = now
now
}
seenDevicesCache[device.address] ?: timeSource.now()
}
)
@@ -264,6 +264,51 @@ object MockPodDataProvider {
),
)
/**
* Dual pods with hand-picked battery levels. The case reads at decile granularity, like the
* public advertisement it would come from.
*/
fun dualPodBatteries(
left: Float? = 0.80f,
right: Float? = 0.45f,
case: Float? = 0.60f,
model: PodModel = PodModel.AIRPODS_PRO2,
label: String = "My AirPods Pro",
): PodDevice = PodDevice(
profileId = "preview-dual-batteries",
label = label,
ble = MockDualBlePodSnapshot(
_model = model,
_label = label,
batteryLeftPodPercent = left,
batteryRightPodPercent = right,
_batteryCasePercent = case,
leftPodIcon = model.leftPodIconRes ?: R.drawable.device_airpods_gen1_left,
rightPodIcon = model.rightPodIconRes ?: R.drawable.device_airpods_gen1_right,
_caseIcon = model.caseIconRes ?: R.drawable.device_airpods_gen1_case,
_address = "AA:BB:CC:DD:EE:FF",
),
aap = null,
)
/** A single-pod device with a hand-picked battery level. */
fun singlePodBattery(
percent: Float,
model: PodModel = PodModel.AIRPODS_MAX,
label: String = "AirPods Max",
): PodDevice = PodDevice(
profileId = "preview-single-battery",
label = label,
ble = MockSingleBlePodSnapshot(
_model = model,
_label = label,
batteryHeadsetPercent = percent,
_isBeingWorn = true,
_address = "AA:BB:CC:DD:EE:FF",
),
aap = null,
)
fun singlePodMonitored(): PodDevice = PodDevice(
profileId = "preview-single",
label = "AirPods Max",
@@ -4,6 +4,8 @@ import android.app.Application
import android.app.NotificationManager
import android.bluetooth.BluetoothManager
import android.content.Context
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Handler
import android.os.HandlerThread
@@ -11,6 +13,7 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import eu.darken.capod.common.MediaControl
import javax.inject.Qualifier
import javax.inject.Singleton
@@ -43,6 +46,21 @@ class AndroidModule {
fun audioCallbackHandler(): Handler =
Handler(HandlerThread("CAPod-MediaControl").apply { start() }.looper)
@Provides
@Singleton
fun duckFocusRequestFactory(): MediaControl.DuckFocusRequestFactory =
MediaControl.DuckFocusRequestFactory { listener ->
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANT)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
)
.setOnAudioFocusChangeListener(listener)
.build()
}
}
@Qualifier
@@ -1,5 +1,6 @@
package eu.darken.capod.common.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -13,15 +14,27 @@ import eu.darken.capod.common.compose.PreviewWrapper
@Composable
fun SettingsCategoryHeader(
text: String,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
subtitle: String? = null,
) {
Text(
text = text,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium,
Column(
modifier = modifier.padding(horizontal = 16.dp, vertical = 12.dp)
)
) {
Text(
text = text,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium,
)
subtitle?.let {
Text(
text = it,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 2.dp),
)
}
}
}
@Preview2
@@ -29,3 +42,12 @@ fun SettingsCategoryHeader(
private fun SettingsCategoryHeaderPreview() = PreviewWrapper {
SettingsCategoryHeader(text = "Other")
}
@Preview2
@Composable
private fun SettingsCategoryHeaderWithSubtitlePreview() = PreviewWrapper {
SettingsCategoryHeader(
text = "Compatibility options",
subtitle = "Don't touch if everything works ;)",
)
}
@@ -0,0 +1,60 @@
package eu.darken.capod.common.theming
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import eu.darken.capod.monitor.core.battery.BatteryTier
/**
* Battery colours that must not move with the palette. `colorScheme.tertiary` is whatever the seed
* happens to produce (olive under the amber theme, teal under blue), so a level warning drawn from
* it reads as decoration rather than as a warning.
*
* The values are picked against the composited backgrounds the overview actually draws on, see
* `BatteryColorsContrastTest`. Critical stays on `colorScheme.error` and good on
* `colorScheme.primary` — both already carry the meaning we want.
*/
@Immutable
data class BatteryColors(
val warnFill: Color,
val warnText: Color,
val positiveText: Color,
) {
companion object {
val Light = BatteryColors(
warnFill = Color(0xFF874400),
warnText = Color(0xFF522300),
positiveText = Color(0xFF003B0C),
)
val Dark = BatteryColors(
warnFill = Color(0xFFFFA726),
warnText = Color(0xFFFFCC80),
positiveText = Color(0xFFA5D6A7),
)
}
}
/**
* Provided by [CapodTheme] from the theme mode it already resolved. Reading
* `isSystemInDarkTheme()` here instead would pick light tokens for an in-app dark override on a
* light system.
*/
val LocalBatteryColors = staticCompositionLocalOf { BatteryColors.Light }
@Composable
fun BatteryTier.fillColor(): Color = when (this) {
BatteryTier.UNKNOWN -> MaterialTheme.colorScheme.surfaceVariant
BatteryTier.CRITICAL -> MaterialTheme.colorScheme.error
BatteryTier.WARN -> LocalBatteryColors.current.warnFill
BatteryTier.GOOD -> MaterialTheme.colorScheme.primary
}
/** Null where the level makes no claim, so callers keep their own default text colour. */
@Composable
fun BatteryTier.textColorOrNull(): Color? = when (this) {
BatteryTier.CRITICAL -> MaterialTheme.colorScheme.error
BatteryTier.WARN -> LocalBatteryColors.current.warnText
BatteryTier.UNKNOWN, BatteryTier.GOOD -> null
}
@@ -6,6 +6,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@@ -32,8 +33,12 @@ fun CapodTheme(
}
}
MaterialTheme(
colorScheme = colorScheme,
content = content,
)
CompositionLocalProvider(
LocalBatteryColors provides if (darkTheme) BatteryColors.Dark else BatteryColors.Light,
) {
MaterialTheme(
colorScheme = colorScheme,
content = content,
)
}
}
@@ -0,0 +1,18 @@
package eu.darken.capod.common.worker
import android.content.Context
import androidx.work.WorkManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
class WorkManagerModule {
@Provides
@Singleton
fun workManager(context: Context): WorkManager = WorkManager.getInstance(context)
}
@@ -7,7 +7,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.AutoAwesome
import androidx.compose.material.icons.twotone.DoNotDisturbOn
import androidx.compose.material.icons.twotone.Headphones
import androidx.compose.material.icons.twotone.Hearing
import androidx.compose.material.icons.twotone.NoiseAware
import androidx.compose.ui.graphics.vector.ImageVector
import eu.darken.capod.R
import eu.darken.capod.pods.core.apple.aap.protocol.AapSetting
@@ -25,7 +25,9 @@ fun AapSetting.AncMode.Value.shortLabel(context: Context): String = context.getS
fun AapSetting.AncMode.Value.icon(): ImageVector = when (this) {
AapSetting.AncMode.Value.OFF -> Icons.TwoTone.DoNotDisturbOn
AapSetting.AncMode.Value.ON -> Icons.TwoTone.Headphones
AapSetting.AncMode.Value.TRANSPARENCY -> Icons.TwoTone.Hearing
// Not Hearing: that glyph is already the "In ear" status chip, so the two looked identical
// sitting in the same card. NoiseAware is the purpose-built ambient/transparency icon.
AapSetting.AncMode.Value.TRANSPARENCY -> Icons.TwoTone.NoiseAware
AapSetting.AncMode.Value.ADAPTIVE -> Icons.TwoTone.AutoAwesome
}
@@ -45,7 +45,9 @@ import eu.darken.capod.common.settings.SettingsSection
import eu.darken.capod.main.ui.devicesettings.cards.AapUnavailableCard
import eu.darken.capod.main.ui.devicesettings.cards.BatteryCard
import eu.darken.capod.main.ui.devicesettings.cards.BatteryHealthTexts
import eu.darken.capod.main.ui.devicesettings.cards.BatteryRuntimeWarningBanner
import eu.darken.capod.main.ui.devicesettings.cards.ControlsCard
import eu.darken.capod.main.ui.devicesettings.cards.DeviceInfoBottomSheet
import eu.darken.capod.main.ui.devicesettings.cards.DeviceInfoCard
import eu.darken.capod.main.ui.devicesettings.cards.NoiseControlCard
import eu.darken.capod.main.ui.devicesettings.cards.NotConnectedCard
@@ -90,6 +92,7 @@ fun DeviceSettingsScreenHost(
var showListeningModeCycleDialog by rememberSaveable { mutableStateOf(false) }
val state by vm.state.collectAsStateWithLifecycle(initialValue = null)
val offRejectedMessage = stringResource(R.string.device_settings_anc_off_rejected_message)
val ancNotConfirmedMessage = stringResource(R.string.anc_mode_not_confirmed_message)
val chargeCapRejectedMessage = stringResource(R.string.device_settings_charge_cap_rejected_message)
val pendingInfoMessage = stringResource(R.string.device_settings_pending_info)
@@ -125,6 +128,10 @@ fun DeviceSettingsScreenHost(
snackbarHostState.showSnackbar(offRejectedMessage)
}
DeviceSettingsViewModel.Event.AncModeNotConfirmedByDevice -> {
snackbarHostState.showSnackbar(ancNotConfirmedMessage)
}
DeviceSettingsViewModel.Event.DynamicEndOfChargeRejectedByDevice -> {
snackbarHostState.showSnackbar(chargeCapRejectedMessage)
}
@@ -235,6 +242,65 @@ fun DeviceSettingsScreen(
val features = device?.model?.features
val enabled = device?.isAapReady == true
val isPro = state.isPro
// Hoisted out of DeviceInfoCard: the runtime warning banner opens the same detail sheet as the
// card's info icon, and both entry points can be scrolled out of composition independently.
var showDeviceDetails by rememberSaveable { mutableStateOf(false) }
val context = LocalContext.current
val stateDetection = device?.ble as? HasStateDetection
val seenFirst = device?.seenFirstAt
val seenLast = device?.seenLastAt
val firstSeen = if (device != null && seenFirst != null && seenLast != null &&
Duration.between(seenFirst, seenLast).toMinutes() >= 1
) {
device.firstSeenFormatted(state.now)
} else null
val locale = LocalConfiguration.current.locales[0]
val zoneId = ZoneId.systemDefault()
val dateFormatter = remember(locale, zoneId) {
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(locale)
.withZone(zoneId)
}
val detailLabels = rememberDeviceInfoDetailLabels()
val batteryHealthTexts = when {
state.batteryHealth != null -> BatteryHealthTexts(
left = state.batteryHealth.left?.let {
stringResource(R.string.device_settings_info_battery_health_value, it.percent)
},
right = state.batteryHealth.right?.let {
stringResource(R.string.device_settings_info_battery_health_value, it.percent)
},
headset = state.batteryHealth.headset?.let {
stringResource(R.string.device_settings_info_battery_health_value, it.percent)
},
)
state.batteryHealthPending -> BatteryHealthTexts(
pending = stringResource(R.string.device_settings_info_battery_health_pending),
)
else -> null
}
val detailItems = if (device != null) {
buildDeviceInfoDetailItems(
info = device.deviceInfo,
labels = detailLabels,
formatDate = { instant -> dateFormatter.format(instant) },
batteryHealth = batteryHealthTexts,
)
} else {
emptyList()
}
LaunchedEffect(detailItems) {
if (detailItems.isEmpty()) showDeviceDetails = false
}
if (showDeviceDetails && detailItems.isNotEmpty()) {
DeviceInfoBottomSheet(
items = detailItems,
onDismiss = { showDeviceDetails = false },
)
}
Scaffold(
topBar = {
@@ -277,45 +343,6 @@ fun DeviceSettingsScreen(
// Device Info
if (device != null) {
item("device_info") {
val context = LocalContext.current
val stateDetection = device.ble as? HasStateDetection
val seenFirst = device.seenFirstAt
val seenLast = device.seenLastAt
val firstSeen = if (seenFirst != null && seenLast != null && Duration.between(seenFirst, seenLast)
.toMinutes() >= 1
) {
device.firstSeenFormatted(state.now)
} else null
val info = device.deviceInfo
val locale = LocalConfiguration.current.locales[0]
val zoneId = ZoneId.systemDefault()
val dateFormatter = remember(locale, zoneId) {
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(locale)
.withZone(zoneId)
}
val detailItems = buildDeviceInfoDetailItems(
info = info,
labels = rememberDeviceInfoDetailLabels(),
formatDate = { instant -> dateFormatter.format(instant) },
batteryHealth = when {
state.batteryHealth != null -> BatteryHealthTexts(
left = state.batteryHealth.left?.let {
stringResource(R.string.device_settings_info_battery_health_value, it)
},
right = state.batteryHealth.right?.let {
stringResource(R.string.device_settings_info_battery_health_value, it)
},
headset = state.batteryHealth.headset?.let {
stringResource(R.string.device_settings_info_battery_health_value, it)
},
)
state.batteryHealthPending -> BatteryHealthTexts(
pending = stringResource(R.string.device_settings_info_battery_health_pending),
)
else -> null
},
)
DeviceInfoCard(
deviceInfo = device.deviceInfo,
modelLabel = buildModelLabel(device),
@@ -326,6 +353,7 @@ fun DeviceSettingsScreen(
detailItems = detailItems,
canRename = device.isAapReady,
onRename = onDeviceNameChange,
onShowDetails = { showDeviceDetails = true },
)
}
}
@@ -340,6 +368,19 @@ fun DeviceSettingsScreen(
}
}
// Listening time has dropped far below the model's rating — details live in the sheet
val runtimeWarning = state.batteryRuntimeWarning
if (device != null && device.hasSelectedPairedDevice && runtimeWarning != null) {
item("battery_runtime_warning") {
BatteryRuntimeWarningBanner(
slot = runtimeWarning.slot,
percent = runtimeWarning.reading.percent,
onClick = { showDeviceDetails = true },
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
}
// Not nearby — no live BLE; settings require the device to be present
if (device != null && device.hasSelectedPairedDevice &&
device.ble == null && !state.isClassicallyConnected
@@ -90,6 +90,7 @@ class DeviceSettingsViewModel @Inject constructor(
data class SendFailed(val command: AapCommand, val message: String?) : Event
data object SystemRenameUnavailable : Event
data object OffModeRejectedByDevice : Event
data object AncModeNotConfirmedByDevice : Event
data object DynamicEndOfChargeRejectedByDevice : Event
}
@@ -109,7 +110,11 @@ class DeviceSettingsViewModel @Inject constructor(
when (command) {
is AapCommand.SetDynamicEndOfCharge ->
events.tryEmit(Event.DynamicEndOfChargeRejectedByDevice)
else -> Unit // Other rejected commands handled elsewhere (e.g. ANC OFF)
// OFF has its own, more specific message via offRejectedEvents.
is AapCommand.SetAncMode -> if (command.mode != AapSetting.AncMode.Value.OFF) {
events.tryEmit(Event.AncModeNotConfirmedByDevice)
}
else -> Unit
}
}
}
@@ -155,9 +160,15 @@ class DeviceSettingsViewModel @Inject constructor(
null
}
}
val now = timeSource.now()
// The runtime figure rides on the same learned data as the estimate — the per-device
// toggle governs both.
val batteryHealth = device
?.takeIf { appleProfile?.batteryEstimateEnabled ?: true }
?.let { BatteryHealth.estimate(drainProfiles[profileId], it.model, now) }
State(
device = device,
now = timeSource.now(),
now = now,
isPro = upgrade.isPro,
isNudgeAvailable = nudgeAvailability != NudgeAvailability.BROKEN,
isForceConnecting = forcing,
@@ -169,12 +180,10 @@ class DeviceSettingsViewModel @Inject constructor(
(it.rightLong !is StemAction.None && it.rightLong !is StemAction.CycleAnc)
} == true,
batteryEstimateEnabled = appleProfile?.batteryEstimateEnabled ?: true,
// Health rides on the same learned data as the estimate — the per-device toggle
// governs both.
batteryHealth = device
?.takeIf { appleProfile?.batteryEstimateEnabled ?: true }
?.let { BatteryHealth.estimate(drainProfiles[profileId], it.model) },
// A model with a rating WILL eventually produce a health figure — surface the
batteryHealth = batteryHealth,
batteryRuntimeWarning = batteryHealth?.lowestPromotable
?.takeIf { it.reading.percent <= BatteryHealth.LOW_RUNTIME_PERCENT },
// A model with a rating WILL eventually produce a runtime figure — surface the
// feature as "still determining" until the listening sessions accumulate. Without
// a paired device the listening gate can never open, so no promise is made.
batteryHealthPending = device != null &&
@@ -207,10 +216,14 @@ class DeviceSettingsViewModel @Inject constructor(
val systemBluetoothName: String? = null,
val hasCustomLongPressStemAction: Boolean = false,
val batteryEstimateEnabled: Boolean = true,
/** Derived per-pod battery health (1..100 each), or null when there isn't enough learned data. */
/** Derived per-pod share of the rated listening time (1..100 each), or null when there
* isn't enough learned data. */
val batteryHealth: BatteryHealth.PerPod? = null,
/** True when health CAN be derived for this device (rated model, feature on) — shows the
* "still determining" placeholder while [batteryHealth] is null. */
/** The worst pod whose runtime has dropped to [BatteryHealth.LOW_RUNTIME_PERCENT] or below
* on enough recent evidence to warn about — drives the warning banner. */
val batteryRuntimeWarning: BatteryHealth.SlotReading? = null,
/** True when a runtime figure CAN be derived for this device (rated model, feature on) —
* shows the "still determining" placeholder while [batteryHealth] is null. */
val batteryHealthPending: Boolean = false,
) {
val reactions: ReactionConfig get() = device?.reactions ?: ReactionConfig()
@@ -0,0 +1,98 @@
package eu.darken.capod.main.ui.devicesettings.cards
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import eu.darken.capod.R
import eu.darken.capod.common.compose.Preview2
import eu.darken.capod.common.compose.PreviewWrapper
import eu.darken.capod.monitor.core.battery.BatteryHealth
@Composable
internal fun BatteryRuntimeWarningBanner(
slot: BatteryHealth.Slot,
percent: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val podLabel = when (slot) {
BatteryHealth.Slot.LEFT -> stringResource(R.string.pods_dual_left_label)
BatteryHealth.Slot.RIGHT -> stringResource(R.string.pods_dual_right_label)
BatteryHealth.Slot.HEADSET -> stringResource(R.string.device_settings_battery_runtime_warning_headset_label)
}
Surface(
onClick = onClick,
color = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.4f),
shape = RoundedCornerShape(12.dp),
modifier = modifier
.fillMaxWidth()
.semantics(mergeDescendants = true) {},
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.Outlined.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.tertiary,
modifier = Modifier
.padding(end = 10.dp)
.size(20.dp),
)
Column(
modifier = Modifier.weight(1f),
) {
Text(
text = stringResource(R.string.device_settings_battery_runtime_warning_title),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.9f),
)
Text(
text = stringResource(
R.string.device_settings_battery_runtime_warning_description,
podLabel,
percent,
),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
)
}
}
}
}
@Preview2
@Composable
private fun BatteryRuntimeWarningBannerEarbudPreview() = PreviewWrapper {
BatteryRuntimeWarningBanner(
slot = BatteryHealth.Slot.LEFT,
percent = 42,
onClick = {},
)
}
@Preview2
@Composable
private fun BatteryRuntimeWarningBannerHeadsetPreview() = PreviewWrapper {
BatteryRuntimeWarningBanner(
slot = BatteryHealth.Slot.HEADSET,
percent = 50,
onClick = {},
)
}
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.twotone.Edit
import androidx.compose.material.icons.twotone.Info
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
@@ -53,13 +52,9 @@ internal fun DeviceInfoCard(
detailItems: List<DeviceDetailItem> = emptyList(),
canRename: Boolean = false,
onRename: (String) -> Unit = {},
onShowDetails: () -> Unit = {},
) {
var showRenameDialog by remember { mutableStateOf(false) }
var showBottomSheet by remember { mutableStateOf(false) }
LaunchedEffect(detailItems) {
if (detailItems.isEmpty()) showBottomSheet = false
}
if (showRenameDialog && deviceInfo != null) {
RenameDialog(
@@ -72,13 +67,6 @@ internal fun DeviceInfoCard(
)
}
if (showBottomSheet && detailItems.isNotEmpty()) {
DeviceInfoBottomSheet(
items = detailItems,
onDismiss = { showBottomSheet = false },
)
}
ElevatedCard(
modifier = Modifier
.fillMaxWidth()
@@ -103,7 +91,7 @@ internal fun DeviceInfoCard(
modifier = Modifier.weight(1f),
)
if (showInfoIconInModelRow) {
IconButton(onClick = { showBottomSheet = true }) {
IconButton(onClick = onShowDetails) {
Icon(
imageVector = Icons.TwoTone.Info,
contentDescription = stringResource(R.string.device_settings_info_details_action),
@@ -144,7 +132,7 @@ internal fun DeviceInfoCard(
} else null,
)
if (showInfoIconInNameRow) {
IconButton(onClick = { showBottomSheet = true }) {
IconButton(onClick = onShowDetails) {
Icon(
imageVector = Icons.TwoTone.Info,
contentDescription = stringResource(R.string.device_settings_info_details_action),
@@ -159,7 +147,7 @@ internal fun DeviceInfoCard(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
IconButton(onClick = { showBottomSheet = true }) {
IconButton(onClick = onShowDetails) {
Icon(
imageVector = Icons.TwoTone.Info,
contentDescription = stringResource(R.string.device_settings_info_details_action),

Some files were not shown because too many files have changed in this diff Show More