From 440d5d4d8ad0181e0d7d0a4de982e6b8767a5206 Mon Sep 17 00:00:00 2001 From: chethan616 Date: Mon, 12 Oct 2026 00:53:44 +0530 Subject: [PATCH 1/3] Word reading keeps alignment; every screen translated - Reading a Word file now lays each paragraph across the full text width, so centred and right-aligned titles sit where Word puts them (only edit mode did before, because its fields filled the width). - Big photos in Word files decode at the size they are shown (ImageDecoder on Android 9+), and a picture that can't be decoded is logged. - The Office engine screen, the colour picker, the spreadsheet's alignment, border and number-format panels, the image editor's filter and crop shape names, Move up/down and several tool messages were English-only; they now come from resources, translated into Spanish, Italian, Portuguese, Russian and Arabic (938 strings, complete in every language). --- .../imageeditor/engine/FilterPresets.kt | 46 +++++------ .../ui/components/GlassColorPicker.kt | 12 +-- .../clearpdf/ui/screen/CreatePdfScreen.kt | 4 +- .../clearpdf/ui/screen/DocxEditorScreen.kt | 50 ++++++++++-- .../clearpdf/ui/screen/MergePdfScreen.kt | 4 +- .../ui/screen/SpreadsheetViewerScreen.kt | 49 ++++++------ .../ui/screen/imageeditor/EditorCropStage.kt | 16 ++-- .../ui/screen/imageeditor/EditorPanels.kt | 4 +- .../ui/viewmodel/HtmlToPdfViewModel.kt | 8 +- .../ui/viewmodel/ImageEditorViewModel.kt | 4 +- .../ui/viewmodel/ImageToolsViewModel.kt | 2 +- .../ui/viewmodel/PdfToImagesViewModel.kt | 6 +- .../ui/viewmodel/WatermarkPdfViewModel.kt | 4 +- app/src/main/res/values-ar/strings.xml | 76 +++++++++++++++++++ .../res/values-ar/strings_office_engine.xml | 34 +++++++++ app/src/main/res/values-es/strings.xml | 76 +++++++++++++++++++ .../res/values-es/strings_office_engine.xml | 34 +++++++++ app/src/main/res/values-it/strings.xml | 76 +++++++++++++++++++ app/src/main/res/values-pt-rBR/strings.xml | 76 +++++++++++++++++++ .../values-pt-rBR/strings_office_engine.xml | 34 +++++++++ app/src/main/res/values-pt/strings.xml | 76 +++++++++++++++++++ .../res/values-pt/strings_office_engine.xml | 34 +++++++++ app/src/main/res/values-ru/strings.xml | 76 +++++++++++++++++++ app/src/main/res/values/strings.xml | 76 +++++++++++++++++++ 24 files changed, 790 insertions(+), 87 deletions(-) create mode 100644 app/src/main/res/values-ar/strings_office_engine.xml create mode 100644 app/src/main/res/values-es/strings_office_engine.xml create mode 100644 app/src/main/res/values-pt-rBR/strings_office_engine.xml create mode 100644 app/src/main/res/values-pt/strings_office_engine.xml 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/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/screen/CreatePdfScreen.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/CreatePdfScreen.kt index abb6006..36a72f5 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/CreatePdfScreen.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/CreatePdfScreen.kt @@ -320,7 +320,7 @@ fun CreatePdfScreen( Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { Icon( Icons.Rounded.ArrowUpward, - "Move up", + androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.move_up), Modifier .size(24.dp) .clip(CircleShape) @@ -331,7 +331,7 @@ fun CreatePdfScreen( ) Icon( Icons.Rounded.ArrowDownward, - "Move down", + androidx.compose.ui.res.stringResource(com.chethan616.clearpdf.R.string.move_down), Modifier .size(24.dp) .clip(CircleShape) diff --git a/app/src/main/java/com/chethan616/clearpdf/ui/screen/DocxEditorScreen.kt b/app/src/main/java/com/chethan616/clearpdf/ui/screen/DocxEditorScreen.kt index 451c9b7..be3ab49 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/screen/DocxEditorScreen.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/screen/DocxEditorScreen.kt @@ -1432,10 +1432,14 @@ private fun ParagraphBlock( } Box(Modifier.weight(1f).alignByBaseline()) { if (editable) ParaField(para, pl, full, env) + // Full width, like the edit field: centred and right-aligned titles were + // drawn at the left while reading because the text only spanned itself. else BasicText( annotated, style = if (first) pl.style else pl.style.copy(textIndent = null), - modifier = if (env.editMode && !readOnly) Modifier.clickable(remember { MutableInteractionSource() }, indication = null) { env.onReadOnlyTap() } else Modifier + modifier = Modifier.fillMaxWidth().then( + if (env.editMode && !readOnly) Modifier.clickable(remember { MutableInteractionSource() }, indication = null) { env.onReadOnlyTap() } else Modifier + ) ) } } @@ -1499,17 +1503,43 @@ private object DocImages { fun decode(key: String, bytes: ByteArray, targetW: Int): DecodedPicture? { cache.get(key)?.let { return DecodedPicture(it.first.asImageBitmap(), it.second) } - 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.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/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/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..047e75d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -836,4 +836,80 @@ اضغط مطولًا على ملف لفتحه أو مشاركته أو تثبيته والمزيد. اجعل الزجاج على ذوقك اضبط التمويه والانكسار والصبغة، أو اختر إعدادًا جاهزًا، في Liquid Glass. + + + ألوان المظهر + الألوان القياسية + الألوان الأخيرة + المزيد من الألوان… + بلا لون + يسار + وسط + يمين + أعلى + منتصف + أسفل + كل الحدود + الحدود الخارجية + حدود خارجية سميكة + الحدود الداخلية + الحد العلوي + الحد السفلي + الحد الأيسر + الحد الأيمن + بلا حدود + عام + رقم + عدد صحيح + عملة + محاسبي + نسبة مئوية + نسبة مئوية (منزلتان) + علمي + تاريخ قصير + تاريخ طويل + وقت + نص + نقل لأعلى + نقل لأسفل + تعذّرت قراءة هذا الملف + أدخل عنوان ويب + أدخل HTML أو حمّله + تعذّر إنشاء PDF + أدخل نص العلامة المائية + اختر صورة للعلامة المائية + تمت إزالة الخلفية + تعذّرت إزالة الخلفية. + مشاركة الصورة + مشاركة الصور + الصفحات المحوّلة: %1$d + زاهٍ + دافئ + بارد + باهت + أحادي + نوار + بني داكن + عتيق + كروم + درامي + ذهبي + أزرق مخضر وبرتقالي + بولارويد + لومو + عكس + تقليل الألوان + رسم تخطيطي + كرتوني + نقش بارز + تظليل متقاطع + نصف درجة + رسم زيتي + مستطيل + مدوّر + دائرة + زاوية مقطوعة + سداسي + قلب + نجمة 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..a1c9234 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -861,4 +861,80 @@ 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 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..e559a65 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -866,4 +866,80 @@ 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 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 85d815b..2badb83 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -859,4 +859,80 @@ 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 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..74ed13f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -859,4 +859,80 @@ 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 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..946b430 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -866,4 +866,80 @@ Удерживайте файл, чтобы открыть, отправить, закрепить и не только. Настройте стекло под себя Настройте размытие, преломление и оттенок или выберите пресет в разделе Liquid Glass. + + + Цвета темы + Стандартные цвета + Недавние цвета + Другие цвета… + Без цвета + Слева + По центру + Справа + Сверху + Посередине + Снизу + Все границы + Внешние границы + Толстые внешние границы + Внутренние границы + Верхняя граница + Нижняя граница + Левая граница + Правая граница + Без границ + Общий + Числовой + Целое + Денежный + Финансовый + Процентный + Процентный (2 знака) + Экспоненциальный + Краткая дата + Полная дата + Время + Текстовый + Переместить вверх + Переместить вниз + Не удалось прочитать файл + Введите веб-адрес + Введите или загрузите HTML + Не удалось создать PDF + Введите текст водяного знака + Выберите изображение для водяного знака + Фон удалён + Не удалось удалить фон. + Поделиться изображением + Поделиться изображениями + Готово страниц: %1$d + Яркий + Тёплый + Холодный + Выцветший + Моно + Нуар + Сепия + Винтаж + Хром + Драматичный + Золотой + Бирюза и оранжевый + Полароид + Ломо + Негатив + Постеризация + Набросок + Мультфильм + Тиснение + Штриховка + Растр + Масло + Прямоугольник + Скруглённый + Круг + Срезанный угол + Шестиугольник + Сердце + Звезда diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 971c2aa..70b6cfe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -866,4 +866,80 @@ 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 From 4b5397a554ff240fdac6c1b719a1125c88044b93 Mon Sep 17 00:00:00 2001 From: chethan616 Date: Mon, 12 Oct 2026 01:06:03 +0530 Subject: [PATCH 2/3] Offer a restart when the new language reads the other way (into or out of Arabic) Strings switch in place, but the layout direction only flips with a fresh start, so the app asked users to close and reopen it themselves. Now a glass dialog offers Restart now (or Later) whenever the switch changes direction. --- .../com/chethan616/clearpdf/ui/DocsApp.kt | 48 +++++++++++++++++++ app/src/main/res/values-ar/strings.xml | 6 +++ app/src/main/res/values-es/strings.xml | 6 +++ app/src/main/res/values-it/strings.xml | 6 +++ app/src/main/res/values-pt-rBR/strings.xml | 6 +++ app/src/main/res/values-pt/strings.xml | 6 +++ app/src/main/res/values-ru/strings.xml | 6 +++ app/src/main/res/values/strings.xml | 6 +++ 8 files changed, 90 insertions(+) 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..29e2f66 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 @@ -363,6 +366,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 +517,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/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 047e75d..b515ed4 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -912,4 +912,10 @@ سداسي قلب نجمة + + + إعادة تشغيل ClearPDF؟ + تُقرأ هذه اللغة في الاتجاه الآخر. أعد تشغيل ClearPDF لتتبدّل كل الشاشات. + إعادة التشغيل الآن + لاحقًا diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a1c9234..4c5a1c9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -937,4 +937,10 @@ 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 diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index e559a65..91e99be 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -942,4 +942,10 @@ Esagono Cuore Stella + + + Riavviare ClearPDF? + Questa lingua si legge nell\'altro verso. Riavvia ClearPDF perché tutte le schermate si adeguino. + Riavvia ora + Più tardi diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2badb83..1ddf0fc 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -935,4 +935,10 @@ 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 diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 74ed13f..da65d6c 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -935,4 +935,10 @@ 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 diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 946b430..dd0c5dd 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -942,4 +942,10 @@ Шестиугольник Сердце Звезда + + + Перезапустить ClearPDF? + Этот язык читается в другую сторону. Перезапустите ClearPDF, чтобы все экраны перестроились. + Перезапустить + Позже diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 70b6cfe..14b8eb5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -942,4 +942,10 @@ Hexagon Heart Star + + + Restart ClearPDF? + This language reads in the other direction. Restart ClearPDF so every screen switches over. + Restart now + Later From f2a3f9afaaabf28829091d2d49278ab9a53166b8 Mon Sep 17 00:00:00 2001 From: chethan616 Date: Mon, 12 Oct 2026 01:19:59 +0530 Subject: [PATCH 3/3] Liquid Glass motion settings, swap share and open, 44 dp reader controls Settings > Liquid Glass > Motion tunes the springs every glass button, slider, sheet and menu shares: bounce, speed, press depth, release jiggle, drag stretch and long-press bloom, with Normal, Performance, Bouncy and Super springy profiles and a button to try it. Settings > Personalization > Swap share and open makes the viewer's round button share on tap and open another file on hold and swipe up. The PDF and PowerPoint viewers' bottom circles (editor tools, auto-scroll, share/open) are now 44 dp like the Word editor. --- DESIGN.md | 11 +++ .../clearpdf/data/repository/Preferences.kt | 7 ++ .../com/chethan616/clearpdf/ui/DocsApp.kt | 6 +- .../clearpdf/ui/components/GlassBead.kt | 2 +- .../ui/components/GlassBottomSheet.kt | 2 +- .../clearpdf/ui/components/GlassDialog.kt | 2 +- .../clearpdf/ui/components/GlassMotion.kt | 25 ++--- .../ui/components/GlassSegmentedControl.kt | 6 +- .../clearpdf/ui/components/MotionSettings.kt | 85 +++++++++++++++++ .../ui/components/ShareMorphButton.kt | 34 +++++-- .../clearpdf/ui/components/ToolTile.kt | 2 +- .../ui/screen/PdfViewerBottomToolbar.kt | 18 ++-- .../clearpdf/ui/screen/SettingsScreen.kt | 92 +++++++++++++++++++ .../clearpdf/ui/utils/DampedDragAnimation.kt | 13 +-- .../clearpdf/ui/utils/InteractiveHighlight.kt | 27 +++--- app/src/main/res/values-ar/strings.xml | 18 ++++ app/src/main/res/values-es/strings.xml | 18 ++++ app/src/main/res/values-it/strings.xml | 18 ++++ app/src/main/res/values-pt-rBR/strings.xml | 18 ++++ app/src/main/res/values-pt/strings.xml | 18 ++++ app/src/main/res/values-ru/strings.xml | 18 ++++ app/src/main/res/values/strings.xml | 18 ++++ 22 files changed, 406 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/com/chethan616/clearpdf/ui/components/MotionSettings.kt 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/ui/DocsApp.kt b/app/src/main/java/com/chethan616/clearpdf/ui/DocsApp.kt index 29e2f66..dc5e279 100644 --- a/app/src/main/java/com/chethan616/clearpdf/ui/DocsApp.kt +++ b/app/src/main/java/com/chethan616/clearpdf/ui/DocsApp.kt @@ -287,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) } 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/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 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/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/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index b515ed4..9a7f655 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -918,4 +918,22 @@ تُقرأ هذه اللغة في الاتجاه الآخر. أعد تشغيل ClearPDF لتتبدّل كل الشاشات. إعادة التشغيل الآن لاحقًا + + + الحركة + طريقة ارتداد كل الأزرار والمنزلقات واللوحات الزجاجية. اختر ملفًا جاهزًا أو اضبط كل جزء ثم اضغط «جرّبها» لتشعر بها. + عادي + الأداء + مرن + مرن جدًا + جرّبها + الارتداد + السرعة + عمق الضغط + الاهتزاز عند الإفلات + التمدد عند السحب + التضخم عند الضغط المطوّل + إعادة ضبط الحركة + تبديل المشاركة والفتح + في العارض، انقر الزر الدائري لمشاركة المستند، واضغط مطولًا ثم اسحب لأعلى لفتح ملف آخر. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4c5a1c9..2a426c6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -943,4 +943,22 @@ 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-it/strings.xml b/app/src/main/res/values-it/strings.xml index 91e99be..839dd3a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -948,4 +948,22 @@ 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 1ddf0fc..960d2a0 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -941,4 +941,22 @@ 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/strings.xml b/app/src/main/res/values-pt/strings.xml index da65d6c..9f4bc02 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -941,4 +941,22 @@ 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-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index dd0c5dd..eace042 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -948,4 +948,22 @@ Этот язык читается в другую сторону. Перезапустите ClearPDF, чтобы все экраны перестроились. Перезапустить Позже + + + Движение + Как пружинят все стеклянные кнопки, ползунки и панели. Выберите профиль или настройте каждую часть и нажмите «Попробовать», чтобы почувствовать. + Обычный + Производительность + Упругий + Суперпружинистый + Попробовать + Отскок + Скорость + Глубина нажатия + Дрожание при отпускании + Растяжение при перетаскивании + Рост при долгом нажатии + Сбросить движение + Поменять «Поделиться» и «Открыть» + В просмотрщике нажмите круглую кнопку, чтобы поделиться документом, а нажмите, удерживайте и проведите вверх, чтобы открыть другой файл. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14b8eb5..7475542 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -948,4 +948,22 @@ 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.