Forum, nested discussions front end UI/UX, rev 2

parent 661140f6
......@@ -129,16 +129,6 @@
/>
</activity>
<activity
android:name=".android.forum.ReadForumPostActivity"
android:label="@string/app_name"
android:parentActivityName=".android.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.forum.ShareForumActivity"
android:label="@string/forums_share_toolbar_header"
......@@ -159,17 +149,6 @@
/>
</activity>
<activity
android:name=".android.forum.WriteForumPostActivity"
android:label="@string/app_name"
android:parentActivityName=".android.NavDrawerActivity"
android:windowSoftInputMode="stateVisible">
<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"
......
<vector android:height="24dp" android:viewportHeight="48.0"
android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M9.1,19.3l14.9,11.8l14.9,-11.8l-1.9,-2.4l-13,10.4l-13,-10.4z"/>
</vector>
<vector android:height="24dp" android:viewportHeight="48.0"
android:viewportWidth="48.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M38.9,28.7l-14.9,-11.8l-14.9,11.8l1.9,2.4l13,-10.4l13,10.4z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@color/window_background"/>
<stroke
android:width="2dp"
android:color="@color/forum_discussion_nested_line"/>
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:drawable="@drawable/chevron48dp_down"/>
<item android:drawable="@drawable/chevron48dp_up"/>
</selector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/forum_discussion_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:scrollToEnd="false"/>
<include
layout="@layout/text_input_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:id="@+id/forum_cell"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<View
android:id="@+id/nested_line_1"
style="@style/DiscussionLevelIndicator"
android:layout_width="@dimen/forum_nested_line_width"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="showingDescendants"/>
<View
android:id="@+id/nested_line_2"
style="@style/DiscussionLevelIndicator"
android:layout_width="@dimen/forum_nested_line_width"
android:layout_height="match_parent"
android:layout_toRightOf="@id/nested_line_1"
android:visibility="gone"/>
<View
android:id="@+id/nested_line_3"
style="@style/DiscussionLevelIndicator"
android:layout_width="@dimen/forum_nested_line_width"
android:layout_height="match_parent"
android:layout_toRightOf="@id/nested_line_2"
android:visibility="gone"/>
<View
android:id="@+id/nested_line_4"
style="@style/DiscussionLevelIndicator"
android:layout_width="@dimen/forum_nested_line_width"
android:layout_height="match_parent"
android:layout_toRightOf="@id/nested_line_3"
android:visibility="gone"/>
<View
android:id="@+id/nested_line_5"
style="@style/DiscussionLevelIndicator"
android:layout_width="@dimen/forum_nested_line_width"
android:layout_height="match_parent"
android:layout_toRightOf="@id/nested_line_4"
android:visibility="gone"/>
<TextView
android:id="@+id/nested_line_text"
android:layout_width="@dimen/forum_nested_indicator"
android:layout_height="@dimen/forum_nested_indicator"
android:layout_centerInParent="true"
android:background="@drawable/level_indicator_circle"
android:gravity="center"
android:textSize="@dimen/text_size_small"
android:visibility="gone"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_weight="1">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_small"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_medium"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."/>
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatar"
android:layout_width="@dimen/forum_avatar_size"
android:layout_height="@dimen/forum_avatar_size"
android:layout_alignLeft="@id/text"
android:layout_below="@id/text"
android:layout_marginRight="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:src="@drawable/ic_launcher"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"
/>
<ImageView
android:id="@+id/chevron"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/text"
android:layout_marginRight="@dimen/margin_medium"
android:layout_marginTop="@dimen/margin_small"
android:clickable="true"
android:src="@drawable/selector_chevron"
android:tint="@color/briar_button_positive"
/>
<TextView
android:id="@+id/btn_reply"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/text"
android:layout_marginRight="@dimen/margin_medium"
android:layout_toLeftOf="@id/chevron"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:padding="@dimen/margin_medium"
android:text="@string/btn_reply"
android:textColor="@color/briar_button_positive"
android:textSize="@dimen/text_size_tiny"/>
<TextView
android:id="@+id/replies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/btn_reply"
android:layout_toLeftOf="@id/btn_reply"
android:padding="@dimen/margin_medium"
android:textSize="@dimen/text_size_tiny"
tools:text="2 replies"/>
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/replies"
android:layout_toLeftOf="@id/replies"
android:layout_toRightOf="@id/avatar"
android:ellipsize="end"
android:maxLines="1"
android:textSize="@dimen/text_size_tiny"
tools:text="09:09 John Smith"/>
<View
android:id="@+id/bottom_divider"
style="@style/Divider.ForumList"
android:layout_width="match_parent"
android:layout_height="@dimen/margin_separator"
android:layout_alignLeft="@id/text"
android:layout_below="@id/btn_reply"/>
</RelativeLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/text_input_container"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/button_bar_background"
android:elevation="@dimen/margin_tiny"
android:orientation="horizontal"
android:paddingLeft="@dimen/margin_large"
android:paddingStart="@dimen/margin_large">
<EditText
android:id="@+id/input_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small"
android:layout_weight="1"
android:inputType="textMultiLine|textCapSentences"
android:maxLines="5"/>
<ImageView
android:id="@+id/btn_send"
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_margin="@dimen/margin_medium"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/send"
android:onClick="sendMessage"
android:src="@drawable/social_send_now_white"
android:tint="@color/briar_primary"
/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BriarRecyclerView">
<attr name="scrollToEnd" format="boolean" />
</declare-styleable>
</resources>
\ No newline at end of file
......@@ -43,4 +43,6 @@
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
<color name="spinner_arrow">@color/briar_blue_dark</color>
<color name="forum_discussion_nested_line">#cfd2d4</color>
<color name="forum_cell_highlight">#ffffff</color>
</resources>
\ No newline at end of file
......@@ -41,5 +41,8 @@
<dimen name="message_bubble_margin_tail">14dp</dimen>
<dimen name="message_bubble_margin_non_tail">51dp</dimen>
<dimen name="message_bubble_timestamp_margin">15dp</dimen>
<dimen name="forum_nested_line_width">2dp</dimen>
<dimen name="forum_nested_indicator">24dp</dimen>
<dimen name="forum_avatar_size">20dp</dimen>
</resources>
......@@ -198,6 +198,17 @@
<string name="introduction_success_title">Introduced contact was added</string>
<string name="introduction_success_text">You have been introduced to %1$s.</string>
<!-- Forum -->
<string name="btn_reply">REPLY</string>
<plurals name="message_replies">
<item quantity="one">%1$d reply</item>
<item quantity="other">%1$d replies</item>
</plurals>
<string name="forum_new_entry_posted">Forum entry posted</string>
<string name="forum_new_entry_received">New forum entry</string>
<string name="forum_new_message_hint">New Entry</string>
<string name="forum_message_reply_hint">New Reply</string>
<!-- Dialogs -->
<string name="dialog_title_lost_password">Lost Password</string>
<string name="dialog_message_lost_password">Password recovery is not possible. Do you want to delete your account?\n\nCaution: This will permanently delete your identities, contacts and messages</string>
......@@ -225,4 +236,6 @@
<!-- Progress titles -->
<string name="progress_title_logout">Signing out of Briar..</string>
<string name="progress_title_please_wait">Please wait..</string>
</resources>
......@@ -140,4 +140,9 @@
<item name="android:layout_marginBottom">16dp</item>
</style>
<style name="DiscussionLevelIndicator">
<item name="android:layout_marginLeft">4dp</item>
<item name="android:background">?android:attr/listDivider</item>
</style>
</resources>
\ No newline at end of file
......@@ -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;
}
}
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);
}
}
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) {