diff --git a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java index 96399c446..09d662959 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java @@ -48,6 +48,7 @@ import helium314.keyboard.keyboard.internal.PopupKeySpec; import helium314.keyboard.keyboard.internal.NonDistinctMultitouchHelper; import helium314.keyboard.keyboard.internal.SlidingKeyInputDrawingPreview; +import helium314.keyboard.keyboard.internal.SpacingInsightDrawingPreview; import helium314.keyboard.keyboard.internal.TimerHandler; import helium314.keyboard.keyboard.internal.KeyboardIconsSet; import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode; @@ -126,6 +127,8 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy private final SlidingKeyInputDrawingPreview mSlidingKeyInputDrawingPreview; // Debug overlay for two-thumb point hinting (#2.1), toggled by PREF_GESTURE_DEBUG_DRAW_POINTS. private final GestureDebugPointsDrawingPreview mGestureDebugPointsDrawingPreview; + // Spacing-policy signal readout (#A11), co-gated by PREF_GESTURE_DEBUG_DRAW_POINTS. + private final SpacingInsightDrawingPreview mSpacingInsightDrawingPreview; // Key preview private final KeyPreviewDrawParams mKeyPreviewDrawParams; @@ -233,6 +236,8 @@ public MainKeyboardView(final Context context, final AttributeSet attrs, final i // Debug overlay last so it draws ON TOP of the gesture trail / floating preview. mGestureDebugPointsDrawingPreview = new GestureDebugPointsDrawingPreview(); mGestureDebugPointsDrawingPreview.setDrawingView(drawingPreviewPlacerView); + mSpacingInsightDrawingPreview = new SpacingInsightDrawingPreview(); + mSpacingInsightDrawingPreview.setDrawingView(drawingPreviewPlacerView); mainKeyboardViewAttr.recycle(); mDrawingPreviewPlacerView = drawingPreviewPlacerView; @@ -518,8 +523,9 @@ private void setGesturePreviewMode(final boolean isGestureTrailEnabled, mGestureTrailsDrawingPreview.setPreviewEnabled(isGestureTrailEnabled); // The debug overlay tracks its own pref and is independent of the user-visible trail — // enable the preview whenever the pref is on so the drawing pass actually runs. - mGestureDebugPointsDrawingPreview.setPreviewEnabled( - Settings.getValues().mGestureDebugDrawPoints); + final boolean debugEnabled = Settings.getValues().mGestureDebugDrawPoints; + mGestureDebugPointsDrawingPreview.setPreviewEnabled(debugEnabled); + mSpacingInsightDrawingPreview.setPreviewEnabled(debugEnabled); } public void showGestureFloatingPreviewText(@NonNull final SuggestedWords suggestedWords, @@ -588,6 +594,16 @@ public void setGestureCommitPending(final boolean pending) { mGestureFloatingTextDrawingPreview.setCommitPending(pending); } + // Implements {@link DrawingProxy#setSpacingInsight} (#A11). The readout is co-gated by + // PREF_GESTURE_DEBUG_DRAW_POINTS so there are no new settings to expose. + @Override + public void setSpacingInsight(final boolean complete, final float prefixRichScore, + final int graceMs, @Nullable final String gate) { + if (!Settings.getValues().mGestureDebugDrawPoints) return; + locatePreviewPlacerView(); + mSpacingInsightDrawingPreview.update(complete, prefixRichScore, graceMs, gate); + } + // Note that this method is called from a non-UI thread. @SuppressWarnings("static-method") public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) { diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/DrawingProxy.java b/app/src/main/java/helium314/keyboard/keyboard/internal/DrawingProxy.java index 731d4df51..7d79f05e3 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/DrawingProxy.java +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/DrawingProxy.java @@ -103,4 +103,22 @@ void setGestureDebugPoints(@NonNull helium314.keyboard.latin.common.InputPointer * normal commit / cancel / continuation. */ void setGestureCommitPending(boolean pending); + + /** + * Push a spacing-policy signal snapshot to the debug overlay (#A11), gated behind + * {@code PREF_GESTURE_DEBUG_DRAW_POINTS}. Called from {@code InputLogic} each time the + * combining-mode timer is armed or cleared. + * + *
Implementations must guard on the debug-draw setting internally; calling with + * the setting off must be a cheap no-op. + * + * @param complete whether the current typed stem is a dictionary word + * @param prefixRichScore fraction of suggestions that are prefix-completions [0..1] + * @param graceMs resolved grace duration (ms); {@code <= 0} clears the overlay + * @param gate active gate label; pass {@code null} to use the default + * ({@value SpacingInsightDrawingPreview#GATE_TIMER}). The two-gate + * branch passes its own label here. + */ + void setSpacingInsight(boolean complete, float prefixRichScore, int graceMs, + @Nullable String gate); } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/SpacingInsightDrawingPreview.java b/app/src/main/java/helium314/keyboard/keyboard/internal/SpacingInsightDrawingPreview.java new file mode 100644 index 000000000..e9aab0b72 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/SpacingInsightDrawingPreview.java @@ -0,0 +1,208 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + */ + +package helium314.keyboard.keyboard.internal; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.os.SystemClock; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import helium314.keyboard.keyboard.PointerTracker; +import helium314.keyboard.latin.common.CoordinateUtils; + +/** + * Debug overlay (#A11) that shows the spacing-policy signals for the most recent combining-mode + * arm, drawn in the bottom-left corner of the keyboard area on top of all other previews. + * + *
Gated behind {@code PREF_GESTURE_DEBUG_DRAW_POINTS}; no new preference required. + * + *
Displayed as decision-first, human-readable text: + *
The snapshot string is built once per signal update (in {@link #update}), not per draw + * frame, so the drawing path is allocation-free. + * + *
Integration point for the gate branch: pass a non-null {@code gate} string to + * {@link #update} (e.g. {@code "timer"}, {@code "two-gate"}) or call {@link #updateGate} to + * re-stamp only the gate label without resetting the other signals. + */ +public final class SpacingInsightDrawingPreview extends AbstractDrawingPreview { + + private static final float TEXT_SIZE_SP = 11f; // scaled in setKeyboardViewGeometry + private static final float PADDING_PX = 8f; + private static final long LINGER_AFTER_CLEAR = 1800L; // keep visible long enough to read + private static final int BG_COLOR = 0xDD1A1A2E; // dark navy, ~87% opaque + private static final int TEXT_COLOR = 0xFFFFFFFF; // primary line + private static final int LABEL_COLOR = 0xFFD0E8FF; // detail line + /** Gate label used when the caller passes {@code null}. */ + public static final String GATE_TIMER = "timer"; + /** Sentinel gate label for "no gate / idle". */ + public static final String GATE_NONE = "none"; + + private final Paint mBgPaint = new Paint(); + private final Paint mTextPaint = new Paint(); + private final Paint mLabelPaint = new Paint(); + + // Keyboard area from setKeyboardViewGeometry – used for corner positioning. + private int mKeyboardWidth; + private int mKeyboardHeight; + + // Raw signal fields retained so gate branch can call updateGate() without re-supplying all. + private boolean mComplete; + private float mPrefixRichScore; + private int mGraceMs; + @Nullable private String mGate; + + /** Primary/detail readout lines; {@code null} primary means overlay draws nothing. */ + @Nullable private String mPrimary; + @Nullable private String mDetail; + private long mVisibleUntilMs; + + public SpacingInsightDrawingPreview() { + mBgPaint.setStyle(Paint.Style.FILL); + mBgPaint.setColor(BG_COLOR); + + mTextPaint.setAntiAlias(true); + mTextPaint.setTypeface(Typeface.MONOSPACE); + mTextPaint.setColor(TEXT_COLOR); + mTextPaint.setTextSize(TEXT_SIZE_SP * 2.5f); // rough default; refined in setKeyboardViewGeometry + + mLabelPaint.setAntiAlias(true); + mLabelPaint.setTypeface(Typeface.MONOSPACE); + mLabelPaint.setColor(LABEL_COLOR); + mLabelPaint.setTextSize(TEXT_SIZE_SP * 2.5f); + } + + @Override + public void setKeyboardViewGeometry(@NonNull final int[] originCoords, + final int width, final int height) { + super.setKeyboardViewGeometry(originCoords, width, height); + mKeyboardWidth = width; + mKeyboardHeight = height; + // Scale text relative to keyboard height so it stays readable at any DPI. + final float textPx = Math.max(24f, height * 0.045f); + mTextPaint.setTextSize(textPx); + mLabelPaint.setTextSize(textPx); + } + + /** + * Push a new signal snapshot. Call from {@code InputLogic} each time the combining-mode + * timer is armed. Call with {@code graceMs <= 0} to clear (e.g. on commit/cancel). + * + * @param complete whether the current stem is a dictionary word + * @param prefixRichScore fraction of suggestions that are prefix-completions [0..1] + * @param graceMs resolved grace duration (ms); {@code <= 0} clears the overlay + * @param gate active gate label; {@code null} renders as {@value #GATE_TIMER} + */ + public void update(final boolean complete, final float prefixRichScore, + final int graceMs, @Nullable final String gate) { + if (graceMs <= 0) { + // Don't disappear immediately on commit/cancel — leave the last decision readable. + if (mPrimary != null) { + mVisibleUntilMs = SystemClock.uptimeMillis() + LINGER_AFTER_CLEAR; + invalidateDrawingView(); + } + return; + } + mComplete = complete; + mPrefixRichScore = prefixRichScore; + mGraceMs = graceMs; + mGate = gate; + buildSnapshot(complete, prefixRichScore, graceMs, gate); + mVisibleUntilMs = SystemClock.uptimeMillis() + Math.max(LINGER_AFTER_CLEAR, graceMs + 800L); + invalidateDrawingView(); + } + + /** + * Re-stamp only the gate label on the current snapshot without resetting the signal + * fields. No-op if there is no active snapshot (no live combining-mode arm). + * + *
This is the integration hook for the gate branch: call it as soon as the gate + * decision is made to update the readout without waiting for the next keystroke. + * + * @param gate new gate label; {@code null} falls back to {@value #GATE_TIMER} + */ + public void updateGate(@Nullable final String gate) { + if (mPrimary == null) return; + mGate = gate; + buildSnapshot(mComplete, mPrefixRichScore, mGraceMs, gate); + mVisibleUntilMs = SystemClock.uptimeMillis() + Math.max(LINGER_AFTER_CLEAR, mGraceMs + 800L); + invalidateDrawingView(); + } + + private void buildSnapshot(final boolean complete, final float prefixRichScore, + final int graceMs, @Nullable final String gate) { + final int prefixPct = Math.round(prefixRichScore * 100f); + final String gateLabel = gate == null ? GATE_TIMER : gate; + + if ("instant".equals(gateLabel)) { + mPrimary = "INSTANT"; + mDetail = "finished word + low prefix"; + } else if ("pause".equals(gateLabel)) { + mPrimary = "WAIT " + graceMs + "ms"; + mDetail = "many continuations · px " + prefixPct + "%"; + } else if (complete) { + mPrimary = "FAST " + graceMs + "ms"; + mDetail = "finished word · px " + prefixPct + "%"; + } else if (prefixRichScore >= 0.50f) { + mPrimary = "WAIT " + graceMs + "ms"; + mDetail = "many continuations · px " + prefixPct + "%"; + } else { + mPrimary = "TIMER " + graceMs + "ms"; + mDetail = "not complete · px " + prefixPct + "%"; + } + } + + @Override + public void drawPreview(@NonNull final Canvas canvas) { + if (!isPreviewEnabled()) return; + final String primary = mPrimary; + if (primary == null) return; + if (SystemClock.uptimeMillis() > mVisibleUntilMs) { + mPrimary = null; + mDetail = null; + return; + } + final String detail = mDetail == null ? "" : mDetail; + + final float textH = mTextPaint.getTextSize(); + final float lineGap = Math.max(2f, textH * 0.18f); + final float primaryW = mTextPaint.measureText(primary); + final float detailW = mLabelPaint.measureText(detail); + final float boxW = Math.min(mKeyboardWidth - PADDING_PX * 2f, + Math.max(primaryW, detailW) + PADDING_PX * 2f); + final float boxH = textH * 2f + lineGap + PADDING_PX * 2f; + + // Position: bottom-left of the keyboard area, inset enough to avoid clipping. + final float left = PADDING_PX; + final float top = Math.max(PADDING_PX, mKeyboardHeight - boxH - PADDING_PX); + final float right = left + boxW; + final float bottom = top + boxH; + + canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, mBgPaint); + canvas.drawText(primary, left + PADDING_PX, top + PADDING_PX + textH, mTextPaint); + canvas.drawText(detail, left + PADDING_PX, + top + PADDING_PX + textH * 2f + lineGap, mLabelPaint); + } + + @Override + public void onDeallocateMemory() { + mPrimary = null; + mDetail = null; + } + + @Override + public void setPreviewPosition(@NonNull final PointerTracker tracker) { + // Position is fixed (bottom-left of keyboard) — no tracker tracking needed. + } +} diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 834cc1561..5f0257c93 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -990,7 +990,14 @@ private void enterCombiningMode(final SettingsValues settingsValues, final boole // separators / cursor-front recompositions there's nothing to auto-commit, and arming // the timer would draw a spurious progress bar. if (!mWordComposer.isComposingWord()) return; - final int graceMs = baseGraceMs + Math.max(0, settingsValues.mCombiningTapExtraMs); + final int graceMs; + if (settingsValues.mSpacingSignalDrivenGrace) { + // #24: vary the grace duration by the per-keystroke word-state signals. + graceMs = signalDrivenGraceMs(baseGraceMs, settingsValues.mSpacingCompleteBonusMs, + settingsValues.mSpacingPrefixPenaltyMs, mSpacingComplete, mSpacingPrefixRichScore); + } else { + graceMs = baseGraceMs + Math.max(0, settingsValues.mCombiningTapExtraMs); + } cancelCombiningTimerOnly(); mInCombiningMode = true; // #14 "only auto-finish swiped words": still ENTER combining mode (so a following swipe @@ -1014,6 +1021,8 @@ private void enterCombiningMode(final SettingsValues settingsValues, final boole && !mSuppressAutospaceForForceNextSpace; kv.setCombiningMode(showAutospaceIndicator, startTime, graceMs, true /* compositionActiveForDebug */); + // #A11: push spacing-policy signals to the debug overlay. + kv.setSpacingInsight(mSpacingComplete, mSpacingPrefixRichScore, graceMs, null); } } @@ -1137,7 +1146,10 @@ void cancelCombiningMode() { if (mInCombiningMode) { mInCombiningMode = false; final MainKeyboardView kv = KeyboardSwitcher.getInstance().getMainKeyboardView(); - if (kv != null) kv.setCombiningMode(false, 0L, 0); + if (kv != null) { + kv.setCombiningMode(false, 0L, 0); + kv.setSpacingInsight(false, 0f, 0, null); // clear the #A11 readout + } } } @@ -1191,7 +1203,10 @@ private void onCombiningGraceExpired() { mPendingCombiningCommit = null; mInCombiningMode = false; final MainKeyboardView kv = KeyboardSwitcher.getInstance().getMainKeyboardView(); - if (kv != null) kv.setCombiningMode(false, 0L, 0); + if (kv != null) { + kv.setCombiningMode(false, 0L, 0); + kv.setSpacingInsight(false, 0f, 0, null); // clear the #A11 readout + } final SettingsValues sv = Settings.getInstance().getCurrent(); if (!mWordComposer.isComposingWord()) return; // Capture whether the word being committed by this timer came from a gesture. We @@ -1461,6 +1476,21 @@ static SpacingSignals computeSpacingSignals(final SuggestedWords suggestedWords) return new SpacingSignals(complete, (float) completions / n); } + private static final int SIGNAL_GRACE_MIN_MS = 100; + private static final int SIGNAL_GRACE_MAX_MS = 3000; + + /** + * #24 signal-driven grace duration: a confident complete word commits sooner (subtract + * {@code completeBonus}), while an extendable prefix-rich stem waits longer (add + * {@code prefixPenalty} scaled by the score), clamped to a sane range. Pure for testability. + */ + static int signalDrivenGraceMs(final int baseMs, final int completeBonusMs, + final int prefixPenaltyMs, final boolean complete, final float prefixRichScore) { + final int ms = baseMs - (complete ? completeBonusMs : 0) + + Math.round(prefixPenaltyMs * prefixRichScore); + return Math.max(SIGNAL_GRACE_MIN_MS, Math.min(SIGNAL_GRACE_MAX_MS, ms)); + } + /** * Handle a consumed event. *
diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt
index c0e875464..5ace6111b 100644
--- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt
+++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt
@@ -151,6 +151,9 @@ object Defaults {
const val PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTURE = false
const val PREF_SPACING_DEFER_GRACE_SPACE = false
const val PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE = true // default on: tapped words shouldn't auto-finish
+ const val PREF_SPACING_SIGNAL_DRIVEN_GRACE = false
+ const val PREF_SPACING_COMPLETE_BONUS_MS = 200 // complete word commits this much sooner
+ const val PREF_SPACING_PREFIX_PENALTY_MS = 400 // max extra wait when fully prefix-rich
const val PREF_COMBINING_AUTOSPACE_SUGGESTIONS = "alternatives_then_next_word"
const val PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORD = true
const val PREF_COMBINING_BACKSPACE_DELETES_COMPOSING_TEXT = true
diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java
index cf89a5379..abbacc1bc 100644
--- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java
+++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java
@@ -163,6 +163,11 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
// #14: when on, the combining grace timer only auto-commits words that include a swipe —
// pure tap-typed words are never auto-finished by the timer. Experimental, default off.
public static final String PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE = "combining_grace_only_after_gesture";
+ // #14/#24 signal-driven grace: vary the grace-timer duration by the per-keystroke word-state
+ // signals (complete / prefix-richness) instead of a fixed value. Experimental, default off.
+ public static final String PREF_SPACING_SIGNAL_DRIVEN_GRACE = "spacing_signal_driven_grace";
+ public static final String PREF_SPACING_COMPLETE_BONUS_MS = "spacing_complete_bonus_ms";
+ public static final String PREF_SPACING_PREFIX_PENALTY_MS = "spacing_prefix_penalty_ms";
// What the suggestion strip shows after the combining grace timer auto-commits a word.
// Values: "keep_alternatives" (1) | "next_word" (2, default) | "alternatives_then_next_word" (3).
public static final String PREF_COMBINING_AUTOSPACE_SUGGESTIONS = "combining_autospace_suggestions";
diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java
index 95efd7756..6a93b1349 100644
--- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java
+++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java
@@ -139,6 +139,9 @@ public class SettingsValues {
public final boolean mCombiningAutospaceOnlyAfterGesture;
public final boolean mSpacingDeferGraceSpace;
public final boolean mCombiningGraceOnlyAfterGesture;
+ public final boolean mSpacingSignalDrivenGrace;
+ public final int mSpacingCompleteBonusMs;
+ public final int mSpacingPrefixPenaltyMs;
// Raw string value: "keep_alternatives" | "next_word" | "alternatives_then_next_word"
public final String mCombiningAutospaceSuggestions;
public final boolean mCombiningBackspaceDeletesGestureWord;
@@ -390,6 +393,13 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
mCombiningGraceOnlyAfterGesture = prefs.getBoolean(
Settings.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE,
Defaults.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE);
+ mSpacingSignalDrivenGrace = prefs.getBoolean(
+ Settings.PREF_SPACING_SIGNAL_DRIVEN_GRACE,
+ Defaults.PREF_SPACING_SIGNAL_DRIVEN_GRACE);
+ mSpacingCompleteBonusMs = prefs.getInt(Settings.PREF_SPACING_COMPLETE_BONUS_MS,
+ Defaults.PREF_SPACING_COMPLETE_BONUS_MS);
+ mSpacingPrefixPenaltyMs = prefs.getInt(Settings.PREF_SPACING_PREFIX_PENALTY_MS,
+ Defaults.PREF_SPACING_PREFIX_PENALTY_MS);
mCombiningAutospaceSuggestions = prefs.getString(Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS,
Defaults.PREF_COMBINING_AUTOSPACE_SUGGESTIONS);
final boolean nonNormalTwoThumbSpacing = mGestureManualSpacing || mCombiningGraceMs > 0;
diff --git a/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt
index e73389788..ea131f980 100644
--- a/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt
+++ b/app/src/main/java/helium314/keyboard/settings/screens/TwoThumbTypingScreen.kt
@@ -71,6 +71,9 @@ fun TwoThumbTypingScreen(
add(Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS)
add(Settings.PREF_SPACING_DEFER_GRACE_SPACE)
add(Settings.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE)
+ add(Settings.PREF_SPACING_SIGNAL_DRIVEN_GRACE)
+ add(Settings.PREF_SPACING_COMPLETE_BONUS_MS)
+ add(Settings.PREF_SPACING_PREFIX_PENALTY_MS)
}
if (nonNormalSpacing) {
add(Settings.PREF_MULTIPART_FULL_WORD_SUGGESTIONS)
@@ -158,6 +161,30 @@ fun createTwoThumbTypingSettings(context: Context) = listOf(
R.string.combining_grace_only_after_gesture_summary) {
SwitchPreference(it, Defaults.PREF_COMBINING_GRACE_ONLY_AFTER_GESTURE)
},
+ Setting(context, Settings.PREF_SPACING_SIGNAL_DRIVEN_GRACE,
+ R.string.spacing_signal_driven_grace, R.string.spacing_signal_driven_grace_summary) {
+ SwitchPreference(it, Defaults.PREF_SPACING_SIGNAL_DRIVEN_GRACE)
+ },
+ Setting(context, Settings.PREF_SPACING_COMPLETE_BONUS_MS,
+ R.string.spacing_complete_bonus, R.string.spacing_complete_bonus_summary) { def ->
+ SliderPreference(
+ name = def.title,
+ key = def.key,
+ default = Defaults.PREF_SPACING_COMPLETE_BONUS_MS,
+ range = 0f..1000f,
+ description = { stringResource(R.string.abbreviation_unit_milliseconds, it.toString()) }
+ )
+ },
+ Setting(context, Settings.PREF_SPACING_PREFIX_PENALTY_MS,
+ R.string.spacing_prefix_penalty, R.string.spacing_prefix_penalty_summary) { def ->
+ SliderPreference(
+ name = def.title,
+ key = def.key,
+ default = Defaults.PREF_SPACING_PREFIX_PENALTY_MS,
+ range = 0f..1500f,
+ description = { stringResource(R.string.abbreviation_unit_milliseconds, it.toString()) }
+ )
+ },
Setting(context, Settings.PREF_COMBINING_AUTOSPACE_SUGGESTIONS,
R.string.combining_autospace_suggestions, R.string.combining_autospace_suggestions_summary) { def ->
val items = listOf(
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 82e563c1c..66bc81de3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -303,6 +303,13 @@