diff --git a/app/build.gradle b/app/build.gradle
index c65168d7..0cd879c0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -60,7 +60,7 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.10'
- implementation 'com.google.android.material:material:1.1.0'
+ implementation 'com.google.android.material:material:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.palette:palette:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
@@ -71,7 +71,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "androidx.work:work-runtime-ktx:2.4.0"
implementation "androidx.fragment:fragment-ktx:1.2.3"
-
+ implementation "androidx.browser:browser:1.2.0"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
diff --git a/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java b/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java
index 9e09bd2e..244f9afd 100644
--- a/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java
+++ b/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java
@@ -29,7 +29,7 @@ public class ReadropsDebugApp extends ReadropsApp implements Configuration.Provi
SoLoader.init(this, false);
initFlipper();
- initNiddler();
+ //initNiddler();
}
private void initFlipper() {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 13390561..9d47cb49 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,9 @@
-
+
{
- final int DRAWABLE_RIGHT = 2;
-
- int drawablePos = (binding.addFeedTextInput.getRight() -
- binding.addFeedTextInput.getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width());
- if (event.getAction() == MotionEvent.ACTION_UP && event.getRawX() >= drawablePos) {
- binding.addFeedTextInput.setText("");
- return true;
- }
-
- return false;
- });
-
viewModel = new ViewModelProvider(this).get(AddFeedsViewModel.class);
parseItemsAdapter = new ItemAdapter<>();
fastAdapter = FastAdapter.with(parseItemsAdapter);
fastAdapter.withSelectable(true);
fastAdapter.withOnClickListener((v, adapter, item, position) -> {
- if (item.isChecked()) {
- item.setChecked(false);
- fastAdapter.notifyAdapterItemChanged(position);
- } else {
- item.setChecked(true);
- fastAdapter.notifyAdapterItemChanged(position);
- }
-
+ item.setChecked(!item.isChecked());
+
+ fastAdapter.notifyAdapterItemChanged(position);
binding.addFeedOk.setEnabled(recyclerViewHasCheckedItems());
return true;
diff --git a/app/src/main/java/com/readrops/app/activities/ItemActivity.java b/app/src/main/java/com/readrops/app/activities/ItemActivity.java
index a1493f25..83e56894 100644
--- a/app/src/main/java/com/readrops/app/activities/ItemActivity.java
+++ b/app/src/main/java/com/readrops/app/activities/ItemActivity.java
@@ -22,6 +22,7 @@ import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ShareCompat;
import androidx.lifecycle.ViewModelProvider;
@@ -199,12 +200,7 @@ public class ItemActivity extends AppCompatActivity {
shareArticle();
return true;
case R.id.item_open:
- int value = Integer.parseInt(SharedPreferencesManager.readString(this,
- SharedPreferencesManager.SharedPrefKey.OPEN_ITEMS_IN));
- if (value == 0)
- openInNavigator();
- else
- openInWebView();
+ openUrl();
return true;
default:
return super.onOptionsItemSelected(item);
@@ -217,6 +213,22 @@ public class ItemActivity extends AppCompatActivity {
super.onBackPressed();
}
+ private void openUrl() {
+ int value = Integer.parseInt(SharedPreferencesManager.readString(this,
+ SharedPreferencesManager.SharedPrefKey.OPEN_ITEMS_IN));
+ switch (value) {
+ case 0:
+ openInNavigator();
+ break;
+ case 1:
+ openInWebView();
+ break;
+ default:
+ openInCustomTab();
+ break;
+ }
+ }
+
private void openInNavigator() {
Intent urlIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(itemWithFeed.getItem().getLink()));
startActivity(urlIntent);
@@ -225,11 +237,27 @@ public class ItemActivity extends AppCompatActivity {
private void openInWebView() {
Intent intent = new Intent(this, WebViewActivity.class);
intent.putExtra(WEB_URL, itemWithFeed.getItem().getLink());
- intent.putExtra(ACTION_BAR_COLOR, itemWithFeed.getColor() != 0 ? itemWithFeed.getColor() : itemWithFeed.getBgColor());
+ intent.putExtra(ACTION_BAR_COLOR, itemWithFeed.getBgColor() != 0 ? itemWithFeed.getBgColor() : itemWithFeed.getColor());
startActivity(intent);
}
+ private void openInCustomTab() {
+ boolean darkTheme = Boolean.parseBoolean(SharedPreferencesManager.readString(this, SharedPreferencesManager.SharedPrefKey.DARK_THEME));
+ int color = itemWithFeed.getBgColor() != 0 ? itemWithFeed.getBgColor() : itemWithFeed.getColor();
+
+ CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder()
+ .addDefaultShareMenuItem()
+ .setToolbarColor(color)
+ .setSecondaryToolbarColor(color)
+ .setColorScheme(darkTheme ? CustomTabsIntent.COLOR_SCHEME_DARK : CustomTabsIntent.COLOR_SCHEME_LIGHT)
+ .enableUrlBarHiding()
+ .setShowTitle(true)
+ .build();
+
+ customTabsIntent.launchUrl(this, Uri.parse(itemWithFeed.getItem().getLink()));
+ }
+
private void shareArticle() {
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
diff --git a/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java b/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java
index 2df601a6..71f8867b 100644
--- a/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java
+++ b/app/src/main/java/com/readrops/app/fragments/settings/AccountSettingsFragment.java
@@ -7,10 +7,9 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
import android.provider.Settings;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -29,6 +28,7 @@ import com.readrops.app.ReadropsApp;
import com.readrops.app.activities.AddAccountActivity;
import com.readrops.app.activities.ManageFeedsFoldersActivity;
import com.readrops.app.activities.NotificationPermissionActivity;
+import com.readrops.app.utils.FileUtils;
import com.readrops.app.utils.PermissionManager;
import com.readrops.app.utils.SharedPreferencesManager;
import com.readrops.app.utils.Utils;
@@ -36,14 +36,10 @@ import com.readrops.app.viewmodels.AccountViewModel;
import com.readrops.db.entities.account.Account;
import com.readrops.db.entities.account.AccountType;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableCompletableObserver;
import io.reactivex.schedulers.Schedulers;
+import kotlin.Unit;
import static android.app.Activity.RESULT_OK;
import static com.readrops.api.opml.OPMLHelper.OPEN_OPML_FILE_REQUEST;
@@ -77,6 +73,7 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
return fragment;
}
+ @SuppressWarnings("ConstantConditions")
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.acount_preferences);
@@ -121,16 +118,7 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
opmlPref.setOnPreferenceClickListener(preference -> {
new MaterialDialog.Builder(getActivity())
.items(R.array.opml_import_export)
- .itemsCallback(((dialog, itemView, position, text) -> {
- if (position == 0) {
- OPMLHelper.openFileIntent(this);
- } else {
- if (PermissionManager.isPermissionGranted(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE))
- exportAsOPMLFile();
- else
- requestExternalStoragePermission();
- }
- }))
+ .itemsCallback(((dialog, itemView, position, text) -> openOPMLMode(position)))
.show();
return true;
});
@@ -179,8 +167,24 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
.show();
}
+ private void openOPMLMode(int position) {
+ if (position == 0) {
+ OPMLHelper.openFileIntent(this);
+ } else {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+ if (PermissionManager.isPermissionGranted(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ exportAsOPMLFile();
+ } else {
+ requestExternalStoragePermission();
+ }
+ } else {
+ exportAsOPMLFile();
+ }
+ }
+ }
+
// region opml import
-
+
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == OPEN_OPML_FILE_REQUEST && resultCode == RESULT_OK && data != null) {
@@ -231,51 +235,34 @@ public class AccountSettingsFragment extends PreferenceFragmentCompat {
//region opml export
private void exportAsOPMLFile() {
+ String fileName = "subscriptions.opml";
+
try {
- String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
- File file = new File(filePath, "subscriptions.opml");
+ String path = FileUtils.writeDownloadFile(getContext(), fileName, "text/xml", outputStream -> {
+ viewModel.getFoldersWithFeeds()
+ .flatMapCompletable(folderListMap -> OPMLParser.write(folderListMap, outputStream))
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .doOnError(e -> Utils.showSnackbar(getView(), e.getMessage()))
+ .subscribe();
- final OutputStream outputStream = new FileOutputStream(file);
+ return Unit.INSTANCE;
+ });
- viewModel.getFoldersWithFeeds()
- .flatMapCompletable(folderListMap -> OPMLParser.write(folderListMap, outputStream))
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .doAfterTerminate(() -> {
- try {
- outputStream.flush();
- outputStream.close();
-
- } catch (IOException e) {
- Log.e(TAG, e.getMessage());
- Utils.showSnackbar(getView(), e.getMessage());
- }
- })
- .subscribe(new DisposableCompletableObserver() {
- @Override
- public void onComplete() {
- displayNotification(file);
- }
-
- @Override
- public void onError(Throwable e) {
- Utils.showSnackbar(getView(), e.getMessage());
- }
- });
+ displayNotification(fileName, path);
} catch (Exception e) {
- Log.e(TAG, e.getMessage());
- Utils.showSnackbar(getView(), e.getMessage());
+ displayErrorMessage();
}
}
- private void displayNotification(File file) {
+ private void displayNotification(String name, String absolutePath) {
Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.parse(file.getAbsolutePath()), "text/plain");
+ intent.setDataAndType(Uri.parse(absolutePath), "text/plain");
Notification notification = new NotificationCompat.Builder(getContext(), ReadropsApp.OPML_EXPORT_CHANNEL_ID)
.setContentTitle(getString(R.string.opml_export))
- .setContentText(file.getName())
+ .setContentText(name)
.setSmallIcon(R.drawable.ic_notif)
.setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
.setAutoCancel(true)
diff --git a/app/src/main/java/com/readrops/app/utils/FileUtils.kt b/app/src/main/java/com/readrops/app/utils/FileUtils.kt
new file mode 100644
index 00000000..9ae24dff
--- /dev/null
+++ b/app/src/main/java/com/readrops/app/utils/FileUtils.kt
@@ -0,0 +1,69 @@
+package com.readrops.app.utils
+
+import android.content.ContentValues
+import android.content.Context
+import android.os.Build
+import android.os.Environment
+import android.provider.MediaStore
+import androidx.annotation.RequiresApi
+import java.io.File
+import java.io.FileOutputStream
+import java.io.OutputStream
+
+object FileUtils {
+
+ @JvmStatic
+ fun writeDownloadFile(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
+ writeFileApi29(context, fileName, mimeType, listener)
+ else
+ writeFileApi28(fileName, listener)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.Q)
+ private fun writeFileApi29(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String {
+ val resolver = context.contentResolver
+ val downloadsUri = MediaStore.Downloads
+ .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
+
+ val fileDetails = ContentValues().apply {
+ put(MediaStore.Downloads.DISPLAY_NAME, fileName)
+ put(MediaStore.Downloads.IS_PENDING, 1)
+ put(MediaStore.Downloads.MIME_TYPE, mimeType)
+ }
+
+ val contentUri = resolver.insert(downloadsUri, fileDetails)
+
+ resolver.openFileDescriptor(contentUri!!, "w", null).use { pfd ->
+ val outputStream = FileOutputStream(pfd?.fileDescriptor!!)
+
+ try {
+ listener(outputStream)
+ } catch (e: Exception) {
+ throw e
+ } finally {
+ outputStream.flush()
+ outputStream.close()
+ }
+ }
+
+ fileDetails.clear()
+ fileDetails.put(MediaStore.Downloads.IS_PENDING, 0)
+ resolver.update(contentUri, fileDetails, null, null)
+
+ return contentUri.path!!
+ }
+
+ private fun writeFileApi28(fileName: String, listener: (OutputStream) -> Unit): String {
+ val filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath
+ val file = File(filePath, fileName)
+
+ val outputStream = FileOutputStream(file)
+ listener(outputStream)
+
+ outputStream.flush()
+ outputStream.close()
+
+ return file.absolutePath
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/readrops/app/utils/GlideModule.java b/app/src/main/java/com/readrops/app/utils/GlideModule.java
deleted file mode 100644
index 7d765a40..00000000
--- a/app/src/main/java/com/readrops/app/utils/GlideModule.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.readrops.app.utils;
-
-import androidx.annotation.NonNull;
-
-import com.bumptech.glide.module.AppGlideModule;
-
-@com.bumptech.glide.annotation.GlideModule
-public class GlideModule extends AppGlideModule {
-
-}
diff --git a/app/src/main/java/com/readrops/app/utils/HtmlParser.java b/app/src/main/java/com/readrops/app/utils/HtmlParser.java
index c1f5b7d2..85c7965d 100644
--- a/app/src/main/java/com/readrops/app/utils/HtmlParser.java
+++ b/app/src/main/java/com/readrops/app/utils/HtmlParser.java
@@ -15,6 +15,7 @@ import org.jsoup.select.Elements;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
@@ -33,25 +34,30 @@ public final class HtmlParser {
* @param url url to request
* @return a list of rss urls with their title
*/
- public static List getFeedLink(String url) throws Exception {
+ public static List getFeedLink(String url) {
List results = new ArrayList<>();
- Document document = Jsoup.parse(getHTMLHeadFromUrl(url), url);
+ String head = getHTMLHeadFromUrl(url);
+ if (head != null) {
+ Document document = Jsoup.parse(head, url);
- Elements elements = document.select("link");
+ Elements elements = document.select("link");
- for (Element element : elements) {
- String type = element.attributes().get("type");
+ for (Element element : elements) {
+ String type = element.attributes().get("type");
- if (isTypeRssFeed(type)) {
- String feedUrl = element.absUrl("href");
- String label = element.attributes().get("title");
+ if (isTypeRssFeed(type)) {
+ String feedUrl = element.absUrl("href");
+ String label = element.attributes().get("title");
- results.add(new ParsingResult(feedUrl, label));
+ results.add(new ParsingResult(feedUrl, label));
+ }
}
- }
- return results;
+ return results;
+ } else {
+ return Collections.emptyList();
+ }
}
private static boolean isTypeRssFeed(String type) {
diff --git a/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt b/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt
new file mode 100644
index 00000000..3138c7dd
--- /dev/null
+++ b/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt
@@ -0,0 +1,21 @@
+package com.readrops.app.utils
+
+import android.content.Context
+import com.bumptech.glide.Glide
+import com.bumptech.glide.Registry
+import com.bumptech.glide.annotation.GlideModule
+import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
+import com.bumptech.glide.load.model.GlideUrl
+import com.bumptech.glide.module.AppGlideModule
+import com.readrops.api.utils.HttpManager
+import java.io.InputStream
+
+@GlideModule
+class ReadropsGlideModule : AppGlideModule() {
+
+ override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
+ val factory = OkHttpUrlLoader.Factory(HttpManager.getInstance().okHttpClient)
+
+ glide.registry.replace(GlideUrl::class.java, InputStream::class.java, factory)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_add_feed.xml b/app/src/main/res/layout/activity_add_feed.xml
index 123511a4..cd53baf3 100644
--- a/app/src/main/res/layout/activity_add_feed.xml
+++ b/app/src/main/res/layout/activity_add_feed.xml
@@ -36,6 +36,7 @@
android:id="@+id/add_feed_input_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
+ app:endIconMode="clear_text"
app:layout_constraintEnd_toStartOf="@id/add_feed_load"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
@@ -44,7 +45,6 @@
android:id="@+id/add_feed_text_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:drawableEnd="@drawable/ic_cancel_grey"
android:hint="@string/feed_url"
android:inputType="text" />
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index fe4f52b0..cc89f85b 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -132,5 +132,6 @@
Afficher la légende
Votre mot de passe d\'API (Configuration > Profil)
Synchroniser
+ Vue navigateur
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 824cd35c..a603ebf8 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -33,11 +33,13 @@
- @string/external_navigator
- @string/webview
+ - @string/navigator_view
- 0
- 1
+ - 2
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 077eaec1..8021181f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -138,4 +138,5 @@
Back
Show caption
Synchronize
+ Navigator view
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 9a14e305..625fc43a 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -16,7 +16,7 @@
android:title="@string/reload_feeds_colors" />