mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Merge branch 'master' into '2005-connect-via-bt-error'
# Conflicts: # briar-android/src/main/java/org/briarproject/briar/android/conversation/BluetoothConnecter.java
This commit is contained in:
@@ -32,6 +32,7 @@ public class DozeFragment extends SetupFragment
|
||||
private DozeView dozeView;
|
||||
private HuaweiProtectedAppsView huaweiProtectedAppsView;
|
||||
private HuaweiAppLaunchView huaweiAppLaunchView;
|
||||
private XiaomiView xiaomiView;
|
||||
private Button next;
|
||||
private boolean secondAttempt = false;
|
||||
|
||||
@@ -53,6 +54,8 @@ public class DozeFragment extends SetupFragment
|
||||
huaweiProtectedAppsView.setOnCheckedChangedListener(this);
|
||||
huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView);
|
||||
huaweiAppLaunchView.setOnCheckedChangedListener(this);
|
||||
xiaomiView = v.findViewById(R.id.xiaomiView);
|
||||
xiaomiView.setOnCheckedChangedListener(this);
|
||||
next = v.findViewById(R.id.next);
|
||||
ProgressBar progressBar = v.findViewById(R.id.progress);
|
||||
|
||||
@@ -98,7 +101,8 @@ public class DozeFragment extends SetupFragment
|
||||
public void onCheckedChanged() {
|
||||
next.setEnabled(dozeView.isChecked() &&
|
||||
huaweiProtectedAppsView.isChecked() &&
|
||||
huaweiAppLaunchView.isChecked());
|
||||
huaweiAppLaunchView.isChecked() &&
|
||||
xiaomiView.isChecked());
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
|
||||
@@ -10,6 +10,7 @@ class DozeHelperImpl implements DozeHelper {
|
||||
Context appContext = context.getApplicationContext();
|
||||
return needsDozeWhitelisting(appContext) ||
|
||||
HuaweiProtectedAppsView.needsToBeShown(appContext) ||
|
||||
HuaweiAppLaunchView.needsToBeShown(appContext);
|
||||
HuaweiAppLaunchView.needsToBeShown(appContext) ||
|
||||
XiaomiView.isXiaomiOrRedmiDevice();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
IBinder token = passwordEntry.getWindowToken();
|
||||
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
Object o = requireContext().getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
viewModel.setPassword(passwordEntry.getText().toString());
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import static org.briarproject.briar.android.account.SetupViewModel.State.CREATE
|
||||
import static org.briarproject.briar.android.account.SetupViewModel.State.DOZE;
|
||||
import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED;
|
||||
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
|
||||
import static org.briarproject.briar.android.util.UiUtils.setInputStateAlwaysVisible;
|
||||
import static org.briarproject.briar.android.util.UiUtils.setInputStateHidden;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -55,10 +57,13 @@ public class SetupActivity extends BaseActivity
|
||||
|
||||
private void onStateChanged(SetupViewModel.State state) {
|
||||
if (state == AUTHOR_NAME) {
|
||||
setInputStateAlwaysVisible(this);
|
||||
showInitialFragment(AuthorNameFragment.newInstance());
|
||||
} else if (state == SET_PASSWORD) {
|
||||
setInputStateAlwaysVisible(this);
|
||||
showPasswordFragment();
|
||||
} else if (state == DOZE) {
|
||||
setInputStateHidden(this);
|
||||
showDozeFragment();
|
||||
} else if (state == CREATED || state == FAILED) {
|
||||
// TODO: Show an error if failed
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.briarproject.briar.android.account;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import static android.os.Build.BRAND;
|
||||
import static org.briarproject.bramble.util.AndroidUtils.getSystemProperty;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
class XiaomiView extends PowerView {
|
||||
|
||||
public XiaomiView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public XiaomiView(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public XiaomiView(Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setText(R.string.setup_xiaomi_text);
|
||||
setButtonText(R.string.setup_xiaomi_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsToBeShown() {
|
||||
return isXiaomiOrRedmiDevice();
|
||||
}
|
||||
|
||||
public static boolean isXiaomiOrRedmiDevice() {
|
||||
return "Xiaomi".equalsIgnoreCase(BRAND) ||
|
||||
"Redmi".equalsIgnoreCase(BRAND);
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
protected int getHelpText() {
|
||||
return R.string.setup_xiaomi_help;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onButtonClick() {
|
||||
int bodyRes = isMiuiTenOrLater()
|
||||
? R.string.setup_xiaomi_dialog_body_new
|
||||
: R.string.setup_xiaomi_dialog_body_old;
|
||||
showOnboardingDialog(getContext(), getContext().getString(bodyRes));
|
||||
setChecked(true);
|
||||
}
|
||||
|
||||
private boolean isMiuiTenOrLater() {
|
||||
String version = getSystemProperty("ro.miui.ui.version.name");
|
||||
if (isNullOrEmpty(version)) return false;
|
||||
version = version.replaceAll("[^\\d]", "");
|
||||
try {
|
||||
return Integer.parseInt(version) >= 10;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,11 @@ import org.briarproject.briar.android.blog.BlogPostFragment;
|
||||
import org.briarproject.briar.android.blog.FeedFragment;
|
||||
import org.briarproject.briar.android.blog.ReblogActivity;
|
||||
import org.briarproject.briar.android.blog.ReblogFragment;
|
||||
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
||||
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
||||
import org.briarproject.briar.android.blog.RssFeedActivity;
|
||||
import org.briarproject.briar.android.blog.RssFeedDeleteFeedDialogFragment;
|
||||
import org.briarproject.briar.android.blog.RssFeedImportFailedDialogFragment;
|
||||
import org.briarproject.briar.android.blog.RssFeedImportFragment;
|
||||
import org.briarproject.briar.android.blog.RssFeedManageFragment;
|
||||
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
||||
import org.briarproject.briar.android.contact.ContactListFragment;
|
||||
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactActivity;
|
||||
@@ -161,9 +164,7 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(IntroductionActivity activity);
|
||||
|
||||
void inject(RssFeedImportActivity activity);
|
||||
|
||||
void inject(RssFeedManageActivity activity);
|
||||
void inject(RssFeedActivity activity);
|
||||
|
||||
void inject(StartupFailureActivity activity);
|
||||
|
||||
@@ -233,4 +234,12 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(
|
||||
BluetoothConnecterDialogFragment bluetoothConnecterDialogFragment);
|
||||
|
||||
void inject(RssFeedImportFragment fragment);
|
||||
|
||||
void inject(RssFeedManageFragment fragment);
|
||||
|
||||
void inject(RssFeedImportFailedDialogFragment fragment);
|
||||
|
||||
void inject(RssFeedDeleteFeedDialogFragment fragment);
|
||||
}
|
||||
|
||||
@@ -20,4 +20,8 @@ public interface BlogModule {
|
||||
@ViewModelKey(BlogViewModel.class)
|
||||
ViewModel bindBlogViewModel(BlogViewModel blogViewModel);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(RssFeedViewModel.class)
|
||||
ViewModel bindRssFeedViewModel(RssFeedViewModel rssFeedViewModel);
|
||||
}
|
||||
|
||||
@@ -131,15 +131,8 @@ public class FeedFragment extends BaseFragment
|
||||
i.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
||||
startActivity(i);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_rss_feeds_import) {
|
||||
Intent i = new Intent(getActivity(), RssFeedImportActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_rss_feeds_manage) {
|
||||
Blog personalBlog = viewModel.getPersonalBlog().getValue();
|
||||
if (personalBlog == null) return false;
|
||||
Intent i = new Intent(getActivity(), RssFeedManageActivity.class);
|
||||
i.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
||||
} else if (itemId == R.id.action_rss_feeds) {
|
||||
Intent i = new Intent(getActivity(), RssFeedActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
|
||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
|
||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RssFeedActivity extends BriarActivity
|
||||
implements BaseFragmentListener {
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RssFeedViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(RssFeedViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
showInitialFragment(RssFeedManageFragment.newInstance());
|
||||
}
|
||||
|
||||
viewModel.getImportResult().observeEvent(this, this::onImportResult);
|
||||
}
|
||||
|
||||
private void onImportResult(RssFeedViewModel.ImportResult result) {
|
||||
if (result == IMPORTED) {
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.findFragmentByTag(RssFeedImportFragment.TAG) != null) {
|
||||
onBackPressed();
|
||||
}
|
||||
} else if (result == FAILED) {
|
||||
RssFeedImportFailedDialogFragment dialog =
|
||||
RssFeedImportFailedDialogFragment.newInstance();
|
||||
dialog.show(getSupportFragmentManager(),
|
||||
RssFeedImportFailedDialogFragment.TAG);
|
||||
} else if (result == EXISTS) {
|
||||
Toast.makeText(this, R.string.blogs_rss_feeds_import_exists,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,91 +7,54 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.BriarAdapter;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
import org.briarproject.briar.api.feed.Feed;
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.formatDate;
|
||||
|
||||
class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
||||
@NotNullByDefault
|
||||
class RssFeedAdapter extends ListAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
||||
|
||||
private final RssFeedListener listener;
|
||||
|
||||
RssFeedAdapter(Context ctx, RssFeedListener listener) {
|
||||
super(ctx, Feed.class);
|
||||
RssFeedAdapter(RssFeedListener listener) {
|
||||
super(new DiffUtil.ItemCallback<Feed>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(Feed a, Feed b) {
|
||||
return a.getUrl().equals(b.getUrl()) &&
|
||||
a.getBlogId().equals(b.getBlogId()) &&
|
||||
a.getAdded() == b.getAdded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(Feed a, Feed b) {
|
||||
return a.getUpdated() == b.getUpdated();
|
||||
}
|
||||
});
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeedViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(
|
||||
R.layout.list_item_rss_feed, parent, false);
|
||||
return new FeedViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(FeedViewHolder ui, int position) {
|
||||
Feed item = getItemAt(position);
|
||||
if (item == null) return;
|
||||
|
||||
// Feed Title
|
||||
ui.title.setText(item.getTitle());
|
||||
|
||||
// Delete Button
|
||||
ui.delete.setOnClickListener(v -> listener.onDeleteClick(item));
|
||||
|
||||
// Author
|
||||
if (item.getRssAuthor() != null) {
|
||||
ui.author.setText(item.getRssAuthor());
|
||||
ui.author.setVisibility(VISIBLE);
|
||||
ui.authorLabel.setVisibility(VISIBLE);
|
||||
} else {
|
||||
ui.author.setVisibility(GONE);
|
||||
ui.authorLabel.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// Imported and Last Updated
|
||||
ui.imported.setText(UiUtils.formatDate(ctx, item.getAdded()));
|
||||
ui.updated.setText(UiUtils.formatDate(ctx, item.getUpdated()));
|
||||
|
||||
// Description
|
||||
if (item.getDescription() != null) {
|
||||
ui.description.setText(item.getDescription());
|
||||
ui.description.setVisibility(VISIBLE);
|
||||
} else {
|
||||
ui.description.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// Open feed's blog when clicked
|
||||
ui.layout.setOnClickListener(v -> listener.onFeedClick(item));
|
||||
ui.bindItem(getItem(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(Feed a, Feed b) {
|
||||
if (a == b) return 0;
|
||||
long aTime = a.getAdded(), bTime = b.getAdded();
|
||||
if (aTime > bTime) return -1;
|
||||
if (aTime < bTime) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(Feed a, Feed b) {
|
||||
return a.getUpdated() == b.getUpdated();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(Feed a, Feed b) {
|
||||
return a.getUrl().equals(b.getUrl()) &&
|
||||
a.getBlogId().equals(b.getBlogId()) &&
|
||||
a.getAdded() == b.getAdded();
|
||||
}
|
||||
|
||||
static class FeedViewHolder extends RecyclerView.ViewHolder {
|
||||
class FeedViewHolder extends RecyclerView.ViewHolder {
|
||||
private final Context ctx;
|
||||
private final View layout;
|
||||
private final TextView title;
|
||||
private final ImageButton delete;
|
||||
@@ -104,6 +67,7 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
||||
private FeedViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
ctx = v.getContext();
|
||||
layout = v;
|
||||
title = v.findViewById(R.id.titleView);
|
||||
delete = v.findViewById(R.id.deleteButton);
|
||||
@@ -113,10 +77,44 @@ class RssFeedAdapter extends BriarAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
|
||||
authorLabel = v.findViewById(R.id.author);
|
||||
description = v.findViewById(R.id.descriptionView);
|
||||
}
|
||||
|
||||
private void bindItem(Feed item) {
|
||||
// Feed Title
|
||||
title.setText(item.getTitle());
|
||||
|
||||
// Delete Button
|
||||
delete.setOnClickListener(v -> listener.onDeleteClick(item));
|
||||
|
||||
// Author
|
||||
if (item.getRssAuthor() != null) {
|
||||
author.setText(item.getRssAuthor());
|
||||
author.setVisibility(VISIBLE);
|
||||
authorLabel.setVisibility(VISIBLE);
|
||||
} else {
|
||||
author.setVisibility(GONE);
|
||||
authorLabel.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// Imported and Last Updated
|
||||
imported.setText(formatDate(ctx, item.getAdded()));
|
||||
updated.setText(formatDate(ctx, item.getUpdated()));
|
||||
|
||||
// Description
|
||||
if (item.getDescription() != null) {
|
||||
description.setText(item.getDescription());
|
||||
description.setVisibility(VISIBLE);
|
||||
} else {
|
||||
description.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// Open feed's blog when clicked
|
||||
layout.setOnClickListener(v -> listener.onFeedClick(item));
|
||||
}
|
||||
}
|
||||
|
||||
interface RssFeedListener {
|
||||
void onFeedClick(Feed feed);
|
||||
|
||||
void onDeleteClick(Feed feed);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RssFeedDeleteFeedDialogFragment extends DialogFragment {
|
||||
final static String TAG = RssFeedDeleteFeedDialogFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RssFeedViewModel viewModel;
|
||||
|
||||
static RssFeedDeleteFeedDialogFragment newInstance(GroupId groupId) {
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(GROUP_ID, groupId.getBytes());
|
||||
RssFeedDeleteFeedDialogFragment f =
|
||||
new RssFeedDeleteFeedDialogFragment();
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context ctx) {
|
||||
super.onAttach(ctx);
|
||||
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(RssFeedViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
GroupId groupId = new GroupId(
|
||||
requireNonNull(requireArguments().getByteArray(GROUP_ID)));
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity(),
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.blogs_rss_remove_feed));
|
||||
builder.setMessage(
|
||||
getString(R.string.blogs_rss_remove_feed_dialog_message));
|
||||
builder.setPositiveButton(R.string.cancel, null);
|
||||
builder.setNegativeButton(R.string.blogs_rss_remove_feed_ok,
|
||||
(dialog, which) -> viewModel.removeFeed(groupId));
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Patterns;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.api.feed.FeedManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
|
||||
|
||||
public class RssFeedImportActivity extends BriarActivity {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(RssFeedImportActivity.class.getName());
|
||||
|
||||
private EditText urlInput;
|
||||
private Button importButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@Inject
|
||||
@IoExecutor
|
||||
Executor ioExecutor;
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
volatile FeedManager feedManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_rss_feed_import);
|
||||
|
||||
urlInput = findViewById(R.id.urlInput);
|
||||
urlInput.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) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
enableOrDisableImportButton();
|
||||
}
|
||||
});
|
||||
urlInput.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == IME_ACTION_DONE && importButton.isEnabled() &&
|
||||
importButton.getVisibility() == VISIBLE) {
|
||||
publish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
importButton = findViewById(R.id.importButton);
|
||||
importButton.setOnClickListener(v -> publish());
|
||||
|
||||
progressBar = findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void enableOrDisableImportButton() {
|
||||
String url = urlInput.getText().toString();
|
||||
importButton.setEnabled(validateAndNormaliseUrl(url) != null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String validateAndNormaliseUrl(String url) {
|
||||
if (!Patterns.WEB_URL.matcher(url).matches()) return null;
|
||||
try {
|
||||
return new URL(url).toString();
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void publish() {
|
||||
// hide import button, show progress bar
|
||||
importButton.setVisibility(GONE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
hideSoftKeyboard(urlInput);
|
||||
|
||||
String url = validateAndNormaliseUrl(urlInput.getText().toString());
|
||||
if (url == null) throw new AssertionError();
|
||||
importFeed(url);
|
||||
}
|
||||
|
||||
private void importFeed(String url) {
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
feedManager.addFeed(url);
|
||||
feedImported();
|
||||
} catch (DbException | IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
importFailed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void feedImported() {
|
||||
runOnUiThreadUnlessDestroyed(this::supportFinishAfterTransition);
|
||||
}
|
||||
|
||||
private void importFailed() {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
// hide progress bar, show publish button
|
||||
progressBar.setVisibility(GONE);
|
||||
importButton.setVisibility(VISIBLE);
|
||||
|
||||
// show error dialog
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(RssFeedImportActivity.this,
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setMessage(R.string.blogs_rss_feeds_import_error);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.try_again_button,
|
||||
(dialog, which) -> publish());
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RssFeedImportFailedDialogFragment extends DialogFragment {
|
||||
final static String TAG = RssFeedImportFailedDialogFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RssFeedViewModel viewModel;
|
||||
|
||||
static RssFeedImportFailedDialogFragment newInstance() {
|
||||
return new RssFeedImportFailedDialogFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context ctx) {
|
||||
super.onAttach(ctx);
|
||||
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(RssFeedViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(requireActivity(),
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setMessage(R.string.blogs_rss_feeds_import_error);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.try_again_button,
|
||||
(dialog, which) -> viewModel.retryImportFeed());
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
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;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RssFeedImportFragment extends BaseFragment {
|
||||
public static final String TAG = RssFeedImportFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RssFeedViewModel viewModel;
|
||||
|
||||
private EditText urlInput;
|
||||
private Button importButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(RssFeedViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireActivity().setTitle(getString(R.string.blogs_rss_feeds_import));
|
||||
View v = inflater.inflate(R.layout.fragment_rss_feed_import,
|
||||
container, false);
|
||||
|
||||
urlInput = v.findViewById(R.id.urlInput);
|
||||
urlInput.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) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
enableOrDisableImportButton();
|
||||
}
|
||||
});
|
||||
urlInput.setOnEditorActionListener((view, actionId, event) -> {
|
||||
if (actionId == IME_ACTION_DONE && importButton.isEnabled() &&
|
||||
importButton.getVisibility() == VISIBLE) {
|
||||
publish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
importButton = v.findViewById(R.id.importButton);
|
||||
importButton.setOnClickListener(view -> publish());
|
||||
|
||||
progressBar = v.findViewById(R.id.progressBar);
|
||||
|
||||
viewModel.getIsImporting().observe(getViewLifecycleOwner(),
|
||||
this::onIsImporting);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void enableOrDisableImportButton() {
|
||||
String url = urlInput.getText().toString();
|
||||
importButton.setEnabled(viewModel.validateAndNormaliseUrl(url) != null);
|
||||
}
|
||||
|
||||
private void publish() {
|
||||
String url = viewModel
|
||||
.validateAndNormaliseUrl(urlInput.getText().toString());
|
||||
if (url == null) throw new AssertionError();
|
||||
viewModel.importFeed(url);
|
||||
}
|
||||
|
||||
private void onIsImporting(Boolean importing) {
|
||||
if (importing) {
|
||||
// show progress bar, hide import button
|
||||
importButton.setVisibility(GONE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
hideSoftKeyboard(urlInput);
|
||||
} else {
|
||||
// show publish button, hide progress bar
|
||||
importButton.setVisibility(VISIBLE);
|
||||
progressBar.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
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.RssFeedAdapter.RssFeedListener;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.api.feed.Feed;
|
||||
import org.briarproject.briar.api.feed.FeedManager;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
public class RssFeedManageActivity extends BriarActivity
|
||||
implements RssFeedListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(RssFeedManageActivity.class.getName());
|
||||
|
||||
private BriarRecyclerView list;
|
||||
private RssFeedAdapter adapter;
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
volatile FeedManager feedManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_rss_feed_manage);
|
||||
|
||||
adapter = new RssFeedAdapter(this, this);
|
||||
|
||||
list = findViewById(R.id.feedList);
|
||||
list.setLayoutManager(new LinearLayoutManager(this));
|
||||
list.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
loadFeeds();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
adapter.clear();
|
||||
list.showProgressBar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.rss_feed_manage_actions, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_rss_feeds_import) {
|
||||
Intent i = new Intent(this, RssFeedImportActivity.class);
|
||||
startActivity(i);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFeedClick(Feed feed) {
|
||||
Intent i = new Intent(this, BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, feed.getBlogId().getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteClick(Feed feed) {
|
||||
DialogInterface.OnClickListener okListener =
|
||||
(dialog, which) -> deleteFeed(feed);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this,
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.blogs_rss_remove_feed));
|
||||
builder.setMessage(
|
||||
getString(R.string.blogs_rss_remove_feed_dialog_message));
|
||||
builder.setPositiveButton(R.string.cancel, null);
|
||||
builder.setNegativeButton(R.string.blogs_rss_remove_feed_ok,
|
||||
okListener);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void loadFeeds() {
|
||||
int revision = adapter.getRevision();
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
displayFeeds(revision, feedManager.getFeeds());
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onLoadError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayFeeds(int revision, List<Feed> feeds) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
if (revision == adapter.getRevision()) {
|
||||
adapter.incrementRevision();
|
||||
if (feeds.isEmpty()) list.showData();
|
||||
else adapter.addAll(feeds);
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
loadFeeds();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteFeed(Feed feed) {
|
||||
runOnDbThread(() -> {
|
||||
try {
|
||||
feedManager.removeFeed(feed);
|
||||
onFeedDeleted(feed);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
onDeleteError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onLoadError() {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
list.setEmptyText(R.string.blogs_rss_feeds_manage_error);
|
||||
list.showData();
|
||||
});
|
||||
}
|
||||
|
||||
private void onFeedDeleted(Feed feed) {
|
||||
runOnUiThreadUnlessDestroyed(() -> {
|
||||
adapter.incrementRevision();
|
||||
adapter.remove(feed);
|
||||
});
|
||||
}
|
||||
|
||||
private void onDeleteError() {
|
||||
runOnUiThreadUnlessDestroyed(() -> Snackbar.make(list,
|
||||
R.string.blogs_rss_feeds_manage_delete_error,
|
||||
LENGTH_LONG).show());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
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 org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.api.feed.Feed;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.blog.RssFeedAdapter.RssFeedListener;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class RssFeedManageFragment extends BaseFragment
|
||||
implements RssFeedListener {
|
||||
public static final String TAG = RssFeedManageFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RssFeedViewModel viewModel;
|
||||
|
||||
private BriarRecyclerView list;
|
||||
private final RssFeedAdapter adapter = new RssFeedAdapter(this);
|
||||
|
||||
public static RssFeedManageFragment newInstance() {
|
||||
return new RssFeedManageFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(RssFeedViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireActivity().setTitle(R.string.blogs_rss_feeds);
|
||||
View v = inflater.inflate(R.layout.fragment_rss_feed_manage,
|
||||
container, false);
|
||||
|
||||
list = v.findViewById(R.id.feedList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
|
||||
viewModel.getFeeds().observe(getViewLifecycleOwner(), result -> result
|
||||
.onError(e -> {
|
||||
list.setEmptyText(R.string.blogs_rss_feeds_manage_error);
|
||||
list.showData();
|
||||
})
|
||||
.onSuccess(feeds -> {
|
||||
adapter.submitList(feeds);
|
||||
if (requireNonNull(feeds).size() == 0) {
|
||||
list.showData();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.rss_feed_manage_actions, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
requireActivity().onBackPressed();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_rss_feeds_import) {
|
||||
showNextFragment(new RssFeedImportFragment());
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFeedClick(Feed feed) {
|
||||
Intent i = new Intent(getActivity(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, feed.getBlogId().getBytes());
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteClick(Feed feed) {
|
||||
RssFeedDeleteFeedDialogFragment dialog =
|
||||
RssFeedDeleteFeedDialogFragment.newInstance(feed.getBlogId());
|
||||
dialog.show(getParentFragmentManager(),
|
||||
RssFeedDeleteFeedDialogFragment.TAG);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.app.Application;
|
||||
import android.util.Patterns;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.LiveResult;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
import org.briarproject.briar.api.feed.Feed;
|
||||
import org.briarproject.briar.api.feed.FeedManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.LogUtils.now;
|
||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
|
||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
|
||||
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
|
||||
|
||||
@NotNullByDefault
|
||||
class RssFeedViewModel extends DbViewModel {
|
||||
enum ImportResult {IMPORTED, FAILED, EXISTS}
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(RssFeedViewModel.class.getName());
|
||||
|
||||
private final FeedManager feedManager;
|
||||
private final Executor ioExecutor;
|
||||
private final Executor dbExecutor;
|
||||
|
||||
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<ImportResult> importResult =
|
||||
new MutableLiveEvent<>();
|
||||
|
||||
@Inject
|
||||
RssFeedViewModel(Application app,
|
||||
FeedManager feedManager,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
TransactionManager db,
|
||||
AndroidExecutor androidExecutor) {
|
||||
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||
this.feedManager = feedManager;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.dbExecutor = dbExecutor;
|
||||
|
||||
loadFeeds();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String validateAndNormaliseUrl(String url) {
|
||||
if (!Patterns.WEB_URL.matcher(url).matches()) return null;
|
||||
try {
|
||||
return new URL(url).toString();
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
LiveData<LiveResult<List<Feed>>> getFeeds() {
|
||||
return feeds;
|
||||
}
|
||||
|
||||
private void loadFeeds() {
|
||||
loadFromDb(this::loadFeeds, feeds::setValue);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private List<Feed> loadFeeds(Transaction txn) throws DbException {
|
||||
long start = now();
|
||||
List<Feed> feeds = feedManager.getFeeds(txn);
|
||||
Collections.sort(feeds);
|
||||
logDuration(LOG, "Loading feeds", start);
|
||||
return feeds;
|
||||
}
|
||||
|
||||
void removeFeed(GroupId groupId) {
|
||||
dbExecutor.execute(() -> {
|
||||
List<Feed> updated = removeListItems(getList(feeds), feed -> {
|
||||
if (feed.getBlogId().equals(groupId)) {
|
||||
try {
|
||||
feedManager.removeFeed(feed);
|
||||
return true;
|
||||
} catch (DbException e) {
|
||||
handleException(e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (updated != null) {
|
||||
feeds.postValue(new LiveResult<>(updated));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LiveEvent<ImportResult> getImportResult() {
|
||||
return importResult;
|
||||
}
|
||||
|
||||
LiveData<Boolean> getIsImporting() {
|
||||
return isImporting;
|
||||
}
|
||||
|
||||
void importFeed(String url) {
|
||||
isImporting.setValue(true);
|
||||
urlFailedImport = null;
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
if (exists(url)) {
|
||||
importResult.postEvent(EXISTS);
|
||||
return;
|
||||
}
|
||||
Feed feed = feedManager.addFeed(url);
|
||||
List<Feed> updated = addListItem(getList(feeds), feed);
|
||||
if (updated != null) {
|
||||
Collections.sort(updated);
|
||||
feeds.postValue(new LiveResult<>(updated));
|
||||
}
|
||||
importResult.postEvent(IMPORTED);
|
||||
} catch (DbException | IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
urlFailedImport = url;
|
||||
importResult.postEvent(FAILED);
|
||||
} finally {
|
||||
isImporting.postValue(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void retryImportFeed() {
|
||||
if (urlFailedImport == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
importFeed(urlFailedImport);
|
||||
}
|
||||
|
||||
private boolean exists(String url) {
|
||||
List<Feed> list = getList(feeds);
|
||||
if (list != null) {
|
||||
for (Feed feed : list) {
|
||||
if (url.equals(feed.getUrl())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,7 @@ class BluetoothConnecter implements EventListener {
|
||||
connect();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void eventOccurred(@NonNull Event e) {
|
||||
if (e instanceof ConnectionOpenedEvent) {
|
||||
@@ -192,40 +193,49 @@ class BluetoothConnecter implements EventListener {
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
bluetoothPlugin.disablePolling();
|
||||
pluginManager.setPluginEnabled(ID, true);
|
||||
|
||||
ioExecutor.execute(() -> {
|
||||
if (!waitForBluetoothActive()) {
|
||||
showToast(R.string.bt_plugin_status_inactive);
|
||||
LOG.warning("Bluetooth plugin didn't become active");
|
||||
return;
|
||||
}
|
||||
showToast(R.string.toast_connect_via_bluetooth_start);
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
String uuid = null;
|
||||
try {
|
||||
uuid = transportPropertyManager
|
||||
.getRemoteProperties(contactId, ID).get(PROP_UUID);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
if (isNullOrEmpty(uuid)) {
|
||||
LOG.warning("PROP_UUID missing for contact");
|
||||
if (!waitForBluetoothActive()) {
|
||||
showToast(R.string.bt_plugin_status_inactive);
|
||||
LOG.warning("Bluetooth plugin didn't become active");
|
||||
return;
|
||||
}
|
||||
DuplexTransportConnection conn = bluetoothPlugin
|
||||
.discoverAndConnectForSetup(uuid);
|
||||
if (conn == null) {
|
||||
waitAfterConnectionFailed();
|
||||
} else {
|
||||
LOG.info("Could connect, handling connection");
|
||||
showToast(R.string.toast_connect_via_bluetooth_start);
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
String uuid = null;
|
||||
try {
|
||||
uuid = transportPropertyManager
|
||||
.getRemoteProperties(contactId, ID)
|
||||
.get(PROP_UUID);
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
if (isNullOrEmpty(uuid)) {
|
||||
LOG.warning("PROP_UUID missing for contact");
|
||||
return;
|
||||
}
|
||||
DuplexTransportConnection conn = bluetoothPlugin
|
||||
.discoverAndConnectForSetup(uuid);
|
||||
if (conn == null) {
|
||||
waitAfterConnectionFailed();
|
||||
} else {
|
||||
LOG.info("Could connect, handling connection");
|
||||
connectionManager
|
||||
.manageOutgoingConnection(contactId, ID, conn);
|
||||
showToast(R.string.toast_connect_via_bluetooth_success);
|
||||
}
|
||||
connectionManager
|
||||
.manageOutgoingConnection(contactId, ID, conn);
|
||||
showToast(R.string.toast_connect_via_bluetooth_success);
|
||||
} finally {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
} finally {
|
||||
eventBus.removeListener(this);
|
||||
bluetoothPlugin.enablePolling();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ class ConversationAdapter
|
||||
}
|
||||
items.beginBatchedUpdates();
|
||||
for (ConversationItem item : toRemove) items.remove(item);
|
||||
updateTimersInBatch();
|
||||
items.endBatchedUpdates();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.briar.android.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
@@ -90,6 +91,9 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
|
||||
import static android.view.KeyEvent.ACTION_DOWN;
|
||||
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
|
||||
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
|
||||
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
|
||||
import static android.view.inputmethod.EditorInfo.IME_NULL;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
@@ -537,4 +541,14 @@ public class UiUtils {
|
||||
Toast.makeText(context, msg, LENGTH_LONG).show();
|
||||
});
|
||||
}
|
||||
|
||||
public static void setInputStateAlwaysVisible(Activity activity) {
|
||||
activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE |
|
||||
SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
||||
}
|
||||
|
||||
public static void setInputStateHidden(Activity activity) {
|
||||
activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE |
|
||||
SOFT_INPUT_STATE_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user