Add a My Blogs tab with option to add new blogs

Clicking the plus in the toolbar open the `CreateBlogActivity` which
allows the user to create a new blog. Only the first identity is
considered, but support for more identities can be easily added later.

The actual list of blogs in the My Blogs tab will be done in the next
commit.
This commit is contained in:
Torsten Grote
2016-06-06 19:01:01 -03:00
parent 759b1c7448
commit 4c4f4ad2d5
18 changed files with 431 additions and 31 deletions

View File

@@ -149,6 +149,16 @@
/>
</activity>
<activity
android:name=".android.blogs.CreateBlogActivity"
android:label="@string/blogs_my_blogs_label"
android:parentActivityName=".android.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.identity.CreateIdentityActivity"
android:label="@string/new_identity_title"

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="@dimen/margin_activity_horizontal"
tools:context=".android.blogs.CreateBlogActivity">
<android.support.design.widget.TextInputLayout
android:id="@+id/titleLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:counterEnabled="true"
app:counterOverflowTextAppearance="@style/BriarTextCounter.Overflow">
<android.support.design.widget.TextInputEditText
android:id="@+id/titleInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/blogs_my_blogs_create_hint_title"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:id="@+id/descLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:counterEnabled="true"
app:counterOverflowTextAppearance="@style/BriarTextCounter.Overflow">
<android.support.design.widget.TextInputEditText
android:id="@+id/descInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/blogs_my_blogs_create_hint_desc"/>
</android.support.design.widget.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/blogs_my_blogs_create_hint_desc_explanation"/>
<Button
android:id="@+id/createBlogButton"
style="@style/BriarButton"
android:layout_marginTop="@dimen/margin_activity_vertical"
android:enabled="false"
android:text="@string/blogs_my_blogs_create"/>
<ProgressBar
android:id="@+id/createBlogProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_activity_vertical"
android:indeterminate="true"
android:visibility="gone"/>
</LinearLayout>
</ScrollView>

View File

@@ -32,9 +32,6 @@
<Button
style="@style/BriarButton"
android:id="@+id/createForumButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@string/create_forum_button" />
<ProgressBar
@@ -42,8 +39,6 @@
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:indeterminate="true"
android:layout_centerHorizontal="true"
android:visibility="gone" />
</LinearLayout>

View File

@@ -34,8 +34,6 @@
<Button
android:id="@+id/createIdentityButton"
style="@style/BriarButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:enabled="false"
android:text="@string/create_identity_button"/>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This is just a placeholder to be replaced by the real My Blogs list -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="@dimen/margin_activity_horizontal"
android:textSize="128sp"
tools:text="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/margin_activity_horizontal"
android:text="There is nothing for you to see here.\n\nMove along and come back later."
android:textSize="@dimen/text_size_large"/>
</LinearLayout>

View File

@@ -88,10 +88,7 @@
<Button
android:id="@+id/makeIntroductionButton"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/introduction_button"
/>
android:text="@string/introduction_button"/>
</LinearLayout>

View File

@@ -34,10 +34,7 @@
<Button
android:id="@+id/shareForumButton"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/forum_share_button"
/>
android:text="@string/forum_share_button"/>
</LinearLayout>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_create_blog"
android:icon="@drawable/ic_add_white"
android:title="@string/blogs_my_blogs_create"
app:showAsAction="ifRoom"/>
</menu>

View File

@@ -249,7 +249,15 @@
<!-- Blogs -->
<string name="blogs_button">Blogs</string>
<string name="blogs_feed">Feed</string>
<string name="blogs_my_blogs">My Blogs</string>
<string name="blogs_my_blogs_create">Create Blog</string>
<string name="blogs_my_blogs_label">Add new Blog</string>
<string name="blogs_my_blogs_create_hint_title">Blog title (cannot be changed later)</string>
<string name="blogs_my_blogs_create_hint_desc">A short description of your new blog</string>
<string name="blogs_my_blogs_create_hint_desc_explanation">Potential readers may or may not subscribe to your blog based on the content of the description.</string>
<string name="blogs_my_blogs_created">Blog created</string>
<string name="blogs_blog_list">Blog List</string>
<string name="blogs_available_blogs">Available Blogs</string>
<string name="blogs_drafts">Drafts</string>

View File

@@ -23,6 +23,8 @@
</style>
<style name="BriarButton" parent="Widget.AppCompat.Button.Colored">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textSize">@dimen/text_size_medium</item>
<item name="android:padding">@dimen/margin_large</item>
</style>
@@ -109,4 +111,9 @@
<item name="tabTextColor">@color/briar_text_primary_inverse</item>
</style>
<!-- This fixes the missing TextAppearance.Design.Counter.Overflow style -->
<style name="BriarTextCounter.Overflow" parent="TextAppearance.Design.Counter">
<item name="android:textColor">@color/briar_button_negative</item>
</style>
</resources>

View File

@@ -2,6 +2,7 @@ package org.briarproject.android;
import android.app.Activity;
import org.briarproject.android.blogs.CreateBlogActivity;
import org.briarproject.android.blogs.MyBlogsFragment;
import org.briarproject.android.contact.ContactListFragment;
import org.briarproject.android.contact.ConversationActivity;
@@ -64,6 +65,8 @@ public interface ActivityComponent {
void inject(ForumActivity activity);
void inject(CreateBlogActivity activity);
void inject(SettingsActivity activity);
void inject(ChangePasswordActivity activity);

View File

@@ -7,6 +7,8 @@ import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.api.ReferenceManager;
import org.briarproject.android.forum.ForumPersistentData;
import org.briarproject.android.report.BriarReportSender;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPostFactory;
import org.briarproject.api.contact.ContactExchangeTask;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent;
@@ -96,6 +98,10 @@ public interface AndroidComponent extends CoreEagerSingletons {
ForumPostFactory forumPostFactory();
BlogManager blogManager();
BlogPostFactory blogPostFactory();
SettingsManager settingsManager();
ContactExchangeTask contactExchangeTask();

View File

@@ -0,0 +1,53 @@
package org.briarproject.android.blogs;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.fragment.BaseFragment;
public class BlogListFragment extends BaseFragment {
public final static String TAG = BlogListFragment.class.getName();
static BlogListFragment newInstance(int num) {
BlogListFragment f = new BlogListFragment();
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_blogs_list, container,
false);
TextView numView = (TextView) v.findViewById(R.id.num);
String num = String.valueOf(getArguments().getInt("num"));
numView.setText(num);
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public String getUniqueTag() {
return TAG;
}
}

View File

@@ -78,7 +78,7 @@ public class BlogsFragment extends BaseFragment {
}
private static class TabAdapter extends FragmentStatePagerAdapter {
private class TabAdapter extends FragmentStatePagerAdapter {
private String[] titles;
TabAdapter(FragmentManager fm, String[] titles) {
@@ -94,9 +94,10 @@ public class BlogsFragment extends BaseFragment {
@Override
public Fragment getItem(int position) {
switch (position) {
// TODO add your fragments here
case 1:
return new MyBlogsFragment();
default:
return MyBlogsFragment.newInstance(position);
return BlogListFragment.newInstance(position);
}
}

View File

@@ -0,0 +1,184 @@
package org.briarproject.android.blogs;
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.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.api.blogs.Blog;
import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.util.StringUtils;
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 android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_DESC_LENGTH;
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_TITLE_LENGTH;
public class CreateBlogActivity extends BriarActivity
implements OnEditorActionListener, OnClickListener {
private static final Logger LOG =
Logger.getLogger(CreateBlogActivity.class.getName());
private TextInputEditText titleInput, descInput;
private Button button;
private ProgressBar progress;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile IdentityManager identityManager;
@Inject
volatile BlogManager blogManager;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_create_blog);
TextInputLayout titleLayout =
(TextInputLayout) findViewById(R.id.titleLayout);
if (titleLayout != null) {
titleLayout.setCounterMaxLength(MAX_BLOG_TITLE_LENGTH);
}
titleInput = (TextInputEditText) findViewById(R.id.titleInput);
TextWatcher nameEntryWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
enableOrDisableCreateButton();
}
};
titleInput.setOnEditorActionListener(this);
titleInput.addTextChangedListener(nameEntryWatcher);
TextInputLayout descLayout =
(TextInputLayout) findViewById(R.id.descLayout);
if (descLayout != null) {
descLayout.setCounterMaxLength(MAX_BLOG_DESC_LENGTH);
}
descInput = (TextInputEditText) findViewById(R.id.descInput);
if (descInput != null) {
descInput.addTextChangedListener(nameEntryWatcher);
}
button = (Button) findViewById(R.id.createBlogButton);
if (button != null) {
button.setOnClickListener(this);
}
progress = (ProgressBar) findViewById(R.id.createBlogProgressBar);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
private void enableOrDisableCreateButton() {
if (progress == null) return; // Not created yet
button.setEnabled(validateTitle() && validateDescription());
}
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
descInput.requestFocus();
return true;
}
private boolean validateTitle() {
String name = titleInput.getText().toString();
int length = StringUtils.toUtf8(name).length;
return length <= MAX_BLOG_TITLE_LENGTH && length > 0;
}
private boolean validateDescription() {
String name = descInput.getText().toString();
int length = StringUtils.toUtf8(name).length;
return length <= MAX_BLOG_DESC_LENGTH && length > 0;
}
@Override
public void onClick(View view) {
if (view == button) {
hideSoftKeyboard(view);
if (!validateTitle()) return;
button.setVisibility(GONE);
progress.setVisibility(VISIBLE);
addBlog(titleInput.getText().toString(),
descInput.getText().toString());
}
}
private void addBlog(final String title, final String description) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();
Collection<LocalAuthor> authors =
identityManager.getLocalAuthors();
// take first identity, don't support more for now
LocalAuthor author = authors.iterator().next();
Blog f = blogManager.addBlog(author, title, description);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Storing blog took " + duration + " ms");
displayBlog(f);
} catch (DbException e) {
// TODO show error, e.g. blog with same title exists
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
finishOnUiThread();
}
}
});
}
private void displayBlog(final Blog b) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO
/* Intent i = new Intent(CreateBlogActivity.this,
BlogActivity.class);
i.putExtra(GROUP_ID, b.getId().getBytes());
i.putExtra(BLOG_NAME, b.getName());
startActivity(i);
*/ Toast.makeText(CreateBlogActivity.this,
R.string.blogs_my_blogs_created, LENGTH_LONG).show();
supportFinishAfterTransition();
}
});
}
}

View File

@@ -1,8 +1,14 @@
package org.briarproject.android.blogs;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -13,6 +19,8 @@ import org.briarproject.android.fragment.BaseFragment;
import javax.inject.Inject;
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
public class MyBlogsFragment extends BaseFragment {
public final static String TAG = MyBlogsFragment.class.getName();
@@ -21,31 +29,52 @@ public class MyBlogsFragment extends BaseFragment {
public MyBlogsFragment() {
}
static MyBlogsFragment newInstance(int num) {
MyBlogsFragment f = new MyBlogsFragment();
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View v = inflater.inflate(R.layout.fragment_blogs_my, container,
false);
TextView numView = (TextView) v.findViewById(R.id.num);
String num = String.valueOf(getArguments().getInt("num"));
numView.setText(num);
numView.setText("My Blogs");
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listener.getActivityComponent().inject(this);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.blogs_my_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_create_blog:
Intent intent =
new Intent(getContext(), CreateBlogActivity.class);
ActivityOptionsCompat options =
makeCustomAnimation(getActivity(),
android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat.startActivity(getActivity(), intent,
options.toBundle());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public String getUniqueTag() {
return TAG;

View File

@@ -56,6 +56,11 @@ public class AndroidUtils {
til.setError(null);
}
public static void setError(TextInputLayout til, int res,
boolean condition) {
setError(til, til.getContext().getString(res), condition);
}
public static String getBluetoothAddress(Context ctx,
BluetoothAdapter adapter) {
// Return the adapter's address if it's valid and not fake

View File

@@ -38,7 +38,7 @@ public class TextAvatarView extends FrameLayout {
}
public void setText(String text) {
character.setText(text);
character.setText(text.toUpperCase());
}
public void setUnreadCount(int count) {