Skip to content
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@
<data android:mimeType="text/html" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data
android:scheme="markor"
android:host="open" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.VIEW" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,29 @@
import other.so.AndroidBug5497Workaround;

public class DocumentActivity extends MarkorBaseActivity {

private Toolbar _toolbar;
private FragmentManager _fragManager;

public static void launch(final Activity activity, final Intent intent) {
final File file = MarkorContextUtils.getIntentFile(intent);
public static void launch(final Activity activity, final Intent rawIntent) {
if (activity == null || rawIntent == null) {
return;
}

final Intent intent = DocumentActivity.normalizeIntent(rawIntent, activity);
final File file = (File) intent.getSerializableExtra(Document.EXTRA_FILE);
final Integer lineNumber = intent.hasExtra(Document.EXTRA_FILE_LINE_NUMBER) ? intent.getIntExtra(Document.EXTRA_FILE_LINE_NUMBER, -1) : null;
final Boolean doPreview = intent.hasExtra(Document.EXTRA_DO_PREVIEW) ? intent.getBooleanExtra(Document.EXTRA_DO_PREVIEW, false) : null;
launch(activity, file, doPreview, lineNumber);
}

public static void launch(final Activity activity, final Uri uri) {
if (activity == null || uri == null) {
return;
}

launch(activity, new Intent(Intent.ACTION_VIEW, uri));
}

public static void launch(
final Activity activity,
final File file,
Expand Down Expand Up @@ -155,19 +167,17 @@ protected void onNewIntent(Intent intent) {
handleLaunchingIntent(intent);
}

private void handleLaunchingIntent(final Intent intent) {
if (intent == null) return;
private void handleLaunchingIntent(final Intent rawIntent) {
if (rawIntent == null) return;

final Intent intent = normalizeIntent(rawIntent, this);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function normalizes the incoming intent

final String intentAction = intent.getAction();
final Uri intentData = intent.getData();

// Pull the file from the intent
// -----------------------------------------------------------------------
final File file = MarkorContextUtils.getIntentFile(intent, this);
final File file = (File) intent.getSerializableExtra(Document.EXTRA_FILE);

final boolean intentIsView = Intent.ACTION_VIEW.equals(intentAction);
final boolean intentIsSend = Intent.ACTION_SEND.equals(intentAction) || Intent.ACTION_SEND_MULTIPLE.equals(intentAction);
final boolean intentIsEdit = Intent.ACTION_EDIT.equals(intentAction);

if (intentIsSend) {
showShareInto(intent);
Expand All @@ -183,24 +193,23 @@ private void handleLaunchingIntent(final Intent intent) {
if (file == null || !_cu.canWriteFile(this, file, false, true)) {
showNotSupportedMessage();
} else {
Integer startLine = null;
// Open in editor/viewer
final Document doc = new Document(file);
final Integer startLine;
if (intent.hasExtra(Document.EXTRA_FILE_LINE_NUMBER)) {
startLine = intent.getIntExtra(Document.EXTRA_FILE_LINE_NUMBER, -1);
} else if (intentData != null) {
final String line = intentData.getQueryParameter("line");
if (line != null) {
startLine = GsTextUtils.tryParseInt(line, -1);
}
} else {
startLine = null;
}

// Start in a specific mode if required. Otherwise let the fragment decide
Boolean startInPreview = null;
if (intent.getBooleanExtra(Document.EXTRA_DO_PREVIEW, false) ||
file.getName().startsWith("index.")
) {
final Boolean startInPreview;
if (intent.hasExtra(Document.EXTRA_DO_PREVIEW)) {
startInPreview = intent.getBooleanExtra(Document.EXTRA_DO_PREVIEW, false);
} else if (file.getName().startsWith("index.")) {
startInPreview = true;
} else {
startInPreview = null;
}

// Three cases
Expand Down Expand Up @@ -231,13 +240,45 @@ private void handleLaunchingIntent(final Intent intent) {
}
}

private boolean isDocumentAlreadyOpen(final Document doc) {
final GsFragmentBase<?, ?> frag = getCurrentVisibleFragment();
if (frag instanceof DocumentEditAndViewFragment) {
final DocumentEditAndViewFragment editFrag = (DocumentEditAndViewFragment) frag;
return editFrag.getDocument().path.equals(doc.path);
private static Intent normalizeIntent(final Intent rawIntent, final Activity activity) {
final Intent intent = new Intent(rawIntent);
final Uri uri = intent.getData();

if (!intent.hasExtra(Document.EXTRA_FILE)) {
final File file = MarkorContextUtils.getIntentFile(intent, activity);
if (file != null) {
intent.putExtra(Document.EXTRA_FILE, file.getAbsoluteFile());
}
}

if (!intent.hasExtra(Document.EXTRA_FILE_LINE_NUMBER) && uri != null) {
final Integer lineNumber = parseNormalizedLine(uri);
if (lineNumber != null) {
intent.putExtra(Document.EXTRA_FILE_LINE_NUMBER, lineNumber);
}
}

if (!intent.hasExtra(Document.EXTRA_DO_PREVIEW) && uri != null) {
final String view = uri.getQueryParameter("view");
if (view != null) {
final Boolean doPreview = GsTextUtils.tryParseBool(view);
if (doPreview != null) {
intent.putExtra(Document.EXTRA_DO_PREVIEW, doPreview);
}
}
}

return intent;
}

private static Integer parseNormalizedLine(final Uri uri) {
final String line = uri != null ? uri.getQueryParameter("line") : null;
if (line == null) {
return null;
}
return false;

final int lineNumber = GsTextUtils.tryParseInt(line, -1);
return lineNumber >= -1 ? lineNumber : null;
}

private void showNotSupportedMessage() {
Expand Down Expand Up @@ -357,4 +398,4 @@ public boolean onKeyDown(int keyCode, KeyEvent event) {
private GsFragmentBase<?, ?> getCurrentVisibleFragment() {
return (GsFragmentBase<?, ?>) getSupportFragmentManager().findFragmentById(R.id.document__placeholder_fragment);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,40 +251,38 @@ public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
protected void onFragmentFirstTimeVisible() {
final Bundle args = getArguments();
final boolean hasLineNumber = args != null && args.containsKey(Document.EXTRA_FILE_LINE_NUMBER);
int startPos = _appSettings.getLastEditPosition(_document.path, _hlEditor.length());
final int targetSelection;
if (hasLineNumber) {
final int lineNumber = args.getInt(Document.EXTRA_FILE_LINE_NUMBER);
startPos = lineNumber >= 0
targetSelection = lineNumber >= 0
? TextViewUtils.getIndexFromLineOffset(_hlEditor.getText(), lineNumber, 0)
: _hlEditor.length();
} else {
_hlEditor.setSelection(startPos);
targetSelection = _appSettings.getLastEditPosition(_document.path, _hlEditor.length());
}

// Restore scroll position for view-mode
if (_webView != null) {
int lastViewHeight = _appSettings.getLastViewHeight(_document.path, 0);
int lastViewScrollY = _appSettings.getLastViewScrollY(_document.path, 0);
final int lastViewHeight = _appSettings.getLastViewHeight(_document.path, 0);
final int lastViewScrollY = _appSettings.getLastViewScrollY(_document.path, 0);
if (lastViewScrollY > 0 && lastViewHeight == _webView.getHeight()) {
_verticalScrollView.post(() -> _webView.scrollTo(0, lastViewScrollY));
}
}

_hlEditor.recomputeHighlighting();
if (hasLineNumber) {
TextViewUtils.setSelectionAndShow(_hlEditor, startPos);
} else {
final int lastEditHeight = _appSettings.getLastEditHeight(_document.path, 0);
final int lastEditScrollY = _appSettings.getLastEditScrollY(_document.path, 0);
final int fallbackPos = startPos;
_verticalScrollView.post(() -> {
// Defer initial placement until after the highlight-triggered reflow work has run.
_hlEditor.post(() -> {
if (!hasLineNumber) {
final int lastEditHeight = _appSettings.getLastEditHeight(_document.path, 0);
if (lastEditHeight > 0 && lastEditHeight == _verticalScrollView.getHeight()) {
final int lastEditScrollY = _appSettings.getLastEditScrollY(_document.path, 0);
_hlEditor.setSelection(targetSelection);
_verticalScrollView.scrollTo(0, lastEditScrollY);
} else {
TextViewUtils.setSelectionAndShow(_hlEditor, fallbackPos);
return;
}
});
}
}
_hlEditor.post(() -> TextViewUtils.setSelectionAndShow(_hlEditor, targetSelection));
});

// Fade in to hide initial jank
_hlEditor.post(() -> _hlEditor.animate().alpha(1).setDuration(250).start());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1363,7 +1363,7 @@ public static void showSelectLinesDialog(
CharSequence text = editText.getText();
int selectionStart = startLine == 0 ? 0 : TextViewUtils.getIndexFromLineOffset(text, startLine - 1, 0) + 1;
int selectionEnd = endLine == -1 ? editText.length() : TextViewUtils.getIndexFromLineOffset(text, endLine, 0);
editText.setSelection(selectionStart, selectionEnd);
TextViewUtils.setSelectionAndShow(editText, selectionStart, selectionEnd);
};

GsSearchOrCustomTextDialog.showMultiChoiceDialogWithSearchFilterUI(activity, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,41 +338,34 @@ public static void selectLines(final EditText edit, final List<Integer> position
}
}

public static void showSelection(final TextView text, Rect visible, final int start, final int end, int offsetY) {
// Get view info
// ------------------------------------------------------------
public static void showSelection(final TextView text, Rect visibleRect, final int start, final int end) {
final Layout layout = text.getLayout();
if (layout == null) {
return;
}

final int _start = Math.min(start, end);
final int _end = Math.max(start, end);
if (_start < 0 || _end > text.length()) {
final int startOffset = Math.min(start, end);
final int endOffset = Math.max(start, end);
if (startOffset < 0 || endOffset > text.length()) {
return;
}
final int lineStart = TextViewUtils.getLineStart(text.getText(), _start);
final int lineStart = TextViewUtils.getLineStart(text.getText(), startOffset);

// Region in Y
// ------------------------------------------------------------
final int startLine = layout.getLineForOffset(lineStart);
final int startLineTop = layout.getLineTop(startLine);

final int endLine = layout.getLineForOffset(_end);
final int endLine = layout.getLineForOffset(endOffset);
final int endLineBottom = layout.getLineBottom(endLine);
final int endLineTop = layout.getLineTop(endLine);
final int lineHeight = endLineBottom - endLineTop;
final int lineHeight = text.getLineHeight();
final int viewportHeight = Math.max(visibleRect.height(), lineHeight);
final int contextLines = 2;
final int desiredTop = Math.max(startLineTop - contextLines * lineHeight, 0);

final Rect region = new Rect();
region.top = Math.max(startLineTop, endLineBottom - visible.height() + lineHeight) + offsetY;
region.bottom = endLineBottom + offsetY;

// Region in X - as handling RTL, text alignment, and centred text etc is
// a huge pain (see TextView.bringPointIntoView), we use a very simple solution.
// ------------------------------------------------------------
final int startLeft = (int) layout.getPrimaryHorizontal(_start);
final int halfWidth = visible.width() / 2;
// Push the start to the middle of the screen
region.top = desiredTop;
region.bottom = Math.max(desiredTop + viewportHeight, endLineBottom + lineHeight);

final int startLeft = (int) layout.getPrimaryHorizontal(startOffset);
final int halfWidth = visibleRect.width() / 2;
region.left = Math.max(startLeft - halfWidth, 0);
region.right = Math.min(startLeft + halfWidth, text.getWidth());

Expand All @@ -382,7 +375,11 @@ public static void showSelection(final TextView text, Rect visible, final int st
public static void showSelection(final TextView text, final int start, final int end) {
Rect visible = new Rect();
text.getLocalVisibleRect(visible);
showSelection(text, visible, start, end, visible.height() - text.getLineHeight());
showSelection(text, visible, start, end);
}

public static void showSelection(final TextView text, final int selection) {
showSelection(text, selection, selection);
}

public static void showSelection(final TextView text) {
Expand All @@ -402,32 +399,9 @@ public static void setSelectionAndShow(final EditText edit, final int... sel) {
edit.requestFocus();
}

edit.setSelection(start, end);
showSelection(edit, start, end);
}
}

/**
* Scroll EditText view to the region that contains the start selection and don‘t insert this selection.
* If start selection is already in current visible region, it will not scroll EditText view.
*
* @param editText EditText view
* @param startSelection Start selection
*/
public static void showSelection(final EditText editText, final int startSelection) {
Layout layout = editText.getLayout();
if (layout == null) {
return;
}

Rect visible = new Rect();
editText.getLocalVisibleRect(visible);
int line = layout.getLineForOffset(startSelection);
int lineHeight = editText.getLineHeight();
if (layout.getLineTop(line) < visible.top - lineHeight) {
showSelection(editText, visible, startSelection, startSelection, -lineHeight * 3);
} else if (layout.getLineBottom(line) > visible.bottom - lineHeight) {
showSelection(editText, visible, startSelection, startSelection, lineHeight * 2);
// Let the reveal request settle before selection handling triggers caret visibility work.
edit.post(() -> edit.setSelection(start, end));
}
}

Expand Down
28 changes: 23 additions & 5 deletions app/src/main/java/net/gsantner/markor/util/MarkorContextUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import net.gsantner.markor.activity.openeditor.OpenEditorTodoActivity;
import net.gsantner.markor.activity.openeditor.OpenFromShortcutOrWidgetActivity;
import net.gsantner.markor.activity.openeditor.OpenShareIntoActivity;
import net.gsantner.markor.model.AppSettings;
import net.gsantner.markor.model.Document;
import net.gsantner.opoc.frontend.filebrowser.GsFileBrowserListAdapter;
import net.gsantner.opoc.util.GsContextUtils;
Expand All @@ -35,6 +36,9 @@
import java.io.File;

public class MarkorContextUtils extends GsContextUtils {
private static final String DEEP_LINK_SCHEME = "markor";
private static final String DEEP_LINK_HOST_OPEN = "open";
private static final String DEEP_LINK_PARAM_PATH = "path";

public MarkorContextUtils(@Nullable final Context context) {
if (context != null) {
Expand Down Expand Up @@ -101,17 +105,31 @@ public static File getIntentFile(final Intent intent, final @Nullable Context co
// By extra path
File file = (File) intent.getSerializableExtra(Document.EXTRA_FILE);

// By Markor URI/deep link path
final Uri uri = intent.getData();
if (file == null && uri != null) {
final String scheme = uri.getScheme();
final String host = uri.getHost();
final boolean markorOpen = DEEP_LINK_SCHEME.equalsIgnoreCase(scheme) && DEEP_LINK_HOST_OPEN.equalsIgnoreCase(host);
if (markorOpen) {
final String path = uri.getQueryParameter(DEEP_LINK_PARAM_PATH);
if (!TextUtils.isEmpty(path)) {
file = new File(path);
if (!file.isAbsolute() && context != null) {
file = new File(AppSettings.get(context).getNotebookDirectory(), path);
}
}
}
}

// By stream etc
if (file == null && context != null) {
file = GsContextUtils.extractFileFromIntent(intent, context);
}

// By url in data
if (file == null) {
try {
file = new File(intent.getData().getPath());
} catch (NullPointerException ignored) {
}
if (file == null && uri != null && uri.getPath() != null) {
file = new File(uri.getPath());
}

return file;
Expand Down
Loading
Loading