diff --git a/DESIGN.md b/DESIGN.md index a726ad7..bfebc1f 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -93,6 +93,13 @@ Use the shared springs in `GlassMotion` (or springs close to them): | `GlassMotion.unfold()` / `unfoldLag()` / `fold()` | Menus: width (0.68, 420), height a beat behind (0.6, 340), close (1, 900) | | `GlassMotion.settle()` / `fade()` | Alpha and layout. Critically damped, never bouncy | +**The user tunes it (Settings -> Liquid Glass -> Motion).** `MotionSettings` holds multipliers on +the numbers above (bounce, speed, press depth, release jiggle, drag stretch, long-press bloom) with +the profiles Normal (the design, exactly), Performance, Bouncy and Super springy. So every bouncing +spring goes through `MotionSettings.damping(designed)` / `stiffness(designed)`, and every press +size goes through the style. The `GlassMotion` springs already do. A new bouncing spring written +by hand must do the same. `settle`, `fade` and layout springs never bounce and ignore it. + **Press jelly (the iOS 27 feel).** - Glass buttons (`LiquidButton`, `LiquidIconButton`) share `liquidPressTransform`. The button swells ~4 dp under the finger and leans toward a dragging finger. On release it springs back and jiggles once, @@ -167,6 +174,10 @@ Use the shared springs in `GlassMotion` (or springs close to them): heading stays blue. Never use a plain RGB invert. Pictures: a photograph is drawn as printed; a scan or figure that `NightPaper.looksLikePaper` turns over with the page. PDF/PPTX pages find their pictures with `PdfImageRegions` and redraw photographs unfiltered (also in zoom tiles). +- The reader's bottom circles (editor tools, auto-scroll, the open/share morph) are 44 dp with + 20 dp icons, the Word editor's size, in the PDF and PowerPoint viewers too. The morph grows to + twice its size while armed. Settings -> Personalization -> Swap share and open makes the tap + share and the hold-and-swipe open; the ink still follows what is behind it. - Word documents open as pages, desktop-like: true proportions and margins, fit to width, pinch to zoom (the page relays out sharp, it is never bitmap-scaled), Word's own page breaks. diff --git a/app/src/main/java/com/chethan616/clearpdf/data/repository/Preferences.kt b/app/src/main/java/com/chethan616/clearpdf/data/repository/Preferences.kt index 266691a..e760f21 100644 --- a/app/src/main/java/com/chethan616/clearpdf/data/repository/Preferences.kt +++ b/app/src/main/java/com/chethan616/clearpdf/data/repository/Preferences.kt @@ -179,6 +179,13 @@ object AppSettingsManager { // see UISensor's own KDoc for why that is the dominant redraw cost on devices sensitive to it. /** Documents open with dark pages whenever the app is in dark mode (#74). */ fun getAutoDarkPages(context: Context): Boolean = prefs(context).getBoolean(KEY_AUTO_DARK_PAGES, false) + + /** + * Swaps the viewer's share/open button: tap shares the document, press-and-hold then swipe up + * opens another file (the default is the other way round). + */ + fun getInvertShareMorph(context: Context): Boolean = prefs(context).getBoolean("invert_share_morph", false) + fun setInvertShareMorph(context: Context, enabled: Boolean) = prefs(context).edit().putBoolean("invert_share_morph", enabled).apply() fun setAutoDarkPages(context: Context, enabled: Boolean) = prefs(context).edit().putBoolean(KEY_AUTO_DARK_PAGES, enabled).apply() fun getReduceGlassMotion(context: Context): Boolean = diff --git a/app/src/main/java/com/chethan616/clearpdf/imageeditor/engine/FilterPresets.kt b/app/src/main/java/com/chethan616/clearpdf/imageeditor/engine/FilterPresets.kt index def188f..51397fc 100644 --- a/app/src/main/java/com/chethan616/clearpdf/imageeditor/engine/FilterPresets.kt +++ b/app/src/main/java/com/chethan616/clearpdf/imageeditor/engine/FilterPresets.kt @@ -21,29 +21,29 @@ import android.graphics.PointF * [intensity] blends the look with its input: matrices are interpolated towards identity, GPU * looks are alpha-composited over the input. */ -enum class FilterPreset(val label: String) { - Vivid("Vivid"), - Warm("Warm"), - Cool("Cool"), - Fade("Fade"), - Mono("Mono"), - Noir("Noir"), - Sepia("Sepia"), - Vintage("Vintage"), - Chrome("Chrome"), - Dramatic("Dramatic"), - Golden("Golden"), - Teal("Teal & Orange"), - Polaroid("Polaroid"), - Lomo("Lomo"), - Invert("Invert"), - Posterize("Posterize"), - Sketch("Sketch"), - Toon("Toon"), - Emboss("Emboss"), - Crosshatch("Crosshatch"), - Halftone("Halftone"), - OilPaint("Oil paint"); +enum class FilterPreset(val label: String, @androidx.annotation.StringRes val labelRes: Int) { + Vivid("Vivid", com.chethan616.clearpdf.R.string.ie_fx_vivid), + Warm("Warm", com.chethan616.clearpdf.R.string.ie_fx_warm), + Cool("Cool", com.chethan616.clearpdf.R.string.ie_fx_cool), + Fade("Fade", com.chethan616.clearpdf.R.string.ie_fx_fade), + Mono("Mono", com.chethan616.clearpdf.R.string.ie_fx_mono), + Noir("Noir", com.chethan616.clearpdf.R.string.ie_fx_noir), + Sepia("Sepia", com.chethan616.clearpdf.R.string.ie_fx_sepia), + Vintage("Vintage", com.chethan616.clearpdf.R.string.ie_fx_vintage), + Chrome("Chrome", com.chethan616.clearpdf.R.string.ie_fx_chrome), + Dramatic("Dramatic", com.chethan616.clearpdf.R.string.ie_fx_dramatic), + Golden("Golden", com.chethan616.clearpdf.R.string.ie_fx_golden), + Teal("Teal & Orange", com.chethan616.clearpdf.R.string.ie_fx_teal), + Polaroid("Polaroid", com.chethan616.clearpdf.R.string.ie_fx_polaroid), + Lomo("Lomo", com.chethan616.clearpdf.R.string.ie_fx_lomo), + Invert("Invert", com.chethan616.clearpdf.R.string.ie_fx_invert), + Posterize("Posterize", com.chethan616.clearpdf.R.string.ie_fx_posterize), + Sketch("Sketch", com.chethan616.clearpdf.R.string.ie_fx_sketch), + Toon("Toon", com.chethan616.clearpdf.R.string.ie_fx_toon), + Emboss("Emboss", com.chethan616.clearpdf.R.string.ie_fx_emboss), + Crosshatch("Crosshatch", com.chethan616.clearpdf.R.string.ie_fx_crosshatch), + Halftone("Halftone", com.chethan616.clearpdf.R.string.ie_fx_halftone), + OilPaint("Oil paint", com.chethan616.clearpdf.R.string.ie_fx_oil_paint); fun matrix(): ColorMatrix? = when (this) { Vivid -> ColorMatrix().apply { setSaturation(1.45f) }.then(contrast(1.08f)) diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/DocsApp.kt b/app/src/main/java/com/chethan616/clearpdf/ui/DocsApp.kt index 196103d..dc5e279 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/DocsApp.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/DocsApp.kt @@ -129,6 +129,9 @@ fun DocsApp(shortcutRoute: String? = null, incomingPdfUri: android.net.Uri? = nu } } var showStarPrompt by rememberSaveable { mutableStateOf(false) } + // Switching into or out of a right-to-left language (Arabic) can't flip the layout in place; + // the app offers a restart instead. + var restartForDirection by rememberSaveable { mutableStateOf(false) } val systemDark = isSystemInDarkTheme() val isDarkMode = when (themeMode) { 1 -> false @@ -284,7 +287,11 @@ fun DocsApp(shortcutRoute: String? = null, incomingPdfUri: android.net.Uri? = nu // Floating glass (dropdown menus, contextual bars) from any depth renders here, above every // screen, refracting the live screen — see GlassOverlayHost. val overlayHost = remember { GlassOverlayHost() } - remember(context) { com.chethan616.clearpdf.ui.components.GlassSettings.load(context); true } + remember(context) { + com.chethan616.clearpdf.ui.components.GlassSettings.load(context) + com.chethan616.clearpdf.ui.components.MotionSettings.load(context) + true + } val platformHaptics = androidx.compose.ui.platform.LocalHapticFeedback.current val haptics = remember(platformHaptics) { com.chethan616.clearpdf.ui.utils.ThrottledHaptics(platformHaptics) } @@ -363,6 +370,7 @@ fun DocsApp(shortcutRoute: String? = null, incomingPdfUri: android.net.Uri? = nu onLocaleChanged = { code -> val normalized = com.chethan616.clearpdf.ui.utils.LocaleHelper.normalizeForUi(code) if (normalized != selectedLocale) { + if (isRtlTag(normalized) != isRtlTag(selectedLocale)) restartForDirection = true // Persist, then switch IN PLACE: the hoisted locale re-provides // LocalResources, so every stringResource re-resolves on the next frame // (the path Onboarding uses). No fade-out + Activity recreate — that was @@ -513,9 +521,53 @@ fun DocsApp(shortcutRoute: String? = null, incomingPdfUri: android.net.Uri? = nu } } + // A new language that reads the other way round (into or out of Arabic): strings + // switch in place, but the layout direction only flips with a fresh start. + GlassDialog( + visible = restartForDirection, + onDismiss = { restartForDirection = false }, + backdrop = contentBackdrop, + title = stringResource(R.string.restart_title), + actions = { + GlassDialogAction(stringResource(R.string.restart_later), onClick = { restartForDirection = false }) + GlassDialogAction( + stringResource(R.string.restart_now), + onClick = { restartForDirection = false; restartApp(context) }, + primary = true + ) + } + ) { + BasicText( + stringResource(R.string.restart_body), + style = TextStyle( + if (!isDarkMode) Color(0xFF3A3A3C) else Color(0xFFD1D1D6), + 15.sp, + textAlign = TextAlign.Center + ), + modifier = Modifier.fillMaxWidth() + ) + } + // Last child: above the tab bar and dialogs, outside the layer it samples. GlassOverlayLayer(overlayHost, contentBackdrop) } } } } + +/** Whether [tag]'s language reads right to left (Arabic, in the app's languages). */ +private fun isRtlTag(tag: String): Boolean = + android.text.TextUtils.getLayoutDirectionFromLocale(java.util.Locale.forLanguageTag(tag)) == android.view.View.LAYOUT_DIRECTION_RTL + +/** + * Starts the app afresh, so a language that reads the other way round lays every screen out in its + * direction (attachBaseContext applies the saved language on the way up). + */ +private fun restartApp(context: android.content.Context) { + val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) + ?.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK) + ?: return + context.startActivity(intent) + context.findActivity()?.finishAffinity() + Runtime.getRuntime().exit(0) +} diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBead.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBead.kt index 8b5e188..c2f063b 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBead.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBead.kt @@ -49,7 +49,7 @@ fun GlassBead( selected -> 1.1f else -> 1f }, - spring(dampingRatio = 0.45f, stiffness = 520f), + spring(dampingRatio = MotionSettings.damping(0.45f), stiffness = MotionSettings.stiffness(520f)), label = "beadScale" ) Box( diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBottomSheet.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBottomSheet.kt index 202431d..fb732a7 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBottomSheet.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassBottomSheet.kt @@ -173,7 +173,7 @@ fun GlassBottomSheet( hidden.snapTo(startFraction) currentOnDismiss() } else { - dragOffset.animateTo(0f, spring(dampingRatio = 0.7f, stiffness = 500f), initialVelocity = velocity) + dragOffset.animateTo(0f, spring(dampingRatio = MotionSettings.damping(0.7f), stiffness = MotionSettings.stiffness(500f)), initialVelocity = velocity) } } ) diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassColorPicker.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassColorPicker.kt index c78477b..1d3c406 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassColorPicker.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassColorPicker.kt @@ -166,7 +166,7 @@ fun GlassColorPicker( themeColors: List = OfficeThemeColors, showAlpha: Boolean = false, onClear: (() -> Unit)? = null, - clearLabel: String = "No colour", + clearLabel: String? = null, standardColors: List = OfficeStandardColors, /** * Optional: the 5 tint/shade variants under each theme swatch. Defaults to [officeVariants]; @@ -238,11 +238,11 @@ fun GlassColorPicker( horizontalArrangement = Arrangement.spacedBy(10.dp) ) { Icon(Icons.Rounded.Block, contentDescription = null, tint = secondary, modifier = Modifier.size(22.dp)) - BasicText(clearLabel, style = TextStyle(textColor, 15.sp, FontWeight.Medium)) + BasicText(clearLabel ?: androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.color_none), style = TextStyle(textColor, 15.sp, FontWeight.Medium)) } } - SectionLabel("Theme colours", secondary) + SectionLabel(androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.color_theme), secondary) // Theme grid: base row, gap, then 5 variant rows. val columns = themeColors.map { listOf(it) + themeVariants(it) } fun pickTheme(col: Int, row: Int) { @@ -262,11 +262,11 @@ fun GlassColorPicker( } } - SectionLabel("Standard colours", secondary) + SectionLabel(androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.color_standard), secondary) SwatchRow(standardColors, color, isDark) { pick(it) } if (recent.isNotEmpty()) { - SectionLabel("Recent colours", secondary) + SectionLabel(androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.color_recent), secondary) SwatchRow( recent.toList() + List((themeColors.size - recent.size).coerceAtLeast(0)) { Color.Unspecified }, color, isDark @@ -293,7 +293,7 @@ fun GlassColorPicker( ) ) ) - BasicText("More colours…", Modifier.weight(1f), style = TextStyle(textColor, 15.sp, FontWeight.Medium)) + BasicText(androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.color_more), Modifier.weight(1f), style = TextStyle(textColor, 15.sp, FontWeight.Medium)) Icon( if (customOpen) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore, contentDescription = null, diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassDialog.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassDialog.kt index ced45ab..0c4c852 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassDialog.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassDialog.kt @@ -122,7 +122,7 @@ fun GlassDialog( LaunchedEffect(visible) { if (visible) { composed = true - progress.animateTo(1f, spring(dampingRatio = 0.72f, stiffness = 420f, visibilityThreshold = 0.001f)) + progress.animateTo(1f, spring(dampingRatio = MotionSettings.damping(0.72f), stiffness = MotionSettings.stiffness(420f), visibilityThreshold = 0.001f)) } else { progress.animateTo(0f, spring(dampingRatio = 1f, stiffness = 900f, visibilityThreshold = 0.001f)) composed = false diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassMotion.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassMotion.kt index de7ac02..fe43458 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassMotion.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassMotion.kt @@ -19,14 +19,17 @@ import androidx.compose.animation.core.spring * the press reads instantly), lifts on [pressOut] (springs once past rest, then settles), and glass * controls add a [wobble] (X and Y squash out of phase, one visible swing). A tap shorter than * [MinPressMillis] still shows the full press before it springs back. + * + * Every bouncing spring here goes through [MotionSettings] (Settings -> Liquid Glass -> Motion), so + * the user's bounce and speed apply everywhere; [settle], [fade] and [fold] never bounce. */ object GlassMotion { /** Underdamped: overshoots ~8% and settles. The capsule/bar "springs open" feel. */ - fun morph(): SpringSpec = spring(dampingRatio = 0.58f, stiffness = 420f) + fun morph(): SpringSpec = spring(MotionSettings.damping(0.58f), MotionSettings.stiffness(420f)) /** Snappier and bouncier — for small elements that should pop, like an icon landing. */ - fun pop(): SpringSpec = spring(dampingRatio = 0.45f, stiffness = 500f) + fun pop(): SpringSpec = spring(MotionSettings.damping(0.45f), MotionSettings.stiffness(500f)) /** Critically damped. For alpha, and for layout properties where overshoot costs a re-measure. */ fun settle(): SpringSpec = spring(dampingRatio = 1f, stiffness = Spring.StiffnessMedium) @@ -35,28 +38,28 @@ object GlassMotion { fun fade(): SpringSpec = settle() /** Press-down and bounce-back for value-driven presses (prefer [rememberJellyPress]). */ - fun press(): SpringSpec = spring(dampingRatio = 0.5f, stiffness = 700f) + fun press(): SpringSpec = spring(MotionSettings.damping(0.5f), MotionSettings.stiffness(700f)) /** Finger down: quick and nearly critically damped, so the press lands in ~80 ms. */ - fun pressIn(): SpringSpec = spring(dampingRatio = 0.72f, stiffness = 900f) + fun pressIn(): SpringSpec = spring(MotionSettings.damping(0.72f), MotionSettings.stiffness(900f)) /** Finger up: springs back past rest once (~2 % of the dip) and settles. */ - fun pressOut(): SpringSpec = spring(dampingRatio = 0.46f, stiffness = 420f) + fun pressOut(): SpringSpec = spring(MotionSettings.damping(0.46f), MotionSettings.stiffness(420f)) /** Release jiggle for glass: one visible out-of-phase squash, the second swing is ~20 %. */ - fun wobble(): SpringSpec = spring(dampingRatio = 0.42f, stiffness = 420f) + fun wobble(): SpringSpec = spring(MotionSettings.damping(0.42f), MotionSettings.stiffness(420f)) /** Menus and popovers unfolding from their anchor: ~5 % overshoot, settled in ~350 ms. */ - fun unfold(): SpringSpec = spring(dampingRatio = 0.68f, stiffness = 420f) + fun unfold(): SpringSpec = spring(MotionSettings.damping(0.68f), MotionSettings.stiffness(420f)) /** The trailing axis of an unfold: a touch softer, so the panel lands like a drop of gel. */ - fun unfoldLag(): SpringSpec = spring(dampingRatio = 0.6f, stiffness = 340f) + fun unfoldLag(): SpringSpec = spring(MotionSettings.damping(0.6f), MotionSettings.stiffness(340f)) /** Folding away: fast and critically damped. Dismissals never bounce. */ - fun fold(): SpringSpec = spring(dampingRatio = 1f, stiffness = 900f) + fun fold(): SpringSpec = spring(dampingRatio = 1f, stiffness = MotionSettings.stiffness(900f)) - /** Uniform pressed scale for tiles, rows and morphing buttons. */ - const val PressedScale = 0.94f + /** Uniform pressed scale for tiles, rows and morphing buttons (0.94 at the designed press depth). */ + val PressedScale: Float get() = 1f - 0.06f * MotionSettings.style.press.coerceIn(0f, 2.5f) /** A tap shorter than this still shows the whole press before releasing. */ const val MinPressMillis = 90L diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassSegmentedControl.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassSegmentedControl.kt index 76f2c3e..c25cb8d 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassSegmentedControl.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/GlassSegmentedControl.kt @@ -102,7 +102,7 @@ fun GlassSegmentedControl( hoverIndex = selectedIndex position.animateTo( selectedIndex.toFloat().fastCoerceIn(0f, (count - 1).toFloat()), - spring(dampingRatio = 0.72f, stiffness = 380f) + spring(dampingRatio = MotionSettings.damping(0.72f), stiffness = MotionSettings.stiffness(380f)) ) { stretch.onPositionSample(value * segPx) } } @@ -160,7 +160,7 @@ fun GlassSegmentedControl( } } } - scope.launch { press.animateTo(0f, spring(0.6f, 400f)) } + scope.launch { press.animateTo(0f, spring(MotionSettings.damping(0.6f), MotionSettings.stiffness(400f))) } val target = if (dragged) hoverIndex else startIndex if (!cancelled && target != currentSelected) { haptics.performHapticFeedback(HapticFeedbackType.SegmentTick) @@ -169,7 +169,7 @@ fun GlassSegmentedControl( // Snap the thumb back if a drag ended on the same segment. hoverIndex = currentSelected scope.launch { - position.animateTo(currentSelected.toFloat(), spring(0.72f, 380f)) { + position.animateTo(currentSelected.toFloat(), spring(MotionSettings.damping(0.72f), MotionSettings.stiffness(380f))) { stretch.onPositionSample(value * segPx) } } diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/MotionSettings.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/MotionSettings.kt new file mode 100644 index 0000000..9b16e80 --- /dev/null +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/MotionSettings.kt @@ -0,0 +1,85 @@ +package com.chethan616.clearpdf.ui.components + +import android.content.Context +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +/** + * How springy the Liquid Glass motion is (Settings -> Liquid Glass -> Motion). Every value is a + * multiplier on the physics the app was designed with, so [Normal] reproduces it exactly: + * + * - [bounce]: 0.5 is the design; higher lowers the springs' damping (more overshoot), lower raises + * it. Springs that must never bounce (fades, layout, dismissals) ignore it. + * - [speed]: spring stiffness. + * - [press]: how far a glass control swells (and a flat one dips) under the finger. + * - [wobble]: the out-of-phase jiggle on release. + * - [stretch]: how far a control leans and stretches toward a dragging finger. + * - [bloom]: how much a long-pressed control grows. + */ +@Immutable +data class MotionStyle( + val bounce: Float = 0.5f, + val speed: Float = 1f, + val press: Float = 1f, + val wobble: Float = 1f, + val stretch: Float = 1f, + val bloom: Float = 1f +) { + companion object { + val Normal = MotionStyle() + val Performance = MotionStyle(bounce = 0.2f, speed = 1.45f, press = 0.6f, wobble = 0f, stretch = 0.5f, bloom = 0.5f) + val Bouncy = MotionStyle(bounce = 0.75f, speed = 0.92f, press = 1.3f, wobble = 1.6f, stretch = 1.2f, bloom = 1.2f) + val SuperSpringy = MotionStyle(bounce = 0.95f, speed = 0.82f, press = 1.6f, wobble = 2.2f, stretch = 1.5f, bloom = 1.5f) + val Presets = listOf(Normal, Performance, Bouncy, SuperSpringy) + } +} + +object MotionSettings { + private const val PREFS = "ui_prefs" + + var style: MotionStyle by mutableStateOf(MotionStyle.Normal) + private set + + private var loaded = false + + fun load(context: Context) { + if (loaded) return + loaded = true + val p = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE) + val d = MotionStyle.Normal + style = MotionStyle( + bounce = p.getFloat("motion_bounce", d.bounce), + speed = p.getFloat("motion_speed", d.speed), + press = p.getFloat("motion_press", d.press), + wobble = p.getFloat("motion_wobble", d.wobble), + stretch = p.getFloat("motion_stretch", d.stretch), + bloom = p.getFloat("motion_bloom", d.bloom) + ) + } + + fun update(context: Context, s: MotionStyle) { + style = s + context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit() + .putFloat("motion_bounce", s.bounce) + .putFloat("motion_speed", s.speed) + .putFloat("motion_press", s.press) + .putFloat("motion_wobble", s.wobble) + .putFloat("motion_stretch", s.stretch) + .putFloat("motion_bloom", s.bloom) + .apply() + } + + /** + * A designed damping ratio at the user's bounce: unchanged at 0.5, up to 1.6x (calmer) at 0, + * down to 0.4x (much springier) at 1. Never critically damped from an underdamped design, and + * never below 0.12 so nothing rings for long. + */ + fun damping(designed: Float): Float { + val f = 1f + (0.5f - style.bounce) * 1.2f + return (designed * f).coerceIn(0.12f, 0.98f) + } + + fun stiffness(designed: Float): Float = designed * style.speed.coerceIn(0.4f, 2.5f) +} diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/ShareMorphButton.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/ShareMorphButton.kt index 3a661e3..ed35ffa 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/ShareMorphButton.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/ShareMorphButton.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.chethan616.clearpdf.R import com.kyant.backdrop.backdrops.LayerBackdrop @@ -86,8 +87,23 @@ fun ShareMorphButton( onShareModeChanged: (Boolean) -> Unit = {}, idleIcon: ImageVector = Icons.Rounded.UploadFile, idleContentDesc: String? = null, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + /** The idle circle's diameter; the capsule grows to twice this. */ + size: Dp = 52.dp, + /** + * Settings -> Personalization -> Swap share and open: tap shares and the hold-and-swipe opens + * another file. Read from settings when not given. + */ + inverted: Boolean? = null ) { + val context = androidx.compose.ui.platform.LocalContext.current + val swap = inverted ?: remember { com.chethan616.clearpdf.data.repository.AppSettingsManager.getInvertShareMorph(context) } + val tapAction = if (swap) onShare else onOpen + val swipeAction = if (swap) onOpen else onShare + val restIcon = if (swap) Icons.Rounded.IosShare else idleIcon + val restDesc = if (swap) stringResource(R.string.viewer_share_document) else idleContentDesc + val armedIcon = if (swap) idleIcon else Icons.Rounded.IosShare + val armedDesc = if (swap) idleContentDesc else stringResource(R.string.viewer_share_document) var shareMode by remember { mutableStateOf(false) } var pressed by remember { mutableStateOf(false) } var dragUp by remember { mutableFloatStateOf(0f) } @@ -101,7 +117,7 @@ fun ShareMorphButton( val height by morph.animateDp( transitionSpec = { GlassMotion.morph() }, label = "shareMorphHeight" - ) { if (it) 104.dp else 52.dp } + ) { if (it) size * 2 else size } // 0 while idle, 1 while morphed. Both icon layers read this, so they cross-fade against the // same curve the height is travelling on. The fade itself stays critically damped — a bouncing // alpha reads as a flicker — while `pop` carries the bounce on scale instead. @@ -178,7 +194,7 @@ fun ShareMorphButton( Box( modifier - .width(52.dp) + .width(size) .height(height) .graphicsLayer { scaleX = jelly.scale; scaleY = jelly.scale } // The viewer's own chrome material, so this sits beside the back and search circles as one @@ -192,7 +208,7 @@ fun ShareMorphButton( tryAwaitRelease() pressed = false }, - onTap = { if (!shareMode) onOpen() } + onTap = { if (!shareMode) tapAction() } ) } .pointerInput(Unit) { @@ -203,7 +219,7 @@ fun ShareMorphButton( }, onDrag = { change, amount -> change.consume(); dragUp += amount.y }, onDragEnd = { - if (dragUp < -thresholdPx) onShare() + if (dragUp < -thresholdPx) swipeAction() shareMode = false; onShareModeChanged(false); dragUp = 0f }, onDragCancel = { shareMode = false; onShareModeChanged(false); dragUp = 0f } @@ -217,7 +233,7 @@ fun ShareMorphButton( Modifier.fillMaxSize().graphicsLayer { alpha = 1f - morphed }, contentAlignment = Alignment.Center ) { - Icon(idleIcon, idleContentDesc, Modifier.size(20.dp), fg) + Icon(restIcon, restDesc, Modifier.size(20.dp), fg) } } if (morphed > 0.001f) { @@ -226,12 +242,12 @@ fun ShareMorphButton( Modifier .fillMaxSize() .graphicsLayer { alpha = morphed } - .padding(vertical = 9.dp), + .padding(vertical = size * 0.17f), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( - Icons.Rounded.IosShare, - stringResource(R.string.viewer_share_document), + armedIcon, + armedDesc, Modifier .size(20.dp) // Overshoots to ~1.08 before settling — the icon lands with the capsule. diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/components/ToolTile.kt b/app/src/main/java/com/chethan616/clearpdf/ui/components/ToolTile.kt index 4a9bb87..04a8c60 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/components/ToolTile.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/components/ToolTile.kt @@ -58,7 +58,7 @@ private fun rememberPressScale(interaction: MutableInteractionSource): State= targetW.coerceAtLeast(1)) sample *= 2 - val bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, BitmapFactory.Options().apply { inSampleSize = sample }) ?: return null + val bmp = decodeScaled(bytes, targetW.coerceAtLeast(1)) + if (bmp == null) { + android.util.Log.w("DocxImages", "Couldn't decode picture $key (${bytes.size} bytes)") + return null + } // A scan or a chart on white turns over with dark paper; a photograph never does. val paper = runCatching { NightPaper.looksLikePaper(bmp) }.getOrDefault(false) cache.put(key, bmp to paper) return DecodedPicture(bmp.asImageBitmap(), paper) } + + /** + * Big camera photos (a 3800 px progressive JPEG is common in Word files) decode through + * ImageDecoder at the size they're shown, in software so the night probe can read pixels; + * older Android, or a format ImageDecoder refuses, falls back to a sampled BitmapFactory. + */ + private fun decodeScaled(bytes: ByteArray, targetW: Int): Bitmap? { + if (android.os.Build.VERSION.SDK_INT >= 28) { + runCatching { + val src = android.graphics.ImageDecoder.createSource(java.nio.ByteBuffer.wrap(bytes)) + return android.graphics.ImageDecoder.decodeBitmap(src) { decoder, info, _ -> + decoder.allocator = android.graphics.ImageDecoder.ALLOCATOR_SOFTWARE + val w = info.size.width + if (w > targetW) { + val h = (info.size.height.toLong() * targetW / w).toInt().coerceAtLeast(1) + decoder.setTargetSize(targetW, h) + } + } + }.onFailure { android.util.Log.w("DocxImages", "ImageDecoder failed: $it") } + } + val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bounds) + if (bounds.outWidth <= 0) return null + var sample = 1 + while (bounds.outWidth / (sample * 2) >= targetW) sample *= 2 + return BitmapFactory.decodeByteArray(bytes, 0, bytes.size, BitmapFactory.Options().apply { inSampleSize = sample }) + } } @Composable @@ -1522,7 +1552,11 @@ private fun DocPicture(info: ObjInfo, pkg: DocxPackage, look: DocxLook, maxWidth val bucket = (px / 128 + 1) * 128 val pic by produceState(null, bytes, bucket) { val b = bytes ?: return@produceState - value = withContext(Dispatchers.IO) { runCatching { DocImages.decode("$rid@${System.identityHashCode(pkg)}@$bucket", b, bucket) }.getOrNull() } + value = withContext(Dispatchers.IO) { + runCatching { DocImages.decode("$rid@${System.identityHashCode(pkg)}@$bucket", b, bucket) } + .onFailure { android.util.Log.w("DocxImages", "Picture $rid failed", it) } + .getOrNull() + } } val ratio = if (info.widthPt > 0f && info.heightPt > 0f) info.heightPt / info.widthPt else pic?.image?.let { it.height.toFloat() / it.width.coerceAtLeast(1) } ?: 0.6f diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/MergePdfScreen.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/MergePdfScreen.kt index b325d1e..be6c93d 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/MergePdfScreen.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/MergePdfScreen.kt @@ -178,14 +178,14 @@ fun MergePdfScreen( Icon(Icons.Rounded.PictureAsPdf, null, Modifier.size(18.dp), accent.copy(0.7f)) BasicText(file, style = TextStyle(sub, 14.sp), modifier = Modifier.weight(1f)) Icon( - Icons.Rounded.ArrowUpward, "Move up", + Icons.Rounded.ArrowUpward, androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.move_up), Modifier .size(18.dp) .clickable(enabled = index > 0) { viewModel.moveFile(index, index - 1) }, if (index > 0) accent else sub.copy(0.45f) ) Icon( - Icons.Rounded.ArrowDownward, "Move down", + Icons.Rounded.ArrowDownward, androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.move_down), Modifier .size(18.dp) .clickable(enabled = index < state.selectedFiles.lastIndex) { viewModel.moveFile(index, index + 1) }, diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfViewerBottomToolbar.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfViewerBottomToolbar.kt index d49bb71..828d8a7 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfViewerBottomToolbar.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/PdfViewerBottomToolbar.kt @@ -750,31 +750,31 @@ internal fun PdfViewerBottomToolbar( horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically ) { - Box(Modifier.size(52.dp), contentAlignment = Alignment.Center) { + Box(Modifier.size(ReaderControlSize), contentAlignment = Alignment.Center) { LiquidIconButton( onClick = { editorOpen = !editorOpen if (!editorOpen) onSetActiveTool(PdfEditTool.None) }, backdrop = backdrop, - modifier = Modifier.size(52.dp), + modifier = Modifier.size(ReaderControlSize), tint = if (editorOpen) accent else Color.Unspecified, surfaceColor = if (!editorOpen && (readerTourStep == 0 || readerTourStep == 2)) LiquidGlassColors.Blue.copy(alpha = 0.22f) else Color.Unspecified ) { - Icon(Icons.Rounded.Edit, stringResource(R.string.viewer_editor_tools), Modifier.size(21.dp), if (editorOpen) Color.White else fg) + Icon(Icons.Rounded.Edit, stringResource(R.string.viewer_editor_tools), Modifier.size(20.dp), if (editorOpen) Color.White else fg) } } - Box(Modifier.size(52.dp), contentAlignment = Alignment.Center) { + Box(Modifier.size(ReaderControlSize), contentAlignment = Alignment.Center) { LiquidIconButton( onClick = onToggleAutoScroll, backdrop = backdrop, - modifier = Modifier.size(52.dp), + modifier = Modifier.size(ReaderControlSize), tint = if (autoScrollActive) LiquidGlassColors.Green else Color.Unspecified ) { Icon( if (autoScrollActive) Icons.Rounded.Pause else Icons.Rounded.PlayArrow, stringResource(if (autoScrollActive) R.string.viewer_auto_scroll_pause else R.string.viewer_auto_scroll_start), - Modifier.size(21.dp), + Modifier.size(20.dp), if (autoScrollActive) Color.White else fg ) } @@ -799,7 +799,7 @@ internal fun PdfViewerBottomToolbar( } Spacer(Modifier.weight(1f)) // Reserve the final slot for ShareMorph, which is layered over this row. - Spacer(Modifier.size(52.dp)) + Spacer(Modifier.size(ReaderControlSize)) } } } @@ -812,6 +812,7 @@ internal fun PdfViewerBottomToolbar( modifier = Modifier.align(Alignment.BottomEnd).zIndex(3f) ) { ShareMorphButton( + size = ReaderControlSize, backdrop = backdrop, glass = if (readerTourStep == 1) LiquidGlassColors.Blue.copy(alpha = 0.2f) else pillGlass, fg = fg, @@ -856,3 +857,6 @@ internal fun PdfViewerBottomToolbar( } } + +/** The reader's bottom circles (editor, auto-scroll, share/open): the Word editor's 44 dp size. */ +private val ReaderControlSize = 44.dp diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/SettingsScreen.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/SettingsScreen.kt index 573a4a7..6f22e9a 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/SettingsScreen.kt @@ -50,6 +50,7 @@ import androidx.compose.material.icons.rounded.PhoneAndroid import androidx.compose.material.icons.rounded.PhotoLibrary import androidx.compose.material.icons.rounded.Refresh import androidx.compose.material.icons.rounded.Star +import androidx.compose.material.icons.rounded.SwapVert import androidx.compose.material.icons.rounded.Tune import androidx.compose.material.icons.rounded.Vibration import androidx.compose.material.icons.rounded.Wallpaper @@ -485,6 +486,17 @@ fun SettingsScreen( backdrop = backdrop ) SettingsDivider(isLight) + // The viewer's open/share morph: swap what the tap and the hold-and-swipe do. + var invertShareMorph by remember { mutableStateOf(AppSettingsManager.getInvertShareMorph(context)) } + SettingsToggleRow( + icon = Icons.Rounded.SwapVert, + title = stringResource(R.string.settings_invert_share_morph), + desc = stringResource(R.string.settings_invert_share_morph_desc), + checked = invertShareMorph, + onCheckedChange = { invertShareMorph = it; AppSettingsManager.setInvertShareMorph(context, it) }, + backdrop = backdrop + ) + SettingsDivider(isLight) SettingsToggleRow( icon = Icons.Rounded.Wallpaper, title = stringResource(R.string.settings_background), @@ -733,6 +745,86 @@ private fun GlassTuningPage( modifier = Modifier.fillMaxWidth() ) } + + MotionTuningSection(backdrop, uiSensor, ink, sub) +} + +/** + * Settings -> Liquid Glass -> Motion: the spring physics every glass control shares (press swell, + * release jiggle, drag stretch, long-press bloom, bounce and speed), with profiles and a button to + * feel the result. Values are multipliers on the designed motion; Normal is the design. + */ +@Composable +private fun MotionTuningSection( + backdrop: Backdrop, + uiSensor: com.chethan616.clearpdf.ui.utils.UISensor, + ink: Color, + sub: Color +) { + val context = LocalContext.current + val motion = com.chethan616.clearpdf.ui.components.MotionSettings.style + fun set(m: com.chethan616.clearpdf.ui.components.MotionStyle) = + com.chethan616.clearpdf.ui.components.MotionSettings.update(context, m) + val presets = listOf( + com.chethan616.clearpdf.ui.components.MotionStyle.Normal to stringResource(R.string.motion_preset_normal), + com.chethan616.clearpdf.ui.components.MotionStyle.Performance to stringResource(R.string.motion_preset_performance), + com.chethan616.clearpdf.ui.components.MotionStyle.Bouncy to stringResource(R.string.motion_preset_bouncy), + com.chethan616.clearpdf.ui.components.MotionStyle.SuperSpringy to stringResource(R.string.motion_preset_super) + ) + SettingsSection(backdrop, uiSensor, Modifier, gap = 10.dp) { + BasicText(stringResource(R.string.glass_motion), style = TextStyle(ink, 17.sp, FontWeight.SemiBold)) + BasicText(stringResource(R.string.glass_motion_desc), style = TextStyle(sub, 13.sp)) + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + presets.forEach { (m, label) -> + GlassChoiceChip(label = label, selected = motion == m, onClick = { set(m) }, backdrop = backdrop, accent = LiquidGlassColors.Purple) + } + if (presets.none { it.first == motion }) { + GlassChoiceChip(label = stringResource(R.string.glass_preset_custom), selected = true, onClick = {}, backdrop = backdrop, accent = LiquidGlassColors.Purple) + } + } + // Press it to feel the current physics. + Box(Modifier.fillMaxWidth().padding(vertical = 6.dp), contentAlignment = Alignment.Center) { + LiquidButton(onClick = {}, backdrop = backdrop, tint = LiquidGlassColors.Purple) { + BasicText( + stringResource(R.string.motion_try), + style = TextStyle(Color.White, 15.sp, FontWeight.SemiBold), + modifier = Modifier.padding(horizontal = 18.dp) + ) + } + } + } + SettingsSection(backdrop, uiSensor, Modifier, gap = 6.dp) { + @Composable + fun knob(label: String, value: Float, range: ClosedFloatingPointRange, onChange: (Float) -> Unit) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + BasicText(label, style = TextStyle(ink, 14.sp, FontWeight.Medium)) + BasicText("%.2f".format(value), style = TextStyle(sub, 13.sp)) + } + LiquidSlider( + value = { value }, + onValueChange = onChange, + valueRange = range, + visibilityThreshold = 0.005f, + backdrop = backdrop, + modifier = Modifier.fillMaxWidth() + ) + } + knob(stringResource(R.string.motion_bounce), motion.bounce, 0f..1f) { set(motion.copy(bounce = it)) } + knob(stringResource(R.string.motion_speed), motion.speed, 0.5f..2f) { set(motion.copy(speed = it)) } + knob(stringResource(R.string.motion_press), motion.press, 0f..2.5f) { set(motion.copy(press = it)) } + knob(stringResource(R.string.motion_wobble), motion.wobble, 0f..3f) { set(motion.copy(wobble = it)) } + knob(stringResource(R.string.motion_stretch), motion.stretch, 0f..2.5f) { set(motion.copy(stretch = it)) } + knob(stringResource(R.string.motion_bloom), motion.bloom, 0f..2.5f) { set(motion.copy(bloom = it)) } + Spacer(Modifier.height(8.dp)) + ToolPrimaryButton( + text = stringResource(R.string.motion_reset), + onClick = { set(com.chethan616.clearpdf.ui.components.MotionStyle.Normal) }, + backdrop = backdrop, + accent = LiquidGlassColors.Purple, + icon = Icons.Rounded.Refresh, + modifier = Modifier.fillMaxWidth() + ) + } } /** One row of the Settings index: tinted icon tile, title, current value, chevron — iOS Settings. */ diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/SpreadsheetViewerScreen.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/SpreadsheetViewerScreen.kt index c79a6fe..fb6f19c 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/SpreadsheetViewerScreen.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/SpreadsheetViewerScreen.kt @@ -189,18 +189,18 @@ private enum class Panel { NONE, FONT_COLOR, FILL_COLOR, ALIGN, BORDERS, NUMBER, private class DropdownState(val r: Int, val c: Int, val anchor: Rect, val items: List, val strict: Boolean) private val NumberFormats = listOf( - "General" to "General", - "Number" to "#,##0.00", - "Integer" to "0", - "Currency" to "\"$\"#,##0.00", - "Accounting" to "_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)", - "Percent" to "0%", - "Percent (2dp)" to "0.00%", - "Scientific" to "0.00E+00", - "Short date" to "dd-mm-yyyy", - "Long date" to "d mmmm yyyy", - "Time" to "h:mm:ss", - "Text" to "@" + R.string.sheet_fmt_general to "General", + R.string.sheet_fmt_number to "#,##0.00", + R.string.sheet_fmt_integer to "0", + R.string.sheet_fmt_currency to "\"$\"#,##0.00", + R.string.sheet_fmt_accounting to "_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)", + R.string.sheet_fmt_percent to "0%", + R.string.sheet_fmt_percent2 to "0.00%", + R.string.sheet_fmt_scientific to "0.00E+00", + R.string.sheet_fmt_short_date to "dd-mm-yyyy", + R.string.sheet_fmt_long_date to "d mmmm yyyy", + R.string.sheet_fmt_time to "h:mm:ss", + R.string.sheet_fmt_text to "@" ) /** @@ -1073,7 +1073,7 @@ private fun AlignPanel( Column(Modifier.fillMaxWidth().padding(horizontal = 4.dp, vertical = 4.dp), verticalArrangement = Arrangement.spacedBy(14.dp)) { PanelLabel(stringResource(R.string.sheet_horizontal), text) GlassSegmentedControl( - options = listOf("Left", "Center", "Right"), + options = listOf(stringResource(R.string.sheet_align_left), stringResource(R.string.sheet_align_center), stringResource(R.string.sheet_align_right)), selectedIndex = hOptions.indexOf(h).coerceAtLeast(0).let { if (h == "general") -1 else it }, onSelect = { onH(hOptions[it]) }, backdrop = backdrop, @@ -1082,7 +1082,7 @@ private fun AlignPanel( ) PanelLabel(stringResource(R.string.sheet_vertical), text) GlassSegmentedControl( - options = listOf("Top", "Middle", "Bottom"), + options = listOf(stringResource(R.string.sheet_align_top), stringResource(R.string.sheet_align_middle), stringResource(R.string.sheet_align_bottom)), selectedIndex = vOptions.indexOf(v).coerceAtLeast(0), onSelect = { onV(vOptions[it]) }, backdrop = backdrop, @@ -1122,15 +1122,15 @@ private fun PanelRow(icon: ImageVector?, label: String, color: Color, tint: Colo @Composable private fun BordersPanel(text: Color, accent: Color, onPick: (BorderPreset) -> Unit) { val items = listOf( - Triple(BorderPreset.ALL, Icons.Rounded.BorderAll, "All borders"), - Triple(BorderPreset.OUTER, Icons.Rounded.BorderOuter, "Outside borders"), - Triple(BorderPreset.THICK_OUTER, Icons.Rounded.BorderStyle, "Thick outside borders"), - Triple(BorderPreset.INNER, Icons.Rounded.BorderInner, "Inside borders"), - Triple(BorderPreset.TOP, Icons.Rounded.BorderTop, "Top border"), - Triple(BorderPreset.BOTTOM, Icons.Rounded.BorderBottom, "Bottom border"), - Triple(BorderPreset.LEFT, Icons.Rounded.BorderLeft, "Left border"), - Triple(BorderPreset.RIGHT, Icons.Rounded.BorderRight, "Right border"), - Triple(BorderPreset.NONE, Icons.Rounded.BorderClear, "No borders") + Triple(BorderPreset.ALL, Icons.Rounded.BorderAll, stringResource(R.string.sheet_border_all)), + Triple(BorderPreset.OUTER, Icons.Rounded.BorderOuter, stringResource(R.string.sheet_border_outer)), + Triple(BorderPreset.THICK_OUTER, Icons.Rounded.BorderStyle, stringResource(R.string.sheet_border_thick)), + Triple(BorderPreset.INNER, Icons.Rounded.BorderInner, stringResource(R.string.sheet_border_inner)), + Triple(BorderPreset.TOP, Icons.Rounded.BorderTop, stringResource(R.string.sheet_border_top)), + Triple(BorderPreset.BOTTOM, Icons.Rounded.BorderBottom, stringResource(R.string.sheet_border_bottom)), + Triple(BorderPreset.LEFT, Icons.Rounded.BorderLeft, stringResource(R.string.sheet_border_left)), + Triple(BorderPreset.RIGHT, Icons.Rounded.BorderRight, stringResource(R.string.sheet_border_right)), + Triple(BorderPreset.NONE, Icons.Rounded.BorderClear, stringResource(R.string.sheet_border_none)) ) Column(Modifier.fillMaxWidth().heightIn(max = 460.dp).verticalScroll(rememberScrollState())) { for ((p, icon, label) in items) PanelRow(icon, label, text, accent) { onPick(p) } @@ -1140,7 +1140,8 @@ private fun BordersPanel(text: Color, accent: Color, onPick: (BorderPreset) -> U @Composable private fun NumberPanel(text: Color, sub: Color, accent: Color, current: String, onPick: (String) -> Unit) { Column(Modifier.fillMaxWidth().heightIn(max = 460.dp).verticalScroll(rememberScrollState())) { - for ((label, code) in NumberFormats) { + for ((labelRes, code) in NumberFormats) { + val label = stringResource(labelRes) val sample = com.chethan616.clearpdf.utils.ExcelCellFormat.apply(if (code.contains("d") && code.contains("y")) "45566" else "1234.5", code) Row( Modifier.fillMaxWidth().heightIn(min = 48.dp) diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorCropStage.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorCropStage.kt index 5fc813b..4972d5f 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorCropStage.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorCropStage.kt @@ -47,16 +47,16 @@ val CropAspects = listOf( CropAspect("9:16", 9f / 16f), ) -data class CropMask(val label: String, val property: CropOutlineProperty) +data class CropMask(val label: String, val property: CropOutlineProperty, @androidx.annotation.StringRes val labelRes: Int = 0) val CropMasks: List = listOf( - CropMask("Rectangle", CropOutlineProperty(OutlineType.Rect, RectCropShape(0, "Rect"))), - CropMask("Rounded", CropOutlineProperty(OutlineType.RoundedRect, RoundedCornerCropShape(1, "Rounded"))), - CropMask("Circle", CropOutlineProperty(OutlineType.Oval, OvalCropShape(2, "Oval"))), - CropMask("Cut corner", CropOutlineProperty(OutlineType.CutCorner, CutCornerCropShape(3, "CutCorner"))), - CropMask("Hexagon", CropOutlineProperty(OutlineType.Polygon, PolygonCropShape(4, "Polygon"))), - CropMask("Heart", CropOutlineProperty(OutlineType.Custom, CustomPathOutline(5, "Heart", Paths.Favorite))), - CropMask("Star", CropOutlineProperty(OutlineType.Custom, CustomPathOutline(6, "Star", Paths.Star))), + CropMask("Rectangle", CropOutlineProperty(OutlineType.Rect, RectCropShape(0, "Rect")), com.chethan616.clearpdf.R.string.ie_mask_rect), + CropMask("Rounded", CropOutlineProperty(OutlineType.RoundedRect, RoundedCornerCropShape(1, "Rounded")), com.chethan616.clearpdf.R.string.ie_mask_rounded), + CropMask("Circle", CropOutlineProperty(OutlineType.Oval, OvalCropShape(2, "Oval")), com.chethan616.clearpdf.R.string.ie_mask_circle), + CropMask("Cut corner", CropOutlineProperty(OutlineType.CutCorner, CutCornerCropShape(3, "CutCorner")), com.chethan616.clearpdf.R.string.ie_mask_cut), + CropMask("Hexagon", CropOutlineProperty(OutlineType.Polygon, PolygonCropShape(4, "Polygon")), com.chethan616.clearpdf.R.string.ie_mask_hexagon), + CropMask("Heart", CropOutlineProperty(OutlineType.Custom, CustomPathOutline(5, "Heart", Paths.Favorite)), com.chethan616.clearpdf.R.string.ie_mask_heart), + CropMask("Star", CropOutlineProperty(OutlineType.Custom, CustomPathOutline(6, "Star", Paths.Star)), com.chethan616.clearpdf.R.string.ie_mask_star), ) /** diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorPanels.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorPanels.kt index 02619b6..7098575 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorPanels.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/imageeditor/EditorPanels.kt @@ -237,7 +237,7 @@ fun CropPanel( } LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { items(CropMasks.size) { i -> - EditorChip(CropMasks[i].label, session.outlineIndex == i, { vm.setOutline(i) }, accent = LiquidGlassColors.Purple) + EditorChip(stringResource(CropMasks[i].labelRes), session.outlineIndex == i, { vm.setOutline(i) }, accent = LiquidGlassColors.Purple) } } } @@ -321,7 +321,7 @@ fun FiltersPanel( item { FilterThumb(stringResource(R.string.ie_filter_none), source, selected == null) { vm.selectFilter(null) } } items(FilterPreset.entries.size) { i -> val p = FilterPreset.entries[i] - FilterThumb(p.label, thumbs[p], selected == p) { vm.selectFilter(p) } + FilterThumb(stringResource(p.labelRes), thumbs[p], selected == p) { vm.selectFilter(p) } } } } diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/utils/DampedDragAnimation.kt b/app/src/main/java/com/chethan616/clearpdf/ui/utils/DampedDragAnimation.kt index 86c2dea..c2b0af8 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/utils/DampedDragAnimation.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/utils/DampedDragAnimation.kt @@ -30,14 +30,15 @@ class DampedDragAnimation( private val valueAnimationSpec = spring(1f, 1000f, visibilityThreshold) - private val velocityAnimationSpec = - spring(0.5f, 300f, visibilityThreshold * 10f) + // The bouncy parts (thumb squash and velocity) follow Settings -> Liquid Glass -> Motion. + private val velocityAnimationSpec get() = + spring(com.chethan616.clearpdf.ui.components.MotionSettings.damping(0.5f), com.chethan616.clearpdf.ui.components.MotionSettings.stiffness(300f), visibilityThreshold * 10f) private val pressProgressAnimationSpec = spring(1f, 1000f, 0.001f) - private val scaleXAnimationSpec = - spring(0.6f, 250f, 0.001f) - private val scaleYAnimationSpec = - spring(0.7f, 250f, 0.001f) + private val scaleXAnimationSpec get() = + spring(com.chethan616.clearpdf.ui.components.MotionSettings.damping(0.6f), com.chethan616.clearpdf.ui.components.MotionSettings.stiffness(250f), 0.001f) + private val scaleYAnimationSpec get() = + spring(com.chethan616.clearpdf.ui.components.MotionSettings.damping(0.7f), com.chethan616.clearpdf.ui.components.MotionSettings.stiffness(250f), 0.001f) private val valueAnimation = Animatable(initialValue, visibilityThreshold) diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/utils/InteractiveHighlight.kt b/app/src/main/java/com/chethan616/clearpdf/ui/utils/InteractiveHighlight.kt index d366eb4..9d71ecb 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/utils/InteractiveHighlight.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/utils/InteractiveHighlight.kt @@ -1,5 +1,6 @@ package com.chethan616.clearpdf.ui.utils +import com.chethan616.clearpdf.ui.components.MotionSettings import android.graphics.RuntimeShader import android.os.Build import android.os.SystemClock @@ -50,14 +51,15 @@ class InteractiveHighlight( ) { // iOS 27 press: the swell lands fast with a small overshoot, the release springs back past rest // once, and [wobble] adds one out-of-phase squash on top (see liquidPressTransform). - private val pressInSpec = spring(0.66f, 620f, 0.001f) - private val pressOutSpec = spring(0.5f, 380f, 0.001f) - private val positionAnimationSpec = spring(0.52f, 360f, Offset.VisibilityThreshold) - private val wobbleSpec = spring(0.42f, 420f, 0.001f) + // Read at use, so Settings -> Liquid Glass -> Motion applies to the next press. + private val pressInSpec get() = spring(MotionSettings.damping(0.66f), MotionSettings.stiffness(620f), 0.001f) + private val pressOutSpec get() = spring(MotionSettings.damping(0.5f), MotionSettings.stiffness(380f), 0.001f) + private val positionAnimationSpec get() = spring(MotionSettings.damping(0.52f), MotionSettings.stiffness(360f), Offset.VisibilityThreshold) + private val wobbleSpec get() = spring(MotionSettings.damping(0.42f), MotionSettings.stiffness(420f), 0.001f) // Bloom in on a soft, slightly slower spring; let go on a livelier one that undershoots rest // a touch before settling — the "boing" that makes the release feel physical. - private val expandInSpec = spring(dampingRatio = 0.52f, stiffness = 340f, visibilityThreshold = 0.001f) - private val expandOutSpec = spring(dampingRatio = 0.46f, stiffness = 420f, visibilityThreshold = 0.001f) + private val expandInSpec get() = spring(dampingRatio = MotionSettings.damping(0.52f), stiffness = MotionSettings.stiffness(340f), visibilityThreshold = 0.001f) + private val expandOutSpec get() = spring(dampingRatio = MotionSettings.damping(0.46f), stiffness = MotionSettings.stiffness(420f), visibilityThreshold = 0.001f) private val pressProgressAnimation = Animatable(0f, 0.001f) private val positionAnimation = Animatable(Offset.Zero, Offset.VectorConverter, Offset.VisibilityThreshold) @@ -136,7 +138,7 @@ half4 main(float2 coord) { // A quick tap still shows the whole swell before it lets go. val held = SystemClock.uptimeMillis() - downAt if (held < MinPressMillis) delay(MinPressMillis - held) - val kick = WobbleKick * pressProgressAnimation.value.fastCoerceIn(0f, 1.1f) + val kick = WobbleKick * MotionSettings.style.wobble.fastCoerceIn(0f, 3f) * pressProgressAnimation.value.fastCoerceIn(0f, 1.1f) launch { wobbleAnimation.animateTo(0f, wobbleSpec, initialVelocity = kick) } launch { pressProgressAnimation.animateTo(0f, pressOutSpec) } launch { positionAnimation.animateTo(startPosition, positionAnimationSpec) } @@ -202,17 +204,20 @@ fun GraphicsLayerScope.liquidPressTransform(h: InteractiveHighlight) { val width = size.width val height = size.height + val motion = MotionSettings.style val progress = h.pressProgress - val bloom = h.expandProgress * (14f.dp.toPx() / size.maxDimension).fastCoerceAtMost(0.12f) - val scale = lerp(1f, 1f + 4f.dp.toPx() / height, progress) + bloom + val bloomAmount = motion.bloom.fastCoerceIn(0f, 2.5f) + val bloom = h.expandProgress * (14f.dp.toPx() * bloomAmount / size.maxDimension).fastCoerceAtMost(0.12f * bloomAmount) + val scale = lerp(1f, 1f + 4f.dp.toPx() * motion.press.fastCoerceIn(0f, 2.5f) / height, progress) + bloom val maxOffset = size.minDimension - val initialDerivative = 0.05f + val stretch = motion.stretch.fastCoerceIn(0f, 2.5f) + val initialDerivative = 0.05f * stretch val offset = h.offset translationX = maxOffset * tanh(initialDerivative * offset.x / maxOffset) translationY = maxOffset * tanh(initialDerivative * offset.y / maxOffset) - val maxDragScale = 4f.dp.toPx() / height + val maxDragScale = 4f.dp.toPx() * stretch / height val offsetAngle = atan2(offset.y, offset.x) val w = h.wobble val wobbleX = w * (2.5f.dp.toPx() / width).fastCoerceAtMost(0.045f) diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/HtmlToPdfViewModel.kt b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/HtmlToPdfViewModel.kt index b623b02..20760e7 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/HtmlToPdfViewModel.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/HtmlToPdfViewModel.kt @@ -55,7 +55,7 @@ class HtmlToPdfViewModel : ViewModel() { runCatching { context.contentResolver.openInputStream(uri)?.use { it.readBytes().toString(Charsets.UTF_8) } }.getOrNull() } if (content == null) { - _uiState.update { it.copy(errorMessage = "Couldn't read that file") } + _uiState.update { it.copy(errorMessage = context.getString(com.chethan616.clearpdf.R.string.html_to_pdf_read_failed)) } } else { _uiState.update { it.copy(html = content, sourceName = name, lastOutputUri = null, resultMessage = null, errorMessage = null) } } @@ -67,11 +67,11 @@ class HtmlToPdfViewModel : ViewModel() { val mode = _uiState.value.mode val rawUrl = _uiState.value.url.trim() if (mode == WebToPdfMode.URL && rawUrl.isBlank()) { - _uiState.update { it.copy(errorMessage = "Enter a web address") } + _uiState.update { it.copy(errorMessage = context.getString(com.chethan616.clearpdf.R.string.html_to_pdf_need_url)) } return } if (mode == WebToPdfMode.HTML && _uiState.value.html.isBlank()) { - _uiState.update { it.copy(errorMessage = "Enter or load some HTML") } + _uiState.update { it.copy(errorMessage = context.getString(com.chethan616.clearpdf.R.string.html_to_pdf_need_html)) } return } // Default the scheme so "example.com" works. @@ -99,7 +99,7 @@ class HtmlToPdfViewModel : ViewModel() { )) _uiState.update { it.copy(isProcessing = false, lastOutputUri = outUri, resultMessage = "PDF created · saved to $saveLabel") } } else { - _uiState.update { it.copy(isProcessing = false, errorMessage = "Couldn't create the PDF") } + _uiState.update { it.copy(isProcessing = false, errorMessage = context.getString(com.chethan616.clearpdf.R.string.html_to_pdf_create_failed)) } } } catch (t: Throwable) { _uiState.update { it.copy(isProcessing = false, errorMessage = t.message ?: "Couldn't convert HTML") } diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageEditorViewModel.kt b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageEditorViewModel.kt index 780e0e3..71680d1 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageEditorViewModel.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageEditorViewModel.kt @@ -459,9 +459,9 @@ class ImageEditorViewModel(app: Application) : AndroidViewModel(app) { result.onSuccess { mask -> flushSession() sessionEdit(Tool.More, BackgroundOp(mask), commitNow = true) - _state.update { it.copy(bgBusy = false, message = "Background removed") } + _state.update { it.copy(bgBusy = false, message = context.getString(com.chethan616.clearpdf.R.string.ie_bg_removed)) } }.onFailure { e -> - _state.update { it.copy(bgBusy = false, message = e.message ?: "Couldn't remove the background.") } + _state.update { it.copy(bgBusy = false, message = context.getString(com.chethan616.clearpdf.R.string.ie_bg_failed)) } } } } diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageToolsViewModel.kt b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageToolsViewModel.kt index a35a36a..14565d6 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageToolsViewModel.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/ImageToolsViewModel.kt @@ -103,7 +103,7 @@ class ImageToolsViewModel : ViewModel() { putExtra(Intent.EXTRA_STREAM, result.uri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - context.startActivity(Intent.createChooser(intent, "Share image").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + context.startActivity(Intent.createChooser(intent, context.getString(com.chethan616.clearpdf.R.string.share_image_chooser)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) } fun clearFeedback() = _uiState.update { it.copy(errorMessage = null, resultMessage = null) } diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfToImagesViewModel.kt b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfToImagesViewModel.kt index 2bde91b..ca10917 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfToImagesViewModel.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/PdfToImagesViewModel.kt @@ -75,11 +75,11 @@ class PdfToImagesViewModel : ViewModel() { isProcessing = false, progress = 1f, resultPages = pages, - resultMessage = "Rendered ${pages.size} image${if (pages.size == 1) "" else "s"}" + resultMessage = context.getString(com.chethan616.clearpdf.R.string.pdf_to_images_done, pages.size) ) } } catch (t: Throwable) { - _uiState.update { it.copy(isProcessing = false, errorMessage = t.message ?: "Failed to convert") } + _uiState.update { it.copy(isProcessing = false, errorMessage = context.getString(com.chethan616.clearpdf.R.string.conversion_failed)) } } } } @@ -110,7 +110,7 @@ class PdfToImagesViewModel : ViewModel() { putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - context.startActivity(Intent.createChooser(intent, "Share images").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + context.startActivity(Intent.createChooser(intent, context.getString(com.chethan616.clearpdf.R.string.share_images_chooser)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) } fun clearFeedback() = _uiState.update { it.copy(errorMessage = null, resultMessage = null) } diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/WatermarkPdfViewModel.kt b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/WatermarkPdfViewModel.kt index 948f807..eed2bea 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/WatermarkPdfViewModel.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/viewmodel/WatermarkPdfViewModel.kt @@ -74,11 +74,11 @@ class WatermarkPdfViewModel : ViewModel() { if (_uiState.value.isProcessing) return val s0 = _uiState.value if (s0.mode == WatermarkMode.TEXT && s0.text.isBlank()) { - _uiState.update { it.copy(errorMessage = "Enter watermark text") } + _uiState.update { it.copy(errorMessage = context.getString(com.chethan616.clearpdf.R.string.watermark_need_text)) } return } if (s0.mode == WatermarkMode.IMAGE && s0.imageUri == null) { - _uiState.update { it.copy(errorMessage = "Pick a watermark image") } + _uiState.update { it.copy(errorMessage = context.getString(com.chethan616.clearpdf.R.string.watermark_need_image)) } return } _uiState.update { it.copy(isProcessing = true, errorMessage = null, resultMessage = null, lastOutputUri = null) } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index d925ba9..9a7f655 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -836,4 +836,104 @@ اضغط مطولًا على ملف لفتحه أو مشاركته أو تثبيته والمزيد. اجعل الزجاج على ذوقك اضبط التمويه والانكسار والصبغة، أو اختر إعدادًا جاهزًا، في Liquid Glass. + + + ألوان المظهر + الألوان القياسية + الألوان الأخيرة + المزيد من الألوان… + بلا لون + يسار + وسط + يمين + أعلى + منتصف + أسفل + كل الحدود + الحدود الخارجية + حدود خارجية سميكة + الحدود الداخلية + الحد العلوي + الحد السفلي + الحد الأيسر + الحد الأيمن + بلا حدود + عام + رقم + عدد صحيح + عملة + محاسبي + نسبة مئوية + نسبة مئوية (منزلتان) + علمي + تاريخ قصير + تاريخ طويل + وقت + نص + نقل لأعلى + نقل لأسفل + تعذّرت قراءة هذا الملف + أدخل عنوان ويب + أدخل HTML أو حمّله + تعذّر إنشاء PDF + أدخل نص العلامة المائية + اختر صورة للعلامة المائية + تمت إزالة الخلفية + تعذّرت إزالة الخلفية. + مشاركة الصورة + مشاركة الصور + الصفحات المحوّلة: %1$d + زاهٍ + دافئ + بارد + باهت + أحادي + نوار + بني داكن + عتيق + كروم + درامي + ذهبي + أزرق مخضر وبرتقالي + بولارويد + لومو + عكس + تقليل الألوان + رسم تخطيطي + كرتوني + نقش بارز + تظليل متقاطع + نصف درجة + رسم زيتي + مستطيل + مدوّر + دائرة + زاوية مقطوعة + سداسي + قلب + نجمة + + + إعادة تشغيل ClearPDF؟ + تُقرأ هذه اللغة في الاتجاه الآخر. أعد تشغيل ClearPDF لتتبدّل كل الشاشات. + إعادة التشغيل الآن + لاحقًا + + + الحركة + طريقة ارتداد كل الأزرار والمنزلقات واللوحات الزجاجية. اختر ملفًا جاهزًا أو اضبط كل جزء ثم اضغط «جرّبها» لتشعر بها. + عادي + الأداء + مرن + مرن جدًا + جرّبها + الارتداد + السرعة + عمق الضغط + الاهتزاز عند الإفلات + التمدد عند السحب + التضخم عند الضغط المطوّل + إعادة ضبط الحركة + تبديل المشاركة والفتح + في العارض، انقر الزر الدائري لمشاركة المستند، واضغط مطولًا ثم اسحب لأعلى لفتح ملف آخر. diff --git a/app/src/main/res/values-ar/strings_office_engine.xml b/app/src/main/res/values-ar/strings_office_engine.xml new file mode 100644 index 0000000..d8ec645 --- /dev/null +++ b/app/src/main/res/values-ar/strings_office_engine.xml @@ -0,0 +1,34 @@ + + + + + محرك Office (مدعوم بـ LibreOffice) + محرك Office + مدعوم بـ LibreOffice + يفتح ملفات Word وExcel وPowerPoint وOpenDocument بتخطيط LibreOffice نفسه للحصول على أدق نتيجة. اختياري، وتظل أدوات العرض المدمجة تعمل بدونه. + غير مثبّت · تنزيل بحجم ~%1$s + مثبّت · %1$s · الإصدار %2$s + يتوفر تحديث · %1$s + جارٍ التنزيل… + جارٍ التنزيل · %1$s من %2$s + في انتظار الشبكة… + جارٍ التحقق والتثبيت… + يحتاج Google Play إلى موافقتك للتنزيل + فشل التثبيت: %1$s + تنزيل + تثبيت + تحديث + حذف + إلغاء + إعادة المحاولة + موافقة + استخدامه لملفات Office + يعود إلى أدوات العرض المدمجة إذا فشل ملف + تراخيص LibreOffice (MPL-2.0) + حذف محرك Office؟ + يحرّر %1$s. ستُفتح ملفات Office بأدوات العرض المدمجة. يمكنك تنزيله مجددًا في أي وقت. + تنزيل محرك Office + حسّن الدقة باستخدام محرك Office + تنزيل اختياري · مدعوم بـ LibreOffice + احصل عليه + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d774782..2a426c6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -861,4 +861,104 @@ Mantén pulsado un archivo para abrirlo, compartirlo, fijarlo y más. Haz tuyo el cristal Ajusta el desenfoque, la refracción y el tinte, o elige un ajuste predefinido, en Liquid Glass. + + + Colores del tema + Colores estándar + Colores recientes + Más colores… + Sin color + Izquierda + Centro + Derecha + Arriba + En medio + Abajo + Todos los bordes + Bordes exteriores + Bordes exteriores gruesos + Bordes interiores + Borde superior + Borde inferior + Borde izquierdo + Borde derecho + Sin bordes + General + Número + Entero + Moneda + Contabilidad + Porcentaje + Porcentaje (2 decimales) + Científico + Fecha corta + Fecha larga + Hora + Texto + Subir + Bajar + No se pudo leer ese archivo + Escribe una dirección web + Escribe o carga código HTML + No se pudo crear el PDF + Escribe el texto de la marca de agua + Elige una imagen para la marca de agua + Fondo eliminado + No se pudo quitar el fondo. + Compartir imagen + Compartir imágenes + Páginas convertidas: %1$d + Vívido + Cálido + Frío + Desvaído + Mono + Noir + Sepia + Vintage + Cromo + Dramático + Dorado + Verde azulado y naranja + Polaroid + Lomo + Invertir + Posterizar + Boceto + Cómic + Relieve + Tramado + Semitono + Óleo + Rectángulo + Redondeado + Círculo + Esquina cortada + Hexágono + Corazón + Estrella + + + ¿Reiniciar ClearPDF? + Este idioma se lee en la otra dirección. Reinicia ClearPDF para que todas las pantallas cambien. + Reiniciar ahora + Más tarde + + + Movimiento + Cómo rebotan todos los botones, controles deslizantes y paneles de cristal. Elige un perfil o ajusta cada parte y pulsa Pruébalo para sentirlo. + Normal + Rendimiento + Rebote + Súper elástico + Pruébalo + Rebote + Velocidad + Profundidad al pulsar + Temblor al soltar + Estiramiento al arrastrar + Crecimiento al mantener + Restablecer movimiento + Intercambiar compartir y abrir + En el visor, toca el botón redondo para compartir el documento y mantenlo pulsado y desliza hacia arriba para abrir otro archivo. diff --git a/app/src/main/res/values-es/strings_office_engine.xml b/app/src/main/res/values-es/strings_office_engine.xml new file mode 100644 index 0000000..bbbc27e --- /dev/null +++ b/app/src/main/res/values-es/strings_office_engine.xml @@ -0,0 +1,34 @@ + + + + + Motor de Office (con tecnología de LibreOffice) + Motor de Office + Con tecnología de LibreOffice + Abre archivos de Word, Excel, PowerPoint y OpenDocument con el diseño propio de LibreOffice para el resultado más fiel. Es opcional: los visores integrados siguen funcionando sin él. + No instalado · descarga de ~%1$s + Instalado · %1$s · v%2$s + Actualización disponible · %1$s + Descargando… + Descargando · %1$s de %2$s + Esperando conexión… + Verificando e instalando… + Google Play necesita tu aprobación para descargar + Error de instalación: %1$s + Descargar + Instalar + Actualizar + Eliminar + Cancelar + Reintentar + Aprobar + Usar para archivos de Office + Si un archivo falla, se usan los visores integrados + Licencias de LibreOffice (MPL-2.0) + ¿Eliminar el motor de Office? + Libera %1$s. Los archivos de Office se abrirán con los visores integrados. Puedes volver a descargarlo cuando quieras. + Descarga del motor de Office + Mejora la fidelidad con el motor de Office + Descarga opcional · con tecnología de LibreOffice + Obtener + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a052ede..839dd3a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -866,4 +866,104 @@ Tieni premuto un file per aprirlo, condividerlo, fissarlo e altro. Personalizza il vetro Regola sfocatura, rifrazione e tinta, oppure scegli un preset, in Liquid Glass. + + + Colori del tema + Colori standard + Colori recenti + Altri colori… + Nessun colore + Sinistra + Centro + Destra + In alto + Al centro + In basso + Tutti i bordi + Bordi esterni + Bordi esterni spessi + Bordi interni + Bordo superiore + Bordo inferiore + Bordo sinistro + Bordo destro + Nessun bordo + Generale + Numero + Intero + Valuta + Contabilità + Percentuale + Percentuale (2 decimali) + Scientifico + Data breve + Data estesa + Ora + Testo + Sposta su + Sposta giù + Impossibile leggere il file + Inserisci un indirizzo web + Inserisci o carica del codice HTML + Impossibile creare il PDF + Inserisci il testo della filigrana + Scegli un\'immagine per la filigrana + Sfondo rimosso + Impossibile rimuovere lo sfondo. + Condividi immagine + Condividi immagini + Pagine convertite: %1$d + Vivace + Caldo + Freddo + Sbiadito + Mono + Noir + Seppia + Vintage + Cromo + Drammatico + Dorato + Ottanio e arancio + Polaroid + Lomo + Inverti + Posterizza + Schizzo + Fumetto + Rilievo + Tratteggio + Mezzitoni + Pittura a olio + Rettangolo + Arrotondato + Cerchio + Angolo tagliato + Esagono + Cuore + Stella + + + Riavviare ClearPDF? + Questa lingua si legge nell\'altro verso. Riavvia ClearPDF perché tutte le schermate si adeguino. + Riavvia ora + Più tardi + + + Movimento + Come rimbalzano tutti i pulsanti, i cursori e i pannelli di vetro. Scegli un profilo o regola ogni parte, poi premi Provalo per sentirlo. + Normale + Prestazioni + Elastico + Super elastico + Provalo + Rimbalzo + Velocità + Profondità della pressione + Tremolio al rilascio + Allungamento al trascinamento + Espansione alla pressione prolungata + Ripristina movimento + Scambia condividi e apri + Nel visualizzatore, tocca il pulsante rotondo per condividere il documento e tienilo premuto e scorri verso l\'alto per aprire un altro file. diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 85d815b..960d2a0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -859,4 +859,104 @@ Toque e segure um arquivo para abrir, compartilhar, fixar e mais. Deixe o vidro do seu jeito Ajuste desfoque, refração e tonalidade, ou escolha uma predefinição, em Liquid Glass. + + + Cores do tema + Cores padrão + Cores recentes + Mais cores… + Sem cor + Esquerda + Centro + Direita + Topo + Meio + Base + Todas as bordas + Bordas externas + Bordas externas grossas + Bordas internas + Borda superior + Borda inferior + Borda esquerda + Borda direita + Sem bordas + Geral + Número + Inteiro + Moeda + Contábil + Porcentagem + Porcentagem (2 casas) + Científico + Data curta + Data longa + Hora + Texto + Mover para cima + Mover para baixo + Não foi possível ler esse arquivo + Digite um endereço da web + Digite ou carregue um HTML + Não foi possível criar o PDF + Digite o texto da marca d\'água + Escolha uma imagem para a marca d\'água + Fundo removido + Não foi possível remover o fundo. + Compartilhar imagem + Compartilhar imagens + Páginas convertidas: %1$d + Vívido + Quente + Frio + Desbotado + Mono + Noir + Sépia + Vintage + Cromo + Dramático + Dourado + Azul-petróleo e laranja + Polaroid + Lomo + Inverter + Posterizar + Esboço + Desenho + Relevo + Hachura + Meio-tom + Pintura a óleo + Retângulo + Arredondado + Círculo + Canto cortado + Hexágono + Coração + Estrela + + + Reiniciar o ClearPDF? + Este idioma é lido na outra direção. Reinicie o ClearPDF para que todas as telas mudem. + Reiniciar agora + Depois + + + Movimento + Como pulam todos os botões, controles deslizantes e painéis de vidro. Escolha um perfil ou ajuste cada parte e toque em Experimentar para sentir. + Normal + Desempenho + Saltitante + Super elástico + Experimentar + Pulo + Velocidade + Profundidade ao tocar + Tremida ao soltar + Esticar ao arrastar + Crescer ao segurar + Redefinir movimento + Trocar compartilhar e abrir + No visualizador, toque no botão redondo para compartilhar o documento e toque, segure e deslize para cima para abrir outro arquivo. diff --git a/app/src/main/res/values-pt-rBR/strings_office_engine.xml b/app/src/main/res/values-pt-rBR/strings_office_engine.xml new file mode 100644 index 0000000..d3abd7e --- /dev/null +++ b/app/src/main/res/values-pt-rBR/strings_office_engine.xml @@ -0,0 +1,34 @@ + + + + + Motor do Office (com tecnologia do LibreOffice) + Motor do Office + Com tecnologia do LibreOffice + Abre arquivos do Word, Excel, PowerPoint e OpenDocument com o layout do próprio LibreOffice, para o resultado mais fiel. É opcional: os visualizadores integrados continuam funcionando sem ele. + Não instalado · download de ~%1$s + Instalado · %1$s · v%2$s + Atualização disponível · %1$s + Baixando… + Baixando · %1$s de %2$s + Aguardando conexão… + Verificando e instalando… + O Google Play precisa da sua aprovação para baixar + Falha na instalação: %1$s + Baixar + Instalar + Atualizar + Excluir + Cancelar + Tentar de novo + Aprovar + Usar para arquivos do Office + Volta aos visualizadores integrados se um arquivo falhar + Licenças do LibreOffice (MPL-2.0) + Excluir o motor do Office? + Libera %1$s. Os arquivos do Office vão abrir com os visualizadores integrados. Você pode baixar de novo quando quiser. + Download do motor do Office + Melhore a fidelidade com o motor do Office + Download opcional · com tecnologia do LibreOffice + Obter + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index bacbfec..9f4bc02 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -859,4 +859,104 @@ Toque e segure um arquivo para abrir, compartilhar, fixar e mais. Deixe o vidro do seu jeito Ajuste desfoque, refração e tonalidade, ou escolha uma predefinição, em Liquid Glass. + + + Cores do tema + Cores padrão + Cores recentes + Mais cores… + Sem cor + Esquerda + Centro + Direita + Topo + Meio + Base + Todas as bordas + Bordas externas + Bordas externas grossas + Bordas internas + Borda superior + Borda inferior + Borda esquerda + Borda direita + Sem bordas + Geral + Número + Inteiro + Moeda + Contábil + Porcentagem + Porcentagem (2 casas) + Científico + Data curta + Data longa + Hora + Texto + Mover para cima + Mover para baixo + Não foi possível ler esse arquivo + Digite um endereço da web + Digite ou carregue um HTML + Não foi possível criar o PDF + Digite o texto da marca d\'água + Escolha uma imagem para a marca d\'água + Fundo removido + Não foi possível remover o fundo. + Compartilhar imagem + Compartilhar imagens + Páginas convertidas: %1$d + Vívido + Quente + Frio + Desbotado + Mono + Noir + Sépia + Vintage + Cromo + Dramático + Dourado + Azul-petróleo e laranja + Polaroid + Lomo + Inverter + Posterizar + Esboço + Desenho + Relevo + Hachura + Meio-tom + Pintura a óleo + Retângulo + Arredondado + Círculo + Canto cortado + Hexágono + Coração + Estrela + + + Reiniciar o ClearPDF? + Este idioma é lido na outra direção. Reinicie o ClearPDF para que todas as telas mudem. + Reiniciar agora + Depois + + + Movimento + Como saltam todos os botões, controlos deslizantes e painéis de vidro. Escolha um perfil ou ajuste cada parte e toque em Experimentar para sentir. + Normal + Desempenho + Saltitante + Super elástico + Experimentar + Salto + Velocidade + Profundidade ao premir + Tremor ao soltar + Esticar ao arrastar + Crescer ao manter premido + Repor movimento + Trocar partilhar e abrir + No visualizador, toque no botão redondo para partilhar o documento e mantenha premido e deslize para cima para abrir outro ficheiro. diff --git a/app/src/main/res/values-pt/strings_office_engine.xml b/app/src/main/res/values-pt/strings_office_engine.xml new file mode 100644 index 0000000..d3abd7e --- /dev/null +++ b/app/src/main/res/values-pt/strings_office_engine.xml @@ -0,0 +1,34 @@ + + + + + Motor do Office (com tecnologia do LibreOffice) + Motor do Office + Com tecnologia do LibreOffice + Abre arquivos do Word, Excel, PowerPoint e OpenDocument com o layout do próprio LibreOffice, para o resultado mais fiel. É opcional: os visualizadores integrados continuam funcionando sem ele. + Não instalado · download de ~%1$s + Instalado · %1$s · v%2$s + Atualização disponível · %1$s + Baixando… + Baixando · %1$s de %2$s + Aguardando conexão… + Verificando e instalando… + O Google Play precisa da sua aprovação para baixar + Falha na instalação: %1$s + Baixar + Instalar + Atualizar + Excluir + Cancelar + Tentar de novo + Aprovar + Usar para arquivos do Office + Volta aos visualizadores integrados se um arquivo falhar + Licenças do LibreOffice (MPL-2.0) + Excluir o motor do Office? + Libera %1$s. Os arquivos do Office vão abrir com os visualizadores integrados. Você pode baixar de novo quando quiser. + Download do motor do Office + Melhore a fidelidade com o motor do Office + Download opcional · com tecnologia do LibreOffice + Obter + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ae1eb60..eace042 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -866,4 +866,104 @@ Удерживайте файл, чтобы открыть, отправить, закрепить и не только. Настройте стекло под себя Настройте размытие, преломление и оттенок или выберите пресет в разделе Liquid Glass. + + + Цвета темы + Стандартные цвета + Недавние цвета + Другие цвета… + Без цвета + Слева + По центру + Справа + Сверху + Посередине + Снизу + Все границы + Внешние границы + Толстые внешние границы + Внутренние границы + Верхняя граница + Нижняя граница + Левая граница + Правая граница + Без границ + Общий + Числовой + Целое + Денежный + Финансовый + Процентный + Процентный (2 знака) + Экспоненциальный + Краткая дата + Полная дата + Время + Текстовый + Переместить вверх + Переместить вниз + Не удалось прочитать файл + Введите веб-адрес + Введите или загрузите HTML + Не удалось создать PDF + Введите текст водяного знака + Выберите изображение для водяного знака + Фон удалён + Не удалось удалить фон. + Поделиться изображением + Поделиться изображениями + Готово страниц: %1$d + Яркий + Тёплый + Холодный + Выцветший + Моно + Нуар + Сепия + Винтаж + Хром + Драматичный + Золотой + Бирюза и оранжевый + Полароид + Ломо + Негатив + Постеризация + Набросок + Мультфильм + Тиснение + Штриховка + Растр + Масло + Прямоугольник + Скруглённый + Круг + Срезанный угол + Шестиугольник + Сердце + Звезда + + + Перезапустить ClearPDF? + Этот язык читается в другую сторону. Перезапустите ClearPDF, чтобы все экраны перестроились. + Перезапустить + Позже + + + Движение + Как пружинят все стеклянные кнопки, ползунки и панели. Выберите профиль или настройте каждую часть и нажмите «Попробовать», чтобы почувствовать. + Обычный + Производительность + Упругий + Суперпружинистый + Попробовать + Отскок + Скорость + Глубина нажатия + Дрожание при отпускании + Растяжение при перетаскивании + Рост при долгом нажатии + Сбросить движение + Поменять «Поделиться» и «Открыть» + В просмотрщике нажмите круглую кнопку, чтобы поделиться документом, а нажмите, удерживайте и проведите вверх, чтобы открыть другой файл. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 971c2aa..7475542 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -866,4 +866,104 @@ Press and hold a file to open, share, pin it and more. Make the glass yours Tune blur, refraction and tint, or pick a preset, in Liquid Glass. + + + Theme colours + Standard colours + Recent colours + More colours… + No colour + Left + Center + Right + Top + Middle + Bottom + All borders + Outside borders + Thick outside borders + Inside borders + Top border + Bottom border + Left border + Right border + No borders + General + Number + Integer + Currency + Accounting + Percent + Percent (2 decimals) + Scientific + Short date + Long date + Time + Text + Move up + Move down + Couldn\'t read that file + Enter a web address + Enter or load some HTML + Couldn\'t create the PDF + Enter watermark text + Pick a watermark image + Background removed + Couldn\'t remove the background. + Share image + Share images + Pages rendered: %1$d + Vivid + Warm + Cool + Fade + Mono + Noir + Sepia + Vintage + Chrome + Dramatic + Golden + Teal & Orange + Polaroid + Lomo + Invert + Posterize + Sketch + Toon + Emboss + Crosshatch + Halftone + Oil paint + Rectangle + Rounded + Circle + Cut corner + Hexagon + Heart + Star + + + Restart ClearPDF? + This language reads in the other direction. Restart ClearPDF so every screen switches over. + Restart now + Later + + + Motion + How every glass button, slider and sheet springs. Pick a profile or tune each part, then press Try it to feel it. + Normal + Performance + Bouncy + Super springy + Try it + Bounce + Speed + Press depth + Release jiggle + Drag stretch + Long-press bloom + Reset motion + Swap share and open + In the viewer, tap the round button to share the document and press, hold and swipe up to open another file.