Commit 99eb22bb authored by Romain Hunault's avatar Romain Hunault
Browse files

Merge branch 'sprint' into 'master'

[RELEASE] Sprint 8

See merge request e/apps/Notes!2
parents 122a8ab6 2404c62f
Pipeline #42693 passed with stage
in 9 minutes and 3 seconds
......@@ -6,3 +6,4 @@
/.idea/
*.iml
/projectFilesBackup/
/app/release/
\ No newline at end of file
[submodule "cert4android"]
path = cert4android
url = ../cert4android.git
branch = sprint
......@@ -6,10 +6,11 @@ android:
components:
- tools
- build-tools-27.0.3
- android-27
- android-28
- extra-android-m2repository
before_install:
- yes | sdkmanager "platforms;android-27"
- yes | sdkmanager "platforms;android-28"
script: ./gradlew testDebug
......@@ -8,28 +8,41 @@ An android client for [Nextcloud Notes App](https://github.com/nextcloud/notes/)
## :arrow_forward: Access
<<<<<<< HEAD
[![Download from Google Play](https://play.google.com/intl/en_us/badges/images/badge_new.png)](https://play.google.com/store/apps/details?id=it.niedermann.notes)
[![Nextcloud Notes App on fdroid.org](https://camo.githubusercontent.com/7df0eafa4433fa4919a56f87c3d99cf81b68d01c/68747470733a2f2f662d64726f69642e6f72672f77696b692f696d616765732f632f63342f462d44726f69642d627574746f6e5f617661696c61626c652d6f6e2e706e67)](https://f-droid.org/repository/browse/?fdid=it.niedermann.notes)
[![Donate with PayPal](https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/master/paypal-donate-button.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K7HVLE6J7SXXA)
=======
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Play Store"
height="80">](https://play.google.com/store/apps/details?id=it.niedermann.owncloud.notes)
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/repository/browse/?fdid=it.niedermann.owncloud.notes)
[<img src="https://raw.githubusercontent.com/stefan-niedermann/paypal-donate-button/be5ad29a95ff890faee0b46d702b714b0a04aadc/paypal-donate-button.png"
alt="Donate with PayPal"
height="80">](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=K7HVLE6J7SXXA)
>>>>>>> steff_master
[![Donate using Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/stefan-niedermann/donate)
## :eyes: Screenshots
![Screenshot of list view](/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png)
![Screenshot of edit view](/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png)
| Navi | List View | Edit Mode |
| :--: | :--: | :--: |
| ![Screenshot of categories in sidebar](/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png) | ![Screenshot of list view](/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png) | ![Screenshot of edit view](/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png) |
## :rocket: Features
* List, create, edit, share, search and delete notes
* Share text and links as new note into the app
* Mark notes as favorite
* Bulk delete
* In-note search ([#106](https://github.com/stefan-niedermann/nextcloud-notes/issues/106))
* Render MarkDown (using [RxMarkdown](https://github.com/yydcdut/RxMarkdown))
* Translated in many languages on [Transifex](https://www.transifex.com/nextcloud/nextcloud/android-notes/)
* Context based formatting ([#363](https://github.com/stefan-niedermann/nextcloud-notes/issues/363))
## :checkered_flag: Planned features
* In-note search ([#106](https://github.com/stefan-niedermann/nextcloud-notes/issues/106))
* Toggle checkboxes in view mode ([#451](https://github.com/stefan-niedermann/nextcloud-notes/issues/451))
* Context based formatting ([#363](https://github.com/stefan-niedermann/nextcloud-notes/issues/363))
* Trashbin ([#238](https://github.com/stefan-niedermann/nextcloud-notes/issues/238))
## :family: Join the team
......
# SSO Announcment
The next version of Notes for android will depend on Nextcloud Single-Sign-On.
## What do you need to do?
It is recommended, to perform a full synchronisation with the old version of the Notes app, before you upgrade.
You have likely installed an up-to-date version of the [Files app](https://play.google.com/store/apps/details?id=com.nextcloud.client). In this case, you will have nothing more to do.
In case you do not have it installed yet, you can get it for free from Play Store or F-Droid.
When you upgrade the Notes app, you will be asked to select an account (which you previously configured at the files app).
It is important that you **select** at the first run **the same account which you already were using** in the Notes app. This will make the first run smooth and make sure, that local edited, but not synced notes won't get lost.
## Benefits for you
- **:lock: Security benefits:** The notes app does no longer have to store a password itself.
- **:electric_plug: Reliability:** The complete network stack could be removed because all network activities are lead through the files app. This allows us to use e. g. the same stack for self signed certificates.
- **:bulb: Comfort:** You won't have to enter a server address, nor a username or a password. Just pick an existing account from the list.
......@@ -2,14 +2,19 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
buildToolsVersion '29.0.1'
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "foundation.e.notes"
minSdkVersion 24
targetSdkVersion 28
versionCode 37
versionName "0.22.3"
versionCode 49
versionName "1.0.1"
}
buildTypes {
release {
......@@ -21,26 +26,30 @@ android {
disable 'MissingTranslation'
abortOnError false
}
dataBinding {
enabled = true
}
}
dependencies {
implementation project(':cert4android')
implementation 'io.reactivex:rxandroid:1.2.0'
implementation 'io.reactivex:rxjava:1.1.5'
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.3.8'
implementation 'com.yydcdut:markdown-processor:0.1.3'
implementation 'com.yydcdut:rxmarkdown-wrapper:0.1.3'
implementation 'com.jakewharton:butterknife:8.8.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
def supportLibVersion = '28.0.0'
implementation 'com.github.bumptech.glide:glide:4.10.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
implementation 'com.jakewharton:butterknife:10.2.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
implementation "androidx.appcompat:appcompat:1.1.0"
implementation "androidx.recyclerview:recyclerview:1.0.0"
implementation "com.google.android.material:material:1.0.0"
implementation "com.android.support:appcompat-v7:${supportLibVersion}"
implementation "com.android.support:design:${supportLibVersion}"
implementation "com.android.support:recyclerview-v7:${supportLibVersion}"
// needed for cert4android (conflict resolution)
implementation "com.android.support:cardview-v7:${supportLibVersion}"
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
......@@ -69,6 +69,12 @@
<data android:mimeType="text/plain" />
</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"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
<activity
android:name="foundation.e.notes.android.activity.AboutActivity"
......@@ -87,6 +93,10 @@
</intent-filter>
</activity>
<activity
android:name=".android.activity.ExceptionActivity"
android:process=":error_activity" />
<receiver
android:name="foundation.e.notes.android.appwidget.CreateNoteWidget"
android:label="@string/widget_create_note">
......
package foundation.e.notes.android;
import android.content.Context;
import android.support.v7.widget.AppCompatAutoCompleteTextView;
import androidx.appcompat.widget.AppCompatAutoCompleteTextView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.WindowManager;
......
package foundation.e.notes.android.activity;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
......@@ -14,9 +16,7 @@ import foundation.e.notes.android.fragment.about.AboutFragmentContributingTab;
import foundation.e.notes.android.fragment.about.AboutFragmentCreditsTab;
import foundation.e.notes.android.fragment.about.AboutFragmentLicenseTab;
import foundation.e.notes.R;
import foundation.e.notes.android.fragment.about.AboutFragmentContributingTab;
import foundation.e.notes.android.fragment.about.AboutFragmentCreditsTab;
import foundation.e.notes.android.fragment.about.AboutFragmentLicenseTab;
import foundation.e.notes.util.ExceptionHandler;
public class AboutActivity extends AppCompatActivity {
......@@ -28,6 +28,7 @@ public class AboutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
setContentView(R.layout.activity_about);
ButterKnife.bind(this);
......@@ -87,4 +88,10 @@ public class AboutActivity extends AppCompatActivity {
}
}
}
}
\ No newline at end of file
@Override
public boolean onSupportNavigateUp() {
finish(); // close this activity as oppose to navigating up
return true;
}
}
......@@ -5,7 +5,7 @@ import android.accounts.AccountManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
......
......@@ -5,28 +5,30 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Calendar;
import foundation.e.notes.android.fragment.BaseNoteFragment;
import foundation.e.notes.android.fragment.NoteEditFragment;
import foundation.e.notes.android.fragment.NotePreviewFragment;
import foundation.e.notes.R;
import foundation.e.notes.android.fragment.BaseNoteFragment;
import foundation.e.notes.android.fragment.NoteEditFragment;
import foundation.e.notes.android.fragment.NotePreviewFragment;
import foundation.e.notes.model.Category;
import foundation.e.notes.model.CloudNote;
import foundation.e.notes.model.DBNote;
import foundation.e.notes.util.NoteUtil;
import foundation.e.notes.util.ExceptionHandler;
public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragment.NoteFragmentListener {
public static final String ACTION_SHORTCUT = "it.niedermann.owncloud.notes.shortcut";
private static final String INTENT_GOOGLE_ASSISTANT = "com.google.android.gm.action.AUTO_SEND";
private static final String MIMETYPE_TEXT_PLAIN = "text/plain";
public static final String PARAM_NOTE_ID = "noteId";
public static final String PARAM_CATEGORY = "category";
......@@ -35,6 +37,7 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
if (savedInstanceState == null) {
launchNoteFragment();
......@@ -118,6 +121,7 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm
} else {
fragment = NotePreviewFragment.newInstance(noteId);
}
if (savedState != null) {
fragment.setInitialSavedState(savedState);
}
......@@ -140,7 +144,11 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm
}
String content = "";
if (Intent.ACTION_SEND.equals(intent.getAction()) && "text/plain".equals(intent.getType())) {
if (
MIMETYPE_TEXT_PLAIN.equals(intent.getType()) &&
(Intent.ACTION_SEND.equals(intent.getAction()) ||
INTENT_GOOGLE_ASSISTANT.equals(intent.getAction()))
) {
content = intent.getStringExtra(Intent.EXTRA_TEXT);
}
......@@ -201,9 +209,9 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(note.getTitle());
if(!note.getCategory().isEmpty()) {
if (!note.getCategory().isEmpty()) {
actionBar.setSubtitle(NoteUtil.extendCategory(note.getCategory()));
}
}
}
}
\ No newline at end of file
}
package foundation.e.notes.android.activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Objects;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import foundation.e.notes.BuildConfig;
import foundation.e.notes.R;
public class ExceptionActivity extends AppCompatActivity {
Throwable throwable;
@BindView(R.id.message)
TextView message;
@BindView(R.id.stacktrace)
TextView stacktrace;
public static final String KEY_THROWABLE = "T";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_exception);
ButterKnife.bind(this);
super.onCreate(savedInstanceState);
throwable = ((Throwable) getIntent().getSerializableExtra(KEY_THROWABLE));
throwable.printStackTrace();
Objects.requireNonNull(getSupportActionBar()).setTitle(getString(R.string.simple_error));
this.message.setText(throwable.getMessage());
this.stacktrace.setText("Version: " + BuildConfig.VERSION_NAME + "\n\n" + getStacktraceOf(throwable));
}
private String getStacktraceOf(Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
@OnClick(R.id.copy)
void copyStacktraceToClipboard() {
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.simple_exception), this.stacktrace.getText());
clipboardManager.setPrimaryClip(clipData);
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.close)
void close() {
finish();
}
}
package foundation.e.notes.android.activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import foundation.e.notes.android.fragment.PreferencesFragment;
import foundation.e.notes.android.fragment.PreferencesFragment;
import foundation.e.notes.util.ExceptionHandler;
/**
* Allows to change application settings.
......@@ -15,6 +16,7 @@ public class PreferencesActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
setResult(RESULT_CANCELED);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new PreferencesFragment())
......
......@@ -6,10 +6,11 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.Menu;
import android.view.View;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import butterknife.BindView;
import butterknife.ButterKnife;
import foundation.e.notes.R;
......@@ -18,6 +19,7 @@ import foundation.e.notes.model.DBNote;
import foundation.e.notes.model.Item;
import foundation.e.notes.model.ItemAdapter;
import foundation.e.notes.util.Notes;
import foundation.e.notes.util.ExceptionHandler;
public class SelectSingleNoteActivity extends NotesListViewActivity {
......@@ -27,6 +29,7 @@ public class SelectSingleNoteActivity extends NotesListViewActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
setResult(Activity.RESULT_CANCELED);
SwipeRefreshLayout swipeRefreshLayout = getSwipeRefreshLayout();
......@@ -34,7 +37,7 @@ public class SelectSingleNoteActivity extends NotesListViewActivity {
ButterKnife.bind(this);
fabCreate.setVisibility(View.GONE);
android.support.v7.app.ActionBar ab = getSupportActionBar();
androidx.appcompat.app.ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setTitle(R.string.activity_select_single_note);
}
......
......@@ -3,30 +3,56 @@ package foundation.e.notes.android.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.design.widget.TextInputLayout;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.webkit.SslErrorHandler;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import foundation.e.cert4android.CustomCertManager;
import foundation.e.cert4android.IOnCertificateDecision;
import butterknife.BindView;
import butterknife.ButterKnife;
import foundation.e.notes.R;
import foundation.e.notes.persistence.NoteSQLiteOpenHelper;
import foundation.e.notes.persistence.NoteServerSyncHelper;
import foundation.e.notes.util.ExceptionHandler;
import foundation.e.notes.util.NotesClientUtil;
import foundation.e.notes.util.NotesClientUtil.LoginStatus;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputLayout;
import java.io.ByteArrayInputStream;
import java.net.URLDecoder;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import static android.os.Process.killProcess;
import static android.os.Process.myPid;
/**
* @author Nihar Thakkar
......@@ -45,10 +71,15 @@ public class SettingsActivity extends AppCompatActivity {
public static final String DEFAULT_SETTINGS = "";
public static final int CREDENTIALS_CHANGED = 3;
public static final String LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":";
public static final String WEBDAV_PATH_4_0_AND_LATER = "/remote.php/webdav";
private SharedPreferences preferences = null;
@BindView(R.id.settings_url)
EditText field_url;
@BindView(R.id.settings_username_wrapper)
TextInputLayout username_wrapper;
@BindView(R.id.settings_username)
EditText field_username;
@BindView(R.id.settings_password)
......@@ -61,11 +92,15 @@ public class SettingsActivity extends AppCompatActivity {
View urlWarnHttp;
private String old_password = "";
private WebView webView;
private boolean first_run = false;
private boolean useWebLogin = true;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
setContentView(R.layout.activity_settings);
ButterKnife.bind(this);
......@@ -79,13 +114,38 @@ public class SettingsActivity extends AppCompatActivity {
}
}
setupListener();
// Load current Preferences
field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS));
field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS));
old_password = preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS);
field_password.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
login();
return true;
}
});
field_password.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
setPasswordHint(hasFocus);
}
});
setPasswordHint(false);
handleSubmitButtonEnabled();
}
private void setupListener() {
field_url.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
new URLValidatorAsyncTask().execute(NotesClientUtil.formatURL(field_url.getText().toString()));
}
});
field_url.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
......@@ -101,7 +161,7 @@ public class SettingsActivity extends AppCompatActivity {
urlWarnHttp.setVisibility(View.GONE);
}
handleSubmitButtonEnabled(field_url.getText(), field_username.getText());
handleSubmitButtonEnabled();
}
@Override
......@@ -117,7 +177,7 @@ public class SettingsActivity extends AppCompatActivity {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
handleSubmitButtonEnabled(field_url.