Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="@string/action_create">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter android:label="@string/action_create">
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<!-- Voice command "note to self" in google search -->
<intent-filter android:label="@string/action_create">
<action android:name="com.google.android.gm.action.AUTO_SEND" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class NotesApplication extends Application {
private static boolean isLocked = true;
private static long lastInteraction = 0;
private static String PREF_KEY_THEME;
private static boolean isGridViewEnabled = false;
private static boolean isGridViewEnabled = true;
private static boolean isSwipeEnabled = true;
private static BrandingUtil brandingUtil;

Expand All @@ -45,7 +45,7 @@ public void onCreate() {
setAppTheme(getAppTheme(getApplicationContext()));
final var prefs = getDefaultSharedPreferences(getApplicationContext());
lockedPreference = prefs.getBoolean(getString(R.string.pref_key_lock), false);
isGridViewEnabled = getDefaultSharedPreferences(this).getBoolean(getString(R.string.pref_key_gridview), false);
isGridViewEnabled = getDefaultSharedPreferences(this).getBoolean(getString(R.string.pref_key_gridview), true);
isSwipeEnabled = getDefaultSharedPreferences(this).getBoolean(getString(R.string.pref_key_swipe_actions), true);
super.onCreate();
brandingUtil = BrandingUtil.getInstance(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static it.niedermann.owncloud.notes.edit.EditNoteActivity.ACTION_SHORTCUT;
import static it.niedermann.owncloud.notes.shared.util.WidgetUtil.pendingIntentFlagCompat;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
Expand All @@ -21,6 +22,11 @@
import android.view.View;
import android.widget.ScrollView;

import android.net.Uri;
import android.widget.Toast;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -53,6 +59,7 @@
import it.niedermann.owncloud.notes.shared.model.DBStatus;
import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
import it.niedermann.owncloud.notes.shared.util.ApiVersionUtil;
import it.niedermann.owncloud.notes.shared.util.NoteImageHelper;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
import it.niedermann.owncloud.notes.shared.util.ShareUtil;

Expand Down Expand Up @@ -83,6 +90,8 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego

protected boolean isNew = true;

private ActivityResultLauncher<String> pickImageLauncher;

@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Expand All @@ -94,6 +103,12 @@ public void onAttach(@NonNull Context context) {
repo = NotesRepository.getInstance(context);
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pickImageLauncher = registerForActivityResult(new ActivityResultContracts.GetContent(), this::onImagePicked);
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Expand Down Expand Up @@ -239,6 +254,12 @@ public void onPrepareOptionsMenu(@NonNull Menu menu) {
if (note != null) {
prepareFavoriteOption(menu.findItem(R.id.menu_favorite));

final var attachItem = menu.findItem(R.id.menu_attach_image);
if (attachItem != null) {
final var utils = BrandingUtil.of(colorAccent, requireContext());
utils.platform.colorToolbarMenuIcon(requireContext(), attachItem);
}

final var preferredApiVersion = ApiVersionUtil.getPreferredApiVersion(localAccount.getApiVersion());
menu.findItem(R.id.menu_title).setVisible(preferredApiVersion != null && preferredApiVersion.compareTo(ApiVersion.API_VERSION_1_0) >= 0);
menu.findItem(R.id.menu_delete).setVisible(!isNew);
Expand All @@ -264,7 +285,12 @@ private void prepareFavoriteOption(MenuItem item) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
if (itemId == R.id.menu_cancel) {
if (itemId == R.id.menu_attach_image) {
if (pickImageLauncher != null) {
pickImageLauncher.launch("image/*");
}
return true;
} else if (itemId == R.id.menu_cancel) {
executor.submit(() -> {
if (originalNote == null) {
repo.deleteNoteAndSync(localAccount, note.getId());
Expand Down Expand Up @@ -315,6 +341,63 @@ public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}

private void onImagePicked(@Nullable Uri uri) {
if (uri == null) {
return;
}
final Context context = getContext();
if (context == null) {
return;
}
Toast.makeText(context, R.string.uploading_image, Toast.LENGTH_SHORT).show();
executor.submit(() -> {
try {
final var ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext().getApplicationContext());
final String attachmentPath = NoteImageHelper.uploadImage(requireContext().getApplicationContext(), ssoAccount, uri);
final String markdown = NoteImageHelper.formatMarkdownImage(attachmentPath);
final Activity activity = getActivity();
if (activity != null && isAdded()) {
activity.runOnUiThread(() -> {
if (isAdded() && getContext() != null) {
insertImageMarkdown(markdown);
Toast.makeText(getContext(), R.string.image_attached, Toast.LENGTH_SHORT).show();
}
});
}
} catch (Exception e) {
Log_OC.e(TAG, "Failed to upload image attachment", e);
final Activity activity = getActivity();
if (activity != null && isAdded()) {
activity.runOnUiThread(() -> {
if (getContext() != null) {
Toast.makeText(getContext(), "Failed to attach image: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
}
});
}
}
});
}

public void insertImageMarkdown(@NonNull String markdown) {
if (this instanceof NoteEditFragment) {
((NoteEditFragment) this).insertTextAtCursor(markdown);
saveNote(null);
} else {
if (note != null) {
String current = note.getContent();
String updated = (current == null || current.isEmpty()) ? markdown : (current + markdown);
note = repo.updateNoteAndSync(localAccount, note, updated, null, null);
if (listener != null) {
listener.onNoteUpdated(note);
}
requireActivity().invalidateOptionsMenu();
if (this instanceof NotePreviewFragment) {
((NotePreviewFragment) this).onNoteLoaded(note);
}
}
}
}

private void pinNoteToHome() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,27 @@
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.net.Uri;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.widget.ProgressBar;
import android.widget.Toast;

import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
Expand All @@ -36,7 +41,9 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;

import it.niedermann.android.sharedpreferences.SharedPreferenceBooleanLiveData;
Expand All @@ -50,6 +57,7 @@
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
import it.niedermann.owncloud.notes.shared.util.NoteImageHelper;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
import it.niedermann.owncloud.notes.shared.util.ShareUtil;

Expand Down Expand Up @@ -343,6 +351,16 @@ private void launchNewNote() {
favorite = categoryPreselection.getType() == FAVORITES;
}

final String action = intent.getAction();
final String type = intent.getType();
final boolean isImageShare = (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action))
&& ((type != null && type.startsWith("image/")) || intent.hasExtra(Intent.EXTRA_STREAM));

if (isImageShare) {
handleImageShare(intent, categoryTitle, favorite);
return;
}

String content = "";
if (
intent.hasExtra(Intent.EXTRA_TEXT) &&
Expand All @@ -363,6 +381,83 @@ private void launchNewNote() {
replaceFragment();
}

private void handleImageShare(Intent intent, String categoryTitle, boolean favorite) {
final List<Uri> imageUris = new ArrayList<>();
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (uris != null) {
imageUris.addAll(uris);
}
} else {
Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (uri != null) {
imageUris.add(uri);
} else if (intent.getData() != null) {
imageUris.add(intent.getData());
}
}

final String initialText = intent.hasExtra(Intent.EXTRA_TEXT) ? ShareUtil.extractSharedText(intent) : "";

if (imageUris.isEmpty()) {
final String content = initialText != null ? initialText : "";
final var newNote = new Note(null, Calendar.getInstance(), NoteUtil.generateNonEmptyNoteTitle(content, this), content, categoryTitle, favorite, null, false, false);
fragment = getNewNoteFragment(newNote);
replaceFragment();
return;
}

final ProgressBar progressBar = new ProgressBar(this);
progressBar.setPadding(0, 48, 0, 48);
final AlertDialog progressDialog = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.uploading_image)
.setView(progressBar)
.setCancelable(false)
.show();

new Thread(() -> {
StringBuilder contentBuilder = new StringBuilder();
if (initialText != null && !initialText.trim().isEmpty()) {
contentBuilder.append(initialText).append("\n\n");
}

try {
final SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
final String notesPath = NoteImageHelper.getNotesPath(getApplicationContext(), ssoAccount, repo);

for (Uri uri : imageUris) {
try {
String attachmentPath = NoteImageHelper.uploadImage(getApplicationContext(), ssoAccount, notesPath, uri);
contentBuilder.append(NoteImageHelper.formatMarkdownImage(attachmentPath));
} catch (Exception e) {
Log.e(TAG, "Failed to upload shared image " + uri, e);
}
}
} catch (Exception e) {
Log.e(TAG, "Failed to get SSO account or notes path for image share", e);
}

final String finalContent = contentBuilder.toString().trim();
runOnUiThread(() -> {
if (isFinishing() || isDestroyed()) {
return;
}
try {
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
} catch (Exception ignored) {
}

String title = NoteUtil.generateNonEmptyNoteTitle(finalContent, EditNoteActivity.this);
final var newNote = new Note(null, Calendar.getInstance(), title, finalContent, categoryTitle, favorite, null, false, false);
fragment = getNewNoteFragment(newNote);
replaceFragment();
Toast.makeText(EditNoteActivity.this, R.string.image_attached, Toast.LENGTH_SHORT).show();
});
}).start();
}


private void launchReadonlyNote() {
final var intent = getIntent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,20 @@ protected void onNoteLoaded(Note note) {
});
}

public void insertTextAtCursor(@NonNull String text) {
if (binding == null || binding.editContent == null) {
return;
}
int start = Math.max(0, binding.editContent.getSelectionStart());
int end = Math.max(0, binding.editContent.getSelectionEnd());
int selStart = Math.min(start, end);
int selEnd = Math.max(start, end);
if (binding.editContent.getText() != null) {
binding.editContent.getText().replace(selStart, selEnd, text, 0, text.length());
binding.editContent.setSelection(selStart + text.length());
}
}

private void openSoftKeyboard() {
binding.editContent.postDelayed(() -> {
binding.editContent.requestFocus();
Expand Down
Loading