mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-16 04:39:54 +01:00
Forum, nested discussions front end UI/UX, rev 2
This commit is contained in:
@@ -8,10 +8,8 @@ import org.briarproject.android.forum.ContactSelectorFragment;
|
||||
import org.briarproject.android.forum.CreateForumActivity;
|
||||
import org.briarproject.android.forum.ForumActivity;
|
||||
import org.briarproject.android.forum.ForumSharingStatusActivity;
|
||||
import org.briarproject.android.forum.ReadForumPostActivity;
|
||||
import org.briarproject.android.forum.ShareForumActivity;
|
||||
import org.briarproject.android.forum.ShareForumMessageFragment;
|
||||
import org.briarproject.android.forum.WriteForumPostActivity;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.identity.CreateIdentityActivity;
|
||||
import org.briarproject.android.introduction.IntroductionActivity;
|
||||
@@ -54,16 +52,12 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(AvailableForumsActivity activity);
|
||||
|
||||
void inject(WriteForumPostActivity activity);
|
||||
|
||||
void inject(CreateForumActivity activity);
|
||||
|
||||
void inject(ShareForumActivity activity);
|
||||
|
||||
void inject(ForumSharingStatusActivity activity);
|
||||
|
||||
void inject(ReadForumPostActivity activity);
|
||||
|
||||
void inject(ForumActivity activity);
|
||||
|
||||
void inject(SettingsActivity activity);
|
||||
|
||||
@@ -12,6 +12,8 @@ import org.briarproject.android.controller.ConfigController;
|
||||
import org.briarproject.android.controller.ConfigControllerImpl;
|
||||
import org.briarproject.android.controller.DbController;
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.forum.ForumController;
|
||||
import org.briarproject.android.forum.ForumControllerImpl;
|
||||
import org.briarproject.android.controller.NavDrawerController;
|
||||
import org.briarproject.android.controller.NavDrawerControllerImpl;
|
||||
import org.briarproject.android.controller.PasswordController;
|
||||
@@ -21,6 +23,7 @@ import org.briarproject.android.controller.SetupControllerImpl;
|
||||
import org.briarproject.android.controller.TransportStateListener;
|
||||
import org.briarproject.android.forum.ContactSelectorFragment;
|
||||
import org.briarproject.android.forum.ForumListFragment;
|
||||
import org.briarproject.android.forum.ForumTestControllerImpl;
|
||||
import org.briarproject.android.forum.ShareForumMessageFragment;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.introduction.ContactChooserFragment;
|
||||
@@ -98,6 +101,22 @@ public class ActivityModule {
|
||||
return dbController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected ForumController provideForumController(
|
||||
ForumControllerImpl forumController) {
|
||||
activity.addLifecycleController(forumController);
|
||||
return forumController;
|
||||
}
|
||||
|
||||
@Named("ForumTestController")
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected ForumController provideForumTestController(
|
||||
ForumTestControllerImpl forumController) {
|
||||
return forumController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected NavDrawerController provideNavDrawerController(
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Application;
|
||||
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.api.ReferenceManager;
|
||||
import org.briarproject.android.forum.ForumPersistentData;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
import org.briarproject.api.crypto.SecretKey;
|
||||
@@ -136,4 +137,10 @@ public class AppModule {
|
||||
eventBus.addListener(notificationManager);
|
||||
return notificationManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ForumPersistentData provideForumPersistence(ForumPersistentData fpd) {
|
||||
return fpd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,23 @@ package org.briarproject.android.forum;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -22,76 +27,61 @@ import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.util.ListLoadingProgressBar;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.GroupRemovedEvent;
|
||||
import org.briarproject.api.event.MessageStateChangedEvent;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.android.controller.handler.ResultHandler;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.android.util.CustomAnimations;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
||||
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||
import static android.view.Gravity.CENTER;
|
||||
import static android.view.Gravity.CENTER_HORIZONTAL;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.forum.ReadForumPostActivity.RESULT_PREV_NEXT;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||
|
||||
public class ForumActivity extends BriarActivity implements EventListener,
|
||||
OnItemClickListener {
|
||||
public class ForumActivity extends BriarActivity implements
|
||||
ForumController.ForumPostListener {
|
||||
|
||||
public static final String FORUM_NAME = "briar.FORUM_NAME";
|
||||
public static final String MIN_TIMESTAMP = "briar.MIN_TIMESTAMP";
|
||||
|
||||
private static final int REQUEST_READ = 2;
|
||||
private static final int REQUEST_FORUM_SHARED = 3;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumActivity.class.getName());
|
||||
|
||||
@Inject protected AndroidNotificationManager notificationManager;
|
||||
private Map<MessageId, byte[]> bodyCache = new HashMap<>();
|
||||
private TextView empty = null;
|
||||
private ForumAdapter adapter = null;
|
||||
private ListView list = null;
|
||||
private ListLoadingProgressBar loading = null;
|
||||
public static final String FORUM_NAME = "briar.FORUM_NAME";
|
||||
public static final String MIN_TIMESTAMP = "briar.MIN_TIMESTAMP";
|
||||
private static final int REQUEST_FORUM_SHARED = 3;
|
||||
|
||||
@Inject
|
||||
protected AndroidNotificationManager notificationManager;
|
||||
|
||||
// uncomment the next line for a test component with dummy data
|
||||
// @Named("ForumTestController")
|
||||
@Inject
|
||||
protected ForumController forumController;
|
||||
|
||||
private BriarRecyclerView recyclerView;
|
||||
private EditText textInput;
|
||||
private ViewGroup inputContainer;
|
||||
private LinearLayoutManager linearLayoutManager;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject protected volatile ForumManager forumManager;
|
||||
@Inject protected volatile EventBus eventBus;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile Forum forum = null;
|
||||
|
||||
protected ForumAdapter forumAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(R.layout.activity_forum);
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException();
|
||||
@@ -99,32 +89,30 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
String forumName = i.getStringExtra(FORUM_NAME);
|
||||
if (forumName != null) setTitle(forumName);
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_MATCH);
|
||||
layout.setOrientation(VERTICAL);
|
||||
layout.setGravity(CENTER_HORIZONTAL);
|
||||
|
||||
empty = new TextView(this);
|
||||
empty.setLayoutParams(MATCH_WRAP_1);
|
||||
empty.setGravity(CENTER);
|
||||
empty.setTextSize(18);
|
||||
empty.setText(R.string.no_forum_posts);
|
||||
empty.setVisibility(GONE);
|
||||
layout.addView(empty);
|
||||
|
||||
adapter = new ForumAdapter(this);
|
||||
list = new ListView(this);
|
||||
list.setLayoutParams(MATCH_WRAP_1);
|
||||
list.setAdapter(adapter);
|
||||
list.setOnItemClickListener(this);
|
||||
list.setVisibility(GONE);
|
||||
layout.addView(list);
|
||||
|
||||
// Show a progress bar while the list is loading
|
||||
loading = new ListLoadingProgressBar(this);
|
||||
layout.addView(loading);
|
||||
|
||||
setContentView(layout);
|
||||
inputContainer = (ViewGroup) findViewById(R.id.text_input_container);
|
||||
inputContainer.setVisibility(GONE);
|
||||
textInput = (EditText) findViewById(R.id.input_text);
|
||||
recyclerView =
|
||||
(BriarRecyclerView) findViewById(R.id.forum_discussion_list);
|
||||
linearLayoutManager = new LinearLayoutManager(this);
|
||||
recyclerView.setLayoutManager(linearLayoutManager);
|
||||
recyclerView.showProgressBar();
|
||||
forumController
|
||||
.loadForum(groupId, new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
setTitle(forumController.getForumName());
|
||||
forumAdapter = new ForumAdapter(
|
||||
forumController.getForumEntries());
|
||||
recyclerView.setAdapter(forumAdapter);
|
||||
recyclerView.showData();
|
||||
} else {
|
||||
// TODO Maybe an error dialog ?
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,14 +120,20 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void displaySnackbar(int stringId) {
|
||||
Snackbar snackbar =
|
||||
Snackbar.make(recyclerView, stringId, Snackbar.LENGTH_SHORT);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
eventBus.addListener(this);
|
||||
notificationManager.blockNotification(groupId);
|
||||
notificationManager.clearForumPostNotification(groupId);
|
||||
loadForum();
|
||||
loadHeaders();
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_FORUM_SHARED && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.forum_shared_snackbar);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,6 +145,27 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (inputContainer.getVisibility() == VISIBLE) {
|
||||
inputContainer.setVisibility(GONE);
|
||||
forumAdapter.setReplyEntry(null);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
private void showTextInput(boolean isNewMessage) {
|
||||
// An animation here would be an overkill because of the keyboard
|
||||
// popping up.
|
||||
inputContainer.setVisibility(View.VISIBLE);
|
||||
textInput.setText("");
|
||||
textInput.requestFocus();
|
||||
textInput.setHint(isNewMessage ? R.string.forum_new_message_hint :
|
||||
R.string.forum_message_reply_hint);
|
||||
showSoftKeyboard(textInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
ActivityOptionsCompat options = ActivityOptionsCompat
|
||||
@@ -159,11 +174,9 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_forum_compose_post:
|
||||
Intent i = new Intent(this, WriteForumPostActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
i.putExtra(FORUM_NAME, forum.getName());
|
||||
i.putExtra(MIN_TIMESTAMP, getMinTimestampForNewPost());
|
||||
startActivity(i);
|
||||
if (inputContainer.getVisibility() != VISIBLE) {
|
||||
showTextInput(true);
|
||||
}
|
||||
return true;
|
||||
case R.id.action_forum_share:
|
||||
Intent i2 = new Intent(this, ShareForumActivity.class);
|
||||
@@ -187,225 +200,34 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
}
|
||||
}
|
||||
|
||||
private void loadForum() {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forum = forumManager.getForum(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading forum " + duration + " ms");
|
||||
displayForumName();
|
||||
} catch (NoSuchGroupException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayForumName() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
setTitle(forum.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadHeaders() {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<ForumPostHeader> headers =
|
||||
forumManager.getPostHeaders(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
displayHeaders(headers);
|
||||
} catch (NoSuchGroupException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayHeaders(final Collection<ForumPostHeader> headers) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
loading.setVisibility(GONE);
|
||||
adapter.clear();
|
||||
if (headers.isEmpty()) {
|
||||
empty.setVisibility(VISIBLE);
|
||||
list.setVisibility(GONE);
|
||||
} else {
|
||||
empty.setVisibility(GONE);
|
||||
list.setVisibility(VISIBLE);
|
||||
for (ForumPostHeader h : headers) {
|
||||
ForumItem item = new ForumItem(h);
|
||||
byte[] body = bodyCache.get(h.getId());
|
||||
if (body == null) loadPostBody(h);
|
||||
else item.setBody(body);
|
||||
adapter.add(item);
|
||||
}
|
||||
adapter.sort(ForumItemComparator.INSTANCE);
|
||||
// Scroll to the bottom
|
||||
list.setSelection(adapter.getCount() - 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPostBody(final ForumPostHeader h) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
byte[] body = forumManager.getPostBody(h.getId());
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading message took " + duration + " ms");
|
||||
displayPost(h.getId(), body);
|
||||
} catch (NoSuchMessageException e) {
|
||||
// The item will be removed when we get the event
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayPost(final MessageId m, final byte[] body) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
bodyCache.put(m, body);
|
||||
int count = adapter.getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ForumItem item = adapter.getItem(i);
|
||||
if (item.getHeader().getId().equals(m)) {
|
||||
item.setBody(body);
|
||||
adapter.notifyDataSetChanged();
|
||||
// Scroll to the bottom
|
||||
list.setSelection(count - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_READ && result == RESULT_PREV_NEXT) {
|
||||
int position = data.getIntExtra("briar.POSITION", -1);
|
||||
if (position >= 0 && position < adapter.getCount())
|
||||
displayPost(position);
|
||||
}
|
||||
else if (request == REQUEST_FORUM_SHARED && result == RESULT_OK) {
|
||||
Snackbar s = Snackbar.make(list, R.string.forum_shared_snackbar,
|
||||
LENGTH_LONG);
|
||||
s.getView().setBackgroundResource(R.color.briar_primary);
|
||||
s.show();
|
||||
}
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
notificationManager.blockNotification(groupId);
|
||||
notificationManager.clearForumPostNotification(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
eventBus.removeListener(this);
|
||||
notificationManager.unblockNotification(groupId);
|
||||
if (isFinishing()) markPostsRead();
|
||||
}
|
||||
|
||||
private void markPostsRead() {
|
||||
List<MessageId> unread = new ArrayList<>();
|
||||
int count = adapter.getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ForumPostHeader h = adapter.getItem(i).getHeader();
|
||||
if (!h.isRead()) unread.add(h.getId());
|
||||
public void sendMessage(View view) {
|
||||
String text = textInput.getText().toString();
|
||||
if (text.trim().length() == 0)
|
||||
return;
|
||||
ForumEntry replyEntry = forumAdapter.getReplyEntry();
|
||||
if (replyEntry == null) {
|
||||
// root post
|
||||
forumController.createPost(StringUtils.toUtf8(text));
|
||||
} else {
|
||||
forumController.createPost(StringUtils.toUtf8(text),
|
||||
replyEntry.getMessageId());
|
||||
}
|
||||
if (unread.isEmpty()) return;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking " + unread.size() + " posts read");
|
||||
markPostsRead(Collections.unmodifiableList(unread));
|
||||
}
|
||||
|
||||
private void markPostsRead(final Collection<MessageId> unread) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
for (MessageId m : unread)
|
||||
forumManager.setReadFlag(m, true);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking read took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
|
||||
if (m.getState() == DELIVERED &&
|
||||
m.getMessage().getGroupId().equals(groupId)) {
|
||||
LOG.info("Message added, reloading");
|
||||
loadHeaders();
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(groupId)) {
|
||||
LOG.info("Forum removed");
|
||||
finishOnUiThread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long getMinTimestampForNewPost() {
|
||||
// Don't use an earlier timestamp than the newest post
|
||||
long timestamp = 0;
|
||||
int count = adapter.getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
long t = adapter.getItem(i).getHeader().getTimestamp();
|
||||
if (t > timestamp) timestamp = t;
|
||||
}
|
||||
return timestamp + 1;
|
||||
}
|
||||
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
displayPost(position);
|
||||
}
|
||||
|
||||
private void displayPost(int position) {
|
||||
ForumPostHeader header = adapter.getItem(position).getHeader();
|
||||
Intent i = new Intent(this, ReadForumPostActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
i.putExtra(FORUM_NAME, forum.getName());
|
||||
i.putExtra("briar.MESSAGE_ID", header.getId().getBytes());
|
||||
Author author = header.getAuthor();
|
||||
if (author != null) {
|
||||
i.putExtra("briar.AUTHOR_NAME", author.getName());
|
||||
i.putExtra("briar.AUTHOR_ID", author.getId().getBytes());
|
||||
}
|
||||
i.putExtra("briar.AUTHOR_STATUS", header.getAuthorStatus().name());
|
||||
i.putExtra("briar.CONTENT_TYPE", header.getContentType());
|
||||
i.putExtra("briar.TIMESTAMP", header.getTimestamp());
|
||||
i.putExtra(MIN_TIMESTAMP, getMinTimestampForNewPost());
|
||||
i.putExtra("briar.POSITION", position);
|
||||
startActivityForResult(i, REQUEST_READ);
|
||||
hideSoftKeyboard(textInput);
|
||||
inputContainer.setVisibility(GONE);
|
||||
forumAdapter.setReplyEntry(null);
|
||||
}
|
||||
|
||||
private void showUnsubscribeDialog() {
|
||||
@@ -413,10 +235,19 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
unsubscribe(forum);
|
||||
Toast.makeText(ForumActivity.this,
|
||||
R.string.forum_left_toast, LENGTH_SHORT)
|
||||
.show();
|
||||
forumController.unsubscribe(
|
||||
new UiResultHandler<Boolean>(
|
||||
ForumActivity.this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
Toast.makeText(ForumActivity.this,
|
||||
R.string.forum_left_toast,
|
||||
LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
AlertDialog.Builder builder =
|
||||
@@ -429,21 +260,346 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void unsubscribe(final Forum f) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.removeForum(f);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Removing forum took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void addLocalEntry(int index, ForumEntry entry) {
|
||||
forumAdapter.addEntry(index, entry, true);
|
||||
displaySnackbar(R.string.forum_new_entry_posted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addForeignEntry(int index, ForumEntry entry) {
|
||||
forumAdapter.addEntry(index, entry, false);
|
||||
displaySnackbar(R.string.forum_new_entry_received);
|
||||
}
|
||||
|
||||
static class ForumViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final TextView textView, lvlText, dateText, repliesText;
|
||||
public final View[] lvls;
|
||||
public final ImageView avatar;
|
||||
public final View chevron, replyButton;
|
||||
public final ViewGroup cell;
|
||||
public final View bottomDivider;
|
||||
|
||||
public ForumViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
textView = (TextView) v.findViewById(R.id.text);
|
||||
lvlText = (TextView) v.findViewById(R.id.nested_line_text);
|
||||
dateText = (TextView) v.findViewById(R.id.date);
|
||||
repliesText = (TextView) v.findViewById(R.id.replies);
|
||||
int[] nestedLineIds = {
|
||||
R.id.nested_line_1, R.id.nested_line_2, R.id.nested_line_3,
|
||||
R.id.nested_line_4, R.id.nested_line_5
|
||||
};
|
||||
lvls = new View[nestedLineIds.length];
|
||||
for (int i = 0; i < lvls.length; i++) {
|
||||
lvls[i] = v.findViewById(nestedLineIds[i]);
|
||||
}
|
||||
avatar = (ImageView) v.findViewById(R.id.avatar);
|
||||
chevron = v.findViewById(R.id.chevron);
|
||||
replyButton = v.findViewById(R.id.btn_reply);
|
||||
cell = (ViewGroup) v.findViewById(R.id.forum_cell);
|
||||
bottomDivider = v.findViewById(R.id.bottom_divider);
|
||||
}
|
||||
}
|
||||
|
||||
public class ForumAdapter extends RecyclerView.Adapter<ForumViewHolder> {
|
||||
|
||||
private final List<ForumEntry> forumEntries;
|
||||
// highlight not depandant on time
|
||||
private ForumEntry replyEntry;
|
||||
// temporary highlight
|
||||
private ForumEntry addedEntry;
|
||||
|
||||
public ForumAdapter(@NonNull List<ForumEntry> forumEntries) {
|
||||
this.forumEntries = forumEntries;
|
||||
}
|
||||
|
||||
private ForumEntry getReplyEntry() {
|
||||
return replyEntry;
|
||||
}
|
||||
|
||||
public void addEntry(int index, ForumEntry entry,
|
||||
boolean isScrolling) {
|
||||
forumEntries.add(index, entry);
|
||||
boolean isShowingDescendants = false;
|
||||
if (entry.getLevel() > 0) {
|
||||
// update parent and make sure descendants are visible
|
||||
// Note that the parent's visibility is guaranteed (otherwise
|
||||
// the reply button would not be visible)
|
||||
for (int i = index - 1; i >= 0; i--) {
|
||||
ForumEntry higherEntry = forumEntries.get(i);
|
||||
if (higherEntry.getLevel() < entry.getLevel()) {
|
||||
// parent found
|
||||
if (!higherEntry.isShowingDescendants()) {
|
||||
isShowingDescendants = true;
|
||||
showDescendants(higherEntry);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isShowingDescendants) {
|
||||
int visiblePos = getVisiblePos(entry);
|
||||
notifyItemInserted(visiblePos);
|
||||
if (isScrolling)
|
||||
linearLayoutManager
|
||||
.scrollToPositionWithOffset(visiblePos, 0);
|
||||
}
|
||||
addedEntry = entry;
|
||||
}
|
||||
|
||||
private boolean hasDescendants(ForumEntry forumEntry) {
|
||||
int i = forumEntries.indexOf(forumEntry);
|
||||
if (i >= 0 && i < forumEntries.size() - 1) {
|
||||
if (forumEntries.get(i + 1).getLevel() >
|
||||
forumEntry.getLevel()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasVisibleDescendants(int visiblePos) {
|
||||
int levelLimit = forumEntries.get(visiblePos).getLevel();
|
||||
for (int i = visiblePos + 1; i < getItemCount(); i++) {
|
||||
ForumEntry entry = getVisibleEntry(i);
|
||||
if (entry.getLevel() <= levelLimit)
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getReplyCount(ForumEntry entry) {
|
||||
int counter = 0;
|
||||
int pos = forumEntries.indexOf(entry);
|
||||
if (pos >= 0) {
|
||||
int ancestorLvl = forumEntries.get(pos).getLevel();
|
||||
for (int i = pos + 1; i < forumEntries.size(); i++) {
|
||||
int descendantLvl = forumEntries.get(i).getLevel();
|
||||
if (descendantLvl <= ancestorLvl)
|
||||
break;
|
||||
if (descendantLvl == ancestorLvl + 1)
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
|
||||
public void setReplyEntry(ForumEntry entry) {
|
||||
if (replyEntry != null) {
|
||||
notifyItemChanged(getVisiblePos(replyEntry));
|
||||
}
|
||||
replyEntry = entry;
|
||||
if (replyEntry != null) {
|
||||
notifyItemChanged(getVisiblePos(replyEntry));
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> getSubTreeIndexes(int pos, int levelLimit) {
|
||||
List<Integer> indexList = new ArrayList<>();
|
||||
|
||||
for (int i = pos + 1; i < getItemCount(); i++) {
|
||||
ForumEntry entry = getVisibleEntry(i);
|
||||
if (entry.getLevel() > levelLimit) {
|
||||
indexList.add(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return indexList;
|
||||
}
|
||||
|
||||
public void showDescendants(ForumEntry forumEntry) {
|
||||
forumEntry.setShowingDescendants(true);
|
||||
int visiblePos = getVisiblePos(forumEntry);
|
||||
List<Integer> indexList =
|
||||
getSubTreeIndexes(visiblePos, forumEntry.getLevel());
|
||||
if (!indexList.isEmpty()) {
|
||||
if (indexList.size() == 1) {
|
||||
notifyItemInserted(indexList.get(0));
|
||||
} else {
|
||||
notifyItemRangeInserted(indexList.get(0),
|
||||
indexList.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void hideDescendants(ForumEntry forumEntry) {
|
||||
int visiblePos = getVisiblePos(forumEntry);
|
||||
List<Integer> indexList =
|
||||
getSubTreeIndexes(visiblePos, forumEntry.getLevel());
|
||||
if (!indexList.isEmpty()) {
|
||||
if (indexList.size() == 1) {
|
||||
notifyItemRemoved(indexList.get(0));
|
||||
} else {
|
||||
notifyItemRangeRemoved(indexList.get(0),
|
||||
indexList.size());
|
||||
}
|
||||
}
|
||||
forumEntry.setShowingDescendants(false);
|
||||
}
|
||||
|
||||
public int getVisiblePos(ForumEntry entry) {
|
||||
int visibleCounter = 0;
|
||||
int levelLimit = -1;
|
||||
for (int i = 0; i < forumEntries.size(); i++) {
|
||||
ForumEntry forumEntry = forumEntries.get(i);
|
||||
if (forumEntry.equals(entry)) {
|
||||
return visibleCounter;
|
||||
} else if (levelLimit >= 0 &&
|
||||
levelLimit < forumEntry.getLevel()) {
|
||||
// entry is in a hidden sub-tree
|
||||
continue;
|
||||
}
|
||||
levelLimit = -1;
|
||||
if (!forumEntry.isShowingDescendants()) {
|
||||
levelLimit = forumEntry.getLevel();
|
||||
}
|
||||
visibleCounter++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ForumEntry getVisibleEntry(int position) {
|
||||
int levelLimit = -1;
|
||||
for (ForumEntry forumEntry : forumEntries) {
|
||||
if (levelLimit >= 0) {
|
||||
if (forumEntry.getLevel() > levelLimit) {
|
||||
continue;
|
||||
}
|
||||
levelLimit = -1;
|
||||
}
|
||||
if (!forumEntry.isShowingDescendants()) {
|
||||
levelLimit = forumEntry.getLevel();
|
||||
}
|
||||
if (position-- == 0) {
|
||||
return forumEntry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForumViewHolder onCreateViewHolder(
|
||||
ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.forum_discussion_cell, parent, false);
|
||||
return new ForumViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(
|
||||
final ForumViewHolder ui, final int position) {
|
||||
final ForumEntry data = getVisibleEntry(position);
|
||||
if (!data.isRead()) {
|
||||
data.setRead(true);
|
||||
forumController.entryRead(data);
|
||||
}
|
||||
ui.textView.setText(data.getText());
|
||||
|
||||
for (int i = 0; i < ui.lvls.length; i++) {
|
||||
ui.lvls[i].setVisibility(i < data.getLevel() ? VISIBLE : GONE);
|
||||
}
|
||||
if (data.getLevel() > 5) {
|
||||
ui.lvlText.setVisibility(VISIBLE);
|
||||
ui.lvlText.setText("" + data.getLevel());
|
||||
} else {
|
||||
ui.lvlText.setVisibility(GONE);
|
||||
}
|
||||
ui.dateText.setText(DateUtils
|
||||
.getRelativeTimeSpanString(ForumActivity.this,
|
||||
data.getTimestamp()) + " " + data.getAuthor());
|
||||
|
||||
int replies = getReplyCount(data);
|
||||
if (replies == 0) {
|
||||
ui.repliesText.setText("");
|
||||
} else {
|
||||
ui.repliesText.setText(getResources()
|
||||
.getQuantityString(R.plurals.message_replies, replies,
|
||||
replies));
|
||||
}
|
||||
ui.avatar.setImageDrawable(
|
||||
new IdenticonDrawable(data.getAuthorId().getBytes()));
|
||||
|
||||
if (hasDescendants(data)) {
|
||||
ui.chevron.setVisibility(VISIBLE);
|
||||
if (hasVisibleDescendants(position)) {
|
||||
ui.chevron.setSelected(false);
|
||||
} else {
|
||||
ui.chevron.setSelected(true);
|
||||
}
|
||||
ui.chevron.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ui.chevron.setSelected(!ui.chevron.isSelected());
|
||||
if (ui.chevron.isSelected()) {
|
||||
hideDescendants(data);
|
||||
} else {
|
||||
showDescendants(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.chevron.setVisibility(INVISIBLE);
|
||||
}
|
||||
if (data.equals(replyEntry)) {
|
||||
ui.cell.setBackgroundColor(ContextCompat
|
||||
.getColor(ForumActivity.this,
|
||||
R.color.forum_cell_highlight));
|
||||
} else if (data.equals(addedEntry)) {
|
||||
CustomAnimations.animateColorTransition(ui.cell, ContextCompat
|
||||
.getColor(ForumActivity.this,
|
||||
R.color.window_background), 3000,
|
||||
new ResultHandler<Void>() {
|
||||
@Override
|
||||
public void onResult(Void result) {
|
||||
ui.setIsRecyclable(true);
|
||||
}
|
||||
});
|
||||
// don't allow cell recycling until the animation finishes
|
||||
ui.setIsRecyclable(false);
|
||||
addedEntry = null;
|
||||
} else {
|
||||
ui.cell.setBackgroundColor(ContextCompat
|
||||
.getColor(ForumActivity.this,
|
||||
R.color.window_background));
|
||||
}
|
||||
ui.replyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (inputContainer.getVisibility() != VISIBLE) {
|
||||
showTextInput(false);
|
||||
}
|
||||
setReplyEntry(data);
|
||||
linearLayoutManager
|
||||
.scrollToPositionWithOffset(position, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
int visibleCounter = 0;
|
||||
int levelLimit = -1;
|
||||
for (ForumEntry forumEntry : forumEntries) {
|
||||
if (levelLimit >= 0) {
|
||||
if (forumEntry.getLevel() > levelLimit) {
|
||||
continue;
|
||||
}
|
||||
levelLimit = -1;
|
||||
}
|
||||
if (!forumEntry.isShowingDescendants()) {
|
||||
levelLimit = forumEntry.getLevel();
|
||||
}
|
||||
visibleCounter++;
|
||||
}
|
||||
return visibleCounter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import org.briarproject.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public interface ForumController extends ActivityLifecycleController {
|
||||
|
||||
void loadForum(GroupId groupId, UiResultHandler<Boolean> resultHandler);
|
||||
String getForumName();
|
||||
List<ForumEntry> getForumEntries();
|
||||
void unsubscribe(UiResultHandler<Boolean> resultHandler);
|
||||
void entryRead(ForumEntry forumEntry);
|
||||
void entriesRead(Collection<ForumEntry> messageIds);
|
||||
void createPost(byte[] body);
|
||||
void createPost(byte[] body, MessageId parentId);
|
||||
|
||||
public interface ForumPostListener {
|
||||
void addLocalEntry(int index, ForumEntry entry);
|
||||
void addForeignEntry(int index, ForumEntry entry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.crypto.KeyParser;
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.GroupRemovedEvent;
|
||||
import org.briarproject.api.event.MessageStateChangedEvent;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPost;
|
||||
import org.briarproject.api.forum.ForumPostFactory;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||
|
||||
public class ForumControllerImpl extends DbControllerImpl
|
||||
implements ForumController, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumControllerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
protected Activity activity;
|
||||
@Inject
|
||||
@CryptoExecutor
|
||||
protected Executor cryptoExecutor;
|
||||
@Inject
|
||||
protected volatile ForumPostFactory forumPostFactory;
|
||||
@Inject
|
||||
protected volatile CryptoComponent crypto;
|
||||
@Inject
|
||||
protected volatile ForumManager forumManager;
|
||||
@Inject
|
||||
protected volatile EventBus eventBus;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
protected ForumPersistentData data;
|
||||
|
||||
private ForumPostListener listener;
|
||||
private MessageId localAdd = null;
|
||||
|
||||
@Inject
|
||||
ForumControllerImpl() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreate() {
|
||||
if (activity instanceof ForumPostListener) {
|
||||
listener = (ForumPostListener) activity;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"An activity that injects the ForumController must " +
|
||||
"implement the ForumPostListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
if (activity.isFinishing()) {
|
||||
data.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void findSingleNewEntry() {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<ForumEntry> oldEntries = getForumEntries();
|
||||
data.clearHeaders();
|
||||
try {
|
||||
loadPosts();
|
||||
List<ForumEntry> allEntries = getForumEntries();
|
||||
int i = 0;
|
||||
for (ForumEntry entry : allEntries) {
|
||||
boolean isNew = true;
|
||||
for (ForumEntry oldEntry : oldEntries) {
|
||||
if (entry.getMessageId()
|
||||
.equals(oldEntry.getMessageId())) {
|
||||
isNew = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isNew) {
|
||||
if (localAdd != null &&
|
||||
entry.getMessageId().equals(localAdd)) {
|
||||
addLocalEntry(i, entry);
|
||||
} else {
|
||||
addForeignEntry(i, entry);
|
||||
}
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} catch (DbException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
MessageStateChangedEvent m = (MessageStateChangedEvent) e;
|
||||
if (m.getState() == DELIVERED &&
|
||||
m.getMessage().getGroupId().equals(data.getGroupId())) {
|
||||
LOG.info("Message added, reloading");
|
||||
findSingleNewEntry();
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(data.getGroupId())) {
|
||||
LOG.info("Forum removed");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activity.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAuthor() throws DbException {
|
||||
Collection<LocalAuthor> localAuthors =
|
||||
identityManager.getLocalAuthors();
|
||||
|
||||
for (LocalAuthor author : localAuthors) {
|
||||
if (author == null)
|
||||
continue;
|
||||
data.setLocalAuthor(author);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPosts() throws DbException {
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<ForumPostHeader> headers =
|
||||
forumManager.getPostHeaders(data.getGroupId());
|
||||
data.addHeaders(headers);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading headers took " + duration + " ms");
|
||||
now = System.currentTimeMillis();
|
||||
for (ForumPostHeader header : headers) {
|
||||
if (data.getBody(header.getId()) == null) {
|
||||
byte[] body = forumManager.getPostBody(header.getId());
|
||||
data.addBody(header.getId(), body);
|
||||
}
|
||||
}
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading bodies took " + duration + " ms");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadForum(final GroupId groupId,
|
||||
final UiResultHandler<Boolean> resultHandler) {
|
||||
LOG.info("Loading forum...");
|
||||
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (data.getGroupId() == null ||
|
||||
!data.getGroupId().equals(groupId)) {
|
||||
data.setGroupId(groupId);
|
||||
long now = System.currentTimeMillis();
|
||||
data.setForum(forumManager.getForum(groupId));
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading forum took " + duration +
|
||||
" ms");
|
||||
now = System.currentTimeMillis();
|
||||
loadAuthor();
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading author took " + duration +
|
||||
" ms");
|
||||
loadPosts();
|
||||
}
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getForumName() {
|
||||
return data.getForum() == null ? null : data.getForum().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumEntry> getForumEntries() {
|
||||
Collection<ForumPostHeader> headers = data.getHeaders();
|
||||
List<ForumEntry> forumEntries = new ArrayList<>();
|
||||
Stack<MessageId> idStack = new Stack<>();
|
||||
|
||||
for (ForumPostHeader h : headers) {
|
||||
if (h.getParentId() == null) {
|
||||
idStack.clear();
|
||||
} else if (idStack.isEmpty() ||
|
||||
!idStack.contains(h.getParentId())) {
|
||||
idStack.push(h.getParentId());
|
||||
} else if (!h.getParentId().equals(idStack.peek())) {
|
||||
do {
|
||||
idStack.pop();
|
||||
} while (!h.getParentId().equals(idStack.peek()));
|
||||
}
|
||||
forumEntries.add(new ForumEntry(h,
|
||||
StringUtils.fromUtf8(data.getBody(h.getId())),
|
||||
idStack.size()));
|
||||
}
|
||||
return forumEntries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe(final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.removeForum(data.getForum());
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Removing forum took " + duration + " ms");
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryRead(ForumEntry forumEntry) {
|
||||
entriesRead(Collections.singletonList(forumEntry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entriesRead(final Collection<ForumEntry> forumEntries) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
for (ForumEntry fe : forumEntries) {
|
||||
forumManager.setReadFlag(fe.getMessageId(), true);
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking read took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPost(byte[] body) {
|
||||
createPost(body, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPost(final byte[] body, final MessageId parentId) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
// Don't use an earlier timestamp than the newest post
|
||||
long timestamp = System.currentTimeMillis();
|
||||
ForumPost p;
|
||||
try {
|
||||
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||
byte[] b = data.getLocalAuthor().getPrivateKey();
|
||||
PrivateKey authorKey = keyParser.parsePrivateKey(b);
|
||||
p = forumPostFactory.createPseudonymousPost(
|
||||
data.getGroupId(), timestamp, parentId,
|
||||
data.getLocalAuthor(), "text/plain", body,
|
||||
authorKey);
|
||||
} catch (GeneralSecurityException | FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
storePost(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addLocalEntry(final int index, final ForumEntry entry) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.addLocalEntry(index, entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addForeignEntry(final int index, final ForumEntry entry) {
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.addForeignEntry(index, entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storePost(final ForumPost p) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
localAdd = p.getMessage().getId();
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.addLocalPost(p);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info(
|
||||
"Storing message took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
public class ForumEntry {
|
||||
|
||||
private final MessageId messageId;
|
||||
private final String text;
|
||||
private final int level;
|
||||
private final long timestamp;
|
||||
private final String author;
|
||||
private final AuthorId authorId;
|
||||
private boolean isShowingDescendants = true;
|
||||
private boolean isRead = true;
|
||||
|
||||
public ForumEntry(ForumPostHeader h, String text, int level) {
|
||||
this(h.getId(), text, level, h.getTimestamp(), h.getAuthor().getName(),
|
||||
h.getAuthor().getId());
|
||||
this.isRead = h.isRead();
|
||||
}
|
||||
|
||||
public ForumEntry(MessageId messageId, String text, int level,
|
||||
long timestamp, String author, AuthorId authorId) {
|
||||
this.messageId = messageId;
|
||||
this.text = text;
|
||||
this.level = level;
|
||||
this.timestamp = timestamp;
|
||||
this.author = author;
|
||||
this.authorId = authorId;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public AuthorId getAuthorId() {
|
||||
return authorId;
|
||||
}
|
||||
|
||||
public boolean isShowingDescendants() {
|
||||
return isShowingDescendants;
|
||||
}
|
||||
|
||||
public void setShowingDescendants(boolean showingDescendants) {
|
||||
this.isShowingDescendants = showingDescendants;
|
||||
}
|
||||
|
||||
public MessageId getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return isRead;
|
||||
}
|
||||
|
||||
public void setRead(boolean read) {
|
||||
isRead = read;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import org.briarproject.api.clients.MessageTree;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.clients.MessageTreeImpl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* This class is a singleton that defines the data that should persist, i.e.
|
||||
* still be present in memory after activity restarts. This class is not thread
|
||||
* safe.
|
||||
*/
|
||||
public class ForumPersistentData {
|
||||
|
||||
protected volatile MessageTree<ForumPostHeader> tree =
|
||||
new MessageTreeImpl<>();
|
||||
private volatile Map<MessageId, byte[]> bodyCache = new HashMap<>();
|
||||
private volatile LocalAuthor localAuthor;
|
||||
private volatile Forum forum;
|
||||
private volatile GroupId groupId;
|
||||
|
||||
@Inject
|
||||
public ForumPersistentData() {
|
||||
|
||||
}
|
||||
|
||||
public void clearAll() {
|
||||
tree.clear();
|
||||
bodyCache.clear();
|
||||
localAuthor = null;
|
||||
forum = null;
|
||||
groupId = null;
|
||||
}
|
||||
|
||||
public void clearHeaders() {
|
||||
tree.clear();
|
||||
}
|
||||
|
||||
public void addHeaders(Collection<ForumPostHeader> headers) {
|
||||
tree.add(headers);
|
||||
}
|
||||
|
||||
public Collection<ForumPostHeader> getHeaders() {
|
||||
return tree.depthFirstOrder();
|
||||
}
|
||||
|
||||
public void addBody(MessageId messageId, byte[] body) {
|
||||
bodyCache.put(messageId, body);
|
||||
}
|
||||
|
||||
public byte[] getBody(MessageId messageId) {
|
||||
return bodyCache.get(messageId);
|
||||
}
|
||||
|
||||
public LocalAuthor getLocalAuthor() {
|
||||
return localAuthor;
|
||||
}
|
||||
|
||||
public void setLocalAuthor(
|
||||
LocalAuthor localAuthor) {
|
||||
this.localAuthor = localAuthor;
|
||||
}
|
||||
|
||||
public Forum getForum() {
|
||||
return forum;
|
||||
}
|
||||
|
||||
public void setForum(Forum forum) {
|
||||
this.forum = forum;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setGroupId(GroupId groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class ForumTestControllerImpl implements ForumController {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumControllerImpl.class.getName());
|
||||
|
||||
private final static String[] AUTHORS = {
|
||||
"Guðmundur",
|
||||
"Jónas",
|
||||
"Geir Þorsteinn Gísli Máni Halldórsson Guðjónsson Mogensen",
|
||||
"Baldur Friðrik",
|
||||
"Anna Katrín",
|
||||
"Þór",
|
||||
"Anna Þorbjörg",
|
||||
"Guðrún",
|
||||
"Helga",
|
||||
"Haraldur"
|
||||
};
|
||||
|
||||
private final static AuthorId[] AUTHOR_ID = new AuthorId[AUTHORS.length];
|
||||
|
||||
static {
|
||||
SecureRandom random = new SecureRandom();
|
||||
for (int i = 0; i < AUTHOR_ID.length; i++) {
|
||||
byte[] b = new byte[UniqueId.LENGTH];
|
||||
random.nextBytes(b);
|
||||
AUTHOR_ID[i] = new AuthorId(b);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private final static String SAGA =
|
||||
"Það er upphaf á sögu þessari að Hákon konungur " +
|
||||
"Aðalsteinsfóstri réð fyrir Noregi og var þetta á ofanverðum " +
|
||||
"hans dögum. Þorkell hét maður; hann var kallaður skerauki; " +
|
||||
"hann bjó í Súrnadal og var hersir að nafnbót. Hann átti sér " +
|
||||
"konu er Ísgerður hét og sonu þrjá barna; hét einn Ari, annar " +
|
||||
"Gísli, þriðji Þorbjörn, hann var þeirra yngstur, og uxu allir " +
|
||||
"upp heima þar. " +
|
||||
"Maður er nefndur Ísi; hann bjó í firði er Fibuli heitir á " +
|
||||
"Norðmæri; kona hans hét Ingigerður en Ingibjörg dóttir. Ari, " +
|
||||
"sonur Þorkels Sýrdæls, biður hennar og var hún honum gefin " +
|
||||
"með miklu fé. Kolur hét þræll er í brott fór með henni.";
|
||||
|
||||
private ForumEntry[] forumEntries;
|
||||
|
||||
@Inject
|
||||
public ForumTestControllerImpl() {
|
||||
|
||||
}
|
||||
|
||||
private void textRandomize(SecureRandom random, int[] i) {
|
||||
for (int e = 0; e < forumEntries.length; e++) {
|
||||
// select a random white-space for the cut-off
|
||||
do {
|
||||
i[e] = Math.abs(random.nextInt() % (SAGA.length()));
|
||||
} while (SAGA.charAt(i[e]) != ' ');
|
||||
}
|
||||
}
|
||||
|
||||
private int levelRandomize(SecureRandom random, int[] l) {
|
||||
int maxl = 0;
|
||||
int lastl = 0;
|
||||
l[0] = 0;
|
||||
for (int e = 1; e < forumEntries.length; e++) {
|
||||
// select random level 1-10
|
||||
do {
|
||||
l[e] = Math.abs(random.nextInt() % 10);
|
||||
} while (l[e] > lastl + 1);
|
||||
lastl = l[e];
|
||||
if (lastl > maxl)
|
||||
maxl = lastl;
|
||||
}
|
||||
return maxl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadForum(GroupId groupId,
|
||||
UiResultHandler<Boolean> resultHandler) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
forumEntries = new ForumEntry[100];
|
||||
// string cut off index
|
||||
int[] i = new int[forumEntries.length];
|
||||
// entry discussion level
|
||||
int[] l = new int[forumEntries.length];
|
||||
|
||||
textRandomize(random, i);
|
||||
int maxLevel;
|
||||
// make sure we get a deep discussion
|
||||
do {
|
||||
maxLevel = levelRandomize(random, l);
|
||||
} while (maxLevel < 6);
|
||||
for (int e = 0; e < forumEntries.length; e++) {
|
||||
int authorIndex = Math.abs(random.nextInt() % AUTHORS.length);
|
||||
long timestamp =
|
||||
System.currentTimeMillis() - Math.abs(random.nextInt());
|
||||
byte[] b = new byte[UniqueId.LENGTH];
|
||||
random.nextBytes(b);
|
||||
forumEntries[e] =
|
||||
new ForumEntry(new MessageId(b), SAGA.substring(0, i[e]),
|
||||
l[e], timestamp, AUTHORS[authorIndex],
|
||||
AUTHOR_ID[authorIndex]);
|
||||
}
|
||||
LOG.info("forum entries: " + forumEntries.length);
|
||||
resultHandler.onResult(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getForumName() {
|
||||
return "SAGA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ForumEntry> getForumEntries() {
|
||||
return forumEntries == null ? null :
|
||||
new ArrayList<ForumEntry>(Arrays.asList(forumEntries));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe(UiResultHandler<Boolean> resultHandler) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entryRead(ForumEntry forumEntry) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entriesRead(Collection<ForumEntry> messageIds) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPost(byte[] body) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createPost(byte[] body, MessageId parentId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreate() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Bundle;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.util.AuthorView;
|
||||
import org.briarproject.android.util.ElasticHorizontalSpace;
|
||||
import org.briarproject.android.util.HorizontalBorder;
|
||||
import org.briarproject.android.util.LayoutUtils;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.Gravity.CENTER;
|
||||
import static android.view.Gravity.CENTER_VERTICAL;
|
||||
import static android.widget.LinearLayout.HORIZONTAL;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
|
||||
import static org.briarproject.android.forum.ForumActivity.MIN_TIMESTAMP;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
|
||||
|
||||
public class ReadForumPostActivity extends BriarActivity
|
||||
implements OnClickListener {
|
||||
|
||||
static final int RESULT_REPLY = RESULT_FIRST_USER;
|
||||
static final int RESULT_PREV_NEXT = RESULT_FIRST_USER + 1;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ReadForumPostActivity.class.getName());
|
||||
|
||||
private GroupId groupId = null;
|
||||
private String forumName = null;
|
||||
private long minTimestamp = -1;
|
||||
private ImageButton prevButton = null, nextButton = null;
|
||||
private ImageButton replyButton = null;
|
||||
private TextView content = null;
|
||||
private int position = -1;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject protected volatile ForumManager forumManager;
|
||||
private volatile MessageId messageId = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException();
|
||||
groupId = new GroupId(b);
|
||||
forumName = i.getStringExtra(FORUM_NAME);
|
||||
if (forumName == null) throw new IllegalStateException();
|
||||
setTitle(forumName);
|
||||
b = i.getByteArrayExtra("briar.MESSAGE_ID");
|
||||
if (b == null) throw new IllegalStateException();
|
||||
messageId = new MessageId(b);
|
||||
String contentType = i.getStringExtra("briar.CONTENT_TYPE");
|
||||
if (contentType == null) throw new IllegalStateException();
|
||||
long timestamp = i.getLongExtra("briar.TIMESTAMP", -1);
|
||||
if (timestamp == -1) throw new IllegalStateException();
|
||||
minTimestamp = i.getLongExtra(MIN_TIMESTAMP, -1);
|
||||
if (minTimestamp == -1) throw new IllegalStateException();
|
||||
position = i.getIntExtra("briar.POSITION", -1);
|
||||
if (position == -1) throw new IllegalStateException();
|
||||
String authorName = i.getStringExtra("briar.AUTHOR_NAME");
|
||||
AuthorId authorId = null;
|
||||
b = i.getByteArrayExtra("briar.AUTHOR_ID");
|
||||
if (b != null) authorId = new AuthorId(b);
|
||||
String s = i.getStringExtra("briar.AUTHOR_STATUS");
|
||||
if (s == null) throw new IllegalStateException();
|
||||
Author.Status authorStatus = Author.Status.valueOf(s);
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_MATCH);
|
||||
layout.setOrientation(VERTICAL);
|
||||
|
||||
ScrollView scrollView = new ScrollView(this);
|
||||
scrollView.setLayoutParams(MATCH_WRAP_1);
|
||||
|
||||
LinearLayout message = new LinearLayout(this);
|
||||
message.setOrientation(VERTICAL);
|
||||
|
||||
LinearLayout header = new LinearLayout(this);
|
||||
header.setLayoutParams(MATCH_WRAP);
|
||||
header.setOrientation(HORIZONTAL);
|
||||
header.setGravity(CENTER_VERTICAL);
|
||||
|
||||
int pad = LayoutUtils.getPadding(this);
|
||||
|
||||
AuthorView authorView = new AuthorView(this);
|
||||
authorView.setPadding(0, pad, pad, pad);
|
||||
authorView.setLayoutParams(WRAP_WRAP_1);
|
||||
authorView.init(authorName, authorId, authorStatus);
|
||||
header.addView(authorView);
|
||||
|
||||
TextView date = new TextView(this);
|
||||
date.setPadding(pad, pad, pad, pad);
|
||||
date.setText(DateUtils.getRelativeTimeSpanString(this, timestamp));
|
||||
header.addView(date);
|
||||
message.addView(header);
|
||||
|
||||
if (contentType.equals("text/plain")) {
|
||||
// Load and display the message body
|
||||
content = new TextView(this);
|
||||
content.setPadding(pad, 0, pad, pad);
|
||||
message.addView(content);
|
||||
loadPostBody();
|
||||
}
|
||||
scrollView.addView(message);
|
||||
layout.addView(scrollView);
|
||||
|
||||
layout.addView(new HorizontalBorder(this));
|
||||
|
||||
LinearLayout footer = new LinearLayout(this);
|
||||
footer.setLayoutParams(MATCH_WRAP);
|
||||
footer.setOrientation(HORIZONTAL);
|
||||
footer.setGravity(CENTER);
|
||||
Resources res = getResources();
|
||||
footer.setBackgroundColor(res.getColor(R.color.button_bar_background));
|
||||
|
||||
prevButton = new ImageButton(this);
|
||||
prevButton.setBackgroundResource(0);
|
||||
prevButton.setImageResource(R.drawable.navigation_previous_item);
|
||||
prevButton.setOnClickListener(this);
|
||||
footer.addView(prevButton);
|
||||
footer.addView(new ElasticHorizontalSpace(this));
|
||||
|
||||
nextButton = new ImageButton(this);
|
||||
nextButton.setBackgroundResource(0);
|
||||
nextButton.setImageResource(R.drawable.navigation_next_item);
|
||||
nextButton.setOnClickListener(this);
|
||||
footer.addView(nextButton);
|
||||
footer.addView(new ElasticHorizontalSpace(this));
|
||||
|
||||
replyButton = new ImageButton(this);
|
||||
replyButton.setBackgroundResource(0);
|
||||
replyButton.setImageResource(R.drawable.social_reply_all);
|
||||
replyButton.setOnClickListener(this);
|
||||
footer.addView(replyButton);
|
||||
layout.addView(footer);
|
||||
|
||||
setContentView(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (isFinishing()) markPostRead();
|
||||
}
|
||||
|
||||
private void markPostRead() {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.setReadFlag(messageId, true);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Marking read took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPostBody() {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
byte[] body = forumManager.getPostBody(messageId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading post took " + duration + " ms");
|
||||
displayPostBody(StringUtils.fromUtf8(body));
|
||||
} catch (NoSuchMessageException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayPostBody(final String body) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
content.setText(body);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
if (view == prevButton) {
|
||||
Intent i = new Intent();
|
||||
i.putExtra("briar.POSITION", position - 1);
|
||||
setResult(RESULT_PREV_NEXT, i);
|
||||
finish();
|
||||
} else if (view == nextButton) {
|
||||
Intent i = new Intent();
|
||||
i.putExtra("briar.POSITION", position + 1);
|
||||
setResult(RESULT_PREV_NEXT, i);
|
||||
finish();
|
||||
} else if (view == replyButton) {
|
||||
Intent i = new Intent(this, WriteForumPostActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
i.putExtra(FORUM_NAME, forumName);
|
||||
i.putExtra("briar.PARENT_ID", messageId.getBytes());
|
||||
i.putExtra(MIN_TIMESTAMP, minTimestamp);
|
||||
startActivity(i);
|
||||
setResult(RESULT_REPLY);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.identity.CreateIdentityActivity;
|
||||
import org.briarproject.android.identity.LocalAuthorItem;
|
||||
import org.briarproject.android.identity.LocalAuthorItemComparator;
|
||||
import org.briarproject.android.identity.LocalAuthorSpinnerAdapter;
|
||||
import org.briarproject.android.util.CommonLayoutParams;
|
||||
import org.briarproject.android.util.LayoutUtils;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.crypto.KeyParser;
|
||||
import org.briarproject.api.crypto.PrivateKey;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPost;
|
||||
import org.briarproject.api.forum.ForumPostFactory;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.text.InputType.TYPE_CLASS_TEXT;
|
||||
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static android.widget.RelativeLayout.ALIGN_PARENT_LEFT;
|
||||
import static android.widget.RelativeLayout.ALIGN_PARENT_RIGHT;
|
||||
import static android.widget.RelativeLayout.CENTER_VERTICAL;
|
||||
import static android.widget.RelativeLayout.LEFT_OF;
|
||||
import static android.widget.RelativeLayout.RIGHT_OF;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
|
||||
import static org.briarproject.android.forum.ForumActivity.MIN_TIMESTAMP;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
|
||||
|
||||
public class WriteForumPostActivity extends BriarActivity
|
||||
implements OnItemSelectedListener, OnClickListener {
|
||||
|
||||
private static final int REQUEST_CREATE_IDENTITY = 2;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(WriteForumPostActivity.class.getName());
|
||||
|
||||
@Inject @CryptoExecutor protected Executor cryptoExecutor;
|
||||
private LocalAuthorSpinnerAdapter adapter = null;
|
||||
private Spinner spinner = null;
|
||||
private ImageButton sendButton = null;
|
||||
private EditText content = null;
|
||||
private AuthorId localAuthorId = null;
|
||||
private GroupId groupId = null;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject protected volatile IdentityManager identityManager;
|
||||
@Inject protected volatile ForumManager forumManager;
|
||||
@Inject protected volatile ForumPostFactory forumPostFactory;
|
||||
@Inject protected volatile CryptoComponent crypto;
|
||||
private volatile MessageId parentId = null;
|
||||
private volatile long minTimestamp = -1;
|
||||
private volatile LocalAuthor localAuthor = null;
|
||||
private volatile Forum forum = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException();
|
||||
groupId = new GroupId(b);
|
||||
String forumName = i.getStringExtra(FORUM_NAME);
|
||||
if (forumName == null) throw new IllegalStateException();
|
||||
setTitle(forumName);
|
||||
minTimestamp = i.getLongExtra(MIN_TIMESTAMP, -1);
|
||||
if (minTimestamp == -1) throw new IllegalStateException();
|
||||
b = i.getByteArrayExtra("briar.PARENT_ID");
|
||||
if (b != null) parentId = new MessageId(b);
|
||||
|
||||
if (state != null) {
|
||||
b = state.getByteArray("briar.LOCAL_AUTHOR_ID");
|
||||
if (b != null) localAuthorId = new AuthorId(b);
|
||||
}
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_WRAP);
|
||||
layout.setOrientation(VERTICAL);
|
||||
int pad = LayoutUtils.getPadding(this);
|
||||
layout.setPadding(pad, 0, pad, pad);
|
||||
|
||||
RelativeLayout header = new RelativeLayout(this);
|
||||
|
||||
TextView from = new TextView(this);
|
||||
from.setId(1);
|
||||
from.setTextSize(18);
|
||||
from.setText(R.string.from);
|
||||
RelativeLayout.LayoutParams left = CommonLayoutParams.relative();
|
||||
left.addRule(ALIGN_PARENT_LEFT);
|
||||
left.addRule(CENTER_VERTICAL);
|
||||
header.addView(from, left);
|
||||
|
||||
adapter = new LocalAuthorSpinnerAdapter(this, true);
|
||||
spinner = new Spinner(this);
|
||||
spinner.setId(2);
|
||||
spinner.setAdapter(adapter);
|
||||
spinner.setOnItemSelectedListener(this);
|
||||
RelativeLayout.LayoutParams between = CommonLayoutParams.relative();
|
||||
between.addRule(CENTER_VERTICAL);
|
||||
between.addRule(RIGHT_OF, 1);
|
||||
between.addRule(LEFT_OF, 3);
|
||||
header.addView(spinner, between);
|
||||
|
||||
sendButton = new ImageButton(this);
|
||||
sendButton.setId(3);
|
||||
sendButton.setBackgroundResource(0);
|
||||
sendButton.setImageResource(R.drawable.social_send_now);
|
||||
sendButton.setEnabled(false); // Enabled after loading the forum
|
||||
sendButton.setOnClickListener(this);
|
||||
RelativeLayout.LayoutParams right = CommonLayoutParams.relative();
|
||||
right.addRule(ALIGN_PARENT_RIGHT);
|
||||
right.addRule(CENTER_VERTICAL);
|
||||
header.addView(sendButton, right);
|
||||
layout.addView(header);
|
||||
|
||||
content = new EditText(this);
|
||||
content.setId(4);
|
||||
int inputType = TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
| TYPE_TEXT_FLAG_CAP_SENTENCES;
|
||||
content.setInputType(inputType);
|
||||
content.setHint(R.string.forum_post_hint);
|
||||
layout.addView(content);
|
||||
|
||||
setContentView(layout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadAuthorsAndForum();
|
||||
}
|
||||
|
||||
private void loadAuthorsAndForum() {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<LocalAuthor> localAuthors =
|
||||
identityManager.getLocalAuthors();
|
||||
forum = forumManager.getForum(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Load took " + duration + " ms");
|
||||
displayAuthorsAndForum(localAuthors);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayAuthorsAndForum(
|
||||
final Collection<LocalAuthor> localAuthors) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
if (localAuthors.isEmpty()) throw new IllegalStateException();
|
||||
adapter.clear();
|
||||
for (LocalAuthor a : localAuthors)
|
||||
adapter.add(new LocalAuthorItem(a));
|
||||
adapter.sort(LocalAuthorItemComparator.INSTANCE);
|
||||
int count = adapter.getCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
LocalAuthorItem item = adapter.getItem(i);
|
||||
if (item == LocalAuthorItem.ANONYMOUS) continue;
|
||||
if (item == LocalAuthorItem.NEW) continue;
|
||||
if (item.getLocalAuthor().getId().equals(localAuthorId)) {
|
||||
localAuthor = item.getLocalAuthor();
|
||||
spinner.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
setTitle(forum.getName());
|
||||
sendButton.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
if (localAuthorId != null) {
|
||||
byte[] b = localAuthorId.getBytes();
|
||||
state.putByteArray("briar.LOCAL_AUTHOR_ID", b);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_CREATE_IDENTITY && result == RESULT_OK) {
|
||||
byte[] b = data.getByteArrayExtra("briar.LOCAL_AUTHOR_ID");
|
||||
if (b == null) throw new IllegalStateException();
|
||||
localAuthorId = new AuthorId(b);
|
||||
loadAuthorsAndForum();
|
||||
}
|
||||
}
|
||||
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position,
|
||||
long id) {
|
||||
LocalAuthorItem item = adapter.getItem(position);
|
||||
if (item == LocalAuthorItem.ANONYMOUS) {
|
||||
localAuthor = null;
|
||||
localAuthorId = null;
|
||||
} else if (item == LocalAuthorItem.NEW) {
|
||||
localAuthor = null;
|
||||
localAuthorId = null;
|
||||
Intent i = new Intent(this, CreateIdentityActivity.class);
|
||||
startActivityForResult(i, REQUEST_CREATE_IDENTITY);
|
||||
} else {
|
||||
localAuthor = item.getLocalAuthor();
|
||||
localAuthorId = localAuthor.getId();
|
||||
}
|
||||
}
|
||||
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
localAuthor = null;
|
||||
localAuthorId = null;
|
||||
}
|
||||
|
||||
public void onClick(View view) {
|
||||
if (forum == null) throw new IllegalStateException();
|
||||
String body = content.getText().toString();
|
||||
if (body.equals("")) return;
|
||||
createPost(StringUtils.toUtf8(body));
|
||||
Toast.makeText(this, R.string.post_sent_toast, LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
private void createPost(final byte[] body) {
|
||||
cryptoExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
// Don't use an earlier timestamp than the newest post
|
||||
long timestamp = System.currentTimeMillis();
|
||||
timestamp = Math.max(timestamp, minTimestamp);
|
||||
ForumPost p;
|
||||
try {
|
||||
if (localAuthor == null) {
|
||||
p = forumPostFactory.createAnonymousPost(groupId,
|
||||
timestamp, parentId, "text/plain", body);
|
||||
} else {
|
||||
KeyParser keyParser = crypto.getSignatureKeyParser();
|
||||
byte[] b = localAuthor.getPrivateKey();
|
||||
PrivateKey authorKey = keyParser.parsePrivateKey(b);
|
||||
p = forumPostFactory.createPseudonymousPost(groupId,
|
||||
timestamp, parentId, localAuthor, "text/plain",
|
||||
body, authorKey);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
storePost(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storePost(final ForumPost p) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
forumManager.addLocalPost(p);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Storing message took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Build;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
@@ -18,6 +19,7 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
private TextView emptyView;
|
||||
private ProgressBar progressBar;
|
||||
private RecyclerView.AdapterDataObserver emptyObserver;
|
||||
private boolean isScrollingToEnd = false;
|
||||
|
||||
public BriarRecyclerView(Context context) {
|
||||
super(context);
|
||||
@@ -25,6 +27,11 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
|
||||
public BriarRecyclerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
TypedArray attributes = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.BriarRecyclerView);
|
||||
isScrollingToEnd = attributes
|
||||
.getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true);
|
||||
}
|
||||
|
||||
public BriarRecyclerView(Context context, AttributeSet attrs,
|
||||
@@ -44,7 +51,7 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
showProgressBar();
|
||||
|
||||
// scroll down when opening keyboard
|
||||
if (Build.VERSION.SDK_INT >= 11) {
|
||||
if (isScrollingToEnd && Build.VERSION.SDK_INT >= 11) {
|
||||
recyclerView.addOnLayoutChangeListener(
|
||||
new View.OnLayoutChangeListener() {
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.android.controller.handler.ResultHandler;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.MeasureSpec.UNSPECIFIED;
|
||||
import static android.view.View.VISIBLE;
|
||||
@@ -21,6 +26,49 @@ public class CustomAnimations {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public static void animateColorTransition(final View view, int color,
|
||||
int duration, final ResultHandler<Void> finishedCallback) {
|
||||
// No soup for Gingerbread
|
||||
if (Build.VERSION.SDK_INT < 11) {
|
||||
return;
|
||||
}
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
ColorDrawable viewColor = (ColorDrawable) view.getBackground();
|
||||
anim.setIntValues(viewColor.getColor(), color);
|
||||
anim.setEvaluator(new ArgbEvaluator());
|
||||
anim.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (finishedCallback != null) finishedCallback.onResult(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
|
||||
}
|
||||
});
|
||||
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
view.setBackgroundColor((Integer)valueAnimator.getAnimatedValue());
|
||||
}
|
||||
});
|
||||
anim.setDuration(duration);
|
||||
|
||||
anim.start();
|
||||
}
|
||||
|
||||
private static void animateHeightGingerbread(ViewGroup viewGroup,
|
||||
boolean isExtending) {
|
||||
// No animations for Gingerbread
|
||||
|
||||
Reference in New Issue
Block a user