From 9a16c9609610bd9dd7969cdc9048724ccfbdffdc Mon Sep 17 00:00:00 2001 From: steckbrief Date: Mon, 26 Nov 2018 23:56:45 +0100 Subject: [PATCH] first version of NextcloudShareBookmark --- .gitignore | 8 + app/.gitignore | 1 + app/build.gradle | 29 +++ app/proguard-rules.pro | 21 ++ .../share/ExampleInstrumentedTest.java | 26 +++ app/src/main/AndroidManifest.xml | 33 ++++ app/src/main/ic_launcher-web.png | Bin 0 -> 25564 bytes .../nextcloud/bookmark/share/Constants.java | 9 + .../bookmark/share/NextcloudBookmark.java | 42 ++++ .../share/NextcloudBookmarkService.java | 25 +++ .../AppCompatPreferenceActivity.java | 109 +++++++++++ .../NextcloudBookmarkShareActivity.java | 51 +++++ .../NextcloudPreferencesActivity.java | 180 ++++++++++++++++++ .../async/NextcloudAddBookmarkAsyncTask.java | 40 ++++ .../async/NextcloudBookmarkRestAsyncTask.java | 30 +++ .../async/NextcloudCheckCredentialsTask.java | 76 ++++++++ .../rest/NextcloudBookmarkRestClient.java | 100 ++++++++++ .../drawable-v24/ic_launcher_foreground.xml | 34 ++++ .../main/res/drawable/ic_info_black_24dp.xml | 9 + .../res/drawable/ic_launcher_background.xml | 170 +++++++++++++++++ .../drawable/ic_notifications_black_24dp.xml | 9 + .../main/res/drawable/ic_sync_black_24dp.xml | 9 + .../layout/activity_nextcloud_preferences.xml | 101 ++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2017 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 2253 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3958 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1404 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 1311 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2544 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 2821 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 3342 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5644 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 4528 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 5972 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 9017 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 6484 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 9285 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 13314 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/dimens.xml | 5 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 29 +++ app/src/main/res/values/styles.xml | 11 ++ app/src/main/res/values/values.xml | 5 + app/src/main/res/xml/preferences.xml | 13 ++ .../bookmark/share/ExampleUnitTest.java | 17 ++ bookmarks.png | Bin 0 -> 503 bytes build.gradle | 27 +++ gradle.properties | 13 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54708 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++++++++++++++++ gradlew.bat | 84 ++++++++ logo.png | Bin 0 -> 15921 bytes settings.gradle | 1 + 57 files changed, 1515 insertions(+) create mode 100644 .gitignore create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/de/thedevstack/android/nextcloud/bookmark/share/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-web.png create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/Constants.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/NextcloudBookmark.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/NextcloudBookmarkService.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/activities/AppCompatPreferenceActivity.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/activities/NextcloudBookmarkShareActivity.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/activities/NextcloudPreferencesActivity.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudAddBookmarkAsyncTask.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudBookmarkRestAsyncTask.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudCheckCredentialsTask.java create mode 100644 app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/rest/NextcloudBookmarkRestClient.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_info_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_notifications_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_sync_black_24dp.xml create mode 100644 app/src/main/res/layout/activity_nextcloud_preferences.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/values.xml create mode 100644 app/src/main/res/xml/preferences.xml create mode 100644 app/src/test/java/de/thedevstack/android/nextcloud/bookmark/share/ExampleUnitTest.java create mode 100644 bookmarks.png create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 logo.png create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09b993d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..b30d562 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + defaultConfig { + applicationId "de.thedevstack.android.nextcloud.bookmark.share" + minSdkVersion 19 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support:support-v4:27.1.1' + implementation 'com.android.support:design:27.1.1' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/de/thedevstack/android/nextcloud/bookmark/share/ExampleInstrumentedTest.java b/app/src/androidTest/java/de/thedevstack/android/nextcloud/bookmark/share/ExampleInstrumentedTest.java new file mode 100644 index 0000000..81828af --- /dev/null +++ b/app/src/androidTest/java/de/thedevstack/android/nextcloud/bookmark/share/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package de.thedevstack.android.nextcloud.bookmark.share; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("de.thedevstack.android.nextcloud.bookmark.share", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..1489cf9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000000000000000000000000000000000000..fac28b1815e861490399bf7f46c5c24bc8e3d49c GIT binary patch literal 25564 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®#%tv&%Gw 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 Build.VERSION_CODES.HONEYCOMB_MR2) { + int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); + + mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); + mLoginFormView.animate().setDuration(shortAnimTime).alpha( + show ? 0 : 1).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); + } + }); + + mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); + mProgressView.animate().setDuration(shortAnimTime).alpha( + show ? 1 : 0).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); + } + }); + } else { + // The ViewPropertyAnimator APIs are not available, so simply show + // and hide the relevant UI components. + mProgressView.setVisibility(show ? View.VISIBLE : View.GONE); + mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE); + } + } + + public void stopProgress() { + this.checkCredentialsTask = null; + showProgress(false); + } + + public void showIncorrectPassword() { + passwordView.setError(getString(R.string.error_incorrect_password)); + passwordView.requestFocus(); + } +} + diff --git a/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudAddBookmarkAsyncTask.java b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudAddBookmarkAsyncTask.java new file mode 100644 index 0000000..14ddcfd --- /dev/null +++ b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudAddBookmarkAsyncTask.java @@ -0,0 +1,40 @@ +package de.thedevstack.android.nextcloud.bookmark.share.async; + +import android.app.Activity; +import android.util.Log; +import android.widget.Toast; + +import de.thedevstack.android.nextcloud.bookmark.share.R; + +/** + */ +public class NextcloudAddBookmarkAsyncTask extends NextcloudBookmarkRestAsyncTask { + + public NextcloudAddBookmarkAsyncTask(Activity context) { + super(context.getApplicationContext()); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + } + + @Override + protected Integer doInBackground(String... params) { + String bookmarkUrl = params[0]; + //String bookmarkTitle = params[1]; + + return this.client.addBookmark(bookmarkUrl); + } + + @Override + protected void onPostExecute(Integer resultCode) { + if (200 == resultCode) { + Log.i("NCAddBookmarkAsync", "Bookmark successfully set"); + Toast.makeText(context, R.string.toast_bookmark_add_success, Toast.LENGTH_LONG).show(); + } else { + Log.e("NCAddBookmarkAsync", "Failed to set Bookmark"); + Toast.makeText(context, R.string.toast_bookmark_add_failed, Toast.LENGTH_LONG).show(); + } + } +} diff --git a/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudBookmarkRestAsyncTask.java b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudBookmarkRestAsyncTask.java new file mode 100644 index 0000000..c1100e0 --- /dev/null +++ b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudBookmarkRestAsyncTask.java @@ -0,0 +1,30 @@ +package de.thedevstack.android.nextcloud.bookmark.share.async; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.preference.PreferenceManager; + +import de.thedevstack.android.nextcloud.bookmark.share.Constants; +import de.thedevstack.android.nextcloud.bookmark.share.rest.NextcloudBookmarkRestClient; + +/** + */ +public abstract class NextcloudBookmarkRestAsyncTask extends AsyncTask { + protected NextcloudBookmarkRestClient client; + protected Context context; + + public NextcloudBookmarkRestAsyncTask(Context context){ + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + String serverUrl = prefs.getString(Constants.SERVER_URL_PREFERENCE_KEY, null); + String username = prefs.getString(Constants.USERNAME_PREFERENCE_KEY, null); + String password = prefs.getString(Constants.PASSWORD_PREFERENCE_KEY, null); + + this.client = new NextcloudBookmarkRestClient(serverUrl, username, password); + this.context = context; + } + + public NextcloudBookmarkRestAsyncTask(String serverUrl, String username, String password) { + this.client = new NextcloudBookmarkRestClient(serverUrl, username, password); + } +} diff --git a/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudCheckCredentialsTask.java b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudCheckCredentialsTask.java new file mode 100644 index 0000000..b701197 --- /dev/null +++ b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/async/NextcloudCheckCredentialsTask.java @@ -0,0 +1,76 @@ +package de.thedevstack.android.nextcloud.bookmark.share.async; + +import android.content.Intent; +import android.preference.PreferenceManager; +import android.util.Log; +import android.widget.Toast; + +import de.thedevstack.android.nextcloud.bookmark.share.Constants; +import de.thedevstack.android.nextcloud.bookmark.share.NextcloudBookmark; +import de.thedevstack.android.nextcloud.bookmark.share.NextcloudBookmarkService; +import de.thedevstack.android.nextcloud.bookmark.share.R; +import de.thedevstack.android.nextcloud.bookmark.share.activities.NextcloudPreferencesActivity; + +/** + * Represents an asynchronous login/registration task used to authenticate + * the user. + */ +public class NextcloudCheckCredentialsTask extends NextcloudBookmarkRestAsyncTask { + private NextcloudPreferencesActivity preferencesActivity; + private NextcloudSettings ncSettings; + private NextcloudBookmark bookmark; + + public NextcloudCheckCredentialsTask(NextcloudPreferencesActivity preferencesActivity, NextcloudBookmark bookmark, String serverUrl, String username, String password) { + this(preferencesActivity, serverUrl, username, password); + this.bookmark = bookmark; + } + + public NextcloudCheckCredentialsTask(NextcloudPreferencesActivity preferencesActivity, String serverUrl, String username, String password) { + super(serverUrl, username, password); + this.preferencesActivity = preferencesActivity; + this.ncSettings = new NextcloudSettings(); + this.ncSettings.serverUrl = serverUrl; + this.ncSettings.username = username; + this.ncSettings.password = password; + } + + @Override + protected Boolean doInBackground(Intent... params) { + return this.client.checkCredentials(); + } + + @Override + protected void onPostExecute(final Boolean success) { + this.preferencesActivity.stopProgress(); + + Log.d("NCCheckCredentials", "result is " + success); + if (success) { + Log.d("NCCheckCredentials", "applying preferences"); + PreferenceManager.getDefaultSharedPreferences(this.preferencesActivity.getApplicationContext()) + .edit() + .putString(Constants.SERVER_URL_PREFERENCE_KEY, this.ncSettings.serverUrl) + .putString(Constants.USERNAME_PREFERENCE_KEY, this.ncSettings.username) + .putString(Constants.PASSWORD_PREFERENCE_KEY, this.ncSettings.password) + .commit(); + if (null != this.bookmark) { + NextcloudBookmarkService.addBookmark(this.preferencesActivity, this.bookmark); + this.preferencesActivity.finish(); + } else { + Toast.makeText(this.preferencesActivity, R.string.toast_settings_update_success, Toast.LENGTH_LONG).show(); + } + } else { + this.preferencesActivity.showIncorrectPassword(); + } + } + + @Override + protected void onCancelled() { + this.preferencesActivity.stopProgress(); + } + + private class NextcloudSettings { + String serverUrl; + String username; + String password; + } +} \ No newline at end of file diff --git a/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/rest/NextcloudBookmarkRestClient.java b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/rest/NextcloudBookmarkRestClient.java new file mode 100644 index 0000000..e92d9a0 --- /dev/null +++ b/app/src/main/java/de/thedevstack/android/nextcloud/bookmark/share/rest/NextcloudBookmarkRestClient.java @@ -0,0 +1,100 @@ +package de.thedevstack.android.nextcloud.bookmark.share.rest; + +import android.util.Base64; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import javax.net.ssl.HttpsURLConnection; + +/** + */ +public class NextcloudBookmarkRestClient { + private static final String LOGTAG = "NCBookmarkRestClient"; + private static final String BOOKMARK_APP_REST_API = "/apps/bookmarks/public/rest/v2/bookmark"; + private final String username; + private final String password; + private final String serverUrl; + private final String restApiBaseUrl; + private HttpsURLConnection urlConnection; + + public NextcloudBookmarkRestClient(String serverUrl, String username, String password) { + this.serverUrl = serverUrl; + this.username = username; + this.password = password; + this.restApiBaseUrl = serverUrl + BOOKMARK_APP_REST_API; + } + + private void openConnection(String url) throws IOException { + this.urlConnection = (HttpsURLConnection) new URL(url).openConnection(); + String encoded = Base64.encodeToString((this.username+":"+this.password).getBytes(StandardCharsets.UTF_8), 0); + this.urlConnection.setRequestProperty("Authorization", "Basic "+encoded); + } + + public boolean checkCredentials() { + Log.d(LOGTAG, "checking credentials"); + boolean validCredentials = false; + try { + this.openConnection(this.restApiBaseUrl); + this.urlConnection.setRequestMethod("HEAD"); + this.urlConnection.connect(); + validCredentials = 401 != this.urlConnection.getResponseCode(); + Log.d(LOGTAG, "credentials are " + validCredentials); + } catch (IOException e) { + System.out.println(e.getMessage()); + } finally { + if (null != this.urlConnection) { + this.urlConnection.disconnect(); + } + return validCredentials; + } + } + + public int addBookmark(String bookmarkUrl) { + Log.d(LOGTAG, "trying to add bookmark for '" + bookmarkUrl + "'"); + int responseCode = -1; + StringBuffer sb = new StringBuffer(); + InputStream is = null; + + try { + this.openConnection(this.restApiBaseUrl + "?url=" + bookmarkUrl); + this.urlConnection.setRequestMethod("POST"); + this.urlConnection.connect(); + + is = new BufferedInputStream(urlConnection.getInputStream()); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String inputLine = ""; + while ((inputLine = br.readLine()) != null) { + sb.append(inputLine); + } + Log.d(LOGTAG, sb.toString()); + + responseCode = this.urlConnection.getResponseCode(); + } catch (Exception e) { + Log.e(LOGTAG, "Error while trying to add bookmark: " + e.getMessage()); + } + finally { + Log.i(LOGTAG, "disconnecting..."); + if (null != this.urlConnection) { + this.urlConnection.disconnect(); + } + + if (is != null) { + try { + is.close(); + } + catch (IOException e) { + Log.d(LOGTAG, "Error closing InputStream"); + } + } + + return responseCode; + } + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_info_black_24dp.xml b/app/src/main/res/drawable/ic_info_black_24dp.xml new file mode 100644 index 0000000..34b8202 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 0000000..e3400cf --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_sync_black_24dp.xml b/app/src/main/res/drawable/ic_sync_black_24dp.xml new file mode 100644 index 0000000..5a283aa --- /dev/null +++ b/app/src/main/res/drawable/ic_sync_black_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_nextcloud_preferences.xml b/app/src/main/res/layout/activity_nextcloud_preferences.xml new file mode 100644 index 0000000..eff8ce5 --- /dev/null +++ b/app/src/main/res/layout/activity_nextcloud_preferences.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +