Commit 61bbd4dd authored by Romain Hunault's avatar Romain Hunault
Browse files

Merge branch 'dev' into 'master'

[RELEASE] Sprint Istanbul

See merge request e/apps/Notes!6
parents 122a8ab6 a71ecc74
Pipeline #48454 passed with stage
in 4 minutes and 33 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
# Nextcloud Notes
An android client for [Nextcloud Notes App](https://github.com/nextcloud/notes/).
[![Build Status](https://travis-ci.org/stefan-niedermann/nextcloud-notes.svg?branch=master)](https://travis-ci.org/stefan-niedermann/nextcloud-notes)
[![GitHub issues](https://img.shields.io/github/issues/stefan-niedermann/nextcloud-notes.svg)](https://github.com/stefan-niedermann/nextcloud-notes/issues)
[![GitHub stars](https://img.shields.io/github/stars/stefan-niedermann/nextcloud-notes.svg)](https://github.com/stefan-niedermann/nextcloud-notes/stargazers)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
## :arrow_forward: Access
[![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)
[![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)
## :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
* Render MarkDown (using [RxMarkdown](https://github.com/yydcdut/RxMarkdown))
* Translated in many languages on [Transifex](https://www.transifex.com/nextcloud/nextcloud/android-notes/)
## :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
* Test the app with different devices
* Report issues in the [issue tracker](https://github.com/stefan-niedermann/nextcloud-notes/issues)
* [Pick a good first issue](https://github.com/nextcloud/server/labels/good%20first%20issue) :notebook:
* Create a [Pull Request](https://opensource.guide/how-to-contribute/#opening-a-pull-request)
* Help translating this app on [Transifex](https://www.transifex.com/nextcloud/nextcloud/android-notes/) :flags:
* Buy this app on [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.notes)
* Send me a bottle of your favorite beer :beers: :wink:
## :link: Requirements
* [Nextcloud](https://nextcloud.com/) instance running
* [Nextcloud Notes](https://github.com/nextcloud/notes) app enabled
## :notebook: License
This project is licensed under the [GNU GENERAL PUBLIC LICENSE](/LICENSE).
## :twisted_rightwards_arrows: Alternatives
If you dislike this app and you are looking for alternatives: Have a look at [MyOwnNotes](https://github.com/aykit/MyOwnNotes).
# Notes
An android client for [Nextcloud Notes App](https://github.com/nextcloud/notes/)
Notes is forked from [Nextcloud Notes](https://github.com/stefan-niedermann/nextcloud-notes)
## Authors
[Authors](https://gitlab.e.foundation/e/apps/Notes/-/blob/master/AUTHORS)
## Release Notes
Check out the [Release Notes](https://gitlab.e.foundation/e/apps/Notes/-/releases) to find out what changed
in each version of Notes.
## Privacy Policy
[Privacy Policy](https://e.foundation/legal-notice-privacy)
[Terms of service](https://e.foundation/legal-notice-privacy)
## License
Notes is licensed under the [GNU General Public License v3.0](https://gitlab.e.foundation/e/apps/Notes/-/blob/master/LICENSE)
# 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,31 @@ 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'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.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'
def supportLibVersion = '28.0.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 butterknife.BindView;
import androidx.appcompat.app.AppCompatActivity;
import butterknife.ButterKnife;
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.android.fragment.about.AboutFragment;
public class AboutActivity extends AppCompatActivity {
@BindView(R.id.pager)
ViewPager mViewPager;
@BindView(R.id.tabs)
TabLayout mTabLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
ButterKnife.bind(this);
getFragmentManager().beginTransaction().replace(R.id.container, new AboutFragment()).commit();
mViewPager.setAdapter(new TabsPagerAdapter(getSupportFragmentManager()));
mTabLayout.setupWithViewPager(mViewPager);
}
private class TabsPagerAdapter extends FragmentPagerAdapter {
private final int PAGE_COUNT = 3;
public TabsPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
@Override
public int getCount() {
return PAGE_COUNT;
}
/**
* return the right fragment for the given position
*/
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new AboutFragmentCreditsTab();
case 1:
return new AboutFragmentContributingTab();
case 2:
return new AboutFragmentLicenseTab();
default:
return null;
}
}
/**
* generate title based on given position
*/
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return getString(R.string.about_credits_tab_title);
case 1:
return getString(R.string.about_contribution_tab_title);
case 2:
return getString(R.string.about_license_tab_title);
default:
return null;
}
}
}
}
\ No newline at end of file
......@@ -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)