mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Allow to import RSS feeds from a file
This commit is contained in:
@@ -5,16 +5,26 @@ import android.os.Bundle;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.FileImportError;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.FileImportSuccess;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.UrlImportError;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.UrlImportSuccess;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||
import org.briarproject.briar.android.fragment.FinalFragment;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showFragment;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RssFeedActivity extends BriarActivity
|
||||
@@ -45,21 +55,37 @@ public class RssFeedActivity extends BriarActivity
|
||||
viewModel.getImportResult().observeEvent(this, this::onImportResult);
|
||||
}
|
||||
|
||||
private void onImportResult(boolean result) {
|
||||
if (result) {
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
private void onImportResult(@Nullable RssImportResult result) {
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (result instanceof UrlImportSuccess) {
|
||||
if (fm.findFragmentByTag(RssFeedImportFragment.TAG) != null) {
|
||||
onBackPressed();
|
||||
}
|
||||
} else {
|
||||
String url = viewModel.getUrlFailedImport();
|
||||
if (url == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
} else if (result instanceof UrlImportError) {
|
||||
String url = ((UrlImportError) result).url;
|
||||
RssFeedImportFailedDialogFragment dialog =
|
||||
RssFeedImportFailedDialogFragment.newInstance(url);
|
||||
dialog.show(getSupportFragmentManager(),
|
||||
RssFeedImportFailedDialogFragment.TAG);
|
||||
dialog.show(fm, RssFeedImportFailedDialogFragment.TAG);
|
||||
} else if (result instanceof FileImportSuccess) {
|
||||
// pop stack back to before the initial import fragment
|
||||
fm.popBackStackImmediate(RssFeedImportFragment.TAG,
|
||||
POP_BACK_STACK_INCLUSIVE);
|
||||
// show success fragment
|
||||
Fragment f = FinalFragment.newInstance(
|
||||
R.string.blogs_rss_feeds_import_success,
|
||||
R.drawable.ic_check_circle_outline,
|
||||
R.color.briar_brand_green, 0
|
||||
);
|
||||
String tag = FinalFragment.TAG;
|
||||
showFragment(fm, f, tag);
|
||||
} else if (result instanceof FileImportError) {
|
||||
// pop stack back to initial import fragment
|
||||
fm.popBackStackImmediate(RssFeedImportFragment.TAG, 0);
|
||||
// show error fragment
|
||||
Fragment f = ErrorFragment.newInstance(
|
||||
getString(R.string.blogs_rss_feeds_import_error));
|
||||
String tag = ErrorFragment.TAG;
|
||||
showFragment(fm, f, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
@@ -13,18 +17,27 @@ import android.widget.ProgressBar;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.fragment.ProgressFragment;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.GetContentAdvanced;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.OpenDocumentAdvanced;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
import static org.briarproject.briar.android.util.UiUtils.launchActivityToOpenFile;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showFragment;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -39,6 +52,15 @@ public class RssFeedImportFragment extends BaseFragment {
|
||||
private Button importButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@RequiresApi(19)
|
||||
private final ActivityResultLauncher<String[]> docLauncher =
|
||||
registerForActivityResult(new OpenDocumentAdvanced(),
|
||||
this::onFileChosen);
|
||||
|
||||
private final ActivityResultLauncher<String> contentLauncher =
|
||||
registerForActivityResult(new GetContentAdvanced(),
|
||||
this::onFileChosen);
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
@@ -52,6 +74,7 @@ public class RssFeedImportFragment extends BaseFragment {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireActivity().setTitle(getString(R.string.blogs_rss_feeds_import));
|
||||
if (SDK_INT >= 19) setHasOptionsMenu(true);
|
||||
View v = inflater.inflate(R.layout.fragment_rss_feed_import,
|
||||
container, false);
|
||||
|
||||
@@ -92,11 +115,40 @@ public class RssFeedImportFragment extends BaseFragment {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (SDK_INT >= 19) {
|
||||
inflater.inflate(R.menu.rss_feed_import_actions, menu);
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_import_file && SDK_INT >= 19) {
|
||||
launchActivityToOpenFile(requireContext(), docLauncher,
|
||||
contentLauncher, "*/*");
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void onFileChosen(@Nullable Uri uri) {
|
||||
if (uri == null) return;
|
||||
// show progress fragment
|
||||
Fragment f = ProgressFragment.newInstance(
|
||||
getString(R.string.blogs_rss_feeds_import_progress));
|
||||
String tag = ProgressFragment.TAG;
|
||||
showFragment(getParentFragmentManager(), f, tag);
|
||||
// view model will import and change state that activity will react to
|
||||
viewModel.importFeed(uri);
|
||||
}
|
||||
|
||||
private void enableOrDisableImportButton() {
|
||||
String url = urlInput.getText().toString();
|
||||
importButton.setEnabled(viewModel.validateAndNormaliseUrl(url) != null);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.util.Patterns;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -11,6 +13,10 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.FileImportError;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.FileImportSuccess;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.UrlImportError;
|
||||
import org.briarproject.briar.android.blog.RssImportResult.UrlImportSuccess;
|
||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||
@@ -20,6 +26,7 @@ import org.briarproject.briar.api.feed.FeedManager;
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
@@ -30,6 +37,7 @@ import java.util.logging.Logger;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
@@ -52,11 +60,9 @@ class RssFeedViewModel extends DbViewModel {
|
||||
private final MutableLiveData<LiveResult<List<Feed>>> feeds =
|
||||
new MutableLiveData<>();
|
||||
|
||||
@Nullable
|
||||
private volatile String urlFailedImport = null;
|
||||
private final MutableLiveData<Boolean> isImporting =
|
||||
new MutableLiveData<>(false);
|
||||
private final MutableLiveEvent<Boolean> importResult =
|
||||
private final MutableLiveEvent<RssImportResult> importResult =
|
||||
new MutableLiveEvent<>();
|
||||
|
||||
@Inject
|
||||
@@ -120,7 +126,7 @@ class RssFeedViewModel extends DbViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
LiveEvent<Boolean> getImportResult() {
|
||||
LiveEvent<RssImportResult> getImportResult() {
|
||||
return importResult;
|
||||
}
|
||||
|
||||
@@ -130,7 +136,6 @@ class RssFeedViewModel extends DbViewModel {
|
||||
|
||||
void importFeed(String url) {
|
||||
isImporting.setValue(true);
|
||||
urlFailedImport = null;
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
Feed feed = feedManager.addFeed(url);
|
||||
@@ -145,19 +150,38 @@ class RssFeedViewModel extends DbViewModel {
|
||||
updated = feedList;
|
||||
}
|
||||
feeds.postValue(new LiveResult<>(updated));
|
||||
importResult.postEvent(true);
|
||||
importResult.postEvent(new UrlImportSuccess());
|
||||
} catch (DbException | IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
urlFailedImport = url;
|
||||
importResult.postEvent(false);
|
||||
importResult.postEvent(new UrlImportError(url));
|
||||
} finally {
|
||||
isImporting.postValue(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getUrlFailedImport() {
|
||||
return urlFailedImport;
|
||||
@UiThread
|
||||
void importFeed(Uri uri) {
|
||||
ContentResolver contentResolver = getApplication().getContentResolver();
|
||||
ioExecutor.execute(() -> {
|
||||
try (InputStream is = contentResolver.openInputStream(uri)) {
|
||||
Feed feed = feedManager.addFeed(is);
|
||||
// Update the feed if it was already present
|
||||
List<Feed> feedList = getList(feeds);
|
||||
if (feedList == null) feedList = new ArrayList<>();
|
||||
List<Feed> updated = updateListItems(feedList,
|
||||
f -> f.equals(feed), f -> feed);
|
||||
// Add the feed if it wasn't already present
|
||||
if (updated == null) {
|
||||
feedList.add(feed);
|
||||
updated = feedList;
|
||||
}
|
||||
feeds.postValue(new LiveResult<>(updated));
|
||||
importResult.postEvent(new FileImportSuccess());
|
||||
} catch (IOException | DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
importResult.postEvent(new FileImportError());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import org.briarproject.nullsafety.NotNullByDefault;
|
||||
|
||||
@NotNullByDefault
|
||||
abstract class RssImportResult {
|
||||
|
||||
static class UrlImportSuccess extends RssImportResult {
|
||||
}
|
||||
|
||||
static class UrlImportError extends RssImportResult {
|
||||
final String url;
|
||||
|
||||
UrlImportError(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
static class FileImportSuccess extends RssImportResult {
|
||||
}
|
||||
|
||||
static class FileImportError extends RssImportResult {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.nullsafety.ParametersNotNullByDefault;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ProgressFragment extends BaseFragment {
|
||||
|
||||
public static final String TAG = ProgressFragment.class.getName();
|
||||
|
||||
private static final String PROGRESS_MSG = "progressMessage";
|
||||
|
||||
public static ProgressFragment newInstance(String message) {
|
||||
ProgressFragment f = new ProgressFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString(PROGRESS_MSG, message);
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
private String progressMessage;
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = requireArguments();
|
||||
progressMessage = args.getString(PROGRESS_MSG);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
View v = inflater
|
||||
.inflate(R.layout.fragment_progress, container, false);
|
||||
TextView msg = v.findViewById(R.id.progressMessage);
|
||||
msg.setText(progressMessage);
|
||||
return v;
|
||||
}
|
||||
|
||||
}
|
||||
43
briar-android/src/main/res/layout/fragment_progress.xml
Normal file
43
briar-android/src/main/res/layout/fragment_progress.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/margin_xlarge">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="@dimen/hero_square"
|
||||
android:layout_height="@dimen/hero_square"
|
||||
android:indeterminate="true"
|
||||
app:layout_constraintBottom_toTopOf="@+id/progressMessage"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.25"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/progressMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_xlarge"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/progress"
|
||||
tools:text="@string/blogs_rss_feeds_import_progress" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
12
briar-android/src/main/res/menu/rss_feed_import_actions.xml
Normal file
12
briar-android/src/main/res/menu/rss_feed_import_actions.xml
Normal file
@@ -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/action_import_file"
|
||||
android:icon="@drawable/ic_add_white"
|
||||
android:title="@string/blogs_rss_feeds_import_title"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
@@ -519,7 +519,10 @@
|
||||
<string name="blogs_rss_feeds_import">Import RSS Feed</string>
|
||||
<string name="blogs_rss_feeds_import_button">Import</string>
|
||||
<string name="blogs_rss_feeds_import_hint">Enter the URL of the RSS feed</string>
|
||||
<string name="blogs_rss_feeds_import_progress">Importing RSS Feed…</string>
|
||||
<string name="blogs_rss_feeds_import_success">Your feed was successfully imported.</string>
|
||||
<string name="blogs_rss_feeds_import_error">We are sorry! There was an error importing your feed.</string>
|
||||
<string name="blogs_rss_feeds_import_title">Import feed from file</string>
|
||||
<string name="blogs_rss_feeds">RSS Feeds</string>
|
||||
<string name="blogs_rss_feeds_manage_imported">Imported:</string>
|
||||
<string name="blogs_rss_feeds_manage_author">Author:</string>
|
||||
|
||||
Reference in New Issue
Block a user