Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-notes.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <stefan.niedermann@googlemail.com>2015-10-01 18:54:20 +0300
committerStefan Niedermann <stefan.niedermann@googlemail.com>2015-10-01 18:54:20 +0300
commit820540ce48d756be16c51889ca8b00f77691369b (patch)
treebf7fb2fd02484c2cf1d8f2e214c396725cef1cc1 /app/src/main
Initial commit
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/AndroidManifest.xml63
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java14
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java66
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java64
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java120
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java359
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java175
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java20
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/model/Note.java74
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/model/NoteAdapter.java70
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java305
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java295
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/util/ICallback.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java207
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java84
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_cancel.pngbin0 -> 402 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_copy.pngbin0 -> 534 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_delete.pngbin0 -> 399 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_done.pngbin0 -> 400 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_done_dark.pngbin0 -> 729 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_edit.pngbin0 -> 302 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_new.pngbin0 -> 97 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_settings.pngbin0 -> 1153 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_action_share.pngbin0 -> 980 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_launcher.pngbin0 -> 2198 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_cancel.pngbin0 -> 304 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_copy.pngbin0 -> 370 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_delete.pngbin0 -> 274 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_done.pngbin0 -> 306 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_done_dark.pngbin0 -> 502 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_edit.pngbin0 -> 239 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_new.pngbin0 -> 97 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_settings.pngbin0 -> 781 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_action_share.pngbin0 -> 646 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_launcher.pngbin0 -> 1685 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_cancel.pngbin0 -> 499 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_copy.pngbin0 -> 663 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_delete.pngbin0 -> 506 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_done.pngbin0 -> 519 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_done_dark.pngbin0 -> 923 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_edit.pngbin0 -> 355 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_new.pngbin0 -> 102 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_settings.pngbin0 -> 1573 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_action_share.pngbin0 -> 1343 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_launcher.pngbin0 -> 3395 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_cancel.pngbin0 -> 834 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_copy.pngbin0 -> 1027 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_delete.pngbin0 -> 715 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_done.pngbin0 -> 732 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_done_dark.pngbin0 -> 1260 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_edit.pngbin0 -> 522 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_new.pngbin0 -> 113 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_settings.pngbin0 -> 2756 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_action_share.pngbin0 -> 2143 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_launcher.pngbin0 -> 4496 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_cancel.pngbin0 -> 1065 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_copy.pngbin0 -> 1398 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_delete.pngbin0 -> 957 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_done.pngbin0 -> 1002 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_done_dark.pngbin0 -> 1732 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_edit.pngbin0 -> 653 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_new.pngbin0 -> 116 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_settings.pngbin0 -> 4088 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_action_share.pngbin0 -> 3361 bytes
-rw-r--r--app/src/main/res/drawable/list_item_background_selector.xml7
-rw-r--r--app/src/main/res/drawable/list_item_color_selector.xml7
-rw-r--r--app/src/main/res/drawable/list_item_color_selector_low.xml7
-rw-r--r--app/src/main/res/drawable/settings.xml8
-rw-r--r--app/src/main/res/layout/activity_about.xml53
-rw-r--r--app/src/main/res/layout/activity_create.xml24
-rw-r--r--app/src/main/res/layout/activity_edit.xml22
-rw-r--r--app/src/main/res/layout/activity_notes_list_view.xml29
-rw-r--r--app/src/main/res/layout/activity_settings.xml103
-rw-r--r--app/src/main/res/layout/activity_single_note.xml21
-rw-r--r--app/src/main/res/layout/fragment_notes_list_view.xml34
-rw-r--r--app/src/main/res/menu/menu_list_context_multiple.xml12
-rw-r--r--app/src/main/res/menu/menu_list_context_single.xml24
-rw-r--r--app/src/main/res/menu/menu_list_view.xml18
-rw-r--r--app/src/main/res/menu/menu_menu_create.xml18
-rw-r--r--app/src/main/res/menu/menu_menu_edit.xml18
-rw-r--r--app/src/main/res/menu/menu_note_list_view.xml24
-rw-r--r--app/src/main/res/values-de/strings.xml55
-rw-r--r--app/src/main/res/values/colors.xml14
-rw-r--r--app/src/main/res/values/dimens.xml12
-rw-r--r--app/src/main/res/values/strings.xml56
-rw-r--r--app/src/main/res/values/styles.xml27
86 files changed, 2517 insertions, 0 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..390d4aa0
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="it.niedermann.owncloud.notes"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="23" />
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.USE_CREDENTIALS" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/OwnCloud"
+ android:fullBackupContent="true"
+ android:supportsRtl="true"
+ >
+ <activity
+ android:name="it.niedermann.owncloud.notes.android.activity.NotesListViewActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="it.niedermann.owncloud.notes.android.activity.NoteActivity"
+ android:label="@string/app_name"
+ android:parentActivityName="it.niedermann.owncloud.notes.android.activity.NotesListViewActivity" >
+ </activity>
+ <activity
+ android:name="it.niedermann.owncloud.notes.android.activity.SettingsActivity"
+ android:label="@string/app_name"
+ android:parentActivityName="it.niedermann.owncloud.notes.android.activity.NotesListViewActivity" >
+ </activity>
+ <activity
+ android:name="it.niedermann.owncloud.notes.android.activity.CreateNoteActivity"
+ android:label="@string/action_create"
+ android:windowSoftInputMode="stateVisible" >
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:mimeType="text/plain" />
+ </intent-filter>
+ </activity>
+ <activity
+ android:name="it.niedermann.owncloud.notes.android.activity.EditNoteActivity"
+ android:label="@string/menu_edit"
+ android:windowSoftInputMode="stateVisible" >
+ </activity>
+ <activity
+ android:name="it.niedermann.owncloud.notes.android.activity.AboutActivity"
+ android:label="@string/menu_about"
+ android:parentActivityName="it.niedermann.owncloud.notes.android.activity.NotesListViewActivity" >
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java
new file mode 100644
index 00000000..d1f58cda
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/AboutActivity.java
@@ -0,0 +1,14 @@
+package it.niedermann.owncloud.notes.android.activity;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import it.niedermann.owncloud.notes.R;
+public class AboutActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_about);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java
new file mode 100644
index 00000000..942905a4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/CreateNoteActivity.java
@@ -0,0 +1,66 @@
+package it.niedermann.owncloud.notes.android.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.model.Note;
+import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
+
+public class CreateNoteActivity extends AppCompatActivity {
+ private EditText editTextField = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_create);
+ editTextField = (EditText) findViewById(R.id.createContent);
+
+ // Get intent, action and MIME type
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ String type = intent.getType();
+
+ if (Intent.ACTION_SEND.equals(action) && type != null) {
+ if ("text/plain".equals(type)) {
+ editTextField.setText(intent.getStringExtra(Intent.EXTRA_TEXT));
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_menu_create, menu);
+ return true;
+ }
+
+ /**
+ * Main-Menu
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case R.id.action_create_save:
+ editTextField.setEnabled(false);
+ String content = editTextField.getText().toString();
+ NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(this);
+ db.addNoteAndSync(content);
+ Intent data = new Intent();
+ //FIXME send correct note back to NotesListView
+ data.putExtra(NotesListViewActivity.CREATED_NOTE, new Note(-1, null, "", content));
+ setResult(RESULT_OK, data);
+ finish();
+ return true;
+ case R.id.action_create_cancel:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java
new file mode 100644
index 00000000..5dadd4b7
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/EditNoteActivity.java
@@ -0,0 +1,64 @@
+package it.niedermann.owncloud.notes.android.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.model.Note;
+import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
+
+public class EditNoteActivity extends AppCompatActivity {
+ private EditText content = null;
+ private Note note = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_edit);
+ note = (Note) getIntent().getSerializableExtra(
+ NoteActivity.EDIT_NOTE);
+ content = (EditText) findViewById(R.id.editContent);
+ content.setEnabled(false);
+ content.setText(note.getContent());
+ content.setSelection(note.getContent().length());
+ content.setEnabled(true);
+ }
+
+ /**
+ * Create Action Menu
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_menu_edit, menu);
+ return true;
+ }
+
+ /**
+ * Handle Action Menu
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case R.id.action_edit_save:
+ content.setEnabled(false);
+ note.setContent(((EditText) findViewById(R.id.editContent)).getText().toString());
+ NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(this);
+ db.updateNoteAndSync(note);
+ Intent data = new Intent();
+ data.setAction(Intent.ACTION_VIEW);
+ data.putExtra(NoteActivity.EDIT_NOTE, note);
+ setResult(RESULT_OK, data);
+ finish();
+ return true;
+ case R.id.action_edit_cancel:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java
new file mode 100644
index 00000000..1b637241
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NoteActivity.java
@@ -0,0 +1,120 @@
+package it.niedermann.owncloud.notes.android.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+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 android.view.View;
+import android.webkit.WebView;
+
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.model.Note;
+import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
+
+public class NoteActivity extends AppCompatActivity implements View.OnClickListener {
+ private Note note = null;
+ private int notePosition = 0;
+ private WebView noteContent = null;
+ private ActionBar actionBar = null;
+ // Intent backToListViewIntent = null;
+ public final static String EDIT_NOTE = "it.niedermann.owncloud.notes.edit_note_id";
+ public final static int EDIT_NOTE_CMD = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // backToListViewIntent = new Intent();
+ setContentView(R.layout.activity_single_note);
+ note = (Note) getIntent().getSerializableExtra(
+ NotesListViewActivity.SELECTED_NOTE);
+ notePosition = getIntent().getIntExtra(
+ NotesListViewActivity.SELECTED_NOTE_POSITION, 0);
+ findViewById(R.id.fab_edit).setOnClickListener(this);
+ actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(note.getTitle());
+ actionBar.setSubtitle(note.getModified("dd.MM.yyyy HH:mm"));
+ }
+ noteContent = (WebView) findViewById(R.id.singleNoteContent);
+ noteContent.loadData(note.getHtmlContent(), "text/html", "UTF-8");
+ }
+
+ @Override
+ public void onClick(View v) {
+ Intent editIntent = new Intent(this, EditNoteActivity.class);
+ editIntent.putExtra(EDIT_NOTE, note);
+ startActivityForResult(editIntent, EDIT_NOTE_CMD);
+ }
+
+ /**
+ * Main-Menu
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.menu_note_list_view, menu);
+ return true;
+ }
+
+ /**
+ * Main-Menu-Handler
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ NoteSQLiteOpenHelper db = null;
+ switch (id) {
+ case R.id.menu_delete:
+ //setContentView(R.layout.activity_notes_list_view);
+ db = new NoteSQLiteOpenHelper(this);
+ db.deleteNoteAndSync(note.getId());
+ finish();
+ return true;
+ case R.id.menu_share:
+ Log.v("Note", "Share Action pressed.");
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.setType("text/plain");
+ shareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
+ note.getTitle());
+ shareIntent.putExtra(android.content.Intent.EXTRA_TEXT,
+ note.getContent());
+ startActivity(shareIntent);
+ return true;
+ case R.id.menu_copy:
+ Log.v("Note", "Copy Action pressed.");
+ db = new NoteSQLiteOpenHelper(this);
+ Note newNote = db.getNote(db.addNoteAndSync(note.getContent()));
+ newNote.setTitle(note.getTitle() + " (" + getResources().getString(R.string.copy) + ")");
+ db.updateNote(newNote);
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Check which request we're responding to
+ if (requestCode == EDIT_NOTE_CMD) {
+ // Make sure the request was successful
+ if (resultCode == RESULT_OK) {
+ Note editedNote = (Note) data.getExtras().getSerializable(
+ EDIT_NOTE);
+ if (editedNote != null) {
+ noteContent.loadData(editedNote.getHtmlContent(), "text/html", "UTF-8");
+ actionBar.setTitle(editedNote.getTitle());
+ actionBar.setSubtitle(editedNote.getModified("dd.MM.yyyy HH:mm"));
+ }
+ // TODO Fire changed note to noteslistviewactivity
+ data.putExtra(NotesListViewActivity.SELECTED_NOTE_POSITION,
+ notePosition);
+ setResult(RESULT_OK, data);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java
new file mode 100644
index 00000000..49fbf6f9
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java
@@ -0,0 +1,359 @@
+package it.niedermann.owncloud.notes.android.activity;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v4.widget.SwipeRefreshLayout;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.view.ActionMode;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.ListView;
+
+import java.util.List;
+
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.model.Note;
+import it.niedermann.owncloud.notes.model.NoteAdapter;
+import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
+import it.niedermann.owncloud.notes.util.ICallback;
+
+public class NotesListViewActivity extends AppCompatActivity implements
+ OnItemClickListener, View.OnClickListener {
+
+ public final static String SELECTED_NOTE = "it.niedermann.owncloud.notes.clicked_note";
+ public final static String CREATED_NOTE = "it.niedermann.owncloud.notes.created_notes";
+ public final static String SELECTED_NOTE_POSITION = "it.niedermann.owncloud.notes.clicked_note_position";
+
+ private final static int create_note_cmd = 0;
+ private final static int show_single_note_cmd = 1;
+ private final static int server_settings = 2;
+ private final static int about = 3;
+
+ private ListView listView = null;
+ private NoteAdapter adapter = null;
+ private ActionMode mActionMode;
+ private SwipeRefreshLayout swipeRefreshLayout = null;
+ private NoteSQLiteOpenHelper db = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // First Run Wizard
+ SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(getApplicationContext());
+ Log.v("Note", "First Run: " + preferences.getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true));
+ if(preferences.getBoolean(SettingsActivity.SETTINGS_FIRST_RUN, true)) {
+ Log.v("Note", "Seems to be the First Run...");
+ Intent settingsIntent = new Intent(this, SettingsActivity.class);
+ startActivityForResult(settingsIntent, server_settings);
+ }
+
+ setContentView(R.layout.activity_notes_list_view);
+
+ // Display Data
+ db = new NoteSQLiteOpenHelper(this);
+ db.synchronizeWithServer();
+ setListView(db.getNotes());
+
+ // Pull to Refresh
+ swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swiperefreshlayout);
+ swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ Log.d("Swipe", "Refreshing Notes");
+ db.synchronizeWithServer();
+ db.getNoteServerSyncHelper().addCallback(new ICallback() {
+ @Override
+ public void onFinish() {
+ swipeRefreshLayout.setRefreshing(false);
+ setListView(db.getNotes());
+ }
+ });
+ }
+ });
+
+ // Floating Action Button
+ findViewById(R.id.fab_create).setOnClickListener(this);
+ }
+
+ /**
+ * Click listener for <strong>Floating Action Button</strong>
+ * <p/>
+ * Creates a new Instance of CreateNoteActivity.
+ *
+ * @param v View
+ */
+ @Override
+ public void onClick(View v) {
+ Intent createIntent = new Intent(this, CreateNoteActivity.class);
+ startActivityForResult(createIntent, create_note_cmd);
+ }
+
+ /**
+ * Allows other classes to set a List of Notes.
+ *
+ * @param noteList List&lt;Note&gt;
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void setListView(List<Note> noteList) {
+ adapter = new NoteAdapter(getApplicationContext(), noteList);
+ listView = (ListView) findViewById(R.id.list_view);
+ listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ listView.setAdapter(adapter);
+ listView.setOnItemClickListener(this);
+ listView.setOnItemLongClickListener(new OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ onListItemSelect(position);
+ return true;
+ }
+ });
+ }
+
+ /**
+ * A short click on one list item. Creates a new instance of NoteActivity.
+ */
+ @Override
+ public void onItemClick(AdapterView<?> parentView, View childView,
+ int position, long id) {
+ listView.setItemChecked(position, !listView.isItemChecked(position));
+ Log.v("Note", "getCheckedItemCount " + listView.getCheckedItemCount());
+ if (listView.getCheckedItemCount() < 1) {
+ removeSelection();
+ Intent intent = new Intent(getApplicationContext(),
+ NoteActivity.class);
+ Note note = adapter.getItem(position);
+ intent.putExtra(SELECTED_NOTE, note);
+ intent.putExtra(SELECTED_NOTE_POSITION, position);
+ Log.v("Note",
+ "notePosition | NotesListViewActivity wurde abgesendet "
+ + position);
+ startActivityForResult(intent, show_single_note_cmd);
+ } else { // perform long click if already something is selected
+ onListItemSelect(position);
+ }
+ }
+
+ /**
+ * Adds the Menu Items to the Action Bar.
+ *
+ * @param menu Menu
+ * @return boolean
+ */
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_list_view, menu);
+ return true;
+ }
+
+ /**
+ * Handels click events on the Buttons in the Action Bar.
+ *
+ * @param item MenuItem - the clicked menu item
+ * @return boolean
+ */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+ switch (id) {
+ case R.id.action_settings:
+ Intent settingsIntent = new Intent(this, SettingsActivity.class);
+ startActivityForResult(settingsIntent, server_settings);
+ return true;
+ case R.id.action_about:
+ Intent aboutIntent = new Intent(this, AboutActivity.class);
+ startActivityForResult(aboutIntent, about);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /**
+ * Handles the Results of started Sub Activities (Created Note, Edited Note)
+ *
+ * @param requestCode int to distinguish between the different Sub Activities
+ * @param resultCode int Return Code
+ * @param data Intent
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Check which request we're responding to
+ if (requestCode == create_note_cmd) {
+ // Make sure the request was successful
+ if (resultCode == RESULT_OK) {
+ Note createdNote = (Note) data.getExtras().getSerializable(
+ CREATED_NOTE);
+ adapter.add(createdNote);
+ }
+ } else if (requestCode == NoteActivity.EDIT_NOTE_CMD) {
+ if (resultCode == RESULT_OK) {
+ Log.v("Note", "Note was edited from single view");
+
+ Note editedNote = (Note) data.getExtras().getSerializable(
+ NoteActivity.EDIT_NOTE);
+ Log.v("Note", "Neuer Titel: " + editedNote);
+
+ int notePosition = data.getExtras().getInt(
+ SELECTED_NOTE_POSITION);
+ Log.v("Note", "notePosition | NotesListViewActivity kam an "
+ + notePosition);
+
+ adapter.remove(adapter.getItem(notePosition));
+ adapter.add(editedNote);
+ }
+ }
+ setListView(db.getNotes());
+ }
+
+ // private class SingleSelectedActionModeCallback implements
+ // ActionMode.Callback {
+ //
+ // @Override
+ // public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // // inflate contextual menu
+ // mode.getMenuInflater().inflate(R.menu.menu_list_context_single,
+ // menu);
+ // return true;
+ // }
+ //
+ // @Override
+ // public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ // return false;
+ // }
+ //
+ // @Override
+ // public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ // switch (item.getItemId()) {
+ // case R.id.menu_delete:
+ // SparseBooleanArray checkedItemPositions = listView
+ // .getCheckedItemPositions();
+ // for (int i = (checkedItemPositions.size() - 1); i >= 0; i--) {
+ // if (checkedItemPositions.valueAt(i)) {
+ //
+ // Note checkedItem = adapter.getItem(checkedItemPositions
+ // .keyAt(i));
+ //
+ // NoteDeleterAsyncTask deleter = new NoteDeleterAsyncTask();
+ // deleter.execute(checkedItem);
+ // }
+ // }
+ // mode.finish(); // Action picked, so close the CAB
+ // return true;
+ // default:
+ // return false;
+ // }
+ // }
+ //
+ // @Override
+ // public void onDestroyActionMode(ActionMode mode) {
+ // removeSelection();
+ // mActionMode = null;
+ // adapter.notifyDataSetChanged();
+ // }
+ // }
+
+ /**
+ * Long click on one item in the list view. It starts the Action Mode and allows selecting more
+ * items and execute bulk functions (e. g. delete)
+ *
+ * @param position int - position of the clicked item
+ */
+ private void onListItemSelect(int position) {
+ listView.setItemChecked(position, !listView.isItemChecked(position));
+ int checkedItemCount = listView.getCheckedItemCount();
+ boolean hasCheckedItems = checkedItemCount > 0;
+
+ if (hasCheckedItems && mActionMode == null) {
+ // TODO differ if one or more items are selected
+ // if (checkedItemCount == 1) {
+ // mActionMode = startActionMode(new
+ // SingleSelectedActionModeCallback());
+ // } else {
+ // there are some selected items, start the actionMode
+ mActionMode = startSupportActionMode(new MultiSelectedActionModeCallback());
+ // }
+ } else if (!hasCheckedItems && mActionMode != null) {
+ // there no selected items, finish the actionMode
+ mActionMode.finish();
+ }
+
+ if (mActionMode != null) {
+ mActionMode.setTitle(String.valueOf(listView.getCheckedItemCount())
+ + " " + getString(R.string.ab_selected));
+ }
+ }
+
+ /**
+ * Handler for the MultiSelect Actions
+ */
+ private class MultiSelectedActionModeCallback implements
+ ActionMode.Callback {
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ // inflate contextual menu
+ mode.getMenuInflater().inflate(R.menu.menu_list_context_multiple,
+ menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ /**
+ * @param mode ActionMode - used to close the Action Bar after all work is done.
+ * @param item MenuItem - the item in the List that contains the Node
+ * @return boolean
+ */
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_delete:
+ SparseBooleanArray checkedItemPositions = listView
+ .getCheckedItemPositions();
+ for (int i = (checkedItemPositions.size() - 1); i >= 0; i--) {
+ if (checkedItemPositions.valueAt(i)) {
+ Note note = adapter.getItem(checkedItemPositions
+ .keyAt(i));
+ db.deleteNoteAndSync(note.getId());
+ adapter.remove(note);
+ }
+ }
+ mode.finish(); // Action picked, so close the CAB
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ removeSelection();
+ mActionMode = null;
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * Removes all selections.
+ */
+ private void removeSelection() {
+ SparseBooleanArray checkedItemPositions = listView
+ .getCheckedItemPositions();
+ for (int i = 0; i < checkedItemPositions.size(); i++) {
+ listView.setItemChecked(i, false);
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java
new file mode 100644
index 00000000..a9cd6006
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/SettingsActivity.java
@@ -0,0 +1,175 @@
+package it.niedermann.owncloud.notes.android.activity;
+
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
+import it.niedermann.owncloud.notes.util.NotesClientUtil;
+
+/**
+ * Allows to set Settings like URL, Username and Password for Server-Synchronization
+ * Created by stefan on 22.09.15.
+ */
+public class SettingsActivity extends AppCompatActivity implements View.OnClickListener {
+
+ public static final String SETTINGS_FIRST_RUN = "firstRun";
+ public static final String SETTINGS_URL = "settingsUrl";
+ public static final String SETTINGS_USERNAME = "settingsUsername";
+ public static final String SETTINGS_PASSWORD = "settingsPassword";
+ public static final String DEFAULT_SETTINGS = "";
+
+ private SharedPreferences preferences = null;
+ private EditText field_url = null;
+ private EditText field_username = null;
+ private EditText field_password = null;
+ private Button btn_submit = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_settings);
+
+ preferences = PreferenceManager
+ .getDefaultSharedPreferences(getApplicationContext());
+
+ field_url = (EditText) findViewById(R.id.settings_url);
+ field_username = (EditText) findViewById(R.id.settings_username);
+ field_password = (EditText) findViewById(R.id.settings_password);
+ btn_submit = (Button) findViewById(R.id.settings_submit);
+
+ field_url.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ String url = ((EditText) findViewById(R.id.settings_url)).getText().toString();
+ new URLValidatorAsyncTask().execute(url);
+
+ if (NotesClientUtil.isHttp(url)) {
+ findViewById(R.id.settings_url_warn_http).setVisibility(View.VISIBLE);
+ } else {
+ findViewById(R.id.settings_url_warn_http).setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+ });
+
+ // Load current Preferences
+ field_url.setText(preferences.getString(SETTINGS_URL, DEFAULT_SETTINGS));
+ field_username.setText(preferences.getString(SETTINGS_USERNAME, DEFAULT_SETTINGS));
+ field_password.setText(preferences.getString(SETTINGS_PASSWORD, DEFAULT_SETTINGS));
+
+ btn_submit.setOnClickListener(this);
+ }
+
+ /**
+ * Handle Submit Button Click
+ * Checks and Writes the new Preferences into the SharedPreferences Object.
+ *
+ * @param v View
+ */
+ @Override
+ public void onClick(View v) {
+ String url = field_url.getText().toString();
+ String username = field_username.getText().toString();
+ String password = field_password.getText().toString();
+
+ if (!url.endsWith("/")) {
+ url += "/";
+ }
+
+ new LoginValidatorAsyncTask().execute(url, username, password);
+ }
+
+ /************************************ Async Tasks ************************************/
+
+ /**
+ * Checks if the given URL returns a valid status code and sets the Check next to the URL-Input Field to visible.
+ * Created by stefan on 23.09.15.
+ */
+ private class URLValidatorAsyncTask extends AsyncTask<String, Void, Boolean> {
+
+ @Override
+ protected void onPreExecute() {
+ findViewById(R.id.settings_url_check).setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ protected Boolean doInBackground(String... params) {
+ return NotesClientUtil.isValidURL(params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean o) {
+ Log.v("Note", "Set Visible: " + o);
+ if (o) {
+ findViewById(R.id.settings_url_check).setVisibility(View.VISIBLE);
+ } else {
+ findViewById(R.id.settings_url_check).setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
+ /**
+ * If Log-In-Credentials are correct, save Credentials to Shared Preferences and finish First Run Wizard.
+ */
+ private class LoginValidatorAsyncTask extends AsyncTask<String, Void, Boolean> {
+ String url, username, password;
+
+ @Override
+ protected void onPreExecute() {
+ btn_submit.setEnabled(false);
+ }
+
+ /**
+ * @param params url, username and password
+ * @return isValidLogin
+ */
+ @Override
+ protected Boolean doInBackground(String... params) {
+ url = params[0];
+ username = params[1];
+ password = params[2];
+ return NotesClientUtil.isValidLogin(url, username, password);
+ }
+
+ @Override
+ protected void onPostExecute(Boolean isValidLogin) {
+ if(isValidLogin) {
+ Log.v("Note", "Valid Credentials.");
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putString(SETTINGS_URL, url);
+ editor.putString(SETTINGS_USERNAME, username);
+ editor.putString(SETTINGS_PASSWORD, password);
+
+ // Now it is no more First Run
+ Log.v("Note", "set First_Run to false.");
+ editor.putBoolean(SETTINGS_FIRST_RUN, false);
+
+ editor.apply();
+
+ NoteSQLiteOpenHelper db = new NoteSQLiteOpenHelper(getApplicationContext());
+ db.synchronizeWithServer();
+
+ finish();
+ } else {
+ Log.v("Note", "Invalid Credentials!");
+ btn_submit.setEnabled(true);
+ //TODO Show Error Message
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java
new file mode 100644
index 00000000..fc27f826
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/model/DBStatus.java
@@ -0,0 +1,20 @@
+package it.niedermann.owncloud.notes.model;
+
+/**
+ * Helps to distinguish between different local change types for Server Synchronization.
+ * Created by stefan on 19.09.15.
+ */
+public enum DBStatus {
+
+ VOID(""), LOCAL_CREATED("LOCAL_CREATED"), LOCAL_EDITED("LOCAL_EDITED"), LOCAL_DELETED("LOCAL_DELETED");
+
+ private final String title;
+
+ public String getTitle() {
+ return title;
+ }
+
+ DBStatus(String title) {
+ this.title = title;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/Note.java b/app/src/main/java/it/niedermann/owncloud/notes/model/Note.java
new file mode 100644
index 00000000..3bf4af7f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/model/Note.java
@@ -0,0 +1,74 @@
+package it.niedermann.owncloud.notes.model;
+
+import android.text.Html;
+
+import com.commonsware.cwac.anddown.AndDown;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+
+import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
+
+@SuppressWarnings("serial")
+public class Note implements Serializable {
+
+ private static final AndDown and_down = new AndDown();
+ private long id = 0;
+ private String title = "";
+ private Calendar modified = null;
+ private String content = "";
+ private String htmlContent = null;
+
+ public Note(long id, Calendar modified, String title, String content) {
+ this.id = id;
+ if(title != null)
+ this.title = Html.fromHtml(and_down.markdownToHtml(title)).toString().trim();
+ this.modified = modified;
+ this.content = content;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public Calendar getModified() {
+ return modified;
+ }
+
+ public String getModified(String format) {
+ return new SimpleDateFormat(format, Locale.GERMANY)
+ .format(this.getModified().getTime());
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ this.htmlContent = null;
+ }
+
+ public String getHtmlContent() {
+ if(htmlContent == null && getContent() != null) {
+ htmlContent = and_down.markdownToHtml(getContent());
+ }
+ return htmlContent;
+ }
+
+ @Override
+ public String toString() {
+ return "#" + getId() + " " + getTitle() + " (" + getModified(NoteSQLiteOpenHelper.DATE_FORMAT) + ")";
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteAdapter.java
new file mode 100644
index 00000000..a4b92915
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteAdapter.java
@@ -0,0 +1,70 @@
+package it.niedermann.owncloud.notes.model;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+import java.util.List;
+
+import it.niedermann.owncloud.notes.R;
+
+public class NoteAdapter extends ArrayAdapter<Note> {
+ private List<Note> noteList = null;
+
+ // private SparseBooleanArray mSelectedItemsIds;
+
+ public NoteAdapter(Context context,
+ List<Note> noteList) {
+ super(context, android.R.layout.simple_list_item_1, noteList);
+ // mSelectedItemsIds = new SparseBooleanArray();
+ this.noteList = noteList;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v = convertView;
+
+ // first check to see if the view is null. if so, we have to inflate it.
+ // to inflate it basically means to render, or show, the view.
+ if (v == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ v = inflater.inflate(R.layout.fragment_notes_list_view, null);
+ }
+
+ /*
+ * Recall that the variable position is sent in as an argument to this
+ * method. The variable simply refers to the position of the current
+ * object in the list. (The ArrayAdapter iterates through the list we
+ * sent it)
+ *
+ * Therefore, i refers to the current Item object.
+ */
+ Note note = noteList.get(position);
+
+ if (note != null) {
+
+ // This is how you obtain a reference to the TextViews.
+ // These TextViews are created in the XML files we defined.
+
+ TextView noteTitle = (TextView) v.findViewById(R.id.noteTitle);
+ TextView noteModified = (TextView) v
+ .findViewById(R.id.noteModified);
+
+ // check to see if each individual textview is null.
+ // if not, assign some text!
+ if (noteTitle != null) {
+ noteTitle.setText(note.getTitle());
+ }
+ if (noteModified != null) {
+ noteModified.setText(note.getModified("dd.MM.yyyy HH:mm"));
+ }
+ }
+
+ // the view must be returned to our activity
+ return v;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java
new file mode 100644
index 00000000..bc272a40
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java
@@ -0,0 +1,305 @@
+package it.niedermann.owncloud.notes.persistence;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+
+import it.niedermann.owncloud.notes.model.DBStatus;
+import it.niedermann.owncloud.notes.model.Note;
+
+/**
+ * Helps to add, get, update and delete Notes with the option to trigger a Resync with the Server.
+ * <p/>
+ * Created by stefan on 19.09.15.
+ */
+public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
+ public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
+
+ private static final int database_version = 4;
+ private static final String database_name = "OWNCLOUD_NOTES";
+ private static final String table_notes = "NOTES";
+ private static final String key_id = "ID";
+ private static final String key_status = "STATUS";
+ private static final String key_title = "TITLE";
+ private static final String key_modified = "MODIFIED";
+ private static final String key_content = "CONTENT";
+ private static final String[] columns = {key_id, key_status, key_title, key_modified, key_content};
+
+ private NoteServerSyncHelper serverSyncHelper = null;
+ private Context context = null;
+
+ public NoteSQLiteOpenHelper(Context context) {
+ super(context, database_name, null, database_version);
+ this.context = context;
+ serverSyncHelper = new NoteServerSyncHelper(this);
+ }
+
+ public NoteServerSyncHelper getNoteServerSyncHelper() {
+ return serverSyncHelper;
+ }
+
+ /**
+ * Creates initial the Database
+ *
+ * @param db Database
+ */
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.v("Note", "Creating Database");
+ db.execSQL("CREATE TABLE '" + table_notes + "' ( '" +
+ key_id + "' INTEGER PRIMARY KEY AUTOINCREMENT, '" +
+ key_status + "' VARCHAR(50), '" +
+ key_title + "' TEXT, '" +
+ key_modified + "' TEXT, '" +
+ key_content + "' TEXT)");
+ }
+
+ /**
+ * Creates a new Note in the Database and adds a Synchronization Flag.
+ *
+ * @param content String
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public long addNoteAndSync(String content) {
+ Log.v("Note", "addNoteAndSync");
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(key_status, DBStatus.LOCAL_CREATED.getTitle());
+ values.put(key_content, content);
+
+ long id = db.insert(table_notes,
+ null,
+ values);
+ db.close();
+ serverSyncHelper.uploadNewNotes();
+ return id;
+ }
+
+ /**
+ * Inserts a note directly into the Database.
+ * No Synchronisation will be triggered! Use addNoteAndSync()!
+ *
+ * @param note Note to be added
+ */
+ public void addNote(Note note) {
+ Log.v("Note", "addNote (" + note + ")");
+ SQLiteDatabase db = this.getWritableDatabase();
+
+ ContentValues values = new ContentValues();
+ values.put(NoteSQLiteOpenHelper.key_id, note.getId());
+ values.put(NoteSQLiteOpenHelper.key_status, DBStatus.VOID.getTitle());
+ values.put(NoteSQLiteOpenHelper.key_title, note.getTitle());
+ values.put(NoteSQLiteOpenHelper.key_modified, note.getModified(NoteSQLiteOpenHelper.DATE_FORMAT));
+ values.put(NoteSQLiteOpenHelper.key_content, note.getContent());
+
+ db.insert(NoteSQLiteOpenHelper.table_notes,
+ null,
+ values);
+ db.close();
+ }
+
+ /**
+ * Get a single Note by ID
+ *
+ * @param id int - ID of the requested Note
+ * @return requested Note
+ */
+ @SuppressWarnings("unused")
+ public Note getNote(long id) {
+ Log.v("Note", "getNote(" + id + ")");
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor =
+ db.query(table_notes,
+ columns,
+ key_id + " = ? AND " + key_status + " != ?",
+ new String[]{String.valueOf(id), DBStatus.LOCAL_DELETED.getTitle()},
+ null,
+ null,
+ null,
+ null);
+ if (cursor != null) {
+ cursor.moveToFirst();
+ }
+ Calendar modified = Calendar.getInstance();
+ try {
+ String modifiedStr = cursor != null ? cursor.getString(3) : null;
+ if (modifiedStr != null)
+ modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr));
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ Note note = new Note(Long.valueOf(cursor != null ? cursor.getString(0) : null), modified, cursor != null ? cursor.getString(2) : null, cursor.getString(4));
+ cursor.close();
+ return note;
+ }
+
+ /**
+ * Returns a list of all Notes in the Database
+ *
+ * @return List&lt;Note&gt;
+ */
+ public List<Note> getNotes() {
+ Log.v("Note", "getNotes");
+ List<Note> notes = new ArrayList<>();
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " != ?", new String[]{DBStatus.LOCAL_DELETED.getTitle()});
+ if (cursor.moveToFirst()) {
+ do {
+ Calendar modified = Calendar.getInstance();
+ try {
+ String modifiedStr = cursor.getString(3);
+ if (modifiedStr != null)
+ modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr));
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4)));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ return notes;
+ }
+
+ /**
+ * Returns a list of all Notes in the Database with a sepcial status, e.g. Edited, Deleted,...
+ *
+ * @return List&lt;Note&gt;
+ */
+ public List<Note> getNotesByStatus(DBStatus status) {
+ Log.v("Note", "getNotesByStatus(" + status.getTitle() + ")");
+ List<Note> notes = new ArrayList<>();
+ SQLiteDatabase db = this.getWritableDatabase();
+ Cursor cursor = db.rawQuery("SELECT * FROM " + table_notes + " WHERE " + key_status + " = ?", new String[]{status.getTitle()});
+ if (cursor.moveToFirst()) {
+ do {
+ Calendar modified = Calendar.getInstance();
+ try {
+ String modifiedStr = cursor.getString(3);
+ if (modifiedStr != null)
+ modified.setTime(new SimpleDateFormat(DATE_FORMAT, Locale.GERMANY).parse(modifiedStr));
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ notes.add(new Note(Long.valueOf(cursor.getString(0)), modified, cursor.getString(2), cursor.getString(4)));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ return notes;
+ }
+
+ /**
+ * Updates a single Note and sets a synchronization Flag.
+ *
+ * @param note Note - Note with the updated Information
+ * @return The number of the Rows affected.
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public int updateNoteAndSync(Note note) {
+ Log.v("Note", "updateNoteAndSync(" + note + ")");
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(key_id, note.getId());
+ values.put(key_status, DBStatus.LOCAL_EDITED.getTitle());
+ values.put(key_title, note.getTitle());
+ values.put(key_modified, note.getModified(DATE_FORMAT));
+ values.put(key_content, note.getContent());
+ int i = db.update(table_notes,
+ values,
+ key_id + " = ?",
+ new String[]{String.valueOf(note.getId())});
+ db.close();
+ serverSyncHelper.uploadEditedNotes();
+ return i;
+ }
+
+ /**
+ * Updates a single Note. No Synchronization will be triggered. Use updateNoteAndSync()!
+ *
+ * @param note Note - Note with the updated Information
+ * @return The number of the Rows affected.
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public int updateNote(Note note) {
+ Log.v("Note", "updateNote(" + note + ")");
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(key_id, note.getId());
+ values.put(key_status, DBStatus.VOID.getTitle());
+ values.put(key_title, note.getTitle());
+ values.put(key_modified, note.getModified(DATE_FORMAT));
+ values.put(key_content, note.getContent());
+ int i = db.update(table_notes,
+ values,
+ key_id + " = ?",
+ new String[]{String.valueOf(note.getId())});
+ db.close();
+ return i;
+ }
+
+ /**
+ * Marks a Note in the Database as Deleted. In the next Synchronization it will be deleted
+ * from the Server.
+ *
+ * @param id long - ID of the Note that should be deleted
+ * @return Affected rows
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public int deleteNoteAndSync(long id) {
+ Log.v("Note", "deleteNoteAndSync(" + id + ")");
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put(key_status, DBStatus.LOCAL_DELETED.getTitle());
+ int i = db.update(table_notes,
+ values,
+ key_id + " = ?",
+ new String[]{String.valueOf(id)});
+ db.close();
+ serverSyncHelper.uploadDeletedNotes();
+ return i;
+ }
+
+ /**
+ * Delete a single Note from the Database
+ *
+ * @param id long - ID of the Note that should be deleted.
+ */
+ public void deleteNote(long id) {
+ Log.v("Note", "deleteNote(" + id + ")");
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.delete(table_notes,
+ key_id + " = ?",
+ new String[]{String.valueOf(id)});
+ db.close();
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.v("Note", "onUpgrade - DELETE *");
+ clearDatabase();
+ }
+
+ public void clearDatabase() {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.delete(table_notes, null, null);
+ db.close();
+ }
+
+ public Context getContext() {
+ return context;
+ }
+
+ public void synchronizeWithServer() {
+ serverSyncHelper.synchronize();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java
new file mode 100644
index 00000000..10722a67
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteServerSyncHelper.java
@@ -0,0 +1,295 @@
+package it.niedermann.owncloud.notes.persistence;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.support.design.widget.Snackbar;
+import android.util.Log;
+import android.view.View;
+
+import org.json.JSONException;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.android.activity.SettingsActivity;
+import it.niedermann.owncloud.notes.model.DBStatus;
+import it.niedermann.owncloud.notes.model.Note;
+import it.niedermann.owncloud.notes.util.ICallback;
+import it.niedermann.owncloud.notes.util.NotesClient;
+
+/**
+ * Helps to synchronize the Database to the Server.
+ * <p/>
+ * Created by stefan on 20.09.15.
+ */
+public class NoteServerSyncHelper {
+
+ private NotesClient client = null;
+ private NoteSQLiteOpenHelper db = null;
+
+ private int operationsCount = 0;
+ private int operationsFinished = 0;
+
+ private List<ICallback> callbacks = new ArrayList<>();
+
+ private final View.OnClickListener goToSettingsListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Activity parent = (Activity) db.getContext();
+ Intent intent = new Intent(parent, SettingsActivity.class);
+ parent.startActivity(intent);
+ }
+ };
+
+ public void addCallback(ICallback callback) {
+ callbacks.add(callback);
+ }
+
+ public boolean isFinished() {
+ return operationsFinished == operationsCount;
+ }
+
+ public NoteServerSyncHelper(NoteSQLiteOpenHelper db) {
+ this.db = db;
+ SharedPreferences preferences = PreferenceManager
+ .getDefaultSharedPreferences(db.getContext().getApplicationContext());
+ String url = preferences.getString(SettingsActivity.SETTINGS_URL,
+ SettingsActivity.DEFAULT_SETTINGS);
+ String username = preferences.getString(SettingsActivity.SETTINGS_USERNAME,
+ SettingsActivity.DEFAULT_SETTINGS);
+ String password = preferences.getString(SettingsActivity.SETTINGS_PASSWORD,
+ SettingsActivity.DEFAULT_SETTINGS);
+ client = new NotesClient(url, username, password);
+ }
+
+ public void synchronize() {
+ uploadEditedNotes();
+ uploadNewNotes();
+ uploadDeletedNotes();
+ downloadNotes();
+ final Handler handler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ for (ICallback callback : callbacks) {
+ callback.onFinish();
+ }
+ }
+ };
+ Timer timer = new Timer();
+ timer.scheduleAtFixedRate(new TimerTask() {
+ public void run() {
+ Log.v("Note", "Sync operations: " + operationsFinished + "/" + operationsCount);
+ if (isFinished()) {
+ handler.obtainMessage(1).sendToTarget();
+ cancel();
+ }
+ }
+ }, 0, 200);
+ }
+
+ public void uploadEditedNotes() {
+ List<Note> notes = db.getNotesByStatus(DBStatus.LOCAL_EDITED);
+ for (Note note : notes) {
+ UploadEditedNotesTask editedNotesTask = new UploadEditedNotesTask();
+ editedNotesTask.execute(note);
+ }
+ }
+
+ public void uploadNewNotes() {
+ List<Note> notes = db.getNotesByStatus(DBStatus.LOCAL_CREATED);
+ for (Note note : notes) {
+ UploadNewNoteTask newNotesTask = new UploadNewNoteTask();
+ newNotesTask.execute(note);
+ }
+ }
+
+ public void uploadDeletedNotes() {
+ List<Note> notes = db.getNotesByStatus(DBStatus.LOCAL_DELETED);
+ for (Note note : notes) {
+ UploadDeletedNoteTask deletedNotesTask = new UploadDeletedNoteTask();
+ deletedNotesTask.execute(note);
+ }
+ }
+
+ public void downloadNotes() {
+ DownloadNotesTask downloadNotesTask = new DownloadNotesTask();
+ downloadNotesTask.execute();
+ }
+
+ private class UploadNewNoteTask extends AsyncTask<Object, Void, Object[]> {
+ @Override
+ protected Object[] doInBackground(Object... params) {
+ operationsCount++;
+ Note oldNote = (Note) params[0];
+ try {
+ Note note = client.createNote(oldNote.getContent());
+ return new Object[]{note, oldNote.getId()};
+ } catch (MalformedURLException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_url_malformed, Snackbar.LENGTH_LONG)
+ .setAction(R.string.snackbar_settings, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Activity parent = (Activity) db.getContext();
+ Intent intent = new Intent(parent, SettingsActivity.class);
+ parent.startActivity(intent);
+ }
+ })
+ .show();
+ e.printStackTrace();
+ } catch (JSONException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_json, Snackbar.LENGTH_LONG)
+ .setAction(R.string.snackbar_settings, new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Activity parent = (Activity) db.getContext();
+ Intent intent = new Intent(parent, SettingsActivity.class);
+ parent.startActivity(intent);
+ }
+ })
+ .show();
+ e.printStackTrace();
+ } catch (IOException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_io, Snackbar.LENGTH_LONG)
+ .show();
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Object[] params) {
+ if(params != null) {
+ Long id = (Long) params[1];
+ if (id != null) {
+ db.deleteNote(((Long) params[1]));
+ }
+ db.addNote((Note) params[0]);
+ }
+ operationsFinished++;
+ }
+ }
+
+ private class UploadEditedNotesTask extends AsyncTask<Object, Void, Note> {
+ @Override
+ protected Note doInBackground(Object... params) {
+ operationsCount++;
+ try {
+ Note oldNote = (Note) params[0];
+ return client.editNote(oldNote.getId(), oldNote.getContent());
+ } catch (MalformedURLException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_url_malformed, Snackbar.LENGTH_LONG)
+ .setAction(R.string.snackbar_settings, goToSettingsListener)
+ .show();
+ e.printStackTrace();
+ } catch (JSONException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_json, Snackbar.LENGTH_LONG)
+ .setAction(R.string.snackbar_settings, goToSettingsListener)
+ .show();
+ e.printStackTrace();
+ } catch (IOException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_io, Snackbar.LENGTH_LONG)
+ .show();
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Note note) {
+ db.updateNote(note);
+ operationsFinished++;
+ }
+ }
+
+ private class UploadDeletedNoteTask extends AsyncTask<Object, Void, Void> {
+ Long id = null;
+
+ @Override
+ protected Void doInBackground(Object... params) {
+ operationsCount++;
+ try {
+ id = ((Note) params[0]).getId();
+ client.deleteNote(id);
+ } catch (MalformedURLException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_url_malformed, Snackbar.LENGTH_LONG)
+ .setAction(R.string.snackbar_settings, goToSettingsListener)
+ .show();
+ e.printStackTrace();
+ } catch (IOException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_io, Snackbar.LENGTH_LONG)
+ .show();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ db.deleteNote(id);
+ operationsFinished++;
+ }
+ }
+
+ private class DownloadNotesTask extends AsyncTask<Object, Void, List<Note>> {
+ private boolean serverError = false;
+
+ @Override
+ protected List<Note> doInBackground(Object... params) {
+ operationsCount++;
+ List<Note> notes = new ArrayList<>();
+ try {
+ notes = client.getNotes();
+ } catch (MalformedURLException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_url_malformed, Snackbar.LENGTH_LONG)
+ .setAction(R.string.snackbar_settings, goToSettingsListener)
+ .show();
+ serverError = true;
+ e.printStackTrace();
+ } catch (JSONException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_json, Snackbar.LENGTH_LONG)
+ .setAction(R.string.snackbar_settings, goToSettingsListener)
+ .show();
+ serverError = true;
+ e.printStackTrace();
+ } catch (IOException e) {
+ Snackbar
+ .make(((Activity) db.getContext()).getWindow().getDecorView(), R.string.error_io, Snackbar.LENGTH_LONG)
+ .show();
+ serverError = true;
+ e.printStackTrace();
+ }
+ return notes;
+ }
+
+ @Override
+ protected void onPostExecute(List<Note> result) {
+ // Clear Database only if there is an Server
+ if(!serverError) {
+ db.clearDatabase();
+ }
+ for (Note note : result) {
+ db.addNote(note);
+ }
+ operationsFinished++;
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/ICallback.java b/app/src/main/java/it/niedermann/owncloud/notes/util/ICallback.java
new file mode 100644
index 00000000..7b0d8c88
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/util/ICallback.java
@@ -0,0 +1,8 @@
+package it.niedermann.owncloud.notes.util;
+
+/**
+ * Created by stefan on 01.10.15.
+ */
+public interface ICallback {
+ public void onFinish();
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java
new file mode 100644
index 00000000..e8b6df6d
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClient.java
@@ -0,0 +1,207 @@
+package it.niedermann.owncloud.notes.util;
+
+import android.util.Base64;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+
+import it.niedermann.owncloud.notes.model.Note;
+
+public class NotesClient {
+
+ private String url = "";
+ private String username = "";
+ private String password = "";
+
+ public NotesClient(String url, String username, String password) {
+ this.url = url;
+ this.username = username;
+ this.password = password;
+ }
+
+ public List<Note> getNotes() throws JSONException,
+ IOException {
+ List<Note> notesList = new ArrayList<>();
+ JSONArray notes = new JSONArray(requestServer("notes", "GET", null));
+ long noteId = 0;
+ String noteTitle = "";
+ String noteContent = "";
+ Calendar noteModified = null;
+ JSONObject currentItem;
+ for (int i = 0; i < notes.length(); i++) {
+ currentItem = notes.getJSONObject(i);
+
+ if (!currentItem.isNull("id")) {
+ noteId = currentItem.getLong("id");
+ }
+ if (!currentItem.isNull("title")) {
+ noteTitle = currentItem.getString("title");
+ }
+ if (!currentItem.isNull("content")) {
+ noteContent = currentItem.getString("content");
+ }
+ if (!currentItem.isNull("modified")) {
+ noteModified = GregorianCalendar.getInstance();
+ noteModified
+ .setTimeInMillis(currentItem.getLong("modified") * 1000);
+ }
+ notesList
+ .add(new Note(noteId, noteModified, noteTitle, noteContent));
+ }
+ return notesList;
+ }
+
+ /**
+ * Fetches on Note by ID from Server
+ * TODO Maybe fetch only id, title and modified from server until a note has been opened?
+ * @param id long - ID of the wanted note
+ * @return Requested Note
+ * @throws JSONException
+ * @throws IOException
+ */
+ @SuppressWarnings("unused")
+ public Note getNoteById(long id) throws
+ JSONException, IOException {
+ long noteId = 0;
+ String noteTitle = "";
+ String noteContent = "";
+ Calendar noteModified = null;
+ JSONObject currentItem = new JSONObject(
+ requestServer("notes/" + id, "GET", null));
+
+ if (!currentItem.isNull("id")) {
+ noteId = currentItem.getLong("id");
+ }
+ if (!currentItem.isNull("title")) {
+ noteTitle = currentItem.getString("title");
+ }
+ if (!currentItem.isNull("content")) {
+ noteContent = currentItem.getString("content");
+ }
+ if (!currentItem.isNull("modified")) {
+ noteModified = GregorianCalendar.getInstance();
+ noteModified
+ .setTimeInMillis(currentItem.getLong("modified") * 1000);
+ }
+ return new Note(noteId, noteModified, noteTitle, noteContent);
+ }
+
+ /**
+ * Creates a Note on the Server
+ * @param content String - Content of the new Note
+ * @return Created Note including generated Title, ID and lastModified-Date
+ * @throws JSONException
+ * @throws IOException
+ */
+ public Note createNote(String content) throws
+ JSONException, IOException {
+ long noteId = 0;
+ String noteTitle = "";
+ String noteContent = "";
+ Calendar noteModified = null;
+
+ JSONObject paramObject = new JSONObject();
+ paramObject.accumulate("content", content);
+ JSONObject currentItem = new JSONObject(requestServer("notes", "POST",
+ paramObject));
+
+ if (!currentItem.isNull("id")) {
+ noteId = currentItem.getLong("id");
+ }
+ if (!currentItem.isNull("title")) {
+ noteTitle = currentItem.getString("title");
+ }
+ if (!currentItem.isNull("content")) {
+ noteContent = currentItem.getString("content");
+ }
+ if (!currentItem.isNull("modified")) {
+ noteModified = GregorianCalendar.getInstance();
+ noteModified
+ .setTimeInMillis(currentItem.getLong("modified") * 1000);
+ }
+ return new Note(noteId, noteModified, noteTitle, noteContent);
+ }
+
+ public Note editNote(long noteId, String content)
+ throws JSONException, IOException {
+ String noteTitle = "";
+ Calendar noteModified = null;
+
+ JSONObject paramObject = new JSONObject();
+ paramObject.accumulate("content", content);
+ JSONObject currentItem = new JSONObject(requestServer(
+ "notes/" + noteId, "PUT", paramObject));
+
+ if (!currentItem.isNull("title")) {
+ noteTitle = currentItem.getString("title");
+ }
+ if (!currentItem.isNull("modified")) {
+ noteModified = GregorianCalendar.getInstance();
+ noteModified
+ .setTimeInMillis(currentItem.getLong("modified") * 1000);
+ }
+ return new Note(noteId, noteModified, noteTitle, content);
+ }
+
+ public void deleteNote(long noteId) throws
+ IOException {
+ this.requestServer("notes/" + noteId, "DELETE", null);
+ }
+
+ /**
+ * Request-Method for POST, PUT with or without JSON-Object-Parameter
+ *
+ * @param target Filepath to the wanted function
+ * @param method GET, POST, DELETE or PUT
+ * @param params JSON Object which shall be transferred to the server.
+ * @return Body of answer
+ * @throws MalformedURLException
+ * @throws IOException
+ */
+ private String requestServer(String target, String method, JSONObject params)
+ throws IOException {
+ String result = "";
+ String targetURL = url + "index.php/apps/notes/api/v0.2/" + target;
+ Log.v("Note", targetURL);
+ HttpURLConnection con = (HttpURLConnection) new URL(targetURL)
+ .openConnection();
+ con.setRequestMethod(method);
+ con.setRequestProperty(
+ "Authorization",
+ "Basic "
+ + new String(Base64.encode((username + ":"
+ + password).getBytes(), Base64.NO_WRAP)));
+ con.setConnectTimeout(10 * 1000); // 10 seconds
+ if (params != null) {
+ con.setFixedLengthStreamingMode(params.toString().getBytes().length);
+ con.setRequestProperty("Content-Type", "application/json");
+ con.setDoOutput(true);
+ OutputStream os = con.getOutputStream();
+ Log.v("Note", params.toString());
+ os.write(params.toString().getBytes());
+ os.flush();
+ os.close();
+ }
+ BufferedReader rd;
+ String line;
+ rd = new BufferedReader(new InputStreamReader(con.getInputStream()));
+ while ((line = rd.readLine()) != null) {
+ result += line;
+ }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java
new file mode 100644
index 00000000..cc02c0a8
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/util/NotesClientUtil.java
@@ -0,0 +1,84 @@
+package it.niedermann.owncloud.notes.util;
+
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Utils for Validation etc
+ * Created by stefan on 25.09.15.
+ */
+public class NotesClientUtil {
+
+ /**
+ * Checks if the given url String starts with http:// or https://
+ *
+ * @param url String
+ * @return true, if the given String is only http
+ */
+ public static boolean isHttp(String url) {
+ return url.length() > 4 && url.startsWith("http") && url.charAt(4) != 's';
+ }
+
+ /**
+ * Checks if the given URL returns a valid status code and sets the Check next to the URL-Input Field to visible.
+ * @param urlStr String URL
+ * @return URL is valid
+ */
+ public static boolean isValidURL(String urlStr) {
+ try {
+ URL url = new URL(urlStr);
+ HttpURLConnection urlc = (HttpURLConnection) url.openConnection();
+ urlc.setRequestProperty("Connection", "close");
+ urlc.setConnectTimeout(1000 * 10); // mTimeout is in seconds
+ urlc.connect();
+ if (urlc.getResponseCode() == 200) {
+ Log.v("Note", "ResponseCode: " + urlc.getResponseCode());
+ return true;
+ } else {
+ return false;
+ }
+ } catch (MalformedURLException e1) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ /**
+ *
+ * @param url String
+ * @param username String
+ * @param password String
+ * @return Username and Password are a valid Login-Combination for the given URL.
+ */
+ public static boolean isValidLogin(String url, String username, String password) {
+ try {
+ String targetURL = url + "index.php/apps/notes/api/v0.2/notes";
+ Log.v("Note", targetURL);
+ HttpURLConnection con = (HttpURLConnection) new URL(targetURL)
+ .openConnection();
+ con.setRequestMethod("GET");
+ con.setRequestProperty(
+ "Authorization",
+ "Basic "
+ + new String(Base64.encode((username + ":"
+ + password).getBytes(), Base64.NO_WRAP)));
+ con.setConnectTimeout(10 * 1000); // 10 seconds
+ con.connect();
+ if (con.getResponseCode() == 200) {
+ return true;
+ }
+ } catch (MalformedURLException e1) {
+ return false;
+ } catch (IOException e) {
+ return false;
+ }
+ return false;
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_action_cancel.png b/app/src/main/res/drawable-hdpi/ic_action_cancel.png
new file mode 100644
index 00000000..71d5e118
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_copy.png b/app/src/main/res/drawable-hdpi/ic_action_copy.png
new file mode 100644
index 00000000..c170a270
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_copy.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_delete.png b/app/src/main/res/drawable-hdpi/ic_action_delete.png
new file mode 100644
index 00000000..21287cbd
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_delete.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_done.png b/app/src/main/res/drawable-hdpi/ic_action_done.png
new file mode 100644
index 00000000..c6503fd0
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_done.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_done_dark.png b/app/src/main/res/drawable-hdpi/ic_action_done_dark.png
new file mode 100644
index 00000000..392a259e
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_done_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_edit.png b/app/src/main/res/drawable-hdpi/ic_action_edit.png
new file mode 100644
index 00000000..02e19d04
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_edit.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_new.png b/app/src/main/res/drawable-hdpi/ic_action_new.png
new file mode 100644
index 00000000..0fdced8f
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_new.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_settings.png b/app/src/main/res/drawable-hdpi/ic_action_settings.png
new file mode 100644
index 00000000..a1a19400
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_settings.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_action_share.png b/app/src/main/res/drawable-hdpi/ic_action_share.png
new file mode 100644
index 00000000..0c460c47
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_action_share.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 00000000..439747ce
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_cancel.png b/app/src/main/res/drawable-mdpi/ic_action_cancel.png
new file mode 100644
index 00000000..46907806
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_copy.png b/app/src/main/res/drawable-mdpi/ic_action_copy.png
new file mode 100644
index 00000000..afd28ddd
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_copy.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_delete.png b/app/src/main/res/drawable-mdpi/ic_action_delete.png
new file mode 100644
index 00000000..9b0e2dbd
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_delete.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_done.png b/app/src/main/res/drawable-mdpi/ic_action_done.png
new file mode 100644
index 00000000..4eb240b3
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_done.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_done_dark.png b/app/src/main/res/drawable-mdpi/ic_action_done_dark.png
new file mode 100644
index 00000000..3f9b108f
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_done_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_edit.png b/app/src/main/res/drawable-mdpi/ic_action_edit.png
new file mode 100644
index 00000000..5a06bff5
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_edit.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_new.png b/app/src/main/res/drawable-mdpi/ic_action_new.png
new file mode 100644
index 00000000..67bb598e
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_new.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_settings.png b/app/src/main/res/drawable-mdpi/ic_action_settings.png
new file mode 100644
index 00000000..8a2f886d
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_settings.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_action_share.png b/app/src/main/res/drawable-mdpi/ic_action_share.png
new file mode 100644
index 00000000..1cafd213
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_action_share.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 00000000..de725181
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_cancel.png b/app/src/main/res/drawable-xhdpi/ic_action_cancel.png
new file mode 100644
index 00000000..172acd0f
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_copy.png b/app/src/main/res/drawable-xhdpi/ic_action_copy.png
new file mode 100644
index 00000000..c4d15780
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_copy.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_delete.png b/app/src/main/res/drawable-xhdpi/ic_action_delete.png
new file mode 100644
index 00000000..beb1637e
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_delete.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_done.png b/app/src/main/res/drawable-xhdpi/ic_action_done.png
new file mode 100644
index 00000000..c297f32d
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_done.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_done_dark.png b/app/src/main/res/drawable-xhdpi/ic_action_done_dark.png
new file mode 100644
index 00000000..3400099d
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_done_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_edit.png b/app/src/main/res/drawable-xhdpi/ic_action_edit.png
new file mode 100644
index 00000000..d6668a05
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_edit.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_new.png b/app/src/main/res/drawable-xhdpi/ic_action_new.png
new file mode 100644
index 00000000..d64c22e9
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_new.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_settings.png b/app/src/main/res/drawable-xhdpi/ic_action_settings.png
new file mode 100644
index 00000000..2fbddd2f
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_settings.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_action_share.png b/app/src/main/res/drawable-xhdpi/ic_action_share.png
new file mode 100644
index 00000000..e275d938
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_action_share.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..be0cc6a5
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png b/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png
new file mode 100644
index 00000000..ef610fb4
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_copy.png b/app/src/main/res/drawable-xxhdpi/ic_action_copy.png
new file mode 100644
index 00000000..eead97da
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_copy.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_delete.png b/app/src/main/res/drawable-xxhdpi/ic_action_delete.png
new file mode 100644
index 00000000..026c3313
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_delete.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_done.png b/app/src/main/res/drawable-xxhdpi/ic_action_done.png
new file mode 100644
index 00000000..e43c8985
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_done.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_done_dark.png b/app/src/main/res/drawable-xxhdpi/ic_action_done_dark.png
new file mode 100644
index 00000000..7f67ee04
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_done_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_edit.png b/app/src/main/res/drawable-xxhdpi/ic_action_edit.png
new file mode 100644
index 00000000..377b2e8d
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_edit.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_new.png b/app/src/main/res/drawable-xxhdpi/ic_action_new.png
new file mode 100644
index 00000000..7e699137
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_new.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_settings.png b/app/src/main/res/drawable-xxhdpi/ic_action_settings.png
new file mode 100644
index 00000000..ccdaf494
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_settings.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_share.png b/app/src/main/res/drawable-xxhdpi/ic_action_share.png
new file mode 100644
index 00000000..19c907fd
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_action_share.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..4f2f0572
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_cancel.png b/app/src/main/res/drawable-xxxhdpi/ic_action_cancel.png
new file mode 100644
index 00000000..ba88f122
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_copy.png b/app/src/main/res/drawable-xxxhdpi/ic_action_copy.png
new file mode 100644
index 00000000..cf9da9d1
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_copy.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_delete.png b/app/src/main/res/drawable-xxxhdpi/ic_action_delete.png
new file mode 100644
index 00000000..7cc287bd
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_delete.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_done.png b/app/src/main/res/drawable-xxxhdpi/ic_action_done.png
new file mode 100644
index 00000000..7cec5ed4
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_done.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_done_dark.png b/app/src/main/res/drawable-xxxhdpi/ic_action_done_dark.png
new file mode 100644
index 00000000..e63d05b4
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_done_dark.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png b/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png
new file mode 100644
index 00000000..bde8b21c
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_new.png b/app/src/main/res/drawable-xxxhdpi/ic_action_new.png
new file mode 100644
index 00000000..165c907d
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_new.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_settings.png b/app/src/main/res/drawable-xxxhdpi/ic_action_settings.png
new file mode 100644
index 00000000..06bf6e3c
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_settings.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_share.png b/app/src/main/res/drawable-xxxhdpi/ic_action_share.png
new file mode 100644
index 00000000..04be4201
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_action_share.png
Binary files differ
diff --git a/app/src/main/res/drawable/list_item_background_selector.xml b/app/src/main/res/drawable/list_item_background_selector.xml
new file mode 100644
index 00000000..bd502458
--- /dev/null
+++ b/app/src/main/res/drawable/list_item_background_selector.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Selector is used for Background Colors in List Items -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- :selected -->
+ <item android:drawable="@color/bg_highlighted" android:state_activated="true"/>
+ <item android:drawable="@color/bg_normal"/>
+</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/list_item_color_selector.xml b/app/src/main/res/drawable/list_item_color_selector.xml
new file mode 100644
index 00000000..14de8d58
--- /dev/null
+++ b/app/src/main/res/drawable/list_item_color_selector.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Selector is used for Header Color in List Items -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- :selected -->
+ <item android:color="@color/fg_default_selection" android:state_activated="true"/>
+ <item android:color="@color/fg_default"/>
+</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/list_item_color_selector_low.xml b/app/src/main/res/drawable/list_item_color_selector_low.xml
new file mode 100644
index 00000000..727f5c93
--- /dev/null
+++ b/app/src/main/res/drawable/list_item_color_selector_low.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Selector is used for Sub Title Color in List Items -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- :selected -->
+ <!--item android:color="@color/fg_contrast" android:state_activated="true"/-->
+ <item android:color="@color/fg_default_low"/>
+</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/settings.xml b/app/src/main/res/drawable/settings.xml
new file mode 100644
index 00000000..3f626603
--- /dev/null
+++ b/app/src/main/res/drawable/settings.xml
@@ -0,0 +1,8 @@
+<!-- drawable/settings.xml -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path android:fillColor="#000" android:pathData="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" />
+</vector> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml
new file mode 100644
index 00000000..31bfe772
--- /dev/null
+++ b/app/src/main/res/layout/activity_about.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="@dimen/activity_horizontal_margin">
+
+ <TextView
+ android:id="@+id/about_version_title"
+ style="?android:attr/listSeparatorTextViewStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/about_version_title"/>
+
+ <TextView
+ android:id="@+id/about_version"
+ style="?android:attr/editTextPreferenceStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:text="@string/about_version"/>
+
+ <TextView
+ android:id="@+id/about_author_title"
+ style="?android:attr/listSeparatorTextViewStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/about_author_title"/>
+
+ <TextView
+ android:id="@+id/about_author"
+ style="?android:attr/editTextPreferenceStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:text="@string/about_author"/>
+
+ <TextView
+ android:id="@+id/about_icon_disclaimer_title"
+ style="?android:attr/listSeparatorTextViewStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/about_icon_disclaimer_title"/>
+
+ <TextView
+ android:id="@+id/about_icon_disclaimer"
+ style="?android:attr/editTextPreferenceStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="10dp"
+ android:text="@string/about_icon_disclaimer"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_create.xml b/app/src/main/res/layout/activity_create.xml
new file mode 100644
index 00000000..03b6ba66
--- /dev/null
+++ b/app/src/main/res/layout/activity_create.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context="it.niedermann.owncloud.notes.android.activity.CreateNoteActivity"
+ android:id="@+id/createContentContainer"
+ android:weightSum="1">
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scrollView">
+
+ <EditText
+ android:id="@+id/createContent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="textMultiLine" />
+ </ScrollView>
+
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_edit.xml b/app/src/main/res/layout/activity_edit.xml
new file mode 100644
index 00000000..a9134c2b
--- /dev/null
+++ b/app/src/main/res/layout/activity_edit.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context="it.niedermann.owncloud.notes.android.activity.CreateNoteActivity"
+ android:id="@+id/editContentContainer" >
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/scrollView2" >
+
+ <EditText
+ android:id="@+id/editContent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ems="10"
+ android:inputType="textMultiLine" />
+ </ScrollView>
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_notes_list_view.xml b/app/src/main/res/layout/activity_notes_list_view.xml
new file mode 100644
index 00000000..128eec61
--- /dev/null
+++ b/app/src/main/res/layout/activity_notes_list_view.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v4.widget.SwipeRefreshLayout
+ android:id="@+id/swiperefreshlayout"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context="it.niedermann.owncloud.notes.android.activity.NotesListViewActivity"
+ tools:ignore="MergeRootFrame">
+
+ <ListView
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ </ListView>
+ </android.support.v4.widget.SwipeRefreshLayout>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab_create"
+ style="@style/fab"
+ android:src="@drawable/ic_action_new"
+ app:backgroundTint="@color/primary"
+ app:rippleColor="@color/primary_dark"/>
+</merge> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 00000000..2a079658
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.GridLayout
+ android:id="@+id/settings_grid_layout"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="@dimen/activity_horizontal_margin"
+ app:columnCount="2"
+ app:orientation="horizontal"
+ >
+
+ <android.support.design.widget.TextInputLayout
+ android:id="@+id/settings_url_wrapper"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_column="0"
+ app:layout_row="0"
+ app:layout_gravity="fill"
+ >
+
+ <EditText
+ android:id="@+id/settings_url"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/settings_url"
+ android:inputType="textUri"/>
+ </android.support.design.widget.TextInputLayout>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/settings_url_check"
+ android:src="@drawable/ic_action_done_dark"
+ android:contentDescription="@string/settings_url_check_description"
+ android:visibility="invisible"
+ app:layout_column="1"
+ app:layout_row="0"
+ />
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/settings_url_warn_http"
+ android:visibility="gone"
+ android:text="@string/settings_url_warn_http"
+ app:layout_columnSpan="2"/>
+
+ <android.support.design.widget.TextInputLayout
+ android:id="@+id/settings_username_wrapper"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_column="0"
+ app:layout_row="2"
+ app:layout_gravity="fill">
+
+ <EditText
+ android:id="@+id/settings_username"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/settings_username"/>
+ </android.support.design.widget.TextInputLayout>
+
+ <android.support.design.widget.TextInputLayout
+ android:id="@+id/settings_password_wrapper"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_column="0"
+ app:layout_row="3"
+ app:layout_gravity="fill">
+
+ <EditText
+ android:id="@+id/settings_password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/settings_password"
+ android:inputType="textPassword"/>
+ </android.support.design.widget.TextInputLayout>
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/settings_password_check"
+ android:src="@drawable/ic_action_done_dark"
+ android:contentDescription="@string/settings_password_check_description"
+ android:visibility="invisible"
+ app:layout_column="1"
+ app:layout_row="3"
+ />
+
+ <Button
+ android:id="@+id/settings_submit"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:shadowColor="@color/fg_default_low"
+ android:text="@string/settings_submit"
+ app:layout_gravity="end"
+ app:layout_row="4"
+ app:layout_column="0"
+ app:layout_columnSpan="2"
+ />
+
+</android.support.v7.widget.GridLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_single_note.xml b/app/src/main/res/layout/activity_single_note.xml
new file mode 100644
index 00000000..0fa8dcb5
--- /dev/null
+++ b/app/src/main/res/layout/activity_single_note.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <WebView
+ android:id="@+id/singleNoteContent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="10dp"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab_edit"
+ style="@style/fab"
+ android:src="@drawable/ic_action_edit"
+ app:backgroundTint="@color/primary"
+ app:rippleColor="@color/primary_dark"/>
+</merge> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_notes_list_view.xml b/app/src/main/res/layout/fragment_notes_list_view.xml
new file mode 100644
index 00000000..c37f00e8
--- /dev/null
+++ b/app/src/main/res/layout/fragment_notes_list_view.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:padding="16dp"
+ android:background="@drawable/list_item_background_selector"
+ android:id="@+id/noteItem" >
+
+ <TextView
+ android:id="@+id/noteTitle"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:layout_alignWithParentIfMissing="true"
+ android:gravity="center_vertical"
+ android:textColor="@drawable/list_item_color_selector"
+ android:textSize="16sp" />
+
+ <TextView
+ android:id="@+id/noteModified"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:ellipsize="marquee"
+ android:singleLine="true"
+ android:layout_below="@id/noteTitle"
+ android:textColor="@drawable/list_item_color_selector_low"
+ android:textSize="14sp" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/app/src/main/res/menu/menu_list_context_multiple.xml b/app/src/main/res/menu/menu_list_context_multiple.xml
new file mode 100644
index 00000000..f32c4146
--- /dev/null
+++ b/app/src/main/res/menu/menu_list_context_multiple.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_delete"
+ android:icon="@drawable/ic_action_delete"
+ android:orderInCategory="100"
+ app:showAsAction="ifRoom"
+ android:title="@string/menu_delete"/>
+
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/menu_list_context_single.xml b/app/src/main/res/menu/menu_list_context_single.xml
new file mode 100644
index 00000000..fbb97083
--- /dev/null
+++ b/app/src/main/res/menu/menu_list_context_single.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" >
+
+ <item
+ android:id="@+id/menu_edit"
+ android:icon="@drawable/ic_action_edit"
+ android:orderInCategory="100"
+ app:showAsAction="ifRoom"
+ android:title="@string/menu_edit"/>
+ <item
+ android:id="@+id/menu_share"
+ android:actionProviderClass="android.widget.ShareActionProvider"
+ android:icon="@drawable/ic_action_share"
+ app:showAsAction="ifRoom"
+ android:title="@string/menu_share"/>
+ <item
+ android:id="@+id/menu_delete"
+ android:icon="@drawable/ic_action_delete"
+ android:orderInCategory="100"
+ app:showAsAction="ifRoom"
+ android:title="@string/menu_delete"/>
+
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/menu_list_view.xml b/app/src/main/res/menu/menu_list_view.xml
new file mode 100644
index 00000000..f3c48bbe
--- /dev/null
+++ b/app/src/main/res/menu/menu_list_view.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:context="com.example.owncloudnotes.NotesListViewActivity"
+ xmlns:app="http://schemas.android.com/apk/res-auto" >
+ <item
+ android:id="@+id/action_settings"
+ android:icon="@drawable/ic_action_settings"
+ android:orderInCategory="100"
+ app:showAsAction="never"
+ android:title="@string/action_settings"/>
+ <item
+ android:id="@+id/action_about"
+ android:orderInCategory="100"
+ app:showAsAction="never"
+ android:title="@string/action_about"/>
+
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/menu_menu_create.xml b/app/src/main/res/menu/menu_menu_create.xml
new file mode 100644
index 00000000..bb9b27c3
--- /dev/null
+++ b/app/src/main/res/menu/menu_menu_create.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" >
+
+ <item
+ android:id="@+id/action_create_cancel"
+ android:icon="@drawable/ic_action_cancel"
+ android:orderInCategory="90"
+ app:showAsAction="always"
+ android:title="@string/action_create_save"/>
+
+ <item
+ android:id="@+id/action_create_save"
+ android:icon="@drawable/ic_action_done"
+ android:orderInCategory="90"
+ app:showAsAction="always"
+ android:title="@string/action_create_save"/>
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/menu_menu_edit.xml b/app/src/main/res/menu/menu_menu_edit.xml
new file mode 100644
index 00000000..db01a4b5
--- /dev/null
+++ b/app/src/main/res/menu/menu_menu_edit.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" >
+
+ <item
+ android:id="@+id/action_edit_cancel"
+ android:icon="@drawable/ic_action_cancel"
+ android:orderInCategory="90"
+ app:showAsAction="always"
+ android:title="@string/action_edit_save"/>
+ <item
+ android:id="@+id/action_edit_save"
+ android:icon="@drawable/ic_action_done"
+ android:orderInCategory="90"
+ app:showAsAction="always"
+ android:title="@string/action_edit_save"/>
+
+</menu>
diff --git a/app/src/main/res/menu/menu_note_list_view.xml b/app/src/main/res/menu/menu_note_list_view.xml
new file mode 100644
index 00000000..bc19345f
--- /dev/null
+++ b/app/src/main/res/menu/menu_note_list_view.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" >
+ <item
+ android:id="@+id/menu_share"
+ android:actionProviderClass="android.widget.ShareActionProvider"
+ android:icon="@drawable/ic_action_share"
+ android:orderInCategory="100"
+ app:showAsAction="never"
+ android:title="@string/menu_share"/>
+ <item
+ android:id="@+id/menu_copy"
+ android:icon="@drawable/ic_action_copy"
+ android:orderInCategory="110"
+ app:showAsAction="never"
+ android:title="@string/menu_copy"/>
+ <item
+ android:id="@+id/menu_delete"
+ android:icon="@drawable/ic_action_delete"
+ android:orderInCategory="120"
+ app:showAsAction="never"
+ android:title="@string/menu_delete"/>
+
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
new file mode 100644
index 00000000..261f963d
--- /dev/null
+++ b/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">OwnCloud Notes</string>
+ <string name="action_create">Neue Notiz</string>
+ <string name="action_settings">Einstellungen</string>
+ <string name="action_create_save">Speichern</string>
+ <string name="action_edit_save">Speichern</string>
+ <string name="action_about">Über</string>
+ <string name="menu_delete">Löschen</string>
+ <string name="menu_copy">Kopieren</string>
+ <string name="menu_edit">Bearbeiten</string>
+ <string name="menu_share">Teilen</string>
+ <string name="menu_about">Über</string>
+
+ <string name="ab_selected">ausgewählt</string>
+
+ <string name="copy">Kopie</string>
+
+ <!-- About -->
+
+ <string name="about_version_title">Version</string>
+ <string name="about_version">Sie benutzen aktuell <strong>v0.1.0</strong></string>
+ <string name="about_author_title">Autor</string>
+ <string name="about_author">Diese Android-App wird entwickelt von <a href="http://www.niedermann.it/">Niedermann IT-Dienstleistungen</a></string>
+ <string name="about_icon_disclaimer_title">App-Icon</string>
+ <string name="about_icon_disclaimer"><p>Ursprüngliches Icon wurde erstellt von <a href="http://www.freepik.com" title="Freepik">Freepik</a> auf <a href="http://www.flaticon.com/free-icon/ceo-copyrighting_15475" title="Flaticon">www.flaticon.com</a></p></string>
+
+ <!-- Settings -->
+
+ <string name="settings_server">Server</string>
+ <string name="settings_url">URL</string>
+ <string name="settings_url_check_description">Zeigt an, ob die angegebene URL erreichbar ist.</string>
+ <string name="settings_url_warn_http">WARNUNG: http ist unsicher. Bitte benutzen Sie https.</string>
+ <string name="settings_username">Benutzername</string>
+ <string name="settings_password">Passwort</string>
+ <string name="settings_password_check_description">Zeigt an, ob die angegebenen Zugangsdaten korrekt sind.</string>
+ <string name="settings_submit">OK</string>
+
+ <!-- Network -->
+
+ <string name="network_connecting">Verbindung wird hergestellt</string>
+ <string name="network_connected">Verbunden</string>
+ <string name="network_disconnected">Kein Netzwerk verfügbar</string>
+
+ <!-- Error -->
+
+ <string name="error_url_malformed">URL nicht korrekt</string>
+ <string name="error_json">JSON Fehler</string>
+ <string name="error_io">IO Fehler</string>
+ <string name="error_username_password_invalid">Benutzername / Passwort nicht korrekt</string>
+
+ <!-- Snackbar Actions -->
+ <string name="snackbar_settings">Einstellungen</string>
+</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..b0fbfe3b
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Colors -->
+
+ <color name="primary">#1d2d44</color>
+ <color name="primary_dark">#112233</color>
+
+ <color name="bg_highlighted">#f3f3f3</color>
+ <color name="bg_normal">#ffffff</color>
+ <color name="fg_default">#000000</color>
+ <color name="fg_default_selection">#333333</color>
+ <color name="fg_default_low">#666666</color>
+ <color name="fg_contrast">#ffffff</color>
+</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 00000000..50c11bc9
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <!-- Default screen margins, per the Android Design guidelines. -->
+ <dimen name="activity_horizontal_margin">16dp</dimen>
+ <dimen name="activity_vertical_margin">16dp</dimen>
+
+ <!-- Buttons -->
+ <dimen name="button_padding">16dp</dimen>
+ <dimen name="button_elevation">5dp</dimen>
+
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..5b4ed838
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">OwnCloud Notes</string>
+ <string name="action_create">New Note</string>
+ <string name="action_settings">Settings</string>
+ <string name="action_create_save">Save</string>
+ <string name="action_edit_save">Save</string>
+ <string name="action_about">About</string>
+ <string name="menu_delete">Delete</string>
+ <string name="menu_copy">Copy</string>
+ <string name="menu_edit">Edit</string>
+ <string name="menu_share">Share</string>
+ <string name="menu_about">About</string>
+
+ <string name="ab_selected">selected</string>
+
+ <string name="copy">Copy</string>
+
+ <!-- About -->
+
+ <string name="about_version_title">Version</string>
+ <string name="about_version">You are currently using <strong>v0.1.0</strong></string>
+ <string name="about_author_title">Author</string>
+ <string name="about_author">This Android-Application is developed and presented by <a href="http://www.niedermann.it/">Niedermann IT-Dienstleistungen</a></string>
+ <string name="about_icon_disclaimer_title">App-Icon</string>
+ <string name="about_icon_disclaimer"><p>Original icon made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from <a href="http://www.flaticon.com/free-icon/ceo-copyrighting_15475" title="Flaticon">www.flaticon.com</a></p></string>
+
+ <!-- Settings -->
+
+ <string name="settings_server">Server</string>
+ <string name="settings_url">URL</string>
+ <string name="settings_url_check_description">Shows if the URL can be pinged.</string>
+ <string name="settings_url_warn_http">WARNING: http is unsafe. Please use https.</string>
+ <string name="settings_username">Username</string>
+ <string name="settings_password">Password</string>
+ <string name="settings_password_check_description">Shows if the given credentials are correct.</string>
+ <string name="settings_submit">OK</string>
+
+ <!-- Network -->
+
+ <string name="network_connecting">Verbindung wird hergestellt</string>
+ <string name="network_connected">Verbunden</string>
+ <string name="network_disconnected">Kein Netzwerk verfügbar</string>
+
+ <!-- Error -->
+
+ <string name="error_url_malformed">URL Malformed</string>
+ <string name="error_json">JSON Error</string>
+ <string name="error_io">IO Error</string>
+ <string name="error_username_password_invalid">Username / Password invalid</string>
+
+ <!-- Snackbar Actions -->
+ <string name="snackbar_settings">Settings</string>
+
+</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..396713a0
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="OwnCloud" parent="@style/Theme.AppCompat.Light.DarkActionBar">
+ <!-- Main theme colors -->
+ <!-- App Bar -->
+ <item name="colorPrimary">@color/primary</item>
+ <!-- Status Bar and contextual Status Bars -->
+ <item name="colorPrimaryDark">@color/primary_dark</item>
+ <!-- Snackbar Action Link -->
+ <item name="colorAccent">@color/primary</item>
+ <item name="android:buttonStyle">@style/OCButton</item>
+ </style>
+ <style name="fab">
+ <item name="android:layout_margin">16dp</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">end|bottom</item>
+ <item name="android:layout_alignParentBottom">true</item>
+ <item name="android:layout_alignParentRight">true</item>
+ </style>
+ <style name="OCButton" parent="android:style/Widget.Button">
+ <item name="android:background">@color/bg_normal</item>
+ <item name="android:textColor">@color/primary</item>
+ <item name="android:padding">@dimen/button_padding</item>
+ <item name="android:elevation">@dimen/button_elevation</item>
+ </style>
+</resources> \ No newline at end of file