From 34d7b180be5f961e0f8ad13c3468c8d54fc9532e Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sun, 20 Sep 2026 13:08:30 +0200 Subject: [PATCH 1/2] Rebuild the editing UI around one tool per tap, and sell it by the tool The strip under the edit bar was up to eleven targets wide and scrolled on a phone: every marking tool carried a second button for its colour, and undo and redo sat at the end where a pdf pushed them off the screen. The strip is now formatting alone. Undo, redo and save are `menu/edit.xml`, so they stand in the bar and do not scroll away, and `EditActionModeCallback` dims them on what the page reports. A sheet or a plain text file shows no strip at all. Every tool is one 48dp square: a tap does the tool's one job and a long press opens the colours it applies, so the chevrons are gone. The colour bar under an icon is what the next tap uses, and it sits at the foot of a deep slot - close under the ink, a squiggle that dips and a strikethrough that stops high read as bars at different heights. The fourteen font sizes are a row under the strip rather than a menu down the screen, scrolled to the size the text is in. A pdf's tools no longer arm themselves. `odr.annotation.press` marks a standing selection and arms only what it could not mark, so `PageView.pressMarkTool` puts every tool but the pen back down. With nothing selected the answer is the `action_annotate_banner` snackbar. The page's `markOnSelection` is therefore off: it marked as the selection was made, which is the opposite order. The bridge states the sheet's gesture too, because an android WebView reports a fine pointer on an emulator. The gate moved from the mode to the tool. Every edition opens every kind the core calls editable, and a locked strip dims what only offers pro and leaves the highlighter working - `highlight` being the formatting style and the pdf's marking tool alike. A mode nobody can do anything in is not worth opening. The two offers say what is still pro; their translations are dropped rather than left stating the old, now false, thing. No string is new: every label reuses one the app already has in nineteen languages, and `action_edit_banner` is gone from all of them. The free highlighter in a text document wants a core carrying opendocument-app/OpenDocument.core#909, which is unreleased: against 7.1.0 the page refuses it and the offer of pro comes up, as it does today. Checked on a Pixel 6 Pro emulator in both themes and both editions: 97 instrumented tests on pro, 97 on lite, the unit tests, lint, and all three flavors build. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FJJdfqpnVCKBNHXjAVxSou --- CLAUDE.md | 30 +- .../droid/test/MainActivityTests.kt | 90 ++- app/src/main/assets/editing-bridge.js | 8 +- .../opendocument/droid/nonfree/Features.kt | 9 +- .../droid/ui/EditActionModeCallback.kt | 92 +++- .../droid/ui/activity/DocumentFragment.kt | 56 +- .../droid/ui/activity/MainActivity.kt | 22 +- .../droid/ui/widget/EditingTools.kt | 519 +++++++++--------- .../opendocument/droid/ui/widget/PageView.kt | 41 +- app/src/main/res/drawable/ic_format_size.xml | 10 + app/src/main/res/layout/item_editing_tool.xml | 48 +- ...l_text.xml => item_editing_tool_badge.xml} | 10 +- .../res/layout/item_editing_tool_chevron.xml | 13 - .../res/layout/item_editing_tool_size.xml | 17 + .../main/res/layout/view_color_palette.xml | 8 - .../res/layout/view_editing_tool_popup.xml | 16 + .../main/res/layout/view_editing_tools.xml | 8 +- app/src/main/res/menu/edit.xml | 18 +- app/src/main/res/values-ca/strings.xml | 3 - app/src/main/res/values-cs/strings.xml | 3 - app/src/main/res/values-da/strings.xml | 3 - app/src/main/res/values-de/strings.xml | 3 - app/src/main/res/values-es/strings.xml | 3 - app/src/main/res/values-et/strings.xml | 3 - app/src/main/res/values-fr/strings.xml | 3 - app/src/main/res/values-ga/strings.xml | 3 - app/src/main/res/values-hi/strings.xml | 3 - app/src/main/res/values-it/strings.xml | 3 - app/src/main/res/values-ja/strings.xml | 3 - app/src/main/res/values-ko/strings.xml | 3 - app/src/main/res/values-pl/strings.xml | 3 - app/src/main/res/values-pt/strings.xml | 3 - app/src/main/res/values-ru/strings.xml | 3 - app/src/main/res/values-sl/strings.xml | 3 - app/src/main/res/values-sv/strings.xml | 3 - app/src/main/res/values-tr/strings.xml | 3 - app/src/main/res/values-zh/strings.xml | 3 - app/src/main/res/values/strings.xml | 13 +- 38 files changed, 675 insertions(+), 410 deletions(-) create mode 100644 app/src/main/res/drawable/ic_format_size.xml rename app/src/main/res/layout/{item_editing_tool_text.xml => item_editing_tool_badge.xml} (68%) delete mode 100644 app/src/main/res/layout/item_editing_tool_chevron.xml create mode 100644 app/src/main/res/layout/item_editing_tool_size.xml delete mode 100644 app/src/main/res/layout/view_color_palette.xml create mode 100644 app/src/main/res/layout/view_editing_tool_popup.xml diff --git a/CLAUDE.md b/CLAUDE.md index 717ec209fb62..19d3a8c3e79b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -119,10 +119,16 @@ Code that has to *ask* reads `Features`, never the flavor name. `Features.withAd from `LINKS_ADS`, which sits in `src/ads` and `src/noAds` next to the classes it stands for, so the flag cannot end up in a build whose code says otherwise. `Features.advancedEditing`, from `ADVANCED_EDITING` in the same two files, is what pro is sold on: new and joined -paragraphs, formatting, and marks on a pdf. Every other edit the core takes - inside one -paragraph, a sheet cell, a plain text file - is in every build. `Features.offersEditing` is the -one list of it; the Edit button still stands on the core's answer, so over a pdf in lite it -offers pro instead. `OpenDocument.ios` draws the same line with the same flag, beside its own +paragraphs, formatting past the highlighter, and a pdf's marks past the highlighter. Every +other edit the core takes - inside one paragraph, a sheet cell, a plain text file - is in every +build. + +**The gate is on the tool, not on the mode.** Every edition opens every kind the core names +editable, so `Features.offersEditing` is the core's answer alone; a locked `EditingTools` dims +what only offers pro and leaves the **highlighter** working, under both the names it has +(`highlight` is the formatting style and the pdf's marking tool alike). A mode nobody can do +anything in is not worth opening, and a reader who has highlighted a paragraph knows what the +rest would do. Do not put the whole-mode gate back. `OpenDocument.ios` draws the same line with the same flag, beside its own `LINKS_ADS`. Do not add a `BuildConfig.FLAVOR` comparison back - it was what made `BillingManager` miss @@ -326,6 +332,22 @@ callbacks. Lite narrows `HtmlConfig.editingScope` to `PARAGRAPH`, and the page r rest with `outOfScope`, which `DocumentFragment` answers with the offer of pro, once an edit. A pdf needs no scaffolding: every pdf page carries `odr.annotation`. +**The bar holds what is done to the document, the strip what is done to the text.** Undo, redo +and save are `menu/edit.xml`, so they do not scroll away, and `EditActionModeCallback` dims them +on what the page reports. `EditingTools` under the bar is the formatting alone, which is why a +sheet or a plain text file shows no strip at all. Every tool is one 48dp square: a tap does the +tool's one job, and a **long press** opens the colours it applies - there is no second button +beside a tool, and the bar under its icon is what the next tap uses, not what the selection is. +The text colour is the exception, and opens its colours on a tap too, because it has no state to +turn off. Do not put the chevrons back, and do not move the sizes into a menu: fourteen of them +covered the document they are about. + +**A pdf's tools mark what is selected; only the pen is a mode.** `odr.annotation.press` marks a +standing selection and arms only what it could not mark, so `PageView.pressMarkTool` puts every +tool but `ink` back down and reports what happened. With nothing selected the answer is the +`action_annotate_banner` snackbar, not a tool left armed. The page's own `markOnSelection` is +therefore off: it marked as the selection was *made*, which is the opposite order. + **Nothing is held open between the render and the save.** `CoreLoader.writeEdits` opens the cached copy again and applies the page's payload with the call its kind takes - `Document.edit` and `save`, `TextFile.edit` and `save`, `PdfFile.annotate`. An edit that throws diff --git a/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt b/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt index 69f1959719fb..3ffe0570c9b8 100644 --- a/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt +++ b/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt @@ -158,17 +158,99 @@ class MainActivityTests { // hierarchy whether the column is open or not, and doesNotExist walks all of it onView(withContentDescription(R.string.menu_edit)).check(doesNotExist()) - // the button is there in every edition: pro marks, lite says what pro would do + // every edition marks up a pdf; what lite does not sell is the tool, and one of them + // is free so that the mode is worth opening onView(withContentDescription(R.string.menu_annotate)).perform(click()) - if (Features.advancedEditing) { - onView(withContentDescription(R.string.tool_mark_draw)).check(matches(isDisplayed())) - } else { + onView(withContentDescription(R.string.tool_mark_draw)).check(matches(isDisplayed())) + onView(withContentDescription(R.string.tool_mark_highlight)).check(matches(isDisplayed())) + + if (!Features.advancedEditing) { + // the pen is pro's, and says so rather than arming itself + onView(withContentDescription(R.string.tool_mark_draw)).perform(click()) + awaitViewWithText(R.string.pro_offer_title) onView(withText(R.string.pro_offer_title)).check(matches(isDisplayed())) } } + /** + * A pdf's tools mark the text that is selected and are then put down again. Only the pen stays + * armed, because a stroke has no selection to go on - and only where this edition sells it: the + * highlighter is the free one, and marks in lite too. + */ + @Test + fun onlyThePenStaysArmed() { + val activity = mainActivityActivityTestRule.activity + val documentFragment = loadDocument(activity, requireTestFile("dummy.pdf")) + val pageView = requireNotNull(documentFragment.pageView) + + enterEditMode(activity, documentFragment) + + Assert.assertTrue( + "the page should carry the annotator", + waitFor(EDIT_MODE_TIMEOUT_MS) { pageAnswers(pageView, "odr.annotation") }, + ) + + val selected = AtomicReference("") + Assert.assertTrue( + "the page should have text to select, and answered '${'$'}{selected.get()}'", + waitFor(EDIT_MODE_TIMEOUT_MS) { + selected.set(selectPageText(pageView)) + + selected.get().isNotEmpty() + }, + ) + + onView(withContentDescription(R.string.tool_mark_highlight)).perform(click()) + + Assert.assertTrue( + "the selection should be marked", + waitFor(EDIT_MODE_TIMEOUT_MS) { + pageAnswers(pageView, "odr.annotation.list().length === 1") + }, + ) + Assert.assertTrue( + "the tool should not stay armed", + pageAnswers(pageView, "odr.annotation.getTool() === null"), + ) + + // the pen is the one that does, and it marks nothing by being pressed. In lite it is + // pro's and says so instead, the highlighter above being the free one + if (!Features.advancedEditing) { + return + } + + onView(withContentDescription(R.string.tool_mark_draw)).perform(click()) + + Assert.assertTrue( + "the pen should stay armed", + waitFor(EDIT_MODE_TIMEOUT_MS) { + pageAnswers(pageView, "odr.annotation.getTool() === 'ink'") + }, + ) + Assert.assertTrue( + "the pen should have marked nothing", + pageAnswers(pageView, "odr.annotation.list().length === 1"), + ) + } + + /** Selects everything the page holds, and answers what that turned out to be. */ + private fun selectPageText(pageView: PageView): String = + evaluateJavascript( + pageView, + "(function(){" + + "var range = document.createRange();" + + "range.selectNodeContents(document.body);" + + "var selection = window.getSelection();" + + "selection.removeAllRanges();" + + "selection.addRange(range);" + + "return selection.toString().trim();" + + "})()", + ) + ?.replace("\"", "") + .orEmpty() + /** A sheet takes cell edits in every edition. */ @Test fun aSheetIsEditedInEveryEdition() { diff --git a/app/src/main/assets/editing-bridge.js b/app/src/main/assets/editing-bridge.js index 8dde6a001446..12a652611d0b 100644 --- a/app/src/main/assets/editing-bridge.js +++ b/app/src/main/assets/editing-bridge.js @@ -27,9 +27,9 @@ bridge.marksChanged(event.count); }; - if (odr.annotation) { - // an armed tool marks a selection as it is made, which is what a touch screen needs: with a - // selection standing, the selection's own toolbar is over the page - odr.annotation.setOptions({ markOnSelection: true }); + if (odr.editing && odr.editing.setSheetOptions) { + // a phone has no double click to spare, and the pointer is not asked: a + // WebView on an emulator answers that one as a mouse + odr.editing.setSheetOptions({ editOnClick: true }); } })(); diff --git a/app/src/main/java/app/opendocument/droid/nonfree/Features.kt b/app/src/main/java/app/opendocument/droid/nonfree/Features.kt index b8a219b1be51..c514bc793f8c 100644 --- a/app/src/main/java/app/opendocument/droid/nonfree/Features.kt +++ b/app/src/main/java/app/opendocument/droid/nonfree/Features.kt @@ -18,7 +18,10 @@ object Features { /** Formatting, new and joined paragraphs, and pdf marks: pro and foss. */ val advancedEditing = ADVANCED_EDITING - /** Whether this build opens the edit mode for [kind], which the core decided. */ - fun offersEditing(kind: EditingKind): Boolean = - kind.isEditable && (kind != EditingKind.ANNOTATION || advancedEditing) + /** + * Whether this build opens the edit mode for [kind], which the core decided. Every edition + * opens every kind it can: what lite does not sell is the individual tool, which carries pro's + * badge in the strip, and one of each kind is free so that the mode is worth opening. + */ + fun offersEditing(kind: EditingKind): Boolean = kind.isEditable } diff --git a/app/src/main/java/app/opendocument/droid/ui/EditActionModeCallback.kt b/app/src/main/java/app/opendocument/droid/ui/EditActionModeCallback.kt index 100b866dcb48..6cbaef54e52f 100644 --- a/app/src/main/java/app/opendocument/droid/ui/EditActionModeCallback.kt +++ b/app/src/main/java/app/opendocument/droid/ui/EditActionModeCallback.kt @@ -2,51 +2,107 @@ package app.opendocument.droid.ui import android.view.Menu import android.view.MenuItem -import android.widget.TextView import androidx.appcompat.view.ActionMode import app.opendocument.droid.R import app.opendocument.droid.background.EditingKind import app.opendocument.droid.ui.activity.DocumentFragment import app.opendocument.droid.ui.activity.MainActivity -/** The edit mode: the bar with save. The tools under it are `EditingTools`. */ +/** + * The edit mode: the bar with undo, redo and save. The tools that change the text itself are + * `EditingTools`, in the strip under the bar. + */ class EditActionModeCallback( private val activity: MainActivity, private val documentFragment: DocumentFragment, ) : ActionMode.Callback { + private var annotating = false + + private var undoItem: MenuItem? = null + private var redoItem: MenuItem? = null + + private var canUndo = false + private var canRedo = false + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { - val annotating = documentFragment.editingKind == EditingKind.ANNOTATION + annotating = documentFragment.editingKind == EditingKind.ANNOTATION - val statusView = TextView(activity) - statusView.setText( - if (annotating) R.string.action_annotate_banner else R.string.action_edit_banner - ) - mode.customView = statusView + // the name of the mode, not a sentence: three buttons leave a phone's bar no room for one + mode.setTitle(if (annotating) R.string.menu_annotate else R.string.menu_edit) mode.menuInflater.inflate(R.menu.edit, menu) + undoItem = menu.findItem(R.id.edit_undo) + redoItem = menu.findItem(R.id.edit_redo) + redoItem?.isVisible = !annotating + + setUndoState(canUndo = false, canRedo = false) + documentFragment.setEditing(true) + if (annotating) { + // what the strip cannot say: a mark goes on the text that is selected + SnackbarHelper.show( + activity, + R.string.action_annotate_banner, + null, + isIndefinite = false, + isError = false, + ) + } + return true } override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = false + /** What the page says can be taken back and put back. */ + fun setUndoState(canUndo: Boolean, canRedo: Boolean) { + this.canUndo = canUndo + this.canRedo = canRedo + + undoItem?.let { setUsable(it, canUndo) } + redoItem?.let { setUsable(it, canRedo) } + } + + /** A bar item is not dimmed by being disabled, so its icon is dimmed here. */ + private fun setUsable(item: MenuItem, usable: Boolean) { + item.isEnabled = usable + item.icon?.alpha = if (usable) OPAQUE else DISABLED_ALPHA + } + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - if (item.itemId != R.id.edit_save) { - return false - } + when (item.itemId) { + R.id.edit_undo -> { + activity.analyticsManager.report("menu_edit_undo") + + documentFragment.undo() + } + + R.id.edit_redo -> { + activity.analyticsManager.report("menu_edit_redo") - // OpenDocument.ios' name for this; menu_save is the button on the document itself - activity.analyticsManager.report("menu_edit_save") + documentFragment.redo() + } - documentFragment.prepareSave({ activity.requestSave() }, false) + R.id.edit_save -> { + // OpenDocument.ios' name for this; menu_save is the button on the document itself + activity.analyticsManager.report("menu_edit_save") + + documentFragment.prepareSave({ activity.requestSave() }, false) + } + + else -> return false + } return true } override fun onDestroyActionMode(mode: ActionMode) { + undoItem = null + redoItem = null + documentFragment.setEditing(false) // the page keeps its edits with the mode off. not when the document is being closed: that @@ -55,4 +111,12 @@ class EditActionModeCallback( activity.confirmLeavingEdits { documentFragment.discardEdits() } } } + + companion object { + + private const val OPAQUE = 255 + + /** Material's opacity for a disabled icon, 38%. */ + private const val DISABLED_ALPHA = 97 + } } diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt index 87c9d2ac8213..e84222ee20e7 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt @@ -437,14 +437,18 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener { reload(requireLastRequest(), requireLastFile()) } - /** The strip under the bar: what the kind of document takes, and undo and redo. */ + /** + * The strip under the bar: what the kind of document takes. A sheet or a plain text file takes + * nothing, so there is no strip over it at all - undo, redo and save are the bar's. + */ private fun showEditingTools(document: LoadedDocument, editing: Boolean) { when { !editing || !document.editing.isEditable -> editingTools.hide() document.editing == EditingKind.DOCUMENT -> editingTools.showFormatting(locked = !Features.advancedEditing) - document.editing == EditingKind.ANNOTATION -> editingTools.showMarking() - else -> editingTools.showPlain() + document.editing == EditingKind.ANNOTATION -> + editingTools.showMarking(locked = !Features.advancedEditing) + else -> editingTools.hide() } } @@ -511,28 +515,41 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener { override fun onMarkTool(tool: String, color: Int, recolor: Boolean) { analyticsManager.report("edit_mark_$tool") - pageView?.pressMarkTool(tool, color, EditingTools.INK_WIDTH, recolor) { armed -> + pageView?.pressMarkTool(tool, color, EditingTools.INK_WIDTH, recolor) { + armed, + marked -> editingTools.setArmedTool(armed) + + // the pen needs no selection, and a colour picked over nothing is not a miss + if (!marked && !recolor && tool != INK_TOOL) { + sayHowToMark() + } } } override fun onLocked() { - (requireActivity() as MainActivity).offerPro(MainActivity.ProFeature.FORMATTING) - } - - override fun onUndo() { - analyticsManager.report("menu_edit_undo") - - undo() + (requireActivity() as MainActivity).offerPro( + if (editingKind == EditingKind.ANNOTATION) MainActivity.ProFeature.PDF + else MainActivity.ProFeature.FORMATTING + ) } + } - override fun onRedo() { - analyticsManager.report("menu_edit_redo") - - redo() - } + /** Says what a mark wants, where a tool was pressed with nothing selected. */ + private fun sayHowToMark() { + if (!isAdded) { + return } + SnackbarHelper.show( + requireActivity(), + R.string.action_annotate_banner, + null, + isIndefinite = false, + isError = false, + ) + } + private fun setEditState(dirty: Boolean, canUndo: Boolean, canRedo: Boolean) { if (!::state.isInitialized) { return @@ -540,9 +557,7 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener { state.editsDirty = dirty - if (::editingTools.isInitialized) { - editingTools.setUndoState(canUndo, canRedo) - } + (activity as? MainActivity)?.editActionMode?.setUndoState(canUndo, canRedo) } /** Says why the page refused an edit, in our words rather than the page's. */ @@ -1491,5 +1506,8 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener { /** What the analytics screen name is when nothing could name the bytes. */ const val UNKNOWN_FILE_TYPE = "N/A" + + /** The one marking tool that stays armed, in the annotator's name for it. */ + const val INK_TOOL = "ink" } } diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt index efab7c0b8028..825438675416 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt @@ -103,9 +103,13 @@ class MainActivity : AppCompatActivity() { } } - // kept because onPause has to stop it; the edit mode needs no such handle + // kept because onPause has to stop it private var ttsActionMode: TtsActionModeCallback? = null + /** Kept because the page's undo state is reported to the bar this owns. */ + var editActionMode: EditActionModeCallback? = null + private set + /** The action mode on screen, so [closeDocument] and [onDestroy] can take it down with them. */ private var currentActionMode: SupportActionMode? = null @@ -621,17 +625,18 @@ class MainActivity : AppCompatActivity() { DocumentActions.ACTION_EDIT -> { analyticsManager.report("menu_edit") - // the button follows the core, so lite shows it over a pdf and offers pro + // the button stands on the core's answer, and every edition opens what it + // names: what lite does not sell is the tool, which says so in the strip val kind = documentFragment?.editingKind ?: return if (!Features.offersEditing(kind)) { - offerPro(ProFeature.PDF) - return } documentFragment?.let { fragment -> - currentActionMode = - startSupportActionMode(EditActionModeCallback(this, fragment)) + val editActionMode = EditActionModeCallback(this, fragment) + this.editActionMode = editActionMode + + currentActionMode = startSupportActionMode(editActionMode) } } } @@ -687,6 +692,7 @@ class MainActivity : AppCompatActivity() { currentActionMode = null ttsActionMode = null + editActionMode = null } // and the framework ones, which is what selecting text in the page raises @@ -761,10 +767,10 @@ class MainActivity : AppCompatActivity() { /** What pro adds, as the reader runs into it. */ enum class ProFeature(@param:StringRes val message: Int) { - /** Formatting text, and starting or joining a paragraph. */ + /** Formatting text past the highlighter, and starting or joining a paragraph. */ FORMATTING(R.string.pro_offer_formatting), - /** Marking up a pdf. */ + /** Marking up a pdf past the highlighter. */ PDF(R.string.pro_offer_markup), } diff --git a/app/src/main/java/app/opendocument/droid/ui/widget/EditingTools.kt b/app/src/main/java/app/opendocument/droid/ui/widget/EditingTools.kt index 223b7d0ef8c6..ba4d347391f1 100644 --- a/app/src/main/java/app/opendocument/droid/ui/widget/EditingTools.kt +++ b/app/src/main/java/app/opendocument/droid/ui/widget/EditingTools.kt @@ -14,14 +14,26 @@ import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.TooltipCompat +import androidx.core.view.AccessibilityDelegateCompat +import androidx.core.view.ViewCompat +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import app.opendocument.droid.R import org.json.JSONObject /** - * The tools under the edit mode's bar, with undo and redo at the end. It only reports taps; which - * tool is on comes back from the page through [setSelectionStyle] and [setArmedTool]. + * The strip of tools under the edit mode's bar: what changes the text, and nothing else. Undo, redo + * and save are the bar's, see `EditActionModeCallback`. + * + * Every tool is one square button, and a tap does the one thing the tool is for. A tool that + * applies a colour shows it in the bar under its icon, and a **long press** opens the colours - + * there is no second button beside it. Which tool is on comes back from the page, through + * [setSelectionStyle] and [setArmedTool]. + * + * In a build without [app.opendocument.droid.nonfree.Features.advancedEditing] the strip is + * `locked`: the highlighter still works, every other tool is dimmed and offers pro, and pro's badge + * stands in front of the row. One free tool of each kind is what makes the mode worth opening - see + * [FREE_TOOL]. */ class EditingTools(context: Context, attributeSet: AttributeSet?) : HorizontalScrollView(context, attributeSet) { @@ -39,10 +51,6 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : /** A tool of pro's was tapped in a build without it. */ fun onLocked() - - fun onUndo() - - fun onRedo() } var listener: Listener? = null @@ -54,29 +62,22 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : private val toggles = mutableMapOf() private val markTools = mutableMapOf() - private val markColors = mutableMapOf() - private var textColor = TEXT_COLORS.first().color - private var highlightColor = HIGHLIGHT_COLORS.first().color + /** What each tool applies, which is what its bar shows. Kept as the document is edited. */ + private val toolColors = STARTING_COLORS.toMutableMap() - /** What the selection shows, as the page last reported it. */ - private var selectionStyle = JSONObject() - - private var textColorBar: View? = null private var highlightTool: View? = null - private var highlightBar: View? = null - private var sizeTool: TextView? = null - private var undoTool: View? = null - private var redoTool: View? = null + private var sizeTool: View? = null - private var canUndo = false - private var canRedo = false + /** The size the selection is in, in points, or null where the runs disagree. */ + private var selectionSize: String? = null init { LayoutInflater.from(context).inflate(R.layout.view_editing_tools, this, true) row = findViewById(R.id.editing_tools_row) + isFillViewport = true isHorizontalScrollBarEnabled = false visibility = View.GONE } @@ -85,53 +86,44 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : visibility = View.GONE } - /** The formatting tools. [locked] adds pro's badge, and makes each tool offer pro. */ + /** + * The formatting tools. [locked] adds pro's badge, and makes every tool but the free one offer + * pro. + */ fun showFormatting(locked: Boolean) { reset(locked) - if (locked) { - val badge = newText(R.string.tool_pro_badge) - badge.isSelected = true - row.addView(badge) - } + addBadge() addToggle("bold", R.drawable.ic_format_bold, R.string.tool_bold) addToggle("italic", R.drawable.ic_format_italic, R.string.tool_italic) addToggle("underline", R.drawable.ic_format_underlined, R.string.tool_underline) - addToggle( - "strikethrough", - R.drawable.ic_format_strikethrough, - R.string.tool_strikethrough, - ) - - // one control: the colors open under it, and the bar shows the selection's own - val textColorTool = newTool(R.drawable.ic_text_color, R.string.tool_text_color) - textColorBar = barOf(textColorTool).also { paintBar(it, textColor) } - textColorTool.setOnClickListener { anchor -> - ifUnlocked { - showPalette(anchor, TEXT_COLORS) { color -> - listener?.onFormat(JSONObject().put("color", hex(color))) - } - } - } - row.addView(textColorTool) - - // a split button: the tool turns the highlight on and off, the arrow picks its colour - val highlight = newTool(R.drawable.ic_marker, R.string.tool_highlight) - highlightBar = barOf(highlight).also { paintBar(it, highlightColor) } + addToggle("strikethrough", R.drawable.ic_format_strikethrough, R.string.tool_strikethrough) + + // the tool has nothing to turn off, so a tap is the colours themselves; the long press + // opens them too, because that is where every other tool keeps its colours + val textColor = newTool(R.drawable.ic_text_color, R.string.tool_text_color, TEXT_COLOR) + paintBar(textColor, TEXT_COLOR) + textColor.setOnClickListener { ifOffered(TEXT_COLOR) { pickTextColor(it) } } + addColorPress(textColor, TEXT_COLOR, R.string.tool_text_color) { pickTextColor(it) } + row.addView(textColor) + + // a tap flips the highlight on the selection, in the colour the long press picked + val highlight = newTool(R.drawable.ic_marker, R.string.tool_highlight, HIGHLIGHT_COLOR) + paintBar(highlight, HIGHLIGHT_COLOR) highlight.setOnClickListener { - ifUnlocked { - // isNull is also true of a key the page left out, where the runs disagree - val on = !selectionStyle.isNull("highlight") - + ifOffered(HIGHLIGHT_COLOR) { listener?.onFormat( - JSONObject().put("highlight", if (on) JSONObject.NULL else hex(highlightColor)) + JSONObject() + .put( + "highlight", + if (highlight.isSelected) JSONObject.NULL + else hex(colorOf(HIGHLIGHT_COLOR)), + ) ) } } - highlightTool = highlight - row.addView(highlight) - addChevron(R.string.tool_highlight) { anchor -> + addColorPress(highlight, HIGHLIGHT_COLOR, R.string.tool_highlight) { anchor -> showPalette(anchor, HIGHLIGHT_COLORS) { color -> if (color == Color.TRANSPARENT) { listener?.onFormat(JSONObject().put("highlight", JSONObject.NULL)) @@ -139,135 +131,92 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : return@showPalette } - highlightColor = color - highlightBar?.let { paintBar(it, color) } + setToolColor(HIGHLIGHT_COLOR, highlight, color) listener?.onFormat(JSONObject().put("highlight", hex(color))) } } + highlightTool = highlight + row.addView(highlight) - val size = newText(R.string.tool_font_size) - size.contentDescription = context.getString(R.string.tool_font_size) - TooltipCompat.setTooltipText(size, context.getString(R.string.tool_font_size)) - size.setOnClickListener { ifUnlocked { showSizes(size) } } + val size = newTool(R.drawable.ic_format_size, R.string.tool_font_size, SIZE_TOOL) + size.setOnClickListener { ifOffered(SIZE_TOOL) { showSizes(it) } } sizeTool = size row.addView(size) - addUndoRedo(redo = true) - - setSelectionStyle(selectionStyle) - - visibility = View.VISIBLE - } - - /** A sheet or a plain text file: nothing to format, so only the way back. */ - fun showPlain() { - reset(false) - - addUndoRedo(redo = true) + setSelectionStyle(JSONObject()) visibility = View.VISIBLE } /** The five marking tools of a pdf, each with a colour of its own. */ - fun showMarking() { - reset(false) + fun showMarking(locked: Boolean) { + reset(locked) - for (mark in MARKS) { - val color = markColors.getOrPut(mark.tool) { mark.color } + addBadge() - val tool = newTool(mark.icon, mark.label) - paintBar(barOf(tool), color) + for (mark in MARKS) { + val tool = newTool(mark.icon, mark.label, mark.tool) + paintBar(tool, mark.tool) tool.setOnClickListener { - listener?.onMarkTool(mark.tool, markColors.getValue(mark.tool), false) + ifOffered(mark.tool) { + listener?.onMarkTool(mark.tool, colorOf(mark.tool), false) + } } - markTools[mark.tool] = tool - row.addView(tool) - - addChevron(mark.label) { anchor -> + addColorPress(tool, mark.tool, mark.label) { anchor -> showPalette(anchor, MARK_COLORS) { picked -> - markColors[mark.tool] = picked - paintBar(barOf(tool), picked) + setToolColor(mark.tool, tool, picked) listener?.onMarkTool(mark.tool, picked, true) } } - } - // a mark is taken back one at a time and never put back - addUndoRedo(redo = false) + markTools[mark.tool] = tool + row.addView(tool) + } visibility = View.VISIBLE } - /** What the page says can be taken back and put back. */ - fun setUndoState(canUndo: Boolean, canRedo: Boolean) { - this.canUndo = canUndo - this.canRedo = canRedo - - undoTool?.let { setUsable(it, canUndo) } - redoTool?.let { setUsable(it, canRedo) } - } - - /** Undo and redo are the page's in every edition, so they are never locked. */ - private fun addUndoRedo(redo: Boolean) { - val undo = newTool(R.drawable.ic_undo, R.string.action_undo) - undo.setOnClickListener { listener?.onUndo() } - undoTool = undo - row.addView(undo) - - if (redo) { - val tool = newTool(R.drawable.ic_redo, R.string.action_redo) - tool.setOnClickListener { listener?.onRedo() } - redoTool = tool - row.addView(tool) + /** Pro's badge, in front of a locked row, saying whose the dimmed tools are. */ + private fun addBadge() { + if (!locked) { + return } - setUndoState(canUndo, canRedo) - } - - private fun setUsable(tool: View, usable: Boolean) { - tool.isEnabled = usable - tool.alpha = if (usable) 1f else DISABLED_ALPHA + val badge = + LayoutInflater.from(context).inflate(R.layout.item_editing_tool_badge, row, false) + as TextView + badge.setText(R.string.tool_pro_badge) + badge.isSelected = true + badge.setOnClickListener { listener?.onLocked() } + row.addView(badge) } /** Shows which of the toggles the selection has on, and the size it is set in. */ fun setSelectionStyle(style: JSONObject) { - selectionStyle = style - for ((property, view) in toggles) { view.isSelected = !locked && style.optBoolean(property, false) } + // isNull is also true of a key the page left out, where the runs disagree highlightTool?.isSelected = !locked && !style.isNull("highlight") - // where the runs disagree, the bars keep what they showed - style - .optString("color") - .takeIf { !style.isNull("color") } - ?.let { parseColor(it) } - ?.let { color -> - textColorBar?.let { paintBar(it, color) } - } - style - .optString("highlight") - .takeIf { !style.isNull("highlight") } - ?.let { parseColor(it) } - ?.let { color -> - highlightColor = color - highlightBar?.let { paintBar(it, color) } + // the caption is the size the text is in; the icon alone means the runs disagree + selectionSize = + style.optString("size", "").removeSuffix("pt").takeIf { + it.isNotEmpty() && !style.isNull("size") } - sizeTool?.text = - style - .optString("size", "") - .removeSuffix("pt") - .takeIf { it.isNotEmpty() && !style.isNull("size") } - ?.let { context.getString(R.string.tool_font_size_points, it) } - ?: context.getString(R.string.tool_font_size) + sizeTool?.let { tool -> + captionOf( + tool, + selectionSize?.let { context.getString(R.string.tool_font_size_points, it) }, + ) + } } - /** Shows which marking tool is armed, or none. */ + /** Shows which marking tool is armed, or none. Only the pen ever is. */ fun setArmedTool(tool: String?) { for ((name, view) in markTools) { view.isSelected = name == tool @@ -280,104 +229,202 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : row.removeAllViews() toggles.clear() markTools.clear() - textColorBar = null highlightTool = null - highlightBar = null sizeTool = null - undoTool = null - redoTool = null - selectionStyle = JSONObject() scrollTo(0, 0) } - private fun ifUnlocked(action: () -> Unit) { - if (locked) { + /** Whether [tool] only offers pro in this build, rather than doing its work. */ + private fun isPro(tool: String) = locked && tool != FREE_TOOL + + private fun ifOffered(tool: String, action: () -> Unit) { + if (isPro(tool)) { listener?.onLocked() } else { action() } } + private fun pickTextColor(anchor: View) { + showPalette(anchor, TEXT_COLORS) { color -> + setToolColor(TEXT_COLOR, anchor, color) + + listener?.onFormat(JSONObject().put("color", hex(color))) + } + } + private fun addToggle(property: String, @DrawableRes icon: Int, @StringRes label: Int) { - val tool = newTool(icon, label) - tool.setOnClickListener { ifUnlocked { listener?.onToggleStyle(property) } } + val tool = newTool(icon, label, property) + tool.setOnClickListener { ifOffered(property) { listener?.onToggleStyle(property) } } toggles[property] = tool row.addView(tool) } - private fun addChevron(@StringRes label: Int, open: (View) -> Unit) { - val chevron = - LayoutInflater.from(context).inflate(R.layout.item_editing_tool_chevron, row, false) - + /** + * Puts the colours of [tool] on its long press. That is where the name of a tool is otherwise + * read out, so the screen reader is told what the press does instead. + */ + private fun addColorPress( + tool: View, + name: String, + @StringRes label: Int, + open: (View) -> Unit, + ) { val description = context.getString(R.string.tool_color_of, context.getString(label)) - chevron.contentDescription = description - TooltipCompat.setTooltipText(chevron, description) - chevron.setOnClickListener { ifUnlocked { open(it) } } + tool.setOnLongClickListener { + ifOffered(name) { open(tool) } - row.addView(chevron) + true + } + + ViewCompat.setAccessibilityDelegate( + tool, + object : AccessibilityDelegateCompat() { + override fun onInitializeAccessibilityNodeInfo( + host: View, + info: AccessibilityNodeInfoCompat, + ) { + super.onInitializeAccessibilityNodeInfo(host, info) + + info.addAction( + AccessibilityNodeInfoCompat.AccessibilityActionCompat( + AccessibilityNodeInfoCompat.ACTION_LONG_CLICK, + description, + ) + ) + } + }, + ) } - private fun newTool(@DrawableRes icon: Int, @StringRes label: Int): View { + private fun newTool(@DrawableRes icon: Int, @StringRes label: Int, name: String): View { val tool = LayoutInflater.from(context).inflate(R.layout.item_editing_tool, row, false) tool.findViewById(R.id.editing_tool_icon).setImageResource(icon) tool.contentDescription = context.getString(label) - // no label beside it, so the name is what a long press turns up + // a tool that only offers pro is dimmed, so the free one is the one that stands out + if (isPro(name)) { + tool.alpha = PRO_ALPHA + } + + // no label beside it, so the name is what a long press turns up - on the tools whose long + // press is not the colours, see addColorPress TooltipCompat.setTooltipText(tool, context.getString(label)) return tool } - private fun newText(@StringRes text: Int): TextView { - val view = - LayoutInflater.from(context).inflate(R.layout.item_editing_tool_text, row, false) - as TextView - view.setText(text) + /** Shows [text] under the tool's icon, or nothing where it is null. */ + private fun captionOf(tool: View, text: String?) { + val caption = tool.findViewById(R.id.editing_tool_caption) - if (text == R.string.tool_pro_badge) { - view.setOnClickListener { listener?.onLocked() } - } + caption.text = text.orEmpty() + caption.visibility = if (text == null) View.GONE else View.VISIBLE + } + + /** What [name] applies, which starts at the first colour its palette offers. */ + @ColorInt private fun colorOf(name: String): Int = toolColors.getValue(name) + + private fun setToolColor(name: String, tool: View, @ColorInt color: Int) { + toolColors[name] = color - return view + paintBar(tool, name) } - private fun barOf(tool: View): View = - tool.findViewById(R.id.editing_tool_bar).also { it.visibility = View.VISIBLE } + /** Shows under [tool]'s icon what it applies, so that the bar is what a tap uses. */ + private fun paintBar(tool: View, name: String) { + val bar = tool.findViewById(R.id.editing_tool_bar) + bar.visibility = View.VISIBLE - private fun paintBar(bar: View, @ColorInt color: Int) { - (bar.background.mutate() as GradientDrawable).setColor(color) + (bar.background.mutate() as GradientDrawable).setColor(colorOf(name)) } + /** + * The sizes, in one row under the strip rather than a menu down the screen: fourteen of them + * would otherwise cover the document they are about. + */ private fun showSizes(anchor: View) { - val popup = PopupMenu(context, anchor) - for ((index, size) in FONT_SIZES.withIndex()) { - popup.menu.add( - 0, - index, - index, - context.getString(R.string.tool_font_size_points, "$size"), - ) - } - popup.setOnMenuItemClickListener { item -> - listener?.onFormat(JSONObject().put("size", "${FONT_SIZES[item.itemId]}pt")) + showRow(anchor, fill = true) { row, popup -> + var current: View? = null + + for (size in FONT_SIZES) { + val chip = + LayoutInflater.from(context) + .inflate(R.layout.item_editing_tool_size, row, false) as TextView + chip.text = context.getString(R.string.tool_font_size_points, "$size") + chip.isSelected = "$size" == selectionSize + chip.setOnClickListener { + popup.dismiss() + + listener?.onFormat(JSONObject().put("size", "${size}pt")) + } - true + if (chip.isSelected) { + current = chip + } + + row.addView(chip) + } + + // the size the text is in is what the reader is looking for, so start there - one + // chip short of it, so that the smaller sizes before it are not hidden + current?.let { chip -> + row.post { + (row.parent as HorizontalScrollView).scrollTo( + (chip.left - chip.width).coerceAtLeast(0), + 0, + ) + } + } } - popup.show() } private fun showPalette(anchor: View, colors: List, picked: (Int) -> Unit) { - val content = LayoutInflater.from(context).inflate(R.layout.view_color_palette, null) - val paletteRow: LinearLayout = content.findViewById(R.id.color_palette_row) + showRow(anchor, fill = false) { row, popup -> + val size = (44 * resources.displayMetrics.density).toInt() + val margin = (2 * resources.displayMetrics.density).toInt() + + for (named in colors) { + val swatch = View(context) + swatch.layoutParams = + LinearLayout.LayoutParams(size, size).apply { setMargins(margin, 0, margin, 0) } + swatch.background = + context.getDrawable(R.drawable.bg_color_swatch)!!.mutate().also { + // no fill at all is the swatch for no highlight + (it as GradientDrawable).setColor(named.color) + } + swatch.contentDescription = context.getString(named.name) + TooltipCompat.setTooltipText(swatch, context.getString(named.name)) + swatch.isClickable = true + swatch.isFocusable = true + swatch.setOnClickListener { + popup.dismiss() + + picked(named.color) + } + + row.addView(swatch) + } + } + } + + /** + * What a tool opens: a row of choices under it. [fill] takes the width of the screen, for a row + * that is too long to stand under one tool. + */ + private fun showRow(anchor: View, fill: Boolean, build: (LinearLayout, PopupWindow) -> Unit) { + val content = LayoutInflater.from(context).inflate(R.layout.view_editing_tool_popup, null) + val row: LinearLayout = content.findViewById(R.id.editing_tool_popup_row) val popup = PopupWindow( content, - LinearLayout.LayoutParams.WRAP_CONTENT, + if (fill) LinearLayout.LayoutParams.MATCH_PARENT + else LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, true, ) @@ -389,32 +436,10 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : } ) - val size = (36 * resources.displayMetrics.density).toInt() - val margin = (4 * resources.displayMetrics.density).toInt() - - for (named in colors) { - val swatch = View(context) - swatch.layoutParams = - LinearLayout.LayoutParams(size, size).apply { setMargins(margin, 0, margin, 0) } - swatch.background = - context.getDrawable(R.drawable.bg_color_swatch)!!.mutate().also { - // no fill at all is the swatch for no highlight - (it as GradientDrawable).setColor(named.color) - } - swatch.contentDescription = context.getString(named.name) - TooltipCompat.setTooltipText(swatch, context.getString(named.name)) - swatch.isClickable = true - swatch.isFocusable = true - swatch.setOnClickListener { - popup.dismiss() - - picked(named.color) - } - - paletteRow.addView(swatch) - } + build(row, popup) - popup.showAsDropDown(anchor) + // a row that fills the screen hangs under the strip, not under the tool that opened it + popup.showAsDropDown(if (fill) this else anchor) } @ColorInt @@ -431,7 +456,6 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : val tool: String, @param:DrawableRes val icon: Int, @param:StringRes val label: Int, - @param:ColorInt val color: Int, ) companion object { @@ -439,18 +463,25 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : /** The width of a line the Draw tool makes, in pdf points. */ const val INK_WIDTH = 2f - /** `#rrggbb`, the one spelling `odr.editing.format` takes. */ - private fun hex(@ColorInt color: Int) = String.format("#%06x", color and 0xffffff) + /** The two formatting tools that carry a colour, named as [toolColors] keys. */ + private const val TEXT_COLOR = "color" + private const val HIGHLIGHT_COLOR = "highlight" - private fun parseColor(hex: String): Int? = - try { - Color.parseColor(hex) - } catch (e: IllegalArgumentException) { - null - } + /** The size tool, which carries no colour and so is only ever a name here. */ + private const val SIZE_TOOL = "size" - /** Material's opacity for a disabled icon, 38%. */ - private const val DISABLED_ALPHA = 0.38f + /** + * The one tool a locked strip still does the work of. The highlighter, under both names it + * has: `highlight` is the formatting style and the pdf's marking tool alike, so a reader of + * either kind of document has the same free tool. + */ + private const val FREE_TOOL = "highlight" + + /** What a tool that only offers pro is drawn at, against the free one beside it. */ + private const val PRO_ALPHA = 0.45f + + /** `#rrggbb`, the one spelling `odr.editing.format` takes. */ + private fun hex(@ColorInt color: Int) = String.format("#%06x", color and 0xffffff) /** Point sizes a document commonly uses. */ private val FONT_SIZES = listOf(8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48) @@ -481,34 +512,30 @@ class EditingTools(context: Context, attributeSet: AttributeSet?) : NamedColor(0xff43a047.toInt(), R.string.color_green), ) - /** The marks a pdf takes, in the annotator's names, each with its starting colour. */ + /** The marks a pdf takes, in the annotator's names. */ private val MARKS = listOf( - Mark( - "highlight", - R.drawable.ic_marker, - R.string.tool_mark_highlight, - 0xffffe633.toInt(), - ), - Mark( - "underline", - R.drawable.ic_format_underlined, - R.string.tool_mark_underline, - 0xffe53935.toInt(), - ), + Mark("highlight", R.drawable.ic_marker, R.string.tool_mark_highlight), + Mark("underline", R.drawable.ic_format_underlined, R.string.tool_mark_underline), Mark( "strikeOut", R.drawable.ic_format_strikethrough, R.string.tool_mark_strike_out, - 0xffe53935.toInt(), - ), - Mark( - "squiggly", - R.drawable.ic_format_squiggly, - R.string.tool_mark_squiggly, - 0xffe53935.toInt(), ), - Mark("ink", R.drawable.ic_draw, R.string.tool_mark_draw, 0xff1e88e5.toInt()), + Mark("squiggly", R.drawable.ic_format_squiggly, R.string.tool_mark_squiggly), + Mark("ink", R.drawable.ic_draw, R.string.tool_mark_draw), + ) + + /** The colour each tool starts with. */ + private val STARTING_COLORS = + mapOf( + TEXT_COLOR to TEXT_COLORS.first().color, + HIGHLIGHT_COLOR to HIGHLIGHT_COLORS.first().color, + "highlight" to 0xffffe633.toInt(), + "underline" to 0xffe53935.toInt(), + "strikeOut" to 0xffe53935.toInt(), + "squiggly" to 0xffe53935.toInt(), + "ink" to 0xff1e88e5.toInt(), ) } } diff --git a/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt b/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt index d3991ebd1422..dae0784d4f58 100644 --- a/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt +++ b/app/src/main/java/app/opendocument/droid/ui/widget/PageView.kt @@ -501,13 +501,20 @@ constructor(context: Context, attributeSet: AttributeSet?) : evaluateJavascript("odr.editing.format($style)", null) } - /** A marking tool pressed, or given a new colour; [callback] gets the tool left armed. */ + /** + * A marking tool pressed, or given a new colour. [callback] gets the tool left armed and + * whether a mark was made. + * + * Only the pen stays armed. Every other tool marks the text that is selected and is then put + * down again, so that a tap is one mark rather than a mode the reader has to leave. The page + * arms what it cannot mark, which is how a press with nothing selected is told apart. + */ fun pressMarkTool( tool: String, color: Int, width: Float, recolor: Boolean, - callback: (String?) -> Unit, + callback: (armed: String?, marked: Boolean) -> Unit, ) { val rgb = "[${android.graphics.Color.red(color) / 255f}," + @@ -516,10 +523,22 @@ constructor(context: Context, attributeSet: AttributeSet?) : val method = if (recolor) "recolor" else "press" evaluateJavascript( - "window.odr && odr.annotation ? odr.annotation.$method(" + - "${JSONObject.quote(tool)}, {color: $rgb, width: $width}) : null" - ) { - callback(decodeString(it)) + "(function(){" + + "if (!window.odr || !odr.annotation) { return null; }" + + "var before = odr.annotation.list().length;" + + "var armed = odr.annotation.$method(${JSONObject.quote(tool)}, " + + "{color: $rgb, width: $width});" + + "if (armed && armed !== 'ink') { odr.annotation.setTool(null); armed = null; }" + + "var marked = odr.annotation.list().length > before;" + + "return JSON.stringify({armed: armed, marked: marked});" + + "})()" + ) { result -> + val answer = decodeObject(result) + + callback( + answer?.optString("armed")?.ifEmpty { null }, + answer?.optBoolean("marked") == true, + ) } } @@ -536,6 +555,16 @@ constructor(context: Context, attributeSet: AttributeSet?) : } /** A string evaluateJavascript answered with, which arrives as a json literal. */ + /** The object a page answered with, which arrives as a json string holding json. */ + private fun decodeObject(result: String?): JSONObject? = + try { + decodeString(result)?.let { JSONObject(it) } + } catch (e: Exception) { + crashManager.log(e) + + null + } + private fun decodeString(result: String?): String? = try { JSONTokener(result ?: "null").nextValue() as? String diff --git a/app/src/main/res/drawable/ic_format_size.xml b/app/src/main/res/drawable/ic_format_size.xml new file mode 100644 index 000000000000..4ec7923a698f --- /dev/null +++ b/app/src/main/res/drawable/ic_format_size.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/item_editing_tool.xml b/app/src/main/res/layout/item_editing_tool.xml index 8ecbd629233d..0948e59d9005 100644 --- a/app/src/main/res/layout/item_editing_tool.xml +++ b/app/src/main/res/layout/item_editing_tool.xml @@ -1,9 +1,18 @@ - + - + + + + + + + + diff --git a/app/src/main/res/layout/item_editing_tool_text.xml b/app/src/main/res/layout/item_editing_tool_badge.xml similarity index 68% rename from app/src/main/res/layout/item_editing_tool_text.xml rename to app/src/main/res/layout/item_editing_tool_badge.xml index 6f094f08e23c..47412b4ef631 100644 --- a/app/src/main/res/layout/item_editing_tool_text.xml +++ b/app/src/main/res/layout/item_editing_tool_badge.xml @@ -1,15 +1,15 @@ - + diff --git a/app/src/main/res/layout/item_editing_tool_chevron.xml b/app/src/main/res/layout/item_editing_tool_chevron.xml deleted file mode 100644 index 1ad4eb320a26..000000000000 --- a/app/src/main/res/layout/item_editing_tool_chevron.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/app/src/main/res/layout/item_editing_tool_size.xml b/app/src/main/res/layout/item_editing_tool_size.xml new file mode 100644 index 000000000000..0009bec1be37 --- /dev/null +++ b/app/src/main/res/layout/item_editing_tool_size.xml @@ -0,0 +1,17 @@ + + + diff --git a/app/src/main/res/layout/view_color_palette.xml b/app/src/main/res/layout/view_color_palette.xml deleted file mode 100644 index 2e947c8269c8..000000000000 --- a/app/src/main/res/layout/view_color_palette.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/app/src/main/res/layout/view_editing_tool_popup.xml b/app/src/main/res/layout/view_editing_tool_popup.xml new file mode 100644 index 000000000000..5bcc4302b01d --- /dev/null +++ b/app/src/main/res/layout/view_editing_tool_popup.xml @@ -0,0 +1,16 @@ + + + + + + diff --git a/app/src/main/res/layout/view_editing_tools.xml b/app/src/main/res/layout/view_editing_tools.xml index f60791647a91..a75d5132dc1c 100644 --- a/app/src/main/res/layout/view_editing_tools.xml +++ b/app/src/main/res/layout/view_editing_tools.xml @@ -1,5 +1,9 @@ - + @@ -8,7 +12,7 @@ android:id="@+id/editing_tools_row" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center_vertical" + android:gravity="center" android:orientation="horizontal" android:paddingStart="8dp" android:paddingTop="4dp" diff --git a/app/src/main/res/menu/edit.xml b/app/src/main/res/menu/edit.xml index e8624b58583f..b6449e1cd654 100644 --- a/app/src/main/res/menu/edit.xml +++ b/app/src/main/res/menu/edit.xml @@ -1,8 +1,24 @@ - + + + + + + Anterior Desa Contacta - Editeu el document a sota i premeu Desa De manera predeterminada, aquesta aplicació només s\'ofereix per obrir documents. Si una altra aplicació, com ara «Samsung My Files», no us deixa obrir un document aquí, activeu aquesta opció perquè s\'ofereixi per a qualsevol tipus de fitxer. OpenDocument Reader us permet veure documents desats en format OpenDocument (.odt, .ods, .odp i .odg) allà on sigueu. Amb OpenDocument Reader podeu llegir i cercar dins dels vostres documents en un tres i no res. @@ -103,8 +102,6 @@ Sense ressaltat Pro Part de Pro - Donar format al text, i afegir o unir paràgrafs, forma part d\'OpenDocument Reader Pro. - Marcar un PDF forma part d\'OpenDocument Reader Pro. Ara no Un salt de línia dins d\'un paràgraf no es pot desar. Premeu Retorn per a un paràgraf nou. Aquesta cel·la conté una fórmula i es queda tal com està. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index eb988f756c45..c30c9f31e0e1 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -43,7 +43,6 @@ Předchozí Uložit Kontakt - Upravte dokument níže a stiskněte Uložit Ve výchozím nastavení tato aplikace nabízí otevírání pouze dokumentů. Pokud vám jiná aplikace, například „Samsung My Files“, nedovolí zde dokument otevřít, zapněte tuto možnost a aplikace se bude nabízet u všech typů souborů. OpenDocument Reader vám umožní prohlížet dokumenty uložené ve formátu OpenDocument (.odt, .ods, .odp a .odg), ať jste kdekoli. S aplikací OpenDocument Reader můžete své dokumenty číst a bleskově v nich vyhledávat. @@ -103,8 +102,6 @@ Bez zvýraznění Pro Součást verze Pro - Formátování textu a přidávání nebo spojování odstavců je součástí OpenDocument Reader Pro. - Označování PDF je součástí OpenDocument Reader Pro. Teď ne Zalomení řádku uvnitř odstavce nelze uložit. Pro nový odstavec stiskněte Enter. Tato buňka obsahuje vzorec a zůstane beze změny. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 0669132b8b72..f7009ef651af 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -43,7 +43,6 @@ Forrige Gem Kontakt - Rediger dit dokument herunder og tryk på Gem Som standard tilbyder appen kun at åbne dokumentfiler. Hvis en anden app – for eksempel Samsung My Files – ikke lader dig åbne et dokument her, så slå dette til, så appen tilbyder at åbne alle filtyper. Med OpenDocument Reader kan du se dokumenter, der er gemt i OpenDocument-format (.odt, .ods, .odp og .odg), uanset hvor du er. Med OpenDocument Reader kan du læse og søge i dine dokumenter hurtigt og nemt. @@ -103,8 +102,6 @@ Ingen fremhævning Pro En del af Pro - Formatering af tekst og tilføjelse eller sammenlægning af afsnit er en del af OpenDocument Reader Pro. - Markering af PDF-filer er en del af OpenDocument Reader Pro. Ikke nu Et linjeskift inde i et afsnit kan ikke gemmes. Tryk på Enter for et nyt afsnit. Cellen indeholder en formel og forbliver, som den er. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 53d48500a29d..a318c0309cc8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -43,7 +43,6 @@ Zurück Speichern Kontakt - Bearbeiten Sie Ihr Dokument unten und tippen Sie auf Speichern Standardmäßig bietet diese App nur an, Dokumente zu öffnen. Wenn Sie mit einer anderen App wie „Samsung My Files“ ein Dokument nicht hier öffnen können, schalten Sie dies ein, damit die App für alle Dateitypen angeboten wird. Mit OpenDocument Reader lesen Sie Dokumente im OpenDocument-Format (.odt, .ods, .odp und .odg) – wo immer Sie gerade sind. Mit OpenDocument Reader können Sie Ihre Dokumente im Handumdrehen lesen und durchsuchen. @@ -103,8 +102,6 @@ Keine Hervorhebung Pro Teil von Pro - Text formatieren sowie Absätze einfügen oder zusammenführen ist Teil von OpenDocument Reader Pro. - PDFs markieren ist Teil von OpenDocument Reader Pro. Nicht jetzt Ein Zeilenumbruch innerhalb eines Absatzes kann nicht gespeichert werden. Drücken Sie die Eingabetaste für einen neuen Absatz. Diese Zelle enthält eine Formel und bleibt, wie sie ist. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e050d71ff807..5de347822547 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -43,7 +43,6 @@ Anterior Guardar Contacto - Edite el documento a continuación y pulse Guardar De forma predeterminada, esta aplicación solo aparece como opción para abrir documentos. Si otra aplicación, como «Mis archivos» de Samsung, no le deja abrir aquí un documento, active esta opción para que aparezca con todos los tipos de archivo. OpenDocument Reader le permite ver documentos almacenados en formato OpenDocument (.odt, .ods, .odp y .odg) dondequiera que se encuentre. Con OpenDocument Reader puede leer sus documentos y buscar en ellos en un abrir y cerrar de ojos. @@ -103,8 +102,6 @@ Sin resaltado Pro Parte de Pro - Dar formato al texto, así como añadir o unir párrafos, forma parte de OpenDocument Reader Pro. - Marcar un PDF forma parte de OpenDocument Reader Pro. Ahora no Un salto de línea dentro de un párrafo no se puede guardar. Pulse Intro para crear un párrafo nuevo. Esa celda contiene una fórmula y se queda como está. diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index eabf600ce171..57107e452931 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -43,7 +43,6 @@ Eelmine Salvesta Võta ühendust - Muuda allpool olevat dokumenti ja vajuta Salvesta Vaikimisi pakub see rakendus avamist ainult dokumendifailidele. Kui mõni teine rakendus, näiteks „Samsung My Files“, ei lase dokumenti siin avada, lülita see sisse, et rakendus pakuks avamist kõigi failitüüpide puhul. OpenDocument Reader võimaldab sul vaadata OpenDocumenti vormingus (.odt, .ods, .odp ja .odg) salvestatud dokumente kõikjal, kus sa parasjagu oled. OpenDocument Readeriga loed oma dokumente ja otsid neist mängleva kergusega. @@ -103,8 +102,6 @@ Esiletõstuta Pro Osa Pro-versioonist - Teksti vormindamine ning lõikude lisamine või ühendamine on osa rakendusest OpenDocument Reader Pro. - PDF-i märgistamine on osa rakendusest OpenDocument Reader Pro. Mitte praegu Reavahetust lõigu sees ei saa salvestada. Uue lõigu jaoks vajuta Enter. Selles lahtris on valem ja see jääb nii, nagu on. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0967f5b64898..44c7926a90ee 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -43,7 +43,6 @@ Précédent Enregistrer Contact - Modifiez votre document ci-dessous et appuyez sur Enregistrer Par défaut, l\'application ne propose d\'ouvrir que les fichiers de type document. Si une autre application comme « Samsung My Files » ne vous permet pas d\'ouvrir un document ici, activez cette option pour l\'associer à tous les types de fichiers. OpenDocument Reader vous permet de consulter les documents enregistrés au format OpenDocument (.odt, .ods, .odp et .odg) où que vous soyez. Grâce à OpenDocument Reader, vous pouvez lire vos documents et y faire des recherches en un clin d\'œil. @@ -103,8 +102,6 @@ Aucun surlignage Pro Inclus dans Pro - La mise en forme du texte, ainsi que l\'ajout ou la fusion de paragraphes, fait partie d\'OpenDocument Reader Pro. - L\'annotation des PDF fait partie d\'OpenDocument Reader Pro. Pas maintenant Un saut de ligne à l\'intérieur d\'un paragraphe ne peut pas être enregistré. Appuyez sur Entrée pour créer un nouveau paragraphe. Cette cellule contient une formule et reste telle quelle. diff --git a/app/src/main/res/values-ga/strings.xml b/app/src/main/res/values-ga/strings.xml index 4b7ddf9c9e8d..c8c596ae9b51 100644 --- a/app/src/main/res/values-ga/strings.xml +++ b/app/src/main/res/values-ga/strings.xml @@ -43,7 +43,6 @@ Roimhe seo Sábháil Teagmháil - Cuir do cháipéis in eagar thíos agus brúigh Sábháil De réir réamhshocraithe, ní thairgeann an aip seo ach comhaid cháipéise a oscailt. Mura ligeann aip eile, ar nós Samsung My Files, duit cáipéis a oscailt anseo, cuir seo ar siúl le clárú do gach cineál comhaid. Le hOpenDocument Reader, tá tú in ann breathnú ar cháipéisí atá i bhformáid OpenDocument (.odt, .ods, .odp, agus .odg) cibé áit a bhfuil tú. Le hOpenDocument Reader, is féidir leat do chuid cáipéisí a léamh agus a chuardach go héasca. @@ -103,8 +102,6 @@ Gan aibhsiú Pro Cuid de Pro - Is cuid de OpenDocument Reader Pro é téacs a fhormáidiú, agus míreanna a chur leis nó a chumasc. - Is cuid de OpenDocument Reader Pro é PDF a mharcáil. Ní anois Ní féidir briseadh líne laistigh de mhír a shábháil. Brúigh Enter le haghaidh míre nua. Tá foirmle sa chill sin agus fanfaidh sí mar atá. diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 81eb4697fa12..c0f2656e6537 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -43,7 +43,6 @@ पिछला सहेजें संपर्क - नीचे अपना दस्तावेज़ संपादित करें, फिर सहेजें दबाएँ डिफ़ॉल्ट रूप से यह ऐप सिर्फ़ दस्तावेज़ फ़ाइलें खोलने के लिए ही दिखाई देता है। अगर Samsung My Files जैसा कोई दूसरा ऐप आपको यहाँ दस्तावेज़ नहीं खोलने देता, तो इसे चालू करें ताकि यह ऐप हर तरह की फ़ाइल के लिए दिखे। OpenDocument Reader से आप OpenDocument प्रारूप (.odt, .ods, .odp और .odg) में सहेजे गए दस्तावेज़ कहीं भी देख सकते हैं। OpenDocument Reader से आप अपने दस्तावेज़ आसानी से पढ़ सकते हैं और उनमें खोज सकते हैं। @@ -103,8 +102,6 @@ कोई हाइलाइट नहीं Pro Pro का हिस्सा - टेक्स्ट को फ़ॉर्मैट करना और अनुच्छेद जोड़ना या मिलाना OpenDocument Reader Pro का हिस्सा है। - PDF चिह्नित करना OpenDocument Reader Pro का हिस्सा है। अभी नहीं अनुच्छेद के भीतर लाइन ब्रेक सहेजा नहीं जा सकता। नए अनुच्छेद के लिए Enter दबाएँ। इस सेल में एक सूत्र है, इसलिए यह जैसा है वैसा ही रहेगा। diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5616e5c050f8..d28468d7e1a7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -43,7 +43,6 @@ Precedente Salva Contattaci - Modifica il documento qui sotto e premi Salva Per impostazione predefinita questa app propone di aprire solo i documenti. Se un\'altra app come \"Samsung My Files\" non ti permette di aprire qui un documento, attiva questa opzione per associarla a tutti i tipi di file. OpenDocument Reader ti consente di visualizzare i documenti salvati in formato OpenDocument (.odt, .ods, .odp e .odg) ovunque tu sia. Con OpenDocument Reader puoi leggere e cercare tra i tuoi documenti in un attimo. @@ -103,8 +102,6 @@ Nessuna evidenziazione Pro Parte di Pro - Formattare il testo e aggiungere o unire paragrafi fa parte di OpenDocument Reader Pro. - Annotare un PDF fa parte di OpenDocument Reader Pro. Non ora Un\'interruzione di riga all\'interno di un paragrafo non può essere salvata. Premi Invio per un nuovo paragrafo. Questa cella contiene una formula e resta così com\'è. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 3eb66f983533..ea49a8206be3 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -43,7 +43,6 @@ 前へ 保存 お問い合わせ - 下のドキュメントを編集して、「保存」を押してください このアプリは通常、ドキュメントファイルだけを開く対象にしています。「Samsung My Files」などの他のアプリから、このアプリでドキュメントを開けないときは、これをオンにするとすべての種類のファイルを開けるようになります。 OpenDocument Reader なら、OpenDocument 形式(.odt、.ods、.odp、.odg)のドキュメントを、どこにいても表示できます。 OpenDocument Reader なら、ドキュメントを読むのも、中を検索するのも簡単です。 @@ -103,8 +102,6 @@ ハイライトなし Pro Pro の機能 - テキストの書式設定と段落の追加・結合は OpenDocument Reader Pro の機能です。 - PDF への書き込みは OpenDocument Reader Pro の機能です。 今はしない 段落内の改行は保存できません。新しい段落にするには Enter を押してください。 このセルには数式があるため、そのままになります。 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 7c530efa5642..330fcd72df42 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -43,7 +43,6 @@ 이전 저장 문의 - 아래에서 문서를 수정한 뒤 저장을 누르세요. 이 앱은 기본적으로 문서 파일만 열도록 등록되어 있습니다. 삼성 ‘내 파일’ 같은 다른 앱에서 이 앱으로 문서를 열 수 없다면, 이 설정을 켜서 모든 파일 형식에 등록하세요. OpenDocument Reader를 사용하면 어디서든 OpenDocument 형식(.odt, .ods, .odp, .odg) 문서를 볼 수 있습니다. OpenDocument Reader로 문서를 손쉽게 읽고 검색할 수 있습니다. @@ -103,8 +102,6 @@ 강조 표시 없음 Pro Pro 기능 - 텍스트 서식 지정과 단락 추가 또는 병합은 OpenDocument Reader Pro 기능입니다. - PDF 표시는 OpenDocument Reader Pro 기능입니다. 나중에 단락 안의 줄 바꿈은 저장할 수 없습니다. 새 단락을 만들려면 Enter를 누르세요. 이 셀에는 수식이 있어 그대로 유지됩니다. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 68fc190eeb99..d9c70478dd15 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -43,7 +43,6 @@ Poprzedni Zapisz Kontakt - Edytuj dokument poniżej i naciśnij Zapisz Domyślnie aplikacja proponuje otwieranie tylko plików dokumentów. Jeśli inna aplikacja, na przykład „Samsung My Files”, nie pozwala otworzyć tutaj dokumentu, włącz tę opcję, aby aplikacja obsługiwała wszystkie typy plików. Dzięki czytnikowi OpenDocument Reader możesz w dowolnym miejscu wyświetlać dokumenty zapisane w formacie OpenDocument (.odt, .ods, .odp i .odg). W OpenDocument Reader możesz czytać i błyskawicznie przeszukiwać swoje dokumenty. @@ -103,8 +102,6 @@ Bez wyróżnienia Pro Część wersji Pro - Formatowanie tekstu oraz dodawanie i łączenie akapitów to część OpenDocument Reader Pro. - Oznaczanie plików PDF to część OpenDocument Reader Pro. Nie teraz Podziału wiersza wewnątrz akapitu nie można zapisać. Naciśnij Enter, aby utworzyć nowy akapit. Ta komórka zawiera formułę i pozostaje bez zmian. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index e746a5eeb998..27bc8c8f3b5d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -43,7 +43,6 @@ Anterior Salvar Contato - Edite seu documento abaixo e toque em Salvar Por padrão, este app só aparece como opção para abrir arquivos de documento. Se outro app, como o “Samsung My Files”, não deixar você abrir um documento aqui, ative esta opção para que ele apareça para qualquer tipo de arquivo. O OpenDocument Reader permite ver documentos armazenados no formato OpenDocument (.odt, .ods, .odp e .odg) onde quer que você esteja. Com o OpenDocument Reader, você lê e pesquisa seus documentos com facilidade. @@ -103,8 +102,6 @@ Sem realce Pro Parte do Pro - Formatar texto e adicionar ou juntar parágrafos faz parte do OpenDocument Reader Pro. - Marcar um PDF faz parte do OpenDocument Reader Pro. Agora não Uma quebra de linha dentro de um parágrafo não pode ser salva. Pressione Enter para um novo parágrafo. Essa célula contém uma fórmula e fica como está. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d8c7e08510b9..68a8913ae5e1 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -43,7 +43,6 @@ Предыдущий Сохранить Связаться - Отредактируйте документ ниже и нажмите «Сохранить» По умолчанию приложение предлагает открывать только документы. Если другое приложение, например «Samsung My Files», не даёт открыть документ здесь, включите этот параметр — тогда приложение будет открывать файлы любых типов. OpenDocument Reader позволяет просматривать документы в формате OpenDocument (.odt, .ods, .odp и .odg), где бы вы ни находились. С OpenDocument Reader вы можете легко читать документы и искать в них нужное. @@ -103,8 +102,6 @@ Без выделения Pro Входит в Pro - Форматирование текста, а также добавление и объединение абзацев входят в OpenDocument Reader Pro. - Разметка PDF входит в OpenDocument Reader Pro. Не сейчас Разрыв строки внутри абзаца нельзя сохранить. Нажмите Enter, чтобы начать новый абзац. В этой ячейке формула, и она останется без изменений. diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index b81c6a38b41b..78f5a3c8acf2 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -43,7 +43,6 @@ Nazaj Shrani Stik z nami - Spodaj uredite dokument in pritisnite Shrani Ta aplikacija privzeto ponuja odpiranje samo za dokumente. Če druga aplikacija, na primer »Samsung Moje datoteke«, ne omogoča, da dokument odprete tukaj, vklopite to možnost, da se bo ponujala za vse vrste datotek. OpenDocument Reader vam kjer koli omogoča ogled dokumentov, shranjenih v obliki OpenDocument (.odt, .ods, .odp in .odg). Z aplikacijo OpenDocument Reader lahko berete in hitro iščete po dokumentih. @@ -103,8 +102,6 @@ Brez označevanja Pro Del različice Pro - Oblikovanje besedila ter dodajanje ali združevanje odstavkov je del aplikacije OpenDocument Reader Pro. - Označevanje PDF-jev je del aplikacije OpenDocument Reader Pro. Ne zdaj Preloma vrstice znotraj odstavka ni mogoče shraniti. Za nov odstavek pritisnite Enter. Ta celica vsebuje formulo in ostane nespremenjena. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index c2b3dd4d2c14..adfad298158d 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -43,7 +43,6 @@ Föregående Spara Kontakt - Redigera dokumentet nedan och tryck på Spara Som standard erbjuder appen bara att öppna dokumentfiler. Om en annan app, till exempel Samsung My Files, inte låter dig öppna ett dokument här, slå på det här så registreras appen för alla filtyper. Med OpenDocument Reader kan du öppna dokument som är sparade i OpenDocument-format (.odt, .ods, .odp och .odg) var du än är. Med OpenDocument Reader läser du dina dokument och söker i dem på nolltid. @@ -103,8 +102,6 @@ Ingen överstrykning Pro En del av Pro - Att formatera text och lägga till eller slå ihop stycken ingår i OpenDocument Reader Pro. - Att markera en PDF ingår i OpenDocument Reader Pro. Inte nu En radbrytning inuti ett stycke kan inte sparas. Tryck på Enter för ett nytt stycke. Cellen innehåller en formel och förblir som den är. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 52a1f590206f..2fc3225ae53b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -43,7 +43,6 @@ Önceki Kaydet İletişim - Belgenizi aşağıda düzenleyin ve Kaydet\'e dokunun Bu uygulama varsayılan olarak yalnızca belge dosyalarını açmayı önerir. Samsung My Files gibi başka bir uygulama belgeyi burada açmanıza izin vermiyorsa, uygulamanın tüm dosya türlerinde görünmesi için bunu açın. OpenDocument Reader, nerede olursanız olun OpenDocument biçiminde (.odt, .ods, .odp ve .odg) kayıtlı belgeleri görüntülemenizi sağlar. OpenDocument Reader ile belgelerinizi rahatça okuyabilir, içlerinde arama yapabilirsiniz. @@ -103,8 +102,6 @@ Vurgu yok Pro Pro\'nun bir parçası - Metni biçimlendirmek ve paragraf eklemek ya da birleştirmek OpenDocument Reader Pro\'nun bir parçasıdır. - PDF işaretlemek OpenDocument Reader Pro\'nun bir parçasıdır. Şimdi değil Paragraf içindeki bir satır sonu kaydedilemez. Yeni paragraf için Enter tuşuna basın. Bu hücre bir formül içeriyor ve olduğu gibi kalıyor. diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c7ab041c5319..d24d1ec5fd2e 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -43,7 +43,6 @@ 上一个 保存 联系我们 - 编辑您的文档,然后点击保存 本应用默认只在打开文档文件时出现。如果“三星我的文件”之类的应用不让您在这里打开文档,请开启此项,让本应用接受所有文件类型。 无论身在何处,OpenDocument Reader 都能让您查看以 OpenDocument 格式(.odt、.ods、.odp 和 .odg)保存的文档。 有了 OpenDocument Reader,阅读和搜索文档都轻而易举。 @@ -103,8 +102,6 @@ 无突出显示 Pro Pro 功能 - 设置文本格式以及添加或合并段落是 OpenDocument Reader Pro 的功能。 - 标注 PDF 是 OpenDocument Reader Pro 的功能。 以后再说 段落内的换行无法保存。按 Enter 键可新建段落。 该单元格包含公式,将保持不变。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4aea09e923b..33187c6ff6ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -64,7 +64,6 @@ Previous Save Contact - Edit your document below and press Save By default this app only offers to open document files. If another app like \"Samsung My Files\" won\'t let you open a document here, turn this on to register for all file types. OpenDocument Reader allows you to view documents that are stored in OpenDocument format (.odt, .ods, .odp and .odg) wherever you are. With OpenDocument Reader you can read and search through your documents in a breeze. @@ -99,6 +98,7 @@ Mark up PDF + Select text, then a tool, to mark it Undo Redo @@ -109,9 +109,9 @@ Text color Highlight Text size - + %1$s color - + %1$s pt Highlight Underline @@ -127,10 +127,11 @@ No highlight Pro - + Part of Pro - Formatting text, and adding or joining paragraphs, is part of OpenDocument Reader Pro. - Marking up a PDF is part of OpenDocument Reader Pro. + Bold, italic, colors and text size, and adding or joining paragraphs, are part of OpenDocument Reader Pro. The highlighter is in every edition. + Underlining, striking out and drawing on a PDF are part of OpenDocument Reader Pro. The highlighter is in every edition. Not now A line break inside a paragraph cannot be saved. Press Enter for a new paragraph. From e4350b502aaee50cc5ac89ef9eeda1b71797f995 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sun, 20 Sep 2026 13:19:34 +0200 Subject: [PATCH 2/2] Draw the highlighter as one, rather than as a third pencil `ic_marker` was Material's `edit` icon, so the highlight tool, the Draw tool and the Edit button were all pencils: `ic_edit` on the button that edits a document, `ic_marker` on the one that marks up a pdf and on the highlighter beside `ic_draw` in the strip. At 22dp they were not worth telling apart. It is now a highlighter: the wedge nib that says the ink goes on in a band, the barrel, and the cap at the back. The colour it lays down is still the bar under it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01FJJdfqpnVCKBNHXjAVxSou --- app/src/main/res/drawable/ic_marker.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/drawable/ic_marker.xml b/app/src/main/res/drawable/ic_marker.xml index 233fb5008a0f..e05e8d2153f2 100644 --- a/app/src/main/res/drawable/ic_marker.xml +++ b/app/src/main/res/drawable/ic_marker.xml @@ -1,3 +1,8 @@ + + android:pathData="M7.27,21.67L9.4,19.55L4.45,14.6L2.33,16.73Z"/> + +