mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Merge branch '496-implement-ui-for-reblogging-and-blog-comments' into 'master'
UI for reblogging and blog comments This MR wraps all blog posts inside a `CardView` and adds a reblog button to blog posts. When this button is pressed, a new activity opens that shows the blog post again and allows the user to reblog it into their own blog. An optional comment can be added. Also a new compound view `AuthorView` is introduced to display authors and their trust level in a consistent way. The `BlogController` has been refactored, so the `FeedController` can share most code through an abstract base class.  Closes #496, #437 See merge request !300
This commit is contained in:
28
.idea/codeStyleSettings.xml
generated
28
.idea/codeStyleSettings.xml
generated
@@ -37,6 +37,34 @@
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="ANNOTATION_PARAMETER_WRAP" value="1" />
|
||||
</JavaCodeStyleSettings>
|
||||
<Objective-C-extensions>
|
||||
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
|
||||
<option name="RELEASE_STYLE" value="IVAR" />
|
||||
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" />
|
||||
<pair source="c" header="h" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
<XML>
|
||||
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||
</XML>
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.briarproject.lifecycle.LifecycleModule;
|
||||
import org.briarproject.properties.PropertiesModule;
|
||||
import org.briarproject.sync.SyncModule;
|
||||
import org.briarproject.transport.TransportModule;
|
||||
import org.briarproject.util.StringUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -60,7 +59,6 @@ import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.briarproject.api.sync.ValidationManager.State.VALID;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -193,8 +191,7 @@ public class BlogManagerTest {
|
||||
assertEquals(1, headers0.size());
|
||||
|
||||
// check that body is there
|
||||
assertArrayEquals(StringUtils.toUtf8(body),
|
||||
blogManager0.getPostBody(p.getMessage().getId()));
|
||||
assertEquals(body, blogManager0.getPostBody(p.getMessage().getId()));
|
||||
|
||||
// make sure that blog0 at author1 doesn't have the post yet
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
@@ -211,8 +208,7 @@ public class BlogManagerTest {
|
||||
assertEquals(POST, headers1.iterator().next().getType());
|
||||
|
||||
// check that body is there
|
||||
assertArrayEquals(StringUtils.toUtf8(body),
|
||||
blogManager1.getPostBody(p.getMessage().getId()));
|
||||
assertEquals(body, blogManager1.getPostBody(p.getMessage().getId()));
|
||||
|
||||
stopLifecycles();
|
||||
}
|
||||
@@ -334,8 +330,7 @@ public class BlogManagerTest {
|
||||
assertEquals(author0, h.getParent().getAuthor());
|
||||
|
||||
// ensure that body can be retrieved from wrapped post
|
||||
assertArrayEquals(StringUtils.toUtf8(body),
|
||||
blogManager0.getPostBody(h.getParentId()));
|
||||
assertEquals(body, blogManager0.getPostBody(h.getParentId()));
|
||||
|
||||
// 1 has only their own comment in their blog
|
||||
headers1 = blogManager1.getPostHeaders(blog1.getId());
|
||||
@@ -375,6 +370,13 @@ public class BlogManagerTest {
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(2, headers1.size());
|
||||
for (BlogPostHeader h : headers1) {
|
||||
if (h.getType() == POST) {
|
||||
assertEquals(body, blogManager1.getPostBody(h.getId()));
|
||||
} else {
|
||||
assertEquals(comment, ((BlogCommentHeader)h).getComment());
|
||||
}
|
||||
}
|
||||
|
||||
stopLifecycles();
|
||||
}
|
||||
|
||||
@@ -211,6 +211,17 @@
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.blogs.ReblogActivity"
|
||||
android:label="@string/blogs_reblog_button"
|
||||
android:parentActivityName=".android.blogs.BlogActivity"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.blogs.BlogActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.blogs.RssFeedImportActivity"
|
||||
android:label="@string/blogs_rss_feeds_import"
|
||||
|
||||
@@ -27,6 +27,7 @@ dependencies {
|
||||
exclude module: 'support-v4'
|
||||
exclude module: 'recyclerview-v7'
|
||||
}
|
||||
compile "com.android.support:cardview-v7:$supportVersion"
|
||||
compile('ch.acra:acra:4.8.5') {
|
||||
exclude module: 'support-v4'
|
||||
exclude module: 'support-annotations'
|
||||
@@ -61,6 +62,7 @@ dependencyVerification {
|
||||
'com.android.support:animated-vector-drawable:06d1963b85aa917099d7757e6a7b3e4dc06889413dc747f625ae8683606db3a1',
|
||||
'com.android.support:support-vector-drawable:799bafe4c3de812386f0b291f744d5d6876452722dd40189b9ab87dbbf594ea1',
|
||||
'com.android.support:recyclerview-v7:44040a888e23e0c93162a3377cfe06751080e3c22d369ab0d4301ef60d63b0fe',
|
||||
'com.android.support:cardview-v7:4595f1c4a28cfa083b6c0920ad4d49e1c2ca4b8302a955e548f68eb63b74931b',
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
17
briar-android/res/drawable/bubble_white.xml
Normal file
17
briar-android/res/drawable/bubble_white.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners
|
||||
android:radius="@dimen/unread_bubble_size"/>
|
||||
|
||||
<solid
|
||||
android:color="@color/briar_text_primary_inverse"/>
|
||||
|
||||
<stroke
|
||||
android:color="@color/briar_text_primary"
|
||||
android:width="1dp"/>
|
||||
|
||||
</shape>
|
||||
|
||||
10
briar-android/res/drawable/ic_our_identity_black.xml
Normal file
10
briar-android/res/drawable/ic_our_identity_black.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:alpha="0.54"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
android:id="@+id/introductionContainer"
|
||||
android:id="@+id/fragmentContainer"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
63
briar-android/res/layout/author_view.xml
Normal file
63
briar-android/res/layout/author_view.xml
Normal file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:showIn="@layout/list_item_blog_post">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatar"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/blogs_avatar_normal_size"
|
||||
android:layout_height="@dimen/blogs_avatar_normal_size"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="@dimen/margin_medium"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatarIcon"
|
||||
android:layout_width="@dimen/blogs_avatar_icon_size"
|
||||
android:layout_height="@dimen/blogs_avatar_icon_size"
|
||||
android:layout_alignBottom="@+id/avatar"
|
||||
android:layout_alignRight="@+id/avatar"
|
||||
android:background="@drawable/bubble_white"
|
||||
android:padding="2dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_repeat"
|
||||
android:visibility="invisible"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/authorName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/avatar"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:layout_toRightOf="@+id/avatar"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Author Name"/>
|
||||
|
||||
<org.briarproject.android.util.TrustIndicatorView
|
||||
android:id="@+id/trustIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/authorName"
|
||||
android:layout_alignTop="@+id/authorName"
|
||||
android:layout_marginLeft="@dimen/margin_small"
|
||||
android:layout_toRightOf="@id/authorName"
|
||||
android:scaleType="center"
|
||||
tools:src="@drawable/trust_indicator_verified"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/authorName"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:layout_toRightOf="@+id/avatar"
|
||||
android:gravity="bottom"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="yesterday"/>
|
||||
|
||||
</merge>
|
||||
46
briar-android/res/layout/fragment_reblog.xml
Normal file
46
briar-android/res/layout/fragment_reblog.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/window_background">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_small">
|
||||
|
||||
<include
|
||||
android:id="@+id/postLayout"
|
||||
layout="@layout/list_item_blog_post"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/inputText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/postLayout"
|
||||
android:layout_margin="@dimen/listitem_vertical_margin"
|
||||
android:gravity="bottom"
|
||||
android:hint="@string/blogs_reblog_comment_hint"
|
||||
android:inputType="textShortMessage|textMultiLine|textCapSentences|textAutoCorrect"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/publishButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_below="@+id/inputText"
|
||||
android:enabled="false"
|
||||
android:text="@string/blogs_reblog_button"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</ScrollView>
|
||||
36
briar-android/res/layout/list_item_blog_comment.xml
Normal file
36
briar-android/res/layout/list_item_blog_comment.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
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="wrap_content"
|
||||
tools:showIn="@layout/list_item_blog_post">
|
||||
|
||||
<View
|
||||
android:id="@+id/inputDivider"
|
||||
style="@style/Divider.Horizontal"/>
|
||||
|
||||
<org.briarproject.android.util.AuthorView
|
||||
android:id="@+id/authorView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:padding="@dimen/listitem_vertical_margin"
|
||||
app:persona="commenter"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bodyView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/authorView"
|
||||
android:paddingBottom="@dimen/listitem_vertical_margin"
|
||||
android:paddingLeft="@dimen/listitem_vertical_margin"
|
||||
android:paddingRight="@dimen/listitem_vertical_margin"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="This is a comment that appears below a blog post. Usually, it is expected to be rather short. Not much longer than this one."/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,118 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
<android.support.v7.widget.CardView
|
||||
style="@style/BriarCard"
|
||||
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="wrap_content"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/listitem_vertical_margin"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatar"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_marginBottom="@dimen/margin_medium"
|
||||
android:layout_marginRight="@dimen/margin_medium"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/authorName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@+id/avatar"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:layout_toRightOf="@+id/avatar"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Author Name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/avatar"
|
||||
android:layout_below="@+id/authorName"
|
||||
android:layout_toEndOf="@+id/avatar"
|
||||
android:layout_toRightOf="@+id/avatar"
|
||||
android:gravity="bottom"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="yesterday"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/newView"
|
||||
style="@style/BriarTag"
|
||||
android:layout_alignBottom="@+id/dateView"
|
||||
android:layout_marginLeft="@dimen/margin_small"
|
||||
android:layout_toRightOf="@+id/dateView"
|
||||
android:text="@string/tag_new"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<org.briarproject.android.util.TrustIndicatorView
|
||||
android:id="@+id/trustIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/authorName"
|
||||
android:layout_alignTop="@+id/authorName"
|
||||
android:layout_marginLeft="@dimen/margin_small"
|
||||
android:layout_toRightOf="@+id/authorName"
|
||||
android:scaleType="center"
|
||||
tools:src="@drawable/trust_indicator_verified"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/chatView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toLeftOf="@+id/commentView"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:src="@drawable/ic_chat"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/commentView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:src="@drawable/ic_repeat"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/titleView"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/avatar"
|
||||
android:layout_marginBottom="@dimen/margin_medium"
|
||||
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:visibility="gone"
|
||||
tools:text="This is a blog post title which can also be longer"/>
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bodyView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/titleView"
|
||||
android:layout_marginEnd="@dimen/margin_medium"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="This is a body text that shows the content of a blog post. This one is not short, but it is also not too long."/>
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/listitem_vertical_margin">
|
||||
|
||||
<View
|
||||
style="@style/Divider.ForumList"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/bodyView"
|
||||
android:layout_marginTop="@dimen/listitem_vertical_margin"/>
|
||||
<org.briarproject.android.util.AuthorView
|
||||
android:id="@+id/rebloggerView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginBottom="@dimen/listitem_horizontal_margin"
|
||||
android:layout_toLeftOf="@+id/commentView"
|
||||
app:persona="reblogger"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<org.briarproject.android.util.AuthorView
|
||||
android:id="@+id/authorView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_below="@+id/rebloggerView"
|
||||
android:layout_marginBottom="@dimen/listitem_vertical_margin"
|
||||
android:layout_toLeftOf="@+id/commentView"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/commentView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/blogs_reblog_comment_hint"
|
||||
android:padding="@dimen/margin_small"
|
||||
android:src="@drawable/ic_repeat"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bodyView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/authorView"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="This is a body text that shows the content of a blog post.\n\nThis one is not short, but it is also not too long."/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/commentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
layout="@layout/list_item_blog_comment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<?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">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_write_blog_post"
|
||||
android:icon="@drawable/forum_item_create_white"
|
||||
android:title="@string/blogs_write_blog_post"
|
||||
android:visible="false"
|
||||
app:showAsAction="ifRoom"
|
||||
tools:visible="true"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_blog_share"
|
||||
@@ -18,6 +27,8 @@
|
||||
android:id="@+id/action_blog_delete"
|
||||
android:icon="@drawable/action_delete_white"
|
||||
android:title="@string/blogs_remove_blog"
|
||||
app:showAsAction="never"/>
|
||||
android:visible="false"
|
||||
app:showAsAction="never"
|
||||
tools:visible="true"/>
|
||||
|
||||
</menu>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?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_write_blog_post"
|
||||
android:icon="@drawable/forum_item_create_white"
|
||||
android:title="@string/blogs_write_blog_post"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
</menu>
|
||||
@@ -1,8 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<declare-styleable name="BriarRecyclerView">
|
||||
<attr name="scrollToEnd" format="boolean" />
|
||||
<attr name="emptyText" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="AuthorView">
|
||||
<attr name="persona" format="enum">
|
||||
<enum name="normal" value="0"/>
|
||||
<enum name="reblogger" value="1"/>
|
||||
<enum name="commenter" value="2"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
@@ -43,4 +43,8 @@
|
||||
<dimen name="forum_nested_indicator">24dp</dimen>
|
||||
<dimen name="forum_avatar_size">20dp</dimen>
|
||||
|
||||
<dimen name="blogs_avatar_normal_size">30dp</dimen>
|
||||
<dimen name="blogs_avatar_icon_size">15dp</dimen>
|
||||
<dimen name="blogs_avatar_comment_size">20dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<string name="nav_drawer_close_description">Close the navigation drawer</string>
|
||||
<string name="contact_list_button">Contacts</string>
|
||||
<string name="forums_button">Forums</string>
|
||||
<string name="blogs_button">Micro Blogs</string>
|
||||
<string name="blogs_button">Blogs</string>
|
||||
<string name="settings_button">Settings</string>
|
||||
<string name="sign_out_button">Sign Out</string>
|
||||
|
||||
@@ -207,7 +207,6 @@
|
||||
<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_empty_state">You don\'t have any blogs.\n\nWhy don\'t you create one now by clicking the plus in the top right screen corner?</string>
|
||||
<string name="blogs_my_blogs_blog_empty_state">This is the place for content of your blog.\n\nIt seems like you haven\'t written anything yet.\n\nPlease tap the pen icon to compose a new blog post.\n\nDon\'t forget to go public and share your blog.</string>
|
||||
<string name="blogs_my_blogs_created">Blog created</string>
|
||||
<string name="blogs_blog_is_empty">This blog is empty</string>
|
||||
<string name="blogs_other_blog_empty_state">This blog is currently empty.\n\nEither the author hasn\'t written anything yet, or the person who shared this blog with you needs to come online, so posts can be synchronized.</string>
|
||||
@@ -227,6 +226,8 @@
|
||||
<string name="blogs_remove_blog_dialog_message">Are you sure that you want to remove this blog and all posts?\nNote that this will not remove the blog from other people\'s devices.</string>
|
||||
<string name="blogs_remove_blog_ok">Remove Blog</string>
|
||||
<string name="blogs_blog_removed">Blog Removed</string>
|
||||
<string name="blogs_reblog_comment_hint">Add an optional comment</string>
|
||||
<string name="blogs_reblog_button">Reblog</string>
|
||||
|
||||
<string name="blogs_blog_list">Blog List</string>
|
||||
<string name="blogs_available_blogs">Available Blogs</string>
|
||||
|
||||
@@ -124,6 +124,11 @@
|
||||
<item name="tabTextColor">@color/briar_text_primary_inverse</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarCard" parent="CardView">
|
||||
<item name="cardUseCompatPadding">true</item>
|
||||
<item name="android:layout_margin">@dimen/margin_small</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>
|
||||
|
||||
@@ -6,10 +6,10 @@ import org.briarproject.android.blogs.BlogActivity;
|
||||
import org.briarproject.android.blogs.BlogFragment;
|
||||
import org.briarproject.android.blogs.BlogListFragment;
|
||||
import org.briarproject.android.blogs.BlogPostFragment;
|
||||
import org.briarproject.android.blogs.BlogsFragment;
|
||||
import org.briarproject.android.blogs.CreateBlogActivity;
|
||||
import org.briarproject.android.blogs.FeedFragment;
|
||||
import org.briarproject.android.blogs.MyBlogsFragment;
|
||||
import org.briarproject.android.blogs.ReblogActivity;
|
||||
import org.briarproject.android.blogs.ReblogFragment;
|
||||
import org.briarproject.android.blogs.RssFeedImportActivity;
|
||||
import org.briarproject.android.blogs.RssFeedManageActivity;
|
||||
import org.briarproject.android.blogs.WriteBlogPostActivity;
|
||||
@@ -93,6 +93,10 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(BlogPostFragment fragment);
|
||||
|
||||
void inject(ReblogFragment fragment);
|
||||
|
||||
void inject(ReblogActivity activity);
|
||||
|
||||
void inject(SettingsActivity activity);
|
||||
|
||||
void inject(ChangePasswordActivity activity);
|
||||
@@ -106,10 +110,8 @@ public interface ActivityComponent {
|
||||
// Fragments
|
||||
void inject(ContactListFragment fragment);
|
||||
void inject(ForumListFragment fragment);
|
||||
void inject(BlogsFragment fragment);
|
||||
void inject(BlogListFragment fragment);
|
||||
void inject(FeedFragment fragment);
|
||||
void inject(MyBlogsFragment fragment);
|
||||
void inject(IntroFragment fragment);
|
||||
void inject(ShowQrCodeFragment fragment);
|
||||
void inject(ContactChooserFragment fragment);
|
||||
|
||||
@@ -7,7 +7,7 @@ import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.blogs.BlogsFragment;
|
||||
import org.briarproject.android.blogs.FeedFragment;
|
||||
import org.briarproject.android.contact.ContactListFragment;
|
||||
import org.briarproject.android.forum.ForumListFragment;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
@@ -27,7 +27,7 @@ public abstract class BriarFragmentActivity extends BriarActivity {
|
||||
actionBar.setTitle(R.string.contact_list_button);
|
||||
} else if (fragmentTag.equals(ForumListFragment.TAG)) {
|
||||
actionBar.setTitle(R.string.forums_button);
|
||||
} else if (fragmentTag.equals(BlogsFragment.TAG)) {
|
||||
} else if (fragmentTag.equals(FeedFragment.TAG)) {
|
||||
actionBar.setTitle(R.string.blogs_button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.blogs.BlogsFragment;
|
||||
import org.briarproject.android.blogs.FeedFragment;
|
||||
import org.briarproject.android.contact.ContactListFragment;
|
||||
import org.briarproject.android.controller.NavDrawerController;
|
||||
import org.briarproject.android.controller.TransportStateListener;
|
||||
@@ -82,7 +82,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
|
||||
startFragment(ContactListFragment.newInstance());
|
||||
}
|
||||
else if (intent.getBooleanExtra(INTENT_BLOGS, false)) {
|
||||
startFragment(BlogsFragment.newInstance());
|
||||
startFragment(FeedFragment.newInstance());
|
||||
}
|
||||
setIntent(null);
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
|
||||
startFragment(ForumListFragment.newInstance());
|
||||
break;
|
||||
case R.id.nav_btn_blogs:
|
||||
startFragment(BlogsFragment.newInstance());
|
||||
startFragment(FeedFragment.newInstance());
|
||||
break;
|
||||
case R.id.nav_btn_settings:
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface BaseController {
|
||||
|
||||
@UiThread
|
||||
void onStart();
|
||||
|
||||
@UiThread
|
||||
void onStop();
|
||||
|
||||
void loadBlogPosts(GroupId g,
|
||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
|
||||
|
||||
void loadBlogPost(BlogPostHeader header,
|
||||
ResultExceptionHandler<BlogPostItem, DbException> handler);
|
||||
|
||||
void loadBlogPost(GroupId g, MessageId m,
|
||||
ResultExceptionHandler<BlogPostItem, DbException> handler);
|
||||
|
||||
void repeatPost(BlogPostItem item, @Nullable String comment,
|
||||
ResultExceptionHandler<Void, DbException> resultHandler);
|
||||
|
||||
void setOnBlogPostAddedListener(OnBlogPostAddedListener listener);
|
||||
|
||||
interface OnBlogPostAddedListener {
|
||||
@UiThread
|
||||
void onBlogPostAdded(BlogPostHeader header, boolean local);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogCommentHeader;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.event.BlogPostAddedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
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 java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
abstract class BaseControllerImpl extends DbControllerImpl
|
||||
implements BaseController, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BaseControllerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
protected Activity activity;
|
||||
@Inject
|
||||
protected EventBus eventBus;
|
||||
@Inject
|
||||
protected AndroidNotificationManager notificationManager;
|
||||
@Inject
|
||||
protected IdentityManager identityManager;
|
||||
|
||||
@Inject
|
||||
protected volatile BlogManager blogManager;
|
||||
|
||||
private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>();
|
||||
private final Map<MessageId, BlogPostHeader> headerCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
protected volatile OnBlogPostAddedListener listener;
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStart() {
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void onStop() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CallSuper
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof BlogPostAddedEvent) {
|
||||
final BlogPostAddedEvent m = (BlogPostAddedEvent) e;
|
||||
LOG.info("New blog post added");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onBlogPostAdded(m.getHeader(), m.isLocal());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) {
|
||||
if (this.listener != null)
|
||||
throw new IllegalStateException("Listener was already set");
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBlogPosts(final GroupId groupId,
|
||||
final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Collection<BlogPostItem> items = loadItems(groupId);
|
||||
handler.onResult(items);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Collection<BlogPostItem> loadItems(GroupId groupId)
|
||||
throws DbException {
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<BlogPostHeader> headers =
|
||||
blogManager.getPostHeaders(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading headers took " + duration + " ms");
|
||||
Collection<BlogPostItem> items = new ArrayList<>(headers.size());
|
||||
now = System.currentTimeMillis();
|
||||
for (BlogPostHeader h : headers) {
|
||||
headerCache.put(h.getId(), h);
|
||||
BlogPostItem item = getItem(h);
|
||||
items.add(item);
|
||||
}
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading bodies took " + duration + " ms");
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBlogPost(final BlogPostHeader header,
|
||||
final ResultExceptionHandler<BlogPostItem, DbException> handler) {
|
||||
|
||||
String body = bodyCache.get(header.getId());
|
||||
if (body != null) {
|
||||
LOG.info("Loaded body from cache");
|
||||
handler.onResult(new BlogPostItem(header, body));
|
||||
return;
|
||||
}
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
BlogPostItem item = getItem(header);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading body took " + duration + " ms");
|
||||
handler.onResult(item);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBlogPost(final GroupId g, final MessageId m,
|
||||
final ResultExceptionHandler<BlogPostItem, DbException> handler) {
|
||||
|
||||
BlogPostHeader header = headerCache.get(m);
|
||||
if (header != null) {
|
||||
LOG.info("Loaded header from cache");
|
||||
loadBlogPost(header, handler);
|
||||
return;
|
||||
}
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
BlogPostHeader header = getPostHeader(g, m);
|
||||
BlogPostItem item = getItem(header);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading post took " + duration + " ms");
|
||||
handler.onResult(item);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repeatPost(final BlogPostItem item,
|
||||
final @Nullable String comment,
|
||||
final ResultExceptionHandler<Void, DbException> handler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
LocalAuthor a = identityManager.getLocalAuthor();
|
||||
Blog b = blogManager.getPersonalBlog(a);
|
||||
BlogPostHeader h = item.getHeader();
|
||||
blogManager.addLocalComment(a, b.getId(), comment, h);
|
||||
handler.onResult(null);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private BlogPostHeader getPostHeader(GroupId g, MessageId m)
|
||||
throws DbException {
|
||||
|
||||
if (g == null) throw new IllegalStateException();
|
||||
BlogPostHeader header = headerCache.get(m);
|
||||
if (header == null) {
|
||||
header = blogManager.getPostHeader(g, m);
|
||||
headerCache.put(m, header);
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
private BlogPostItem getItem(BlogPostHeader h) throws DbException {
|
||||
String body;
|
||||
if (h instanceof BlogCommentHeader) {
|
||||
BlogCommentHeader c = (BlogCommentHeader) h;
|
||||
BlogCommentItem item = new BlogCommentItem(c);
|
||||
body = getPostBody(item.getPostHeader().getId());
|
||||
item.setBody(body);
|
||||
return item;
|
||||
} else {
|
||||
body = getPostBody(h.getId());
|
||||
return new BlogPostItem(h, body);
|
||||
}
|
||||
}
|
||||
|
||||
private String getPostBody(MessageId m) throws DbException {
|
||||
String body = bodyCache.get(m);
|
||||
if (body == null) {
|
||||
body = blogManager.getPostBody(m);
|
||||
if (body != null) bodyCache.put(m, body);
|
||||
}
|
||||
//noinspection ConstantConditions
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import android.widget.ProgressBar;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.blogs.BlogController.BlogPostListener;
|
||||
import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
|
||||
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
@@ -33,16 +33,16 @@ import javax.inject.Inject;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
public class BlogActivity extends BriarActivity implements BlogPostListener,
|
||||
public class BlogActivity extends BriarActivity implements
|
||||
OnBlogPostAddedListener,
|
||||
OnBlogPostClickListener, BaseFragmentListener {
|
||||
|
||||
static final int REQUEST_WRITE_POST = 1;
|
||||
static final int REQUEST_SHARE = 2;
|
||||
static final String BLOG_NAME = "briar.BLOG_NAME";
|
||||
static final String IS_MY_BLOG = "briar.IS_MY_BLOG";
|
||||
public static final String BLOG_NAME = "briar.BLOG_NAME";
|
||||
static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
|
||||
|
||||
private static final String POST_ID = "briar.POST_ID";
|
||||
public static final String POST_ID = "briar.POST_ID";
|
||||
|
||||
private GroupId groupId;
|
||||
private ProgressBar progressBar;
|
||||
@@ -50,7 +50,7 @@ public class BlogActivity extends BriarActivity implements BlogPostListener,
|
||||
private BlogPagerAdapter blogPagerAdapter;
|
||||
private BlogPostPagerAdapter postPagerAdapter;
|
||||
private String blogName;
|
||||
private boolean myBlog, isNew;
|
||||
private boolean isNew;
|
||||
private MessageId savedPostId;
|
||||
|
||||
@Inject
|
||||
@@ -67,12 +67,11 @@ public class BlogActivity extends BriarActivity implements BlogPostListener,
|
||||
groupId = new GroupId(b);
|
||||
blogController.setGroupId(groupId);
|
||||
|
||||
// Name of the Blog from Intent
|
||||
// Name of the blog
|
||||
blogName = i.getStringExtra(BLOG_NAME);
|
||||
if (blogName != null) setTitle(blogName);
|
||||
|
||||
// Is this our blog and was it just created?
|
||||
myBlog = i.getBooleanExtra(IS_MY_BLOG, false);
|
||||
// Was this blog just created?
|
||||
isNew = i.getBooleanExtra(IS_NEW_BLOG, false);
|
||||
|
||||
setContentView(R.layout.activity_blog);
|
||||
@@ -254,7 +253,7 @@ public class BlogActivity extends BriarActivity implements BlogPostListener,
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return BlogFragment.newInstance(groupId, blogName, myBlog, isNew);
|
||||
return BlogFragment.newInstance(groupId, blogName, isNew);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.api.blogs.BlogCommentHeader;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@UiThread
|
||||
class BlogCommentItem extends BlogPostItem {
|
||||
|
||||
private static final BlogCommentComparator COMPARATOR =
|
||||
new BlogCommentComparator();
|
||||
|
||||
private final BlogPostHeader postHeader;
|
||||
private final List<BlogCommentHeader> comments = new ArrayList<>();
|
||||
|
||||
BlogCommentItem(BlogCommentHeader header) {
|
||||
super(header, null);
|
||||
postHeader = collectComments(header);
|
||||
Collections.sort(comments, COMPARATOR);
|
||||
}
|
||||
|
||||
private BlogPostHeader collectComments(BlogPostHeader header) {
|
||||
if (header instanceof BlogCommentHeader) {
|
||||
BlogCommentHeader comment = (BlogCommentHeader) header;
|
||||
if (comment.getComment() != null)
|
||||
comments.add(comment);
|
||||
return collectComments(comment.getParent());
|
||||
} else {
|
||||
return header;
|
||||
}
|
||||
}
|
||||
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlogCommentHeader getHeader() {
|
||||
return (BlogCommentHeader) super.getHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
BlogPostHeader getPostHeader() {
|
||||
return postHeader;
|
||||
}
|
||||
|
||||
List<BlogCommentHeader> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
private static class BlogCommentComparator
|
||||
implements Comparator<BlogCommentHeader> {
|
||||
@Override
|
||||
public int compare(BlogCommentHeader h1, BlogCommentHeader h2) {
|
||||
// re-use same comparator used for blog posts, but reverse it
|
||||
return BlogPostItem.compare(h2, h1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,26 +11,20 @@ import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface BlogController extends ActivityLifecycleController {
|
||||
public interface BlogController extends BaseController {
|
||||
|
||||
void setGroupId(GroupId g);
|
||||
|
||||
void loadBlogPosts(
|
||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
|
||||
|
||||
void loadBlogPost(BlogPostHeader header,
|
||||
ResultExceptionHandler<BlogPostItem, DbException> handler);
|
||||
|
||||
void loadBlogPost(MessageId m,
|
||||
ResultExceptionHandler<BlogPostItem, DbException> handler);
|
||||
|
||||
void isMyBlog(ResultExceptionHandler<Boolean, DbException> handler);
|
||||
|
||||
void canDeleteBlog(ResultExceptionHandler<Boolean, DbException> handler);
|
||||
|
||||
void deleteBlog(ResultExceptionHandler<Void, DbException> handler);
|
||||
|
||||
interface BlogPostListener {
|
||||
@UiThread
|
||||
void onBlogPostAdded(BlogPostHeader header, boolean local);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,107 +1,76 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.event.BlogPostAddedEvent;
|
||||
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.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class BlogControllerImpl extends DbControllerImpl
|
||||
implements BlogController, EventListener {
|
||||
public class BlogControllerImpl extends BaseControllerImpl
|
||||
implements ActivityLifecycleController, BlogController, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BlogControllerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
protected Activity activity;
|
||||
@Inject
|
||||
protected EventBus eventBus;
|
||||
@Inject
|
||||
protected AndroidNotificationManager notificationManager;
|
||||
|
||||
@Inject
|
||||
protected volatile BlogManager blogManager;
|
||||
|
||||
private final Map<MessageId, byte[]> bodyCache = new ConcurrentHashMap<>();
|
||||
private final Map<MessageId, BlogPostHeader> headerCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private volatile BlogPostListener listener;
|
||||
private volatile GroupId groupId = null;
|
||||
|
||||
@Inject
|
||||
BlogControllerImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreate() {
|
||||
if (activity instanceof OnBlogPostAddedListener) {
|
||||
listener = (OnBlogPostAddedListener) activity;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"An activity that injects the BlogController must " +
|
||||
"implement the OnBlogPostAddedListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
super.onStart(); // TODO: Should be called when activity starts. #609
|
||||
notificationManager.blockNotification(groupId);
|
||||
notificationManager.clearBlogPostNotification(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
super.onStop(); // TODO: Should be called when activity stops. #609
|
||||
notificationManager.unblockNotification(groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGroupId(GroupId g) {
|
||||
groupId = g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreate() {
|
||||
if (activity instanceof BlogPostListener) {
|
||||
listener = (BlogPostListener) activity;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"An activity that injects the BlogController must " +
|
||||
"implement the BlogPostListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResume() {
|
||||
notificationManager.blockNotification(groupId);
|
||||
notificationManager.clearBlogPostNotification(groupId);
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
notificationManager.unblockNotification(groupId);
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
if (e instanceof BlogPostAddedEvent) {
|
||||
final BlogPostAddedEvent m = (BlogPostAddedEvent) e;
|
||||
if (m.getGroupId().equals(groupId)) {
|
||||
LOG.info("New blog post added");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onBlogPostAdded(m.getHeader(), m.isLocal());
|
||||
}
|
||||
});
|
||||
BlogPostAddedEvent s = (BlogPostAddedEvent) e;
|
||||
if (s.getGroupId().equals(groupId)) {
|
||||
super.eventOccurred(e);
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
@@ -122,86 +91,27 @@ public class BlogControllerImpl extends DbControllerImpl
|
||||
public void loadBlogPosts(
|
||||
final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<BlogPostHeader> headers =
|
||||
blogManager.getPostHeaders(groupId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading headers took " + duration + " ms");
|
||||
List<BlogPostItem> items = new ArrayList<>(headers.size());
|
||||
now = System.currentTimeMillis();
|
||||
for (BlogPostHeader h : headers) {
|
||||
headerCache.put(h.getId(), h);
|
||||
byte[] body = getPostBody(h.getId());
|
||||
items.add(new BlogPostItem(groupId, h, body));
|
||||
}
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading bodies took " + duration + " ms");
|
||||
handler.onResult(items);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBlogPost(final BlogPostHeader header,
|
||||
final ResultExceptionHandler<BlogPostItem, DbException> handler) {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
byte[] body = bodyCache.get(header.getId());
|
||||
if (body != null) {
|
||||
LOG.info("Loaded body from cache");
|
||||
handler.onResult(new BlogPostItem(groupId, header, body));
|
||||
return;
|
||||
}
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
byte[] body = getPostBody(header.getId());
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading body took " + duration + " ms");
|
||||
handler.onResult(new BlogPostItem(groupId, header, body));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
loadBlogPosts(groupId, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBlogPost(final MessageId m,
|
||||
final ResultExceptionHandler<BlogPostItem, DbException> handler) {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
BlogPostHeader header = headerCache.get(m);
|
||||
if (header != null) {
|
||||
LOG.info("Loaded header from cache");
|
||||
loadBlogPost(header, handler);
|
||||
return;
|
||||
}
|
||||
loadBlogPost(groupId, m, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isMyBlog(
|
||||
final ResultExceptionHandler<Boolean, DbException> handler) {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
BlogPostHeader header = getPostHeader(m);
|
||||
byte[] body = getPostBody(m);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading post took " + duration + " ms");
|
||||
handler.onResult(new BlogPostItem(groupId, header, body));
|
||||
LocalAuthor a = identityManager.getLocalAuthor();
|
||||
Blog b = blogManager.getBlog(groupId);
|
||||
handler.onResult(b.getAuthor().getId().equals(a.getId()));
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
@@ -209,25 +119,7 @@ public class BlogControllerImpl extends DbControllerImpl
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private BlogPostHeader getPostHeader(MessageId m) throws DbException {
|
||||
if (groupId == null) throw new IllegalStateException();
|
||||
BlogPostHeader header = headerCache.get(m);
|
||||
if (header == null) {
|
||||
header = blogManager.getPostHeader(groupId, m);
|
||||
headerCache.put(m, header);
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
private byte[] getPostBody(MessageId m) throws DbException {
|
||||
byte[] body = bodyCache.get(m);
|
||||
if (body == null) {
|
||||
body = blogManager.getPostBody(m);
|
||||
if (body != null) bodyCache.put(m, body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,7 +19,7 @@ import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.blogs.BlogController.BlogPostListener;
|
||||
import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
|
||||
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
@@ -42,12 +42,12 @@ import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
|
||||
import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
|
||||
import static org.briarproject.android.blogs.BlogActivity.IS_NEW_BLOG;
|
||||
import static org.briarproject.android.blogs.BlogActivity.REQUEST_SHARE;
|
||||
import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST;
|
||||
|
||||
public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
public class BlogFragment extends BaseFragment implements
|
||||
OnBlogPostAddedListener {
|
||||
|
||||
public final static String TAG = BlogFragment.class.getName();
|
||||
|
||||
@@ -56,20 +56,18 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
|
||||
private GroupId groupId;
|
||||
private String blogName;
|
||||
private boolean myBlog;
|
||||
private BlogPostAdapter adapter;
|
||||
private BriarRecyclerView list;
|
||||
private MenuItem deleteButton;
|
||||
private MenuItem writeButton, deleteButton;
|
||||
|
||||
static BlogFragment newInstance(GroupId groupId, String name,
|
||||
boolean myBlog, boolean isNew) {
|
||||
boolean isNew) {
|
||||
|
||||
BlogFragment f = new BlogFragment();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putByteArray(GROUP_ID, groupId.getBytes());
|
||||
bundle.putString(BLOG_NAME, name);
|
||||
bundle.putBoolean(IS_MY_BLOG, myBlog);
|
||||
bundle.putBoolean(IS_NEW_BLOG, isNew);
|
||||
|
||||
f.setArguments(bundle);
|
||||
@@ -88,7 +86,6 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
||||
groupId = new GroupId(b);
|
||||
blogName = args.getString(BLOG_NAME);
|
||||
myBlog = args.getBoolean(IS_MY_BLOG);
|
||||
boolean isNew = args.getBoolean(IS_NEW_BLOG);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
@@ -99,12 +96,7 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
list.showProgressBar();
|
||||
if (myBlog) {
|
||||
list.setEmptyText(
|
||||
getString(R.string.blogs_my_blogs_blog_empty_state));
|
||||
} else {
|
||||
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
|
||||
}
|
||||
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
|
||||
|
||||
// show snackbar if this blog was just created
|
||||
if (isNew) {
|
||||
@@ -128,7 +120,8 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (!myBlog) checkIfBlogCanBeDeleted();
|
||||
checkIfThisIsMyBlog();
|
||||
checkIfBlogCanBeDeleted();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,13 +139,10 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (myBlog) {
|
||||
inflater.inflate(R.menu.blogs_my_blog_actions, menu);
|
||||
} else {
|
||||
inflater.inflate(R.menu.blogs_blog_actions, menu);
|
||||
deleteButton = menu.findItem(R.id.action_blog_delete);
|
||||
deleteButton.setVisible(false);
|
||||
}
|
||||
inflater.inflate(R.menu.blogs_blog_actions, menu);
|
||||
writeButton = menu.findItem(R.id.action_write_blog_post);
|
||||
deleteButton = menu.findItem(R.id.action_blog_delete);
|
||||
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@@ -199,7 +189,9 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_SHARE && result == RESULT_OK) {
|
||||
if (request == REQUEST_WRITE_POST && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.blogs_blog_post_created);
|
||||
} else if (request == REQUEST_SHARE && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.blogs_sharing_snackbar);
|
||||
}
|
||||
}
|
||||
@@ -217,7 +209,12 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
@Override
|
||||
public void onResultUi(BlogPostItem post) {
|
||||
adapter.add(post);
|
||||
if (local) list.scrollToPosition(0);
|
||||
if (local) {
|
||||
list.scrollToPosition(0);
|
||||
displaySnackbar(R.string.blogs_blog_post_created);
|
||||
} else {
|
||||
displaySnackbar(R.string.blogs_blog_post_received);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -251,6 +248,25 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void checkIfThisIsMyBlog() {
|
||||
blogController.canDeleteBlog(
|
||||
new UiResultExceptionHandler<Boolean, DbException>(
|
||||
getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(Boolean isMyBlog) {
|
||||
if (isMyBlog) {
|
||||
showWriteButton();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void checkIfBlogCanBeDeleted() {
|
||||
blogController.canDeleteBlog(
|
||||
new UiResultExceptionHandler<Boolean, DbException>(
|
||||
@@ -270,6 +286,11 @@ public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
});
|
||||
}
|
||||
|
||||
private void showWriteButton() {
|
||||
if (writeButton != null)
|
||||
writeButton.setVisible(true);
|
||||
}
|
||||
|
||||
private void showDeleteButton() {
|
||||
if (deleteButton != null)
|
||||
deleteButton.setVisible(true);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
@@ -9,7 +8,6 @@ import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -23,12 +21,10 @@ import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
|
||||
import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
|
||||
|
||||
class BlogListAdapter extends
|
||||
RecyclerView.Adapter<BlogListAdapter.BlogViewHolder> {
|
||||
@@ -136,7 +132,6 @@ class BlogListAdapter extends
|
||||
Blog b = item.getBlog();
|
||||
i.putExtra(GROUP_ID, b.getId().getBytes());
|
||||
i.putExtra(BLOG_NAME, b.getName());
|
||||
i.putExtra(IS_MY_BLOG, item.isOurs());
|
||||
ActivityOptionsCompat options = ActivityOptionsCompat
|
||||
.makeCustomAnimation(ctx, android.R.anim.fade_in,
|
||||
android.R.anim.fade_out);
|
||||
|
||||
@@ -6,25 +6,15 @@ import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.AndroidUtils;
|
||||
import org.briarproject.android.util.TrustIndicatorView;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
class BlogPostAdapter extends
|
||||
RecyclerView.Adapter<BlogPostAdapter.BlogPostHolder> {
|
||||
class BlogPostAdapter extends RecyclerView.Adapter<BlogPostViewHolder> {
|
||||
|
||||
private SortedList<BlogPostItem> posts = new SortedList<>(
|
||||
BlogPostItem.class, new SortedList.Callback<BlogPostItem>() {
|
||||
|
||||
@Override
|
||||
public int compare(BlogPostItem a, BlogPostItem b) {
|
||||
return a.compareTo(b);
|
||||
@@ -60,7 +50,6 @@ class BlogPostAdapter extends
|
||||
return a.getId().equals(b.getId());
|
||||
}
|
||||
});
|
||||
|
||||
private final Context ctx;
|
||||
private final OnBlogPostClickListener listener;
|
||||
|
||||
@@ -70,34 +59,18 @@ class BlogPostAdapter extends
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlogPostHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
public BlogPostViewHolder onCreateViewHolder(ViewGroup parent,
|
||||
int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_blog_post, parent, false);
|
||||
return new BlogPostHolder(v);
|
||||
BlogPostViewHolder ui = new BlogPostViewHolder(v);
|
||||
ui.setOnBlogPostClickListener(listener);
|
||||
return ui;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final BlogPostHolder ui, int position) {
|
||||
final BlogPostItem post = getItem(position);
|
||||
|
||||
Author author = post.getAuthor();
|
||||
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
|
||||
ui.avatar.setImageDrawable(d);
|
||||
ui.author.setText(author.getName());
|
||||
ui.trust.setTrustLevel(post.getAuthorStatus());
|
||||
|
||||
// date
|
||||
ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp()));
|
||||
|
||||
// post body
|
||||
ui.body.setText(StringUtils.fromUtf8(post.getBody()));
|
||||
|
||||
ui.layout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onBlogPostClick(post);
|
||||
}
|
||||
});
|
||||
public void onBindViewHolder(BlogPostViewHolder ui, int position) {
|
||||
ui.bindItem(getItem(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,27 +102,6 @@ class BlogPostAdapter extends
|
||||
return posts.size() == 0;
|
||||
}
|
||||
|
||||
static class BlogPostHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ViewGroup layout;
|
||||
private final CircleImageView avatar;
|
||||
private final TextView author;
|
||||
private final TrustIndicatorView trust;
|
||||
private final TextView date;
|
||||
private final TextView body;
|
||||
|
||||
BlogPostHolder(View v) {
|
||||
super(v);
|
||||
|
||||
layout = (ViewGroup) v;
|
||||
avatar = (CircleImageView) v.findViewById(R.id.avatar);
|
||||
author = (TextView) v.findViewById(R.id.authorName);
|
||||
trust = (TrustIndicatorView) v.findViewById(R.id.trustIndicator);
|
||||
date = (TextView) v.findViewById(R.id.dateView);
|
||||
body = (TextView) v.findViewById(R.id.bodyView);
|
||||
}
|
||||
}
|
||||
|
||||
interface OnBlogPostClickListener {
|
||||
void onBlogPostClick(BlogPostItem post);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import org.briarproject.android.util.TrustIndicatorView;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -27,7 +26,6 @@ import javax.inject.Inject;
|
||||
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static org.briarproject.android.util.AndroidUtils.MIN_RESOLUTION;
|
||||
|
||||
public class BlogPostFragment extends BaseFragment {
|
||||
@@ -135,11 +133,7 @@ public class BlogPostFragment extends BaseFragment {
|
||||
if (ctx != null) {
|
||||
ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp()));
|
||||
}
|
||||
|
||||
// TODO remove #598
|
||||
ui.title.setVisibility(GONE);
|
||||
|
||||
ui.body.setText(StringUtils.fromUtf8(post.getBody()));
|
||||
ui.body.setText(post.getBody());
|
||||
}
|
||||
|
||||
private static class BlogPostViewHolder {
|
||||
@@ -148,7 +142,6 @@ public class BlogPostFragment extends BaseFragment {
|
||||
private final TextView authorName;
|
||||
private final TrustIndicatorView trust;
|
||||
private final TextView date;
|
||||
private final TextView title;
|
||||
private final TextView body;
|
||||
|
||||
private BlogPostViewHolder(View v) {
|
||||
@@ -156,7 +149,6 @@ public class BlogPostFragment extends BaseFragment {
|
||||
authorName = (TextView) v.findViewById(R.id.authorName);
|
||||
trust = (TrustIndicatorView) v.findViewById(R.id.trustIndicator);
|
||||
date = (TextView) v.findViewById(R.id.date);
|
||||
title = (TextView) v.findViewById(R.id.title);
|
||||
body = (TextView) v.findViewById(R.id.body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.android.blogs;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.identity.Author;
|
||||
@@ -9,19 +10,17 @@ import org.briarproject.api.identity.Author.Status;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
// This class is not thread-safe
|
||||
@UiThread
|
||||
class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
|
||||
private final GroupId groupId;
|
||||
private final BlogPostHeader header;
|
||||
private final byte[] body;
|
||||
protected String body;
|
||||
private boolean read;
|
||||
|
||||
BlogPostItem(GroupId groupId, BlogPostHeader header, byte[] body) {
|
||||
this.groupId = groupId;
|
||||
BlogPostItem(BlogPostHeader header, @Nullable String body) {
|
||||
this.header = header;
|
||||
this.body = body;
|
||||
read = header.isRead();
|
||||
this.read = header.isRead();
|
||||
}
|
||||
|
||||
public MessageId getId() {
|
||||
@@ -29,17 +28,13 @@ class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
return header.getGroupId();
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return header.getTimestamp();
|
||||
}
|
||||
|
||||
public long getTimeReceived() {
|
||||
return header.getTimeReceived();
|
||||
}
|
||||
|
||||
public Author getAuthor() {
|
||||
return header.getAuthor();
|
||||
}
|
||||
@@ -48,7 +43,7 @@ class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
return header.getAuthorStatus();
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -56,15 +51,23 @@ class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
return read;
|
||||
}
|
||||
|
||||
public void setRead(boolean read) {
|
||||
this.read = read;
|
||||
public BlogPostHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
BlogPostHeader getPostHeader() {
|
||||
return getHeader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull BlogPostItem other) {
|
||||
if (this == other) return 0;
|
||||
return compare(getHeader(), other.getHeader());
|
||||
}
|
||||
|
||||
protected static int compare(BlogPostHeader h1, BlogPostHeader h2) {
|
||||
// The newest post comes first
|
||||
long aTime = getTimeReceived(), bTime = other.getTimeReceived();
|
||||
long aTime = h1.getTimeReceived(), bTime = h2.getTimeReceived();
|
||||
if (aTime > bTime) return -1;
|
||||
if (aTime < bTime) return 1;
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.android.util.AuthorView;
|
||||
import org.briarproject.api.blogs.BlogCommentHeader;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.android.blogs.BlogActivity.POST_ID;
|
||||
import static org.briarproject.api.blogs.MessageType.POST;
|
||||
|
||||
@UiThread
|
||||
class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final Context ctx;
|
||||
private final ViewGroup layout;
|
||||
private final AuthorView reblogger;
|
||||
private final AuthorView author;
|
||||
private final ImageView reblogButton;
|
||||
private final TextView body;
|
||||
private final ViewGroup commentContainer;
|
||||
|
||||
private OnBlogPostClickListener listener;
|
||||
|
||||
BlogPostViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
ctx = v.getContext();
|
||||
layout = (ViewGroup) v;
|
||||
reblogger = (AuthorView) v.findViewById(R.id.rebloggerView);
|
||||
author = (AuthorView) v.findViewById(R.id.authorView);
|
||||
reblogButton = (ImageView) v.findViewById(R.id.commentView);
|
||||
body = (TextView) v.findViewById(R.id.bodyView);
|
||||
commentContainer =
|
||||
(ViewGroup) v.findViewById(R.id.commentContainer);
|
||||
}
|
||||
|
||||
void setOnBlogPostClickListener(OnBlogPostClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
void setVisibility(int visibility) {
|
||||
layout.setVisibility(visibility);
|
||||
}
|
||||
|
||||
void hideReblogButton() {
|
||||
reblogButton.setVisibility(GONE);
|
||||
}
|
||||
|
||||
void setTransitionName(MessageId id) {
|
||||
ViewCompat.setTransitionName(layout, getTransitionName(id));
|
||||
}
|
||||
|
||||
private String getTransitionName(MessageId id) {
|
||||
return "blogPost" + id.hashCode();
|
||||
}
|
||||
|
||||
void bindItem(final BlogPostItem item) {
|
||||
setTransitionName(item.getId());
|
||||
layout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (listener != null) {
|
||||
listener.onBlogPostClick(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// author and date
|
||||
BlogPostHeader post = item.getPostHeader();
|
||||
Author a = post.getAuthor();
|
||||
author.setAuthor(a);
|
||||
author.setAuthorStatus(post.getAuthorStatus());
|
||||
author.setDate(post.getTimestamp());
|
||||
// TODO make author clickable more often #624
|
||||
if (item.getHeader().getType() == POST) {
|
||||
author.setBlogLink(post.getGroupId());
|
||||
} else {
|
||||
author.unsetBlogLink();
|
||||
}
|
||||
|
||||
// post body
|
||||
body.setText(item.getBody());
|
||||
|
||||
// reblog button
|
||||
reblogButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(ctx, ReblogActivity.class);
|
||||
i.putExtra(GROUP_ID, item.getGroupId().getBytes());
|
||||
i.putExtra(POST_ID, item.getId().getBytes());
|
||||
|
||||
ActivityOptionsCompat options =
|
||||
makeSceneTransitionAnimation((Activity) ctx, layout,
|
||||
getTransitionName(item.getId()));
|
||||
ActivityCompat
|
||||
.startActivity((Activity) ctx, i, options.toBundle());
|
||||
}
|
||||
});
|
||||
|
||||
// comments
|
||||
commentContainer.removeAllViews();
|
||||
if (item instanceof BlogCommentItem) {
|
||||
onBindComment((BlogCommentItem) item);
|
||||
} else {
|
||||
reblogger.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBindComment(final BlogCommentItem item) {
|
||||
// reblogger
|
||||
reblogger.setAuthor(item.getAuthor());
|
||||
reblogger.setAuthorStatus(item.getAuthorStatus());
|
||||
reblogger.setDate(item.getTimestamp());
|
||||
reblogger.setBlogLink(item.getGroupId());
|
||||
reblogger.setVisibility(VISIBLE);
|
||||
|
||||
// comments
|
||||
for (BlogCommentHeader c : item.getComments()) {
|
||||
View v = LayoutInflater.from(ctx)
|
||||
.inflate(R.layout.list_item_blog_comment,
|
||||
commentContainer, false);
|
||||
|
||||
AuthorView author = (AuthorView) v.findViewById(R.id.authorView);
|
||||
TextView body = (TextView) v.findViewById(R.id.bodyView);
|
||||
|
||||
author.setAuthor(c.getAuthor());
|
||||
author.setAuthorStatus(c.getAuthorStatus());
|
||||
author.setDate(c.getTimestamp());
|
||||
// TODO make author clickable #624
|
||||
|
||||
body.setText(c.getComment());
|
||||
|
||||
commentContainer.addView(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
|
||||
public class BlogsFragment extends BaseFragment {
|
||||
|
||||
public final static String TAG = BlogsFragment.class.getName();
|
||||
|
||||
private static final String SELECTED_TAB = "selectedTab";
|
||||
private TabLayout tabLayout;
|
||||
|
||||
public static BlogsFragment newInstance() {
|
||||
|
||||
Bundle args = new Bundle();
|
||||
|
||||
BlogsFragment fragment = new BlogsFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blogs, container,
|
||||
false);
|
||||
|
||||
tabLayout = (TabLayout) v.findViewById(R.id.tabLayout);
|
||||
ViewPager viewPager = (ViewPager) v.findViewById(R.id.pager);
|
||||
|
||||
String[] titles = {
|
||||
getString(R.string.blogs_feed),
|
||||
getString(R.string.blogs_my_blogs),
|
||||
getString(R.string.blogs_blog_list),
|
||||
getString(R.string.blogs_available_blogs),
|
||||
getString(R.string.blogs_drafts)
|
||||
};
|
||||
TabAdapter tabAdapter =
|
||||
new TabAdapter(getChildFragmentManager(), titles);
|
||||
viewPager.setAdapter(tabAdapter);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
tabLayout.setVisibility(GONE);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int position = savedInstanceState.getInt(SELECTED_TAB, 0);
|
||||
viewPager.setCurrentItem(position);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt(SELECTED_TAB, tabLayout.getSelectedTabPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
|
||||
private static class TabAdapter extends FragmentStatePagerAdapter {
|
||||
private String[] titles;
|
||||
|
||||
TabAdapter(FragmentManager fm, String[] titles) {
|
||||
super(fm);
|
||||
this.titles = titles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 1;
|
||||
// return titles.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return FeedFragment.newInstance();
|
||||
// switch (position) {
|
||||
// case 0:
|
||||
// return FeedFragment.newInstance();
|
||||
// case 1:
|
||||
// return new MyBlogsFragment();
|
||||
// default:
|
||||
// return BlogListFragment.newInstance(position);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return titles[position];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,6 @@ 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;
|
||||
@@ -35,11 +34,9 @@ import javax.inject.Inject;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
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.android.blogs.BlogActivity.BLOG_NAME;
|
||||
import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
|
||||
import static org.briarproject.android.blogs.BlogActivity.IS_NEW_BLOG;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_DESC_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_TITLE_LENGTH;
|
||||
@@ -180,7 +177,6 @@ public class CreateBlogActivity extends BriarActivity
|
||||
new Intent(CreateBlogActivity.this, BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, b.getId().getBytes());
|
||||
i.putExtra(BLOG_NAME, b.getName());
|
||||
i.putExtra(IS_MY_BLOG, true);
|
||||
i.putExtra(IS_NEW_BLOG, true);
|
||||
ActivityOptionsCompat options =
|
||||
makeCustomAnimation(CreateBlogActivity.this,
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.android.controller.handler.ResultHandler;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.db.DbException;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface FeedController {
|
||||
public interface FeedController extends BaseController {
|
||||
|
||||
void onResume();
|
||||
|
||||
void onPause();
|
||||
|
||||
void loadPosts(ResultHandler<Collection<BlogPostItem>> resultHandler);
|
||||
void loadBlogPosts(
|
||||
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
|
||||
|
||||
void loadPersonalBlog(ResultHandler<Blog> resultHandler);
|
||||
|
||||
void setOnBlogPostAddedListener(OnBlogPostAddedListener listener);
|
||||
|
||||
interface OnBlogPostAddedListener {
|
||||
void onBlogPostAdded(final BlogPostItem post);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.handler.ResultExceptionHandler;
|
||||
import org.briarproject.android.controller.handler.ResultHandler;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.event.BlogPostAddedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -24,82 +17,56 @@ import javax.inject.Inject;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class FeedControllerImpl extends DbControllerImpl
|
||||
implements FeedController, EventListener {
|
||||
public class FeedControllerImpl extends BaseControllerImpl
|
||||
implements FeedController {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FeedControllerImpl.class.getName());
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Inject
|
||||
AndroidNotificationManager notificationManager;
|
||||
@Inject
|
||||
protected EventBus eventBus;
|
||||
|
||||
@Inject
|
||||
protected volatile BlogManager blogManager;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
|
||||
private volatile OnBlogPostAddedListener listener;
|
||||
|
||||
@Inject
|
||||
FeedControllerImpl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
notificationManager.blockAllBlogPostNotifications();
|
||||
notificationManager.clearAllBlogPostNotifications();
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
notificationManager.unblockAllBlogPostNotifications();
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (!(e instanceof BlogPostAddedEvent)) return;
|
||||
|
||||
LOG.info("New blog post added");
|
||||
if (listener != null) {
|
||||
BlogPostAddedEvent m = (BlogPostAddedEvent) e;
|
||||
BlogPostHeader header = m.getHeader();
|
||||
addPost(m.getGroupId(), header);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPosts(
|
||||
final ResultHandler<Collection<BlogPostItem>> resultHandler) {
|
||||
|
||||
LOG.info("Loading blog posts...");
|
||||
public void loadBlogPosts(
|
||||
final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
|
||||
LOG.info("Loading all blog posts...");
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Collection<BlogPostItem> posts = new ArrayList<>();
|
||||
try {
|
||||
// load blog posts
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<BlogPostItem> posts = new ArrayList<>();
|
||||
for (Blog b : blogManager.getBlogs()) {
|
||||
Collection<BlogPostHeader> header =
|
||||
blogManager.getPostHeaders(b.getId());
|
||||
for (BlogPostHeader h : header) {
|
||||
byte[] body = blogManager.getPostBody(h.getId());
|
||||
posts.add(new BlogPostItem(b.getId(), h, body));
|
||||
try {
|
||||
posts.addAll(loadItems(b.getId()));
|
||||
} catch (NoSuchGroupException | NoSuchMessageException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading posts took " + duration + " ms");
|
||||
resultHandler.onResult(posts);
|
||||
LOG.info("Loading all posts took " + duration + " ms");
|
||||
handler.onResult(posts);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(null);
|
||||
handler.onException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -114,8 +81,7 @@ public class FeedControllerImpl extends DbControllerImpl
|
||||
try {
|
||||
// load blog posts
|
||||
long now = System.currentTimeMillis();
|
||||
Author a =
|
||||
identityManager.getLocalAuthors().iterator().next();
|
||||
Author a = identityManager.getLocalAuthor();
|
||||
Blog b = blogManager.getPersonalBlog(a);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -130,25 +96,4 @@ public class FeedControllerImpl extends DbControllerImpl
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private void addPost(final GroupId groupId, final BlogPostHeader header) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
byte[] body = blogManager.getPostBody(header.getId());
|
||||
BlogPostItem post = new BlogPostItem(groupId, header, body);
|
||||
listener.onBlogPostAdded(post);
|
||||
} catch (DbException ex) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,11 +17,15 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
|
||||
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -32,11 +36,10 @@ import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.android.blogs.BlogActivity.BLOG_NAME;
|
||||
import static org.briarproject.android.blogs.BlogActivity.IS_MY_BLOG;
|
||||
import static org.briarproject.android.blogs.BlogActivity.REQUEST_WRITE_POST;
|
||||
|
||||
public class FeedFragment extends BaseFragment implements
|
||||
OnBlogPostClickListener, FeedController.OnBlogPostAddedListener {
|
||||
OnBlogPostClickListener, OnBlogPostAddedListener {
|
||||
|
||||
public final static String TAG = FeedFragment.class.getName();
|
||||
|
||||
@@ -48,7 +51,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
private BriarRecyclerView list;
|
||||
private Blog personalBlog = null;
|
||||
|
||||
static FeedFragment newInstance() {
|
||||
public static FeedFragment newInstance() {
|
||||
FeedFragment f = new FeedFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
@@ -95,6 +98,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
feedController.onStart();
|
||||
feedController.loadPersonalBlog(
|
||||
new UiResultHandler<Blog>(getActivity()) {
|
||||
@Override
|
||||
@@ -102,33 +106,30 @@ public class FeedFragment extends BaseFragment implements
|
||||
personalBlog = b;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
list.startPeriodicUpdate();
|
||||
feedController.onResume();
|
||||
feedController.loadPosts(
|
||||
new UiResultHandler<Collection<BlogPostItem>>(getActivity()) {
|
||||
feedController.loadBlogPosts(
|
||||
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
|
||||
getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(Collection<BlogPostItem> posts) {
|
||||
if (posts == null) {
|
||||
// TODO show error?
|
||||
} else if (posts.isEmpty()) {
|
||||
if (posts.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.addAll(posts);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO
|
||||
}
|
||||
});
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
feedController.onStop();
|
||||
list.stopPeriodicUpdate();
|
||||
feedController.onPause();
|
||||
// TODO save list position in database/preferences?
|
||||
}
|
||||
|
||||
@@ -171,33 +172,30 @@ public class FeedFragment extends BaseFragment implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostAdded(final BlogPostItem post) {
|
||||
listener.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.add(post);
|
||||
showSnackBar(R.string.blogs_blog_post_received);
|
||||
}
|
||||
});
|
||||
public void onBlogPostAdded(BlogPostHeader header, final boolean local) {
|
||||
feedController.loadBlogPost(header,
|
||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
||||
getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(BlogPostItem post) {
|
||||
adapter.add(post);
|
||||
if (local) {
|
||||
showSnackBar(R.string.blogs_blog_post_created);
|
||||
} else {
|
||||
showSnackBar(R.string.blogs_blog_post_received);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostClick(BlogPostItem post) {
|
||||
byte[] groupId = post.getGroupId().getBytes();
|
||||
String name = getString(R.string.blogs_personal_blog,
|
||||
post.getAuthor().getName());
|
||||
boolean myBlog = personalBlog != null &&
|
||||
personalBlog.getId().equals(post.getGroupId());
|
||||
|
||||
Intent i = new Intent(getActivity(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId);
|
||||
i.putExtra(BLOG_NAME, name);
|
||||
i.putExtra(IS_MY_BLOG, myBlog);
|
||||
ActivityOptionsCompat options =
|
||||
makeCustomAnimation(getActivity(),
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right);
|
||||
startActivity(i, options.toBundle());
|
||||
// TODO Open detail view of post
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -228,5 +226,4 @@ public class FeedFragment extends BaseFragment implements
|
||||
}
|
||||
s.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
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.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchGroupException;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class MyBlogsFragment extends BaseFragment {
|
||||
|
||||
public final static String TAG = MyBlogsFragment.class.getName();
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(TAG);
|
||||
private BriarRecyclerView list;
|
||||
private BlogListAdapter adapter;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
volatile BlogManager blogManager;
|
||||
|
||||
@Inject
|
||||
public MyBlogsFragment() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
adapter = new BlogListAdapter(getActivity());
|
||||
|
||||
list = (BriarRecyclerView) inflater
|
||||
.inflate(R.layout.fragment_blogs_my, container, false);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
list.setEmptyText(getString(R.string.blogs_my_blogs_empty_state));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
listener.getActivityComponent().inject(this);
|
||||
// Starting from here, we can use injected objects
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
adapter.clear();
|
||||
list.showProgressBar();
|
||||
loadBlogs();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void loadBlogs() {
|
||||
listener.runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// load blogs
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<BlogListItem> blogs = new ArrayList<>();
|
||||
Collection<LocalAuthor> authors =
|
||||
identityManager.getLocalAuthors();
|
||||
LocalAuthor a = authors.iterator().next();
|
||||
for (Blog b : blogManager.getBlogs(a)) {
|
||||
try {
|
||||
Collection<BlogPostHeader> headers =
|
||||
blogManager.getPostHeaders(b.getId());
|
||||
blogs.add(new BlogListItem(b, headers, true));
|
||||
} catch (NoSuchGroupException e) {
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
displayBlogs(blogs);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Full blog load took " + duration + " ms");
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayBlogs(final Collection<BlogListItem> items) {
|
||||
listener.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (items.size() == 0) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.addAll(items);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.transition.Fade;
|
||||
import android.transition.Transition;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.android.blogs.BlogActivity.POST_ID;
|
||||
|
||||
public class ReblogActivity extends BriarActivity implements
|
||||
BaseFragmentListener {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
setTransition();
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
byte[] groupId = intent.getByteArrayExtra(GROUP_ID);
|
||||
if (groupId == null)
|
||||
throw new IllegalArgumentException("No group ID in intent");
|
||||
byte[] postId = intent.getByteArrayExtra(POST_ID);
|
||||
if (postId == null)
|
||||
throw new IllegalArgumentException("No post message ID in intent");
|
||||
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
ReblogFragment f = ReblogFragment
|
||||
.newInstance(new GroupId(groupId), new MessageId(postId));
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.fragmentContainer, f)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLoadingScreen(boolean isBlocking, int stringId) {
|
||||
// this is handled by the fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLoadingScreen() {
|
||||
// this is handled by the fragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentCreated(String tag) {
|
||||
|
||||
}
|
||||
|
||||
@TargetApi(21)
|
||||
private void setTransition() {
|
||||
Transition fade = new Fade();
|
||||
fade.excludeTarget(android.R.id.statusBarBackground, true);
|
||||
fade.excludeTarget(R.id.action_bar_container, true);
|
||||
fade.excludeTarget(android.R.id.navigationBarBackground, true);
|
||||
getWindow().setExitTransition(fade);
|
||||
getWindow().setEnterTransition(fade);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.controller.handler.UiResultExceptionHandler;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.View.FOCUS_DOWN;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.android.blogs.BlogActivity.POST_ID;
|
||||
|
||||
public class ReblogFragment extends BaseFragment {
|
||||
|
||||
public static final String TAG = ReblogFragment.class.getName();
|
||||
|
||||
private BaseFragmentListener listener;
|
||||
private ViewHolder ui;
|
||||
private GroupId blogId;
|
||||
private MessageId postId;
|
||||
private BlogPostItem item;
|
||||
|
||||
@Inject
|
||||
FeedController feedController;
|
||||
|
||||
static ReblogFragment newInstance(GroupId groupId, MessageId messageId) {
|
||||
ReblogFragment f = new ReblogFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putByteArray(GROUP_ID, groupId.getBytes());
|
||||
args.putByteArray(POST_ID, messageId.getBytes());
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
listener = (BaseFragmentListener) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(
|
||||
"Using class must implement BaseFragmentListener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
Bundle args = getArguments();
|
||||
blogId = new GroupId(args.getByteArray(GROUP_ID));
|
||||
postId = new MessageId(args.getByteArray(POST_ID));
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_reblog, container,
|
||||
false);
|
||||
ui = new ViewHolder(v);
|
||||
ui.post.setTransitionName(postId);
|
||||
showProgressBar();
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
listener.getActivityComponent().inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
// TODO: Load blog post when fragment is created. #631
|
||||
feedController.loadBlogPost(blogId, postId,
|
||||
new UiResultExceptionHandler<BlogPostItem, DbException>(
|
||||
getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(BlogPostItem result) {
|
||||
item = result;
|
||||
bindViewHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void bindViewHolder() {
|
||||
if (item == null) return;
|
||||
|
||||
hideProgressBar();
|
||||
|
||||
ui.post.bindItem(item);
|
||||
ui.post.hideReblogButton();
|
||||
|
||||
ui.publish.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
send();
|
||||
finish();
|
||||
}
|
||||
});
|
||||
ui.publish.setEnabled(true);
|
||||
ui.scrollView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ui.scrollView.fullScroll(FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void send() {
|
||||
String comment = getComment();
|
||||
feedController.repeatPost(item, comment,
|
||||
new UiResultExceptionHandler<Void, DbException>(getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(Void result) {
|
||||
// do nothing, this fragment is gone already
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// do nothing, this fragment is gone already
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getComment() {
|
||||
if (ui.input.getText().length() == 0) return null;
|
||||
return ui.input.getText().toString();
|
||||
}
|
||||
|
||||
private void showProgressBar() {
|
||||
ui.progressBar.setVisibility(VISIBLE);
|
||||
ui.input.setVisibility(GONE);
|
||||
ui.publish.setVisibility(GONE);
|
||||
}
|
||||
|
||||
private void hideProgressBar() {
|
||||
ui.progressBar.setVisibility(INVISIBLE);
|
||||
ui.input.setVisibility(VISIBLE);
|
||||
ui.publish.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private static class ViewHolder {
|
||||
|
||||
private final ScrollView scrollView;
|
||||
private final ProgressBar progressBar;
|
||||
private final BlogPostViewHolder post;
|
||||
private final EditText input;
|
||||
private final Button publish;
|
||||
|
||||
private ViewHolder(View v) {
|
||||
scrollView = (ScrollView) v.findViewById(R.id.scrollView);
|
||||
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
|
||||
post = new BlogPostViewHolder(v.findViewById(R.id.postLayout));
|
||||
input = (EditText) v.findViewById(R.id.inputText);
|
||||
publish = (Button) v.findViewById(R.id.publishButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.android.fragment;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.Fragment;
|
||||
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
@@ -39,18 +40,27 @@ public abstract class BaseFragment extends Fragment {
|
||||
listener.onFragmentCreated(getUniqueTag());
|
||||
}
|
||||
|
||||
@UiThread
|
||||
protected void finish() {
|
||||
getActivity().supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
public interface BaseFragmentListener {
|
||||
|
||||
@UiThread
|
||||
void showLoadingScreen(boolean isBlocking, int stringId);
|
||||
|
||||
@UiThread
|
||||
void hideLoadingScreen();
|
||||
|
||||
void runOnUiThread(Runnable runnable);
|
||||
|
||||
void runOnDbThread(Runnable runnable);
|
||||
|
||||
@UiThread
|
||||
ActivityComponent getActivityComponent();
|
||||
|
||||
@UiThread
|
||||
void onFragmentCreated(String tag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ public class IntroductionActivity extends BriarActivity implements
|
||||
if (contactId == -1)
|
||||
throw new IllegalArgumentException("Wrong ContactId");
|
||||
|
||||
setContentView(R.layout.activity_introduction);
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.introductionContainer,
|
||||
.add(R.id.fragmentContainer,
|
||||
ContactChooserFragment.newInstance())
|
||||
.commit();
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class IntroductionActivity extends BriarActivity implements
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right)
|
||||
.addSharedElement(view, "avatar")
|
||||
.replace(R.id.introductionContainer, messageFragment,
|
||||
.replace(R.id.fragmentContainer, messageFragment,
|
||||
ContactChooserFragment.TAG)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
|
||||
@@ -343,12 +343,12 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
});
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
@Override
|
||||
protected void finish() {
|
||||
getActivity().getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context ctx, Intent intent) {
|
||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||
|
||||
141
briar-android/src/org/briarproject/android/util/AuthorView.java
Normal file
141
briar-android/src/org/briarproject/android/util/AuthorView.java
Normal file
@@ -0,0 +1,141 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.blogs.BlogActivity;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.Author.Status;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.graphics.Typeface.BOLD;
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation;
|
||||
import static android.util.TypedValue.COMPLEX_UNIT_PX;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.api.identity.Author.Status.OURSELVES;
|
||||
|
||||
public class AuthorView extends RelativeLayout {
|
||||
|
||||
private final CircleImageView avatar;
|
||||
private final ImageView avatarIcon;
|
||||
private final TextView authorName;
|
||||
private final TextView date;
|
||||
private final TrustIndicatorView trustIndicator;
|
||||
|
||||
public AuthorView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.author_view, this, true);
|
||||
|
||||
avatar = (CircleImageView) findViewById(R.id.avatar);
|
||||
avatarIcon = (ImageView) findViewById(R.id.avatarIcon);
|
||||
authorName = (TextView) findViewById(R.id.authorName);
|
||||
date = (TextView) findViewById(R.id.dateView);
|
||||
trustIndicator = (TrustIndicatorView) findViewById(R.id.trustIndicator);
|
||||
|
||||
TypedArray attributes =
|
||||
context.obtainStyledAttributes(attrs, R.styleable.AuthorView);
|
||||
int persona = attributes.getInteger(R.styleable.AuthorView_persona, 0);
|
||||
setPersona(persona);
|
||||
attributes.recycle();
|
||||
}
|
||||
|
||||
public AuthorView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public void setAuthor(Author author) {
|
||||
authorName.setText(author.getName());
|
||||
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
|
||||
avatar.setImageDrawable(d);
|
||||
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setAuthorStatus(Status status) {
|
||||
trustIndicator.setTrustLevel(status);
|
||||
if (status == OURSELVES) {
|
||||
authorName.setTypeface(authorName.getTypeface(), BOLD);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setDate(long date) {
|
||||
this.date.setText(AndroidUtils.formatDate(getContext(), date));
|
||||
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
public void setBlogLink(final GroupId groupId) {
|
||||
setClickable(true);
|
||||
TypedValue outValue = new TypedValue();
|
||||
getContext().getTheme().resolveAttribute(
|
||||
android.R.attr.selectableItemBackground, outValue, true);
|
||||
setBackgroundResource(outValue.resourceId);
|
||||
setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(getContext(), BlogActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
ActivityOptionsCompat options =
|
||||
makeCustomAnimation(getContext(),
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right);
|
||||
Intent[] intents = {i};
|
||||
ContextCompat.startActivities(getContext(), intents,
|
||||
options.toBundle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void unsetBlogLink() {
|
||||
setClickable(false);
|
||||
setBackgroundResource(android.R.color.transparent);
|
||||
setOnClickListener(null);
|
||||
}
|
||||
|
||||
private void setPersona(int persona) {
|
||||
switch (persona) {
|
||||
// reblogger
|
||||
case 1:
|
||||
avatarIcon.setVisibility(VISIBLE);
|
||||
break;
|
||||
// commenter
|
||||
case 2:
|
||||
ViewGroup.LayoutParams params = avatar.getLayoutParams();
|
||||
int size = getResources().getDimensionPixelSize(
|
||||
R.dimen.blogs_avatar_comment_size);
|
||||
params.height = size;
|
||||
params.width = size;
|
||||
avatar.setLayoutParams(params);
|
||||
float textSize = getResources()
|
||||
.getDimensionPixelSize(R.dimen.text_size_tiny);
|
||||
authorName.setTextSize(COMPLEX_UNIT_PX, textSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,11 +26,6 @@ public class TrustIndicatorView extends ImageView {
|
||||
}
|
||||
|
||||
public void setTrustLevel(Status status) {
|
||||
if (status == OURSELVES) {
|
||||
setVisibility(GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
int res;
|
||||
switch (status) {
|
||||
case ANONYMOUS:
|
||||
@@ -42,11 +37,17 @@ public class TrustIndicatorView extends ImageView {
|
||||
case VERIFIED:
|
||||
res = R.drawable.trust_indicator_verified;
|
||||
break;
|
||||
case OURSELVES:
|
||||
res = R.drawable.ic_our_identity_black;
|
||||
break;
|
||||
default:
|
||||
res = R.drawable.trust_indicator_unknown;
|
||||
}
|
||||
setImageDrawable(ContextCompat.getDrawable(getContext(), res));
|
||||
setVisibility(VISIBLE);
|
||||
|
||||
invalidate();
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public interface BlogManager {
|
||||
BlogPostHeader getPostHeader(GroupId g, MessageId m) throws DbException;
|
||||
|
||||
/** Returns the body of the blog post with the given ID. */
|
||||
byte[] getPostBody(MessageId m) throws DbException;
|
||||
String getPostBody(MessageId m) throws DbException;
|
||||
|
||||
/** Returns the headers of all posts in the given blog. */
|
||||
Collection<BlogPostHeader> getPostHeaders(GroupId g) throws DbException;
|
||||
|
||||
@@ -516,7 +516,7 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPostBody(MessageId m) throws DbException {
|
||||
public String getPostBody(MessageId m) throws DbException {
|
||||
try {
|
||||
BdfList message = clientHelper.getMessageAsList(m);
|
||||
if (message == null) throw new DbException();
|
||||
@@ -526,15 +526,14 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO directly return String (#598)
|
||||
private byte[] getPostBody(BdfList message) throws FormatException {
|
||||
private String getPostBody(BdfList message) throws FormatException {
|
||||
MessageType type = MessageType.valueOf(message.getLong(0).intValue());
|
||||
if (type == POST) {
|
||||
// type, body, signature
|
||||
return StringUtils.toUtf8(message.getString(1));
|
||||
return message.getString(1);
|
||||
} else if (type == WRAPPED_POST) {
|
||||
// type, p_group descriptor, p_timestamp, p_content, p_signature
|
||||
return StringUtils.toUtf8(message.getString(3));
|
||||
return message.getString(3);
|
||||
} else {
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user