diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 0e11dbd84..f335e5b5b 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -168,6 +168,17 @@
/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/briar-android/res/menu/blogs_write_blog_post_actions.xml b/briar-android/res/menu/blogs_write_blog_post_actions.xml
new file mode 100644
index 000000000..11befe036
--- /dev/null
+++ b/briar-android/res/menu/blogs_write_blog_post_actions.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 6b2ca75b1..793e97d7a 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -264,6 +264,9 @@
NEW
more
Write Blog Post
+ Add a title (optional)
+ Type your blog post here
+ Publish
Blog List
Available Blogs
diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java
index 64ecc4cdb..c55e56f83 100644
--- a/briar-android/src/org/briarproject/android/ActivityComponent.java
+++ b/briar-android/src/org/briarproject/android/ActivityComponent.java
@@ -6,6 +6,7 @@ import org.briarproject.android.blogs.BlogActivity;
import org.briarproject.android.blogs.CreateBlogActivity;
import org.briarproject.android.blogs.MyBlogsFragment;
import org.briarproject.android.contact.ContactListFragment;
+import org.briarproject.android.blogs.WriteBlogPostActivity;
import org.briarproject.android.contact.ConversationActivity;
import org.briarproject.android.forum.AvailableForumsActivity;
import org.briarproject.android.forum.ContactSelectorFragment;
@@ -70,6 +71,8 @@ public interface ActivityComponent {
void inject(BlogActivity activity);
+ void inject(WriteBlogPostActivity activity);
+
void inject(SettingsActivity activity);
void inject(ChangePasswordActivity activity);
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
index 097f6b092..aed08eb1e 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogActivity.java
@@ -3,6 +3,8 @@ package org.briarproject.android.blogs;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.view.Menu;
import android.view.MenuInflater;
@@ -25,6 +27,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
+import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
@@ -63,7 +66,7 @@ public class BlogActivity extends BriarActivity {
if (blogName != null) setTitle(blogName);
myBlog = i.getBooleanExtra(IS_MY_BLOG, false);
- adapter = new BlogPostAdapter(this, blogName);
+ adapter = new BlogPostAdapter(this, groupId, blogName);
list = (BriarRecyclerView) this.findViewById(R.id.postList);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
@@ -103,11 +106,15 @@ public class BlogActivity extends BriarActivity {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_write_blog_post:
-/* Intent i = new Intent(this, WriteBlogPostActivity.class);
+ Intent i = new Intent(this, WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(BLOG_NAME, blogName);
- startActivityForResult(i, WRITE_POST);
-*/ return true;
+ ActivityOptionsCompat options =
+ makeCustomAnimation(this, android.R.anim.slide_in_left,
+ android.R.anim.slide_out_right);
+ ActivityCompat.startActivityForResult(this, i, WRITE_POST,
+ options.toBundle());
+ return true;
default:
return super.onOptionsItemSelected(item);
}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
index 8aea31ee3..728b33a5f 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostAdapter.java
@@ -10,6 +10,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
+import org.briarproject.api.sync.GroupId;
import org.briarproject.util.StringUtils;
import java.util.Collection;
@@ -25,17 +26,7 @@ class BlogPostAdapter extends
@Override
public int compare(BlogPostItem a, BlogPostItem b) {
- if (a == b) return 0;
- // The blog with the newest message comes first
- long aTime = a.getTimestamp(), bTime = b.getTimestamp();
- if (aTime > bTime) return -1;
- if (aTime < bTime) return 1;
- // Break ties by post title
- if (a.getTitle() != null && b.getTitle() != null) {
- return String.CASE_INSENSITIVE_ORDER
- .compare(a.getTitle(), b.getTitle());
- }
- return 0;
+ return a.compareTo(b);
}
@Override
@@ -70,10 +61,12 @@ class BlogPostAdapter extends
});
private final Context ctx;
+ private final GroupId blogGroupId;
private final String blogTitle;
- BlogPostAdapter(Context ctx, String blogTitle) {
+ BlogPostAdapter(Context ctx, GroupId blogGroupId, String blogTitle) {
this.ctx = ctx;
+ this.blogGroupId = blogGroupId;
this.blogTitle = blogTitle;
}
@@ -99,6 +92,13 @@ class BlogPostAdapter extends
// post body
ui.body.setText(StringUtils.fromUtf8(item.getBody()));
+ ui.layout.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // TODO #428
+ }
+ });
+
// date
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTimestamp()));
@@ -117,6 +117,10 @@ class BlogPostAdapter extends
return posts.get(position);
}
+ public void add(BlogPostItem item) {
+ posts.add(item);
+ }
+
public void addAll(Collection items) {
posts.addAll(items);
}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
index 3efdea6c7..f4cb2a66b 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
@@ -1,12 +1,14 @@
package org.briarproject.android.blogs;
+import android.support.annotation.NonNull;
+
import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.Author.Status;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
-class BlogPostItem {
+class BlogPostItem implements Comparable {
private final BlogPostHeader header;
private final byte[] body;
@@ -49,4 +51,19 @@ class BlogPostItem {
public boolean isRead() {
return read;
}
+
+ @Override
+ public int compareTo(@NonNull BlogPostItem other) {
+ if (this == other) return 0;
+ // The blog with the newest message comes first
+ long aTime = getTimestamp(), bTime = other.getTimestamp();
+ if (aTime > bTime) return -1;
+ if (aTime < bTime) return 1;
+ // Break ties by post title
+ if (getTitle() != null && other.getTitle() != null) {
+ return String.CASE_INSENSITIVE_ORDER
+ .compare(getTitle(), other.getTitle());
+ }
+ return 0;
+ }
}
diff --git a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
new file mode 100644
index 000000000..91873c9ff
--- /dev/null
+++ b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
@@ -0,0 +1,188 @@
+package org.briarproject.android.blogs;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.design.widget.TextInputEditText;
+import android.support.design.widget.TextInputLayout;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import org.briarproject.R;
+import org.briarproject.android.ActivityComponent;
+import org.briarproject.android.BriarActivity;
+import org.briarproject.api.FormatException;
+import org.briarproject.api.blogs.BlogManager;
+import org.briarproject.api.blogs.BlogPost;
+import org.briarproject.api.blogs.BlogPostFactory;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.util.StringUtils;
+
+import java.security.GeneralSecurityException;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
+import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
+
+public class WriteBlogPostActivity extends BriarActivity
+ implements OnEditorActionListener {
+
+ private static final Logger LOG =
+ Logger.getLogger(WriteBlogPostActivity.class.getName());
+ private static final String contentType = "text/plain";
+
+ private TextInputEditText titleInput;
+ private EditText bodyInput;
+ private Button publishButton;
+ private ProgressBar progressBar;
+
+ // Fields that are accessed from background threads must be volatile
+ private volatile GroupId groupId;
+ @Inject
+ protected volatile IdentityManager identityManager;
+ @Inject
+ volatile BlogPostFactory blogPostFactory;
+ @Inject
+ volatile BlogManager blogManager;
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(state);
+
+ Intent i = getIntent();
+ byte[] b = i.getByteArrayExtra(GROUP_ID);
+ if (b == null) throw new IllegalStateException("No Group in intent.");
+ groupId = new GroupId(b);
+// String blogName = i.getStringExtra(BLOG_NAME);
+// if (blogName != null) setTitle(blogName);
+
+ setContentView(R.layout.activity_write_blog_post);
+// String title =
+// getTitle() + ": " + getString(R.string.blogs_write_blog_post);
+// setTitle(title);
+
+ TextInputLayout titleLayout =
+ (TextInputLayout) findViewById(R.id.titleLayout);
+ if (titleLayout != null) {
+ titleLayout.setCounterMaxLength(MAX_BLOG_POST_TITLE_LENGTH);
+ }
+ titleInput = (TextInputEditText) findViewById(R.id.titleInput);
+ if (titleInput != null) {
+ titleInput.setOnEditorActionListener(this);
+ }
+
+ bodyInput = (EditText) findViewById(R.id.bodyInput);
+ bodyInput.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) {
+ showOrHidePublishButton();
+ }
+ });
+
+ publishButton = (Button) findViewById(R.id.publishButton);
+ publishButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ publish();
+ }
+ });
+
+ progressBar = (ProgressBar) findViewById(R.id.progressBar);
+ }
+
+ @Override
+ public void injectActivity(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ @Override
+ public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
+ bodyInput.requestFocus();
+ return true;
+ }
+
+ private void showOrHidePublishButton() {
+ int bodyLength =
+ StringUtils.toUtf8(bodyInput.getText().toString()).length;
+ if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH &&
+ titleInput.getText().length() <= MAX_BLOG_POST_TITLE_LENGTH)
+ publishButton.setEnabled(true);
+ else
+ publishButton.setEnabled(false);
+ }
+
+ private void publish() {
+ // title
+ String title = titleInput.getText().toString();
+ if (title.length() > MAX_BLOG_POST_TITLE_LENGTH) return;
+ if (title.length() == 0) title = null;
+
+ // body
+ byte[] body = StringUtils.toUtf8(bodyInput.getText().toString());
+
+ // hide publish button, show progress bar
+ publishButton.setVisibility(GONE);
+ progressBar.setVisibility(VISIBLE);
+
+ storePost(title, body);
+ }
+
+ private void storePost(final String title, final byte[] body) {
+ runOnDbThread(new Runnable() {
+ @Override
+ public void run() {
+ long now = System.currentTimeMillis();
+ try {
+ Collection authors =
+ identityManager.getLocalAuthors();
+ LocalAuthor author = authors.iterator().next();
+ BlogPost p = blogPostFactory
+ .createBlogPost(groupId, title, now, null, author,
+ contentType, body);
+ blogManager.addLocalPost(p);
+ postPublished();
+ } catch (DbException | GeneralSecurityException | FormatException e) {
+ // TODO show error
+ if (LOG.isLoggable(WARNING))
+ LOG.log(WARNING, e.toString(), e);
+ }
+ }
+ });
+ }
+
+ private void postPublished() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ setResult(RESULT_OK);
+ supportFinishAfterTransition();
+ }
+ });
+ }
+
+}