mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 03:39:05 +01:00
Merge branch '410-my-blogs-tab-with-option-to-add-new-blogs' into 'master'
Micro Blogs UI **Attention:** This MR includes several other commits which are supposed to end up in separate MRs. I suggest that you review **per commit**. Once the first two commits have green light, I can split out the other commits into other MRs. This way I don't have to work myself through a long rebase chain every time I make a change to the bottom MR. This MR is full of commits that introduce features that we will not be using initially. The last commit implements the Micro Blogs UI on top of the framework the first commits establish and hides/disables all future features for now. I suggest we merge this as is and clean things up later when we have a clearer idea what features we will be doing eventually.      Closes #436 See merge request !214
This commit is contained in:
@@ -150,6 +150,36 @@
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.blogs.CreateBlogActivity"
|
||||
android:label="@string/blogs_my_blogs_label"
|
||||
android:parentActivityName=".android.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.blogs.BlogActivity"
|
||||
android:parentActivityName=".android.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.NavDrawerActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.blogs.WriteBlogPostActivity"
|
||||
android:label="@string/blogs_write_blog_post"
|
||||
android:parentActivityName=".android.blogs.BlogActivity"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.blogs.BlogActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.identity.CreateIdentityActivity"
|
||||
android:label="@string/new_identity_title"
|
||||
|
||||
10
briar-android/res/drawable/ic_chat.xml
Normal file
10
briar-android/res/drawable/ic_chat.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:alpha="0.54"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM6,9h12v2L6,11L6,9zM14,14L6,14v-2h8v2zM18,8L6,8L6,6h12v2z"/>
|
||||
</vector>
|
||||
10
briar-android/res/drawable/ic_repeat.xml
Normal file
10
briar-android/res/drawable/ic_repeat.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:alpha="0.54"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/>
|
||||
</vector>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="15dp"
|
||||
android:width="31dp"
|
||||
android:height="12dp"
|
||||
android:viewportHeight="20"
|
||||
android:viewportWidth="49">
|
||||
<path
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="15dp"
|
||||
android:width="31dp"
|
||||
android:height="12dp"
|
||||
android:viewportHeight="20"
|
||||
android:viewportWidth="49">
|
||||
<path
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="15dp"
|
||||
android:width="31dp"
|
||||
android:height="12dp"
|
||||
android:viewportHeight="20"
|
||||
android:viewportWidth="49">
|
||||
<path
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="15dp"
|
||||
android:width="31dp"
|
||||
android:height="12dp"
|
||||
android:viewportHeight="20"
|
||||
android:viewportWidth="49">
|
||||
<path
|
||||
|
||||
21
briar-android/res/layout/activity_blog.xml
Normal file
21
briar-android/res/layout/activity_blog.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".android.blogs.BlogActivity"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"/>
|
||||
|
||||
</FrameLayout>
|
||||
69
briar-android/res/layout/activity_create_blog.xml
Normal file
69
briar-android/res/layout/activity_create_blog.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_activity_horizontal"
|
||||
tools:context=".android.blogs.CreateBlogActivity">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/titleLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:counterEnabled="true"
|
||||
app:counterOverflowTextAppearance="@style/BriarTextCounter.Overflow">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/titleInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/blogs_my_blogs_create_hint_title"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/descLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:counterEnabled="true"
|
||||
app:counterOverflowTextAppearance="@style/BriarTextCounter.Overflow">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/descInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/blogs_my_blogs_create_hint_desc"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/blogs_my_blogs_create_hint_desc_explanation"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/createBlogButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_marginTop="@dimen/margin_activity_vertical"
|
||||
android:enabled="false"
|
||||
android:text="@string/blogs_my_blogs_create"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/createBlogProgressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_activity_vertical"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -32,9 +32,6 @@
|
||||
<Button
|
||||
style="@style/BriarButton"
|
||||
android:id="@+id/createForumButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:text="@string/create_forum_button" />
|
||||
|
||||
<ProgressBar
|
||||
@@ -42,8 +39,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:visibility="gone" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -34,8 +34,6 @@
|
||||
<Button
|
||||
android:id="@+id/createIdentityButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:enabled="false"
|
||||
android:text="@string/create_identity_button"/>
|
||||
|
||||
56
briar-android/res/layout/activity_write_blog_post.xml
Normal file
56
briar-android/res/layout/activity_write_blog_post.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_small"
|
||||
tools:context=".android.blogs.WriteBlogPostActivity">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/titleLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:counterEnabled="true"
|
||||
app:counterOverflowTextAppearance="@style/BriarTextCounter.Overflow">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/titleInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/blogs_write_blog_post_title_hint"
|
||||
android:inputType="textCapWords|textCapSentences|textAutoCorrect"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/bodyInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="bottom"
|
||||
android:hint="@string/blogs_write_blog_post_body_hint"
|
||||
android:inputType="textMultiLine|textLongMessage|textCapSentences|textAutoCorrect">
|
||||
|
||||
<requestFocus/>
|
||||
|
||||
</EditText>
|
||||
|
||||
<Button
|
||||
android:id="@+id/publishButton"
|
||||
style="@style/BriarButton"
|
||||
android:enabled="false"
|
||||
android:text="@string/blogs_publish_blog_post"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
10
briar-android/res/layout/fragment_blog.xml
Normal file
10
briar-android/res/layout/fragment_blog.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.briarproject.android.util.BriarRecyclerView
|
||||
android:id="@+id/postList"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:scrollToEnd="false"
|
||||
tools:context=".android.blogs.BlogActivity"/>
|
||||
73
briar-android/res/layout/fragment_blog_post.xml
Normal file
73
briar-android/res/layout/fragment_blog_post.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_activity_horizontal">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatar"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
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:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Author Name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
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:textSize="@dimen/text_size_tiny"
|
||||
tools:text="yesterday"/>
|
||||
|
||||
<org.briarproject.android.util.TrustIndicatorView
|
||||
android:id="@+id/trustIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_small"
|
||||
android:layout_toRightOf="@+id/authorName"
|
||||
tools:src="@drawable/trust_indicator_verified"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/avatar"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="bold"
|
||||
tools:text="This Is A Blog Post Title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/title"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
tools:text="Body of Blog Post. This could be insanely large or just a short text as well."/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</ScrollView>
|
||||
26
briar-android/res/layout/fragment_blogs_list.xml
Normal file
26
briar-android/res/layout/fragment_blogs_list.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This is just a placeholder to be replaced by the real My Blogs list -->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/num"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="@dimen/margin_activity_horizontal"
|
||||
android:textSize="128sp"
|
||||
tools:text="1"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_activity_horizontal"
|
||||
android:text="There is nothing for you to see here.\n\nMove along and come back later."
|
||||
android:textSize="@dimen/text_size_large"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,26 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This is just a placeholder to be replaced by the real My Blogs list -->
|
||||
<LinearLayout
|
||||
<org.briarproject.android.util.BriarRecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/num"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="@dimen/margin_activity_horizontal"
|
||||
android:textSize="128sp"
|
||||
tools:text="1"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_activity_horizontal"
|
||||
android:text="There is nothing for you to see here.\n\nMove along and come back later."
|
||||
android:textSize="@dimen/text_size_large"/>
|
||||
|
||||
</LinearLayout>
|
||||
tools:listitem="@layout/list_item_blog"/>
|
||||
|
||||
@@ -123,10 +123,7 @@
|
||||
<Button
|
||||
android:id="@+id/makeIntroductionButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/introduction_button"
|
||||
/>
|
||||
android:text="@string/introduction_button"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
79
briar-android/res/layout/list_item_blog.xml
Normal file
79
briar-android/res/layout/list_item_blog.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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:background="?attr/selectableItemBackground">
|
||||
|
||||
<org.briarproject.android.util.TextAvatarView
|
||||
android:id="@+id/avatarView"
|
||||
android:layout_width="@dimen/listitem_picture_frame_size"
|
||||
android:layout_height="@dimen/listitem_picture_frame_size"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginTop="@dimen/margin_medium"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nameView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginTop="@dimen/listitem_horizontal_margin"
|
||||
android:layout_toEndOf="@+id/avatarView"
|
||||
android:layout_toRightOf="@+id/avatarView"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="This is a name of a blog"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/postCountView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/nameView"
|
||||
android:layout_marginBottom="@dimen/margin_small"
|
||||
android:layout_toEndOf="@+id/avatarView"
|
||||
android:layout_toRightOf="@+id/avatarView"
|
||||
android:paddingTop="@dimen/margin_small"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="1337 posts"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/nameView"
|
||||
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:paddingTop="@dimen/margin_small"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Dec 24"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/postCountView"
|
||||
android:layout_toEndOf="@+id/avatarView"
|
||||
android:layout_toRightOf="@+id/avatarView"
|
||||
android:textColor="@color/briar_text_tertiary"
|
||||
tools:text="@string/blogs_blog_is_empty"/>
|
||||
|
||||
<View
|
||||
style="@style/Divider.ForumList"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/statusView"
|
||||
android:layout_marginTop="@dimen/listitem_horizontal_margin"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
118
briar-android/res/layout/list_item_blog_post.xml
Normal file
118
briar-android/res/layout/list_item_blog_post.xml
Normal file
@@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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">
|
||||
|
||||
<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"
|
||||
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"/>
|
||||
|
||||
<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."/>
|
||||
|
||||
<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"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
tools:text="This is a name of a forum"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/unreadView"
|
||||
android:id="@+id/postCountView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/forumNameView"
|
||||
@@ -62,7 +62,7 @@
|
||||
style="@style/Divider.ForumList"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/unreadView"/>
|
||||
android:layout_below="@+id/postCountView"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
@@ -34,10 +34,7 @@
|
||||
<Button
|
||||
android:id="@+id/shareForumButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/forum_share_button"
|
||||
/>
|
||||
android:text="@string/forum_share_button"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
12
briar-android/res/menu/blogs_feed_actions.xml
Normal file
12
briar-android/res/menu/blogs_feed_actions.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_write_blog_post"
|
||||
android:icon="@drawable/forum_item_create_white"
|
||||
android:title="@string/blogs_write_blog_post"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
</menu>
|
||||
12
briar-android/res/menu/blogs_my_actions.xml
Normal file
12
briar-android/res/menu/blogs_my_actions.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_create_blog"
|
||||
android:icon="@drawable/ic_add_white"
|
||||
android:title="@string/blogs_my_blogs_create"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
</menu>
|
||||
18
briar-android/res/menu/blogs_my_blog_actions.xml
Normal file
18
briar-android/res/menu/blogs_my_blog_actions.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_delete_blog"
|
||||
android:icon="@drawable/action_delete_white"
|
||||
android:title="@string/blogs_delete_blog"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
12
briar-android/res/menu/blogs_write_blog_post_actions.xml
Normal file
12
briar-android/res/menu/blogs_write_blog_post_actions.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_publish_blog_post"
|
||||
android:title="@string/blogs_publish_blog_post"
|
||||
android:icon="@drawable/social_send_now_white"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
</menu>
|
||||
@@ -20,6 +20,7 @@
|
||||
<dimen name="text_size_xlarge">34sp</dimen>
|
||||
|
||||
<dimen name="listitem_horizontal_margin">16dp</dimen>
|
||||
<dimen name="listitem_vertical_margin">10dp</dimen>
|
||||
<dimen name="listitem_text_left_margin">72dp</dimen>
|
||||
<dimen name="listitem_height_one_line_avatar">56dp</dimen>
|
||||
<dimen name="listitem_height_contact_selector">68dp</dimen>
|
||||
|
||||
@@ -84,12 +84,12 @@
|
||||
<string name="forum_leave">Leave Forum</string>
|
||||
<string name="forum_left_toast">Left Forum</string>
|
||||
<string name="forum_sharing_status">Sharing Status</string>
|
||||
<string name="forum_no_posts">No posts</string>
|
||||
<string name="no_posts">No posts</string>
|
||||
<plurals name="unread_posts">
|
||||
<item quantity="one">%d unread post</item>
|
||||
<item quantity="other">%d unread posts</item>
|
||||
</plurals>
|
||||
<plurals name="forum_posts">
|
||||
<plurals name="posts">
|
||||
<item quantity="one">%d post</item>
|
||||
<item quantity="other">%d posts</item>
|
||||
</plurals>
|
||||
@@ -251,9 +251,39 @@
|
||||
<string name="progress_title_please_wait">Please wait..</string>
|
||||
|
||||
<!-- Blogs -->
|
||||
<string name="blogs_button">Blogs</string>
|
||||
<string name="blogs_button">Micro Blogs</string>
|
||||
<string name="blogs_feed">Feed</string>
|
||||
|
||||
<string name="blogs_my_blogs">My Blogs</string>
|
||||
<string name="blogs_my_blogs_create">Create Blog</string>
|
||||
<string name="blogs_my_blogs_label">Add new Blog</string>
|
||||
<string name="blogs_my_blogs_create_hint_title">Blog title (cannot be changed later)</string>
|
||||
<string name="blogs_my_blogs_create_hint_desc">A short description of your new blog</string>
|
||||
<string name="blogs_my_blogs_create_hint_desc_explanation">Potential readers may or may not subscribe to your blog based on the content of the description.</string>
|
||||
<string name="blogs_my_blogs_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>
|
||||
<string name="tag_new">NEW</string>
|
||||
<string name="blogs_post_more">more</string>
|
||||
<string name="blogs_write_blog_post">Write Blog Post</string>
|
||||
<string name="blogs_write_blog_post_title_hint">Add a title (optional)</string>
|
||||
<string name="blogs_write_blog_post_body_hint">Type your blog post here</string>
|
||||
<string name="blogs_publish_blog_post">Publish</string>
|
||||
<string name="blogs_blog_post_created">Blog Post Created</string>
|
||||
<string name="blogs_blog_post_received">New Blog Post Received</string>
|
||||
<string name="blogs_blog_post_scroll_to">Scroll To</string>
|
||||
<string name="blogs_blog_failed_to_load">Blog failed to load</string>
|
||||
<string name="blogs_blog_post_failed_to_load">Blog Post failed to load</string>
|
||||
<string name="blogs_feed_empty_state">This is the global blog feed.\n\nIt looks like nobody blogged anything, yet.\n\nBe the first and tap the pen icon to write a new blog post.</string>
|
||||
<string name="blogs_delete_blog">Delete Blog</string>
|
||||
<string name="blogs_delete_blog_dialog_message">Are you sure that you want to delete this Blog and all posts?\nNote that this will not delete the blog from other people\'s devices.</string>
|
||||
<string name="blogs_delete_blog_ok">Delete Blog</string>
|
||||
<string name="blogs_delete_blog_cancel">Keep</string>
|
||||
<string name="blogs_blog_deleted">Blog Deleted</string>
|
||||
<string name="blogs_remove_blog">Remove Blog</string>
|
||||
|
||||
<string name="blogs_blog_list">Blog List</string>
|
||||
<string name="blogs_available_blogs">Available Blogs</string>
|
||||
<string name="blogs_drafts">Drafts</string>
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
</style>
|
||||
|
||||
<style name="BriarButton" parent="Widget.AppCompat.Button.Colored">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:padding">@dimen/margin_large</item>
|
||||
</style>
|
||||
@@ -55,6 +57,17 @@
|
||||
<item name="android:textColor">@android:color/primary_text_light</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarTag">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_marginRight">@dimen/margin_medium</item>
|
||||
<item name="android:paddingLeft">3dp</item>
|
||||
<item name="android:paddingRight">3dp</item>
|
||||
<item name="android:background">@color/briar_primary</item>
|
||||
<item name="android:textSize">@dimen/text_size_tiny</item>
|
||||
<item name="android:textColor">@color/briar_text_primary_inverse</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider">
|
||||
<item name="android:background">@color/divider</item>
|
||||
</style>
|
||||
@@ -109,4 +122,9 @@
|
||||
<item name="tabTextColor">@color/briar_text_primary_inverse</item>
|
||||
</style>
|
||||
|
||||
<!-- This fixes the missing TextAppearance.Design.Counter.Overflow style -->
|
||||
<style name="BriarTextCounter.Overflow" parent="TextAppearance.Design.Counter">
|
||||
<item name="android:textColor">@color/briar_button_negative</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -42,6 +42,8 @@
|
||||
<item name="colorPrimary">@color/briar_primary</item>
|
||||
<item name="colorPrimaryDark">@color/briar_primary_dark</item>
|
||||
<item name="colorAccent">@color/briar_accent</item>
|
||||
<item name="buttonBarPositiveButtonStyle">@style/BriarButtonFlat.Positive</item>
|
||||
<item name="buttonBarNegativeButtonStyle">@style/BriarButtonFlat.Negative</item>
|
||||
<item name="android:textColorPrimary">@color/briar_text_primary</item>
|
||||
<item name="android:textColorPrimaryInverse">@color/briar_text_primary_inverse</item>
|
||||
<item name="android:textColorSecondary">@color/briar_text_secondary</item>
|
||||
@@ -49,6 +51,12 @@
|
||||
<item name="android:textColorTertiary">@color/briar_text_tertiary</item>
|
||||
<item name="android:textColorTertiaryInverse">@color/briar_text_tertiary_inverse</item>
|
||||
<item name="android:textColorLink">@color/briar_text_link</item>
|
||||
<item name="android:windowAnimationStyle">@style/DialogAnimation</item>
|
||||
</style>
|
||||
|
||||
<style name="DialogAnimation" parent="@android:style/Animation.Dialog">
|
||||
<item name="android:windowEnterAnimation">@android:anim/fade_in</item>
|
||||
<item name="android:windowExitAnimation">@android:anim/fade_out</item>
|
||||
</style>
|
||||
|
||||
<!-- This fixes a UI bug in the support preference library -->
|
||||
|
||||
@@ -2,7 +2,15 @@ package org.briarproject.android;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
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.WriteBlogPostActivity;
|
||||
import org.briarproject.android.contact.ContactListFragment;
|
||||
import org.briarproject.android.contact.ConversationActivity;
|
||||
import org.briarproject.android.forum.ForumInvitationsActivity;
|
||||
@@ -13,7 +21,6 @@ import org.briarproject.android.forum.ForumListFragment;
|
||||
import org.briarproject.android.forum.ForumSharingStatusActivity;
|
||||
import org.briarproject.android.forum.ShareForumActivity;
|
||||
import org.briarproject.android.forum.ShareForumMessageFragment;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.identity.CreateIdentityActivity;
|
||||
import org.briarproject.android.introduction.ContactChooserFragment;
|
||||
import org.briarproject.android.introduction.IntroductionActivity;
|
||||
@@ -64,6 +71,16 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(ForumActivity activity);
|
||||
|
||||
void inject(CreateBlogActivity activity);
|
||||
|
||||
void inject(BlogActivity activity);
|
||||
|
||||
void inject(WriteBlogPostActivity activity);
|
||||
|
||||
void inject(BlogFragment fragment);
|
||||
|
||||
void inject(BlogPostFragment fragment);
|
||||
|
||||
void inject(SettingsActivity activity);
|
||||
|
||||
void inject(ChangePasswordActivity activity);
|
||||
@@ -73,7 +90,9 @@ public interface ActivityComponent {
|
||||
// Fragments
|
||||
void inject(ContactListFragment fragment);
|
||||
void inject(ForumListFragment fragment);
|
||||
void inject(BaseFragment fragment);
|
||||
void inject(BlogsFragment fragment);
|
||||
void inject(BlogListFragment fragment);
|
||||
void inject(FeedFragment fragment);
|
||||
void inject(MyBlogsFragment fragment);
|
||||
void inject(ChooseIdentityFragment fragment);
|
||||
void inject(ShowQrCodeFragment fragment);
|
||||
|
||||
@@ -4,6 +4,10 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.briarproject.android.blogs.BlogController;
|
||||
import org.briarproject.android.blogs.BlogControllerImpl;
|
||||
import org.briarproject.android.blogs.FeedController;
|
||||
import org.briarproject.android.blogs.FeedControllerImpl;
|
||||
import org.briarproject.android.controller.BriarController;
|
||||
import org.briarproject.android.controller.BriarControllerImpl;
|
||||
import org.briarproject.android.controller.ConfigController;
|
||||
@@ -107,6 +111,20 @@ public class ActivityModule {
|
||||
return forumController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
BlogController provideBlogController(BlogControllerImpl blogController) {
|
||||
activity.addLifecycleController(blogController);
|
||||
return blogController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected FeedController provideFeedController(
|
||||
FeedControllerImpl feedController) {
|
||||
return feedController;
|
||||
}
|
||||
|
||||
@ActivityScope
|
||||
@Provides
|
||||
protected NavDrawerController provideNavDrawerController(
|
||||
|
||||
@@ -5,8 +5,11 @@ import org.briarproject.CoreModule;
|
||||
import org.briarproject.android.api.AndroidExecutor;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.api.ReferenceManager;
|
||||
import org.briarproject.android.blogs.BlogPersistentData;
|
||||
import org.briarproject.android.forum.ForumPersistentData;
|
||||
import org.briarproject.android.report.BriarReportSender;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPostFactory;
|
||||
import org.briarproject.api.contact.ContactExchangeTask;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
@@ -96,6 +99,10 @@ public interface AndroidComponent extends CoreEagerSingletons {
|
||||
|
||||
ForumPostFactory forumPostFactory();
|
||||
|
||||
BlogManager blogManager();
|
||||
|
||||
BlogPostFactory blogPostFactory();
|
||||
|
||||
SettingsManager settingsManager();
|
||||
|
||||
ContactExchangeTask contactExchangeTask();
|
||||
@@ -112,6 +119,8 @@ public interface AndroidComponent extends CoreEagerSingletons {
|
||||
|
||||
ForumPersistentData forumPersistentData();
|
||||
|
||||
BlogPersistentData blogPersistentData();
|
||||
|
||||
@IoExecutor
|
||||
Executor ioExecutor();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Application;
|
||||
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.api.ReferenceManager;
|
||||
import org.briarproject.android.blogs.BlogPersistentData;
|
||||
import org.briarproject.android.forum.ForumPersistentData;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
@@ -143,4 +144,10 @@ public class AppModule {
|
||||
ForumPersistentData provideForumPersistence() {
|
||||
return new ForumPersistentData();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
BlogPersistentData provideBlogPersistence() {
|
||||
return new BlogPersistentData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
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.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
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.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.android.fragment.BaseFragment.BaseFragmentListener;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
|
||||
public class BlogActivity extends BriarActivity implements BlogPostListener,
|
||||
OnBlogPostClickListener, BaseFragmentListener {
|
||||
|
||||
static final int REQUEST_WRITE_POST = 1;
|
||||
static final String BLOG_NAME = "briar.BLOG_NAME";
|
||||
static final String IS_MY_BLOG = "briar.IS_MY_BLOG";
|
||||
static final String IS_NEW_BLOG = "briar.IS_NEW_BLOG";
|
||||
|
||||
private static final String BLOG_PAGER_ADAPTER = "briar.BLOG_PAGER_ADAPTER";
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BlogActivity.class.getName());
|
||||
|
||||
private ProgressBar progressBar;
|
||||
private ViewPager pager;
|
||||
private BlogPagerAdapter blogPagerAdapter;
|
||||
private BlogPostPagerAdapter postPagerAdapter;
|
||||
private String blogName;
|
||||
private boolean myBlog, isNew;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
private volatile GroupId groupId = null;
|
||||
@Inject
|
||||
BlogController blogController;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
// GroupId from Intent
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No Group in intent.");
|
||||
groupId = new GroupId(b);
|
||||
|
||||
// Name of the Blog from Intent
|
||||
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);
|
||||
isNew = i.getBooleanExtra(IS_NEW_BLOG, false);
|
||||
|
||||
setContentView(R.layout.activity_blog);
|
||||
|
||||
pager = (ViewPager) findViewById(R.id.pager);
|
||||
progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
hideLoadingScreen();
|
||||
|
||||
blogPagerAdapter = new BlogPagerAdapter(getSupportFragmentManager());
|
||||
if (state == null || state.getBoolean(BLOG_PAGER_ADAPTER, true)) {
|
||||
pager.setAdapter(blogPagerAdapter);
|
||||
} else {
|
||||
// this initializes and restores the postPagerAdapter
|
||||
loadBlogPosts();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
// remember which adapter we had active
|
||||
outState.putBoolean(BLOG_PAGER_ADAPTER,
|
||||
pager.getAdapter() == blogPagerAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (pager.getAdapter() == postPagerAdapter) {
|
||||
pager.setAdapter(blogPagerAdapter);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLoadingScreen(boolean isBlocking, int stringId) {
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
private void showLoadingScreen() {
|
||||
showLoadingScreen(false, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLoadingScreen() {
|
||||
progressBar.setVisibility(GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFragmentCreated(String tag) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostClick(final int position) {
|
||||
loadBlogPosts(position, true);
|
||||
}
|
||||
|
||||
private void loadBlogPosts() {
|
||||
loadBlogPosts(0, false);
|
||||
}
|
||||
|
||||
private void loadBlogPosts(final int position, final boolean setItem) {
|
||||
showLoadingScreen();
|
||||
blogController
|
||||
.loadBlog(groupId, false, new UiResultHandler<Boolean>(this) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
Collection<BlogPostItem> posts =
|
||||
blogController.getBlogPosts();
|
||||
|
||||
if (postPagerAdapter == null) {
|
||||
postPagerAdapter = new BlogPostPagerAdapter(
|
||||
getSupportFragmentManager(),
|
||||
posts.size());
|
||||
} else {
|
||||
postPagerAdapter.setSize(posts.size());
|
||||
}
|
||||
pager.setAdapter(postPagerAdapter);
|
||||
if (setItem) pager.setCurrentItem(position);
|
||||
} else {
|
||||
Toast.makeText(BlogActivity.this,
|
||||
R.string.blogs_blog_post_failed_to_load,
|
||||
LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostAdded(final BlogPostItem post, final boolean local) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (blogPagerAdapter != null) {
|
||||
BlogFragment f = blogPagerAdapter.getFragment();
|
||||
if (f != null && f.isVisible()) {
|
||||
f.onBlogPostAdded(post, local);
|
||||
}
|
||||
}
|
||||
|
||||
if (postPagerAdapter != null) {
|
||||
postPagerAdapter.onBlogPostAdded();
|
||||
postPagerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
|
||||
// The BlogPostAddedEvent arrives when the controller is not listening,
|
||||
// so we need to manually reload the blog posts :(
|
||||
if (requestCode == REQUEST_WRITE_POST && resultCode == RESULT_OK) {
|
||||
BlogFragment f = blogPagerAdapter.getFragment();
|
||||
if (f != null && f.isVisible()) {
|
||||
f.reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class BlogPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private BlogFragment fragment = null;
|
||||
|
||||
BlogPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return BlogFragment.newInstance(groupId, blogName, myBlog, isNew);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object instantiateItem(ViewGroup container, int position) {
|
||||
// save a reference to the single fragment here for later
|
||||
fragment =
|
||||
(BlogFragment) super.instantiateItem(container, position);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
BlogFragment getFragment() {
|
||||
return fragment;
|
||||
}
|
||||
}
|
||||
|
||||
private class BlogPostPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private int size;
|
||||
|
||||
BlogPostPagerAdapter(FragmentManager fm, int size) {
|
||||
super(fm);
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
MessageId postIdOfPos = blogController.getBlogPostId(position);
|
||||
return BlogPostFragment.newInstance(groupId, postIdOfPos);
|
||||
}
|
||||
|
||||
void onBlogPostAdded() {
|
||||
size++;
|
||||
}
|
||||
|
||||
void setSize(int size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.TreeSet;
|
||||
|
||||
public interface BlogController extends ActivityLifecycleController {
|
||||
|
||||
void loadBlog(final GroupId groupId, final boolean reload,
|
||||
final UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
TreeSet<BlogPostItem> getBlogPosts();
|
||||
|
||||
@Nullable
|
||||
BlogPostItem getBlogPost(MessageId postId);
|
||||
|
||||
@Nullable
|
||||
MessageId getBlogPostId(int position);
|
||||
|
||||
void deleteBlog(final UiResultHandler<Boolean> resultHandler);
|
||||
|
||||
interface BlogPostListener {
|
||||
void onBlogPostAdded(final BlogPostItem post, final boolean local);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
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.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeSet;
|
||||
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 {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BlogControllerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
protected Activity activity;
|
||||
@Inject
|
||||
protected volatile BlogManager blogManager;
|
||||
@Inject
|
||||
protected volatile EventBus eventBus;
|
||||
@Inject
|
||||
protected BlogPersistentData data;
|
||||
|
||||
private volatile BlogPostListener listener;
|
||||
|
||||
@Inject
|
||||
BlogControllerImpl() {
|
||||
}
|
||||
|
||||
@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() {
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityPause() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityDestroy() {
|
||||
if (activity.isFinishing()) {
|
||||
data.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof BlogPostAddedEvent) {
|
||||
final BlogPostAddedEvent m = (BlogPostAddedEvent) e;
|
||||
if (m.getGroupId().equals(data.getGroupId())) {
|
||||
LOG.info("New blog post added");
|
||||
final BlogPostHeader header = m.getHeader();
|
||||
try {
|
||||
final byte[] body = blogManager.getPostBody(header.getId());
|
||||
final BlogPostItem post = new BlogPostItem(header, body);
|
||||
data.addPost(post);
|
||||
listener.onBlogPostAdded(post, m.isLocal());
|
||||
} catch (DbException ex) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
} else if (e instanceof GroupRemovedEvent) {
|
||||
GroupRemovedEvent s = (GroupRemovedEvent) e;
|
||||
if (s.getGroup().getId().equals(data.getGroupId())) {
|
||||
LOG.info("Blog removed");
|
||||
activity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
activity.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBlog(final GroupId groupId, final boolean reload,
|
||||
final UiResultHandler<Boolean> resultHandler) {
|
||||
|
||||
LOG.info("Loading blog...");
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (reload || data.getGroupId() == null ||
|
||||
!data.getGroupId().equals(groupId)) {
|
||||
data.setGroupId(groupId);
|
||||
// load blog posts
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<BlogPostItem> posts = new ArrayList<>();
|
||||
Collection<BlogPostHeader> header =
|
||||
blogManager.getPostHeaders(groupId);
|
||||
for (BlogPostHeader h : header) {
|
||||
byte[] body = blogManager.getPostBody(h.getId());
|
||||
posts.add(new BlogPostItem(h, body));
|
||||
}
|
||||
data.setPosts(posts);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Post header load took " + duration +
|
||||
" ms");
|
||||
}
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public TreeSet<BlogPostItem> getBlogPosts() {
|
||||
return data.getBlogPosts();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public BlogPostItem getBlogPost(MessageId id) {
|
||||
for (BlogPostItem item : getBlogPosts()) {
|
||||
if (item.getId().equals(id)) return item;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MessageId getBlogPostId(int position) {
|
||||
int i = 0;
|
||||
for (BlogPostItem post : getBlogPosts()) {
|
||||
if (i == position) return post.getId();
|
||||
i++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteBlog(final UiResultHandler<Boolean> resultHandler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (data.getGroupId() == null) {
|
||||
resultHandler.onResult(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Blog b = blogManager.getBlog(data.getGroupId());
|
||||
blogManager.removeBlog(b);
|
||||
resultHandler.onResult(true);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
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 android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.blogs.BlogController.BlogPostListener;
|
||||
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||
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_WRITE_POST;
|
||||
|
||||
public class BlogFragment extends BaseFragment implements BlogPostListener {
|
||||
|
||||
public final static String TAG = BlogFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
BlogController blogController;
|
||||
|
||||
private GroupId groupId;
|
||||
private String blogName;
|
||||
private boolean myBlog;
|
||||
private BlogPostAdapter adapter;
|
||||
private BriarRecyclerView list;
|
||||
|
||||
static BlogFragment newInstance(GroupId groupId, String name,
|
||||
boolean myBlog, 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);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
Bundle args = getArguments();
|
||||
byte[] b = args.getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No Group found.");
|
||||
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);
|
||||
|
||||
adapter = new BlogPostAdapter(getActivity(),
|
||||
(OnBlogPostClickListener) getActivity());
|
||||
list = (BriarRecyclerView) v.findViewById(R.id.postList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
if (myBlog) {
|
||||
list.setEmptyText(
|
||||
getString(R.string.blogs_my_blogs_blog_empty_state));
|
||||
} else {
|
||||
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
|
||||
}
|
||||
|
||||
// show snackbar if this blog was just created
|
||||
if (isNew) {
|
||||
Snackbar s = Snackbar.make(list, R.string.blogs_my_blogs_created,
|
||||
LENGTH_LONG);
|
||||
s.getView().setBackgroundResource(R.color.briar_primary);
|
||||
s.show();
|
||||
|
||||
// show only once
|
||||
args.putBoolean(IS_NEW_BLOG, false);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
loadData(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (myBlog) {
|
||||
inflater.inflate(R.menu.blogs_my_blog_actions, menu);
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
getActivity().onBackPressed();
|
||||
return true;
|
||||
case R.id.action_write_blog_post:
|
||||
Intent i =
|
||||
new Intent(getActivity(), WriteBlogPostActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
i.putExtra(BLOG_NAME, blogName);
|
||||
ActivityOptionsCompat options =
|
||||
makeCustomAnimation(getActivity(),
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right);
|
||||
ActivityCompat.startActivityForResult(getActivity(), i,
|
||||
REQUEST_WRITE_POST, options.toBundle());
|
||||
return true;
|
||||
case R.id.action_delete_blog:
|
||||
showDeleteDialog();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostAdded(BlogPostItem post, boolean local) {
|
||||
adapter.add(post);
|
||||
if (local) list.scrollToPosition(0);
|
||||
}
|
||||
|
||||
private void loadData(final boolean reload) {
|
||||
blogController.loadBlog(groupId, reload,
|
||||
new UiResultHandler<Boolean>(getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (result) {
|
||||
Collection<BlogPostItem> posts =
|
||||
blogController.getBlogPosts();
|
||||
if (posts.size() > 0) {
|
||||
adapter.addAll(posts);
|
||||
if (reload) list.scrollToPosition(0);
|
||||
} else {
|
||||
list.showData();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getActivity(),
|
||||
R.string.blogs_blog_failed_to_load,
|
||||
LENGTH_SHORT).show();
|
||||
getActivity().supportFinishAfterTransition();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void reload() {
|
||||
loadData(true);
|
||||
}
|
||||
|
||||
private void showDeleteDialog() {
|
||||
DialogInterface.OnClickListener okListener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
deleteBlog();
|
||||
}
|
||||
};
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.blogs_delete_blog));
|
||||
builder.setMessage(
|
||||
getString(R.string.blogs_delete_blog_dialog_message));
|
||||
builder.setPositiveButton(R.string.blogs_delete_blog_cancel, null);
|
||||
builder.setNegativeButton(R.string.blogs_delete_blog_ok, okListener);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void deleteBlog() {
|
||||
blogController.deleteBlog(
|
||||
new UiResultHandler<Boolean>(getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
if (!result) return;
|
||||
Toast.makeText(getActivity(),
|
||||
R.string.blogs_blog_deleted, LENGTH_SHORT)
|
||||
.show();
|
||||
getActivity().supportFinishAfterTransition();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
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;
|
||||
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;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.TextAvatarView;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
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> {
|
||||
|
||||
private SortedList<BlogListItem> blogs = new SortedList<>(
|
||||
BlogListItem.class, new SortedList.Callback<BlogListItem>() {
|
||||
|
||||
@Override
|
||||
public int compare(BlogListItem a, BlogListItem b) {
|
||||
if (a == b) return 0;
|
||||
// The blog with the newest message comes first
|
||||
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
|
||||
if (aTime > bTime) return -1;
|
||||
if (aTime < bTime) return 1;
|
||||
// Break ties by blog name
|
||||
String aName = a.getName();
|
||||
String bName = b.getName();
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
notifyItemRangeInserted(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
notifyItemRangeRemoved(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count) {
|
||||
notifyItemRangeChanged(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(BlogListItem a, BlogListItem b) {
|
||||
return a.getBlog().equals(b.getBlog()) &&
|
||||
a.getTimestamp() == b.getTimestamp() &&
|
||||
a.getUnreadCount() == b.getUnreadCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(BlogListItem a, BlogListItem b) {
|
||||
return a.getBlog().equals(b.getBlog());
|
||||
}
|
||||
});
|
||||
|
||||
private final Activity ctx;
|
||||
|
||||
BlogListAdapter(Activity ctx) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlogViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_blog, parent, false);
|
||||
return new BlogViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(BlogViewHolder ui, int position) {
|
||||
final BlogListItem item = getItem(position);
|
||||
|
||||
// Avatar
|
||||
ui.avatar.setText(item.getName().substring(0, 1));
|
||||
ui.avatar.setBackgroundBytes(item.getBlog().getId().getBytes());
|
||||
ui.avatar.setUnreadCount(item.getUnreadCount());
|
||||
|
||||
// Blog Name
|
||||
ui.name.setText(item.getName());
|
||||
|
||||
// Post Count
|
||||
int postCount = item.getPostCount();
|
||||
ui.postCount.setText(ctx.getResources()
|
||||
.getQuantityString(R.plurals.posts, postCount, postCount));
|
||||
ui.postCount.setTextColor(
|
||||
ContextCompat.getColor(ctx, R.color.briar_text_secondary));
|
||||
|
||||
// Date and Status
|
||||
if (item.isEmpty()) {
|
||||
ui.date.setVisibility(GONE);
|
||||
ui.avatar.setProblem(true);
|
||||
ui.status.setText(ctx.getString(R.string.blogs_blog_is_empty));
|
||||
ui.status.setVisibility(VISIBLE);
|
||||
} else {
|
||||
long timestamp = item.getTimestamp();
|
||||
ui.date.setText(
|
||||
DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
||||
ui.date.setVisibility(VISIBLE);
|
||||
ui.avatar.setProblem(false);
|
||||
ui.status.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// Open Blog on Click
|
||||
ui.layout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent i = new Intent(ctx, BlogActivity.class);
|
||||
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);
|
||||
ActivityCompat.startActivity(ctx, i, options.toBundle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return blogs.size();
|
||||
}
|
||||
|
||||
public BlogListItem getItem(int position) {
|
||||
return blogs.get(position);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlogListItem getItem(GroupId g) {
|
||||
for (int i = 0; i < blogs.size(); i++) {
|
||||
BlogListItem item = blogs.get(i);
|
||||
if (item.getBlog().getGroup().getId().equals(g)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addAll(Collection<BlogListItem> items) {
|
||||
blogs.addAll(items);
|
||||
}
|
||||
|
||||
void updateItem(BlogListItem item) {
|
||||
BlogListItem oldItem = getItem(item.getBlog().getGroup().getId());
|
||||
int position = blogs.indexOf(oldItem);
|
||||
blogs.updateItemAt(position, item);
|
||||
}
|
||||
|
||||
public void remove(BlogListItem item) {
|
||||
blogs.remove(item);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
blogs.clear();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return blogs.size() == 0;
|
||||
}
|
||||
|
||||
static class BlogViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ViewGroup layout;
|
||||
private final TextAvatarView avatar;
|
||||
private final TextView name;
|
||||
private final TextView postCount;
|
||||
private final TextView date;
|
||||
private final TextView status;
|
||||
|
||||
BlogViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
layout = (ViewGroup) v;
|
||||
avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
|
||||
name = (TextView) v.findViewById(R.id.nameView);
|
||||
postCount = (TextView) v.findViewById(R.id.postCountView);
|
||||
date = (TextView) v.findViewById(R.id.dateView);
|
||||
status = (TextView) v.findViewById(R.id.statusView);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
|
||||
public class BlogListFragment extends BaseFragment {
|
||||
|
||||
public final static String TAG = BlogListFragment.class.getName();
|
||||
|
||||
static BlogListFragment newInstance(int num) {
|
||||
BlogListFragment f = new BlogListFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("num", num);
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blogs_list, container,
|
||||
false);
|
||||
|
||||
TextView numView = (TextView) v.findViewById(R.id.num);
|
||||
String num = String.valueOf(getArguments().getInt("num"));
|
||||
numView.setText(num);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
class BlogListItem {
|
||||
|
||||
private final Blog blog;
|
||||
private final int postCount;
|
||||
private final long timestamp;
|
||||
private final int unread;
|
||||
private final boolean ours;
|
||||
|
||||
BlogListItem(Blog blog, Collection<BlogPostHeader> headers, boolean ours) {
|
||||
this.blog = blog;
|
||||
if (headers.isEmpty()) {
|
||||
postCount = 0;
|
||||
timestamp = 0;
|
||||
unread = 0;
|
||||
} else {
|
||||
BlogPostHeader newest = null;
|
||||
long timestamp = -1;
|
||||
int unread = 0;
|
||||
for (BlogPostHeader h : headers) {
|
||||
if (h.getTimestamp() > timestamp) {
|
||||
timestamp = h.getTimestamp();
|
||||
newest = h;
|
||||
}
|
||||
if (!h.isRead()) unread++;
|
||||
}
|
||||
this.postCount = headers.size();
|
||||
this.timestamp = newest.getTimestamp();
|
||||
this.unread = unread;
|
||||
}
|
||||
this.ours = ours;
|
||||
}
|
||||
|
||||
Blog getBlog() {
|
||||
return blog;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return blog.getName();
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return postCount == 0;
|
||||
}
|
||||
|
||||
int getPostCount() {
|
||||
return postCount;
|
||||
}
|
||||
|
||||
long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
int getUnreadCount() {
|
||||
return unread;
|
||||
}
|
||||
|
||||
boolean isOurs() {
|
||||
return ours;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* This class is a singleton that defines the data that should persist, i.e.
|
||||
* still be present in memory after activity restarts. This class is not thread
|
||||
* safe.
|
||||
*/
|
||||
public class BlogPersistentData {
|
||||
|
||||
private volatile GroupId groupId;
|
||||
private volatile TreeSet<BlogPostItem> posts = new TreeSet<>();
|
||||
|
||||
public BlogPersistentData() {
|
||||
|
||||
}
|
||||
|
||||
public void setGroupId(GroupId groupId) {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public void setPosts(Collection<BlogPostItem> posts) {
|
||||
this.posts.clear();
|
||||
this.posts.addAll(posts);
|
||||
}
|
||||
|
||||
void addPost(BlogPostItem post) {
|
||||
posts.add(post);
|
||||
}
|
||||
|
||||
TreeSet<BlogPostItem> getBlogPosts() {
|
||||
return posts;
|
||||
}
|
||||
|
||||
void clearAll() {
|
||||
groupId = null;
|
||||
posts.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.content.Context;
|
||||
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;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
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> {
|
||||
|
||||
private SortedList<BlogPostItem> posts = new SortedList<>(
|
||||
BlogPostItem.class, new SortedList.Callback<BlogPostItem>() {
|
||||
|
||||
@Override
|
||||
public int compare(BlogPostItem a, BlogPostItem b) {
|
||||
return a.compareTo(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInserted(int position, int count) {
|
||||
notifyItemRangeInserted(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoved(int position, int count) {
|
||||
notifyItemRangeRemoved(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMoved(int fromPosition, int toPosition) {
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(int position, int count) {
|
||||
notifyItemRangeChanged(position, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) {
|
||||
return a.isRead() == b.isRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) {
|
||||
return a.getId().equals(b.getId());
|
||||
}
|
||||
});
|
||||
|
||||
private final Context ctx;
|
||||
private final OnBlogPostClickListener listener;
|
||||
|
||||
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener) {
|
||||
this.ctx = ctx;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlogPostHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(ctx).inflate(
|
||||
R.layout.list_item_blog_post, parent, false);
|
||||
return new BlogPostHolder(v);
|
||||
}
|
||||
|
||||
@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(
|
||||
DateUtils.getRelativeTimeSpanString(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(ui.getAdapterPosition());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return posts.size();
|
||||
}
|
||||
|
||||
public BlogPostItem getItem(int position) {
|
||||
return posts.get(position);
|
||||
}
|
||||
|
||||
public void add(BlogPostItem item) {
|
||||
posts.add(item);
|
||||
}
|
||||
|
||||
public void addAll(Collection<BlogPostItem> items) {
|
||||
posts.addAll(items);
|
||||
}
|
||||
|
||||
public void remove(BlogPostItem item) {
|
||||
posts.remove(item);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
posts.clear();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
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 unread;
|
||||
private final ImageView chat;
|
||||
private final ImageView comment;
|
||||
private final TextView title;
|
||||
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);
|
||||
unread = (TextView) v.findViewById(R.id.newView);
|
||||
chat = (ImageView) v.findViewById(R.id.chatView);
|
||||
comment = (ImageView) v.findViewById(R.id.commentView);
|
||||
title = (TextView) v.findViewById(R.id.titleView);
|
||||
body = (TextView) v.findViewById(R.id.bodyView);
|
||||
}
|
||||
}
|
||||
|
||||
interface OnBlogPostClickListener {
|
||||
void onBlogPostClick(int position);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.util.TrustIndicatorView;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
|
||||
public class BlogPostFragment extends BaseFragment {
|
||||
|
||||
public final static String TAG = BlogPostFragment.class.getName();
|
||||
|
||||
private final static String BLOG_POST_ID = "briar.BLOG_NAME";
|
||||
|
||||
private GroupId groupId;
|
||||
private MessageId postId;
|
||||
private BlogPostViewHolder ui;
|
||||
|
||||
@Inject
|
||||
BlogController blogController;
|
||||
|
||||
static BlogPostFragment newInstance(GroupId groupId, MessageId postId) {
|
||||
BlogPostFragment f = new BlogPostFragment();
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putByteArray(GROUP_ID, groupId.getBytes());
|
||||
bundle.putByteArray(BLOG_POST_ID, postId.getBytes());
|
||||
|
||||
f.setArguments(bundle);
|
||||
return f;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
byte[] b = getArguments().getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No Group found.");
|
||||
groupId = new GroupId(b);
|
||||
byte[] p = getArguments().getByteArray(BLOG_POST_ID);
|
||||
if (p == null) throw new IllegalStateException("No MessageId found.");
|
||||
postId = new MessageId(p);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog_post, container,
|
||||
false);
|
||||
ui = new BlogPostViewHolder(v);
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
blogController.loadBlog(groupId, false,
|
||||
new UiResultHandler<Boolean>((Activity) listener) {
|
||||
@Override
|
||||
public void onResultUi(Boolean result) {
|
||||
listener.hideLoadingScreen();
|
||||
if (result) {
|
||||
BlogPostItem post =
|
||||
blogController.getBlogPost(postId);
|
||||
if (post != null) {
|
||||
bind(post);
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getActivity(),
|
||||
R.string.blogs_blog_post_failed_to_load,
|
||||
LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
getActivity().onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void bind(BlogPostItem post) {
|
||||
Author author = post.getAuthor();
|
||||
IdenticonDrawable d = new IdenticonDrawable(author.getId().getBytes());
|
||||
ui.avatar.setImageDrawable(d);
|
||||
ui.authorName.setText(author.getName());
|
||||
ui.trust.setTrustLevel(post.getAuthorStatus());
|
||||
ui.date.setText(
|
||||
DateUtils.getRelativeTimeSpanString(post.getTimestamp()));
|
||||
|
||||
if (post.getTitle() != null) {
|
||||
ui.title.setText(post.getTitle());
|
||||
} else {
|
||||
ui.title.setVisibility(GONE);
|
||||
}
|
||||
|
||||
ui.body.setText(StringUtils.fromUtf8(post.getBody()));
|
||||
}
|
||||
|
||||
private static class BlogPostViewHolder {
|
||||
private ImageView avatar;
|
||||
private TextView authorName;
|
||||
private TrustIndicatorView trust;
|
||||
private TextView date;
|
||||
private TextView title;
|
||||
private TextView body;
|
||||
|
||||
BlogPostViewHolder(View v) {
|
||||
avatar = (ImageView) v.findViewById(R.id.avatar);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.briarproject.api.blogs.BlogPostHeader;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.identity.Author.Status;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
// This class is not thread-safe
|
||||
class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
|
||||
private final BlogPostHeader header;
|
||||
private final byte[] body;
|
||||
private boolean read;
|
||||
|
||||
BlogPostItem(BlogPostHeader header, byte[] body) {
|
||||
this.header = header;
|
||||
this.body = body;
|
||||
read = header.isRead();
|
||||
}
|
||||
|
||||
public MessageId getId() {
|
||||
return header.getId();
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return header.getTitle();
|
||||
}
|
||||
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return header.getTimestamp();
|
||||
}
|
||||
|
||||
public long getTimeReceived() {
|
||||
return header.getTimeReceived();
|
||||
}
|
||||
|
||||
public Author getAuthor() {
|
||||
return header.getAuthor();
|
||||
}
|
||||
|
||||
Status getAuthorStatus() {
|
||||
return header.getAuthorStatus();
|
||||
}
|
||||
|
||||
public void setRead(boolean read) {
|
||||
this.read = read;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NonNull BlogPostItem other) {
|
||||
if (this == other) return 0;
|
||||
// The blog with the newest message comes first
|
||||
long aTime = getTimeReceived(), bTime = other.getTimeReceived();
|
||||
if (aTime > bTime) return -1;
|
||||
if (aTime < bTime) return 1;
|
||||
// Break ties by post title
|
||||
if (getTitle() != null && other.getTitle() != null) {
|
||||
return String.CASE_INSENSITIVE_ORDER
|
||||
.compare(getTitle(), other.getTitle());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ 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();
|
||||
@@ -54,6 +56,8 @@ public class BlogsFragment extends BaseFragment {
|
||||
viewPager.setAdapter(tabAdapter);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
tabLayout.setVisibility(GONE);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
int position = savedInstanceState.getInt(SELECTED_TAB, 0);
|
||||
viewPager.setCurrentItem(position);
|
||||
@@ -88,16 +92,21 @@ public class BlogsFragment extends BaseFragment {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return titles.length;
|
||||
return 1;
|
||||
// return titles.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position) {
|
||||
// TODO add your fragments here
|
||||
default:
|
||||
return MyBlogsFragment.newInstance(position);
|
||||
}
|
||||
return FeedFragment.newInstance();
|
||||
// switch (position) {
|
||||
// case 0:
|
||||
// return FeedFragment.newInstance();
|
||||
// case 1:
|
||||
// return new MyBlogsFragment();
|
||||
// default:
|
||||
// return BlogListFragment.newInstance(position);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.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;
|
||||
|
||||
public class CreateBlogActivity extends BriarActivity
|
||||
implements OnEditorActionListener, OnClickListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CreateBlogActivity.class.getName());
|
||||
|
||||
private TextInputEditText titleInput, descInput;
|
||||
private Button button;
|
||||
private ProgressBar progress;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
volatile BlogManager blogManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(R.layout.activity_create_blog);
|
||||
|
||||
TextInputLayout titleLayout =
|
||||
(TextInputLayout) findViewById(R.id.titleLayout);
|
||||
if (titleLayout != null) {
|
||||
titleLayout.setCounterMaxLength(MAX_BLOG_TITLE_LENGTH);
|
||||
}
|
||||
titleInput = (TextInputEditText) findViewById(R.id.titleInput);
|
||||
TextWatcher nameEntryWatcher = new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence text, int start,
|
||||
int lengthBefore, int lengthAfter) {
|
||||
enableOrDisableCreateButton();
|
||||
}
|
||||
};
|
||||
titleInput.setOnEditorActionListener(this);
|
||||
titleInput.addTextChangedListener(nameEntryWatcher);
|
||||
|
||||
TextInputLayout descLayout =
|
||||
(TextInputLayout) findViewById(R.id.descLayout);
|
||||
if (descLayout != null) {
|
||||
descLayout.setCounterMaxLength(MAX_BLOG_DESC_LENGTH);
|
||||
}
|
||||
descInput = (TextInputEditText) findViewById(R.id.descInput);
|
||||
if (descInput != null) {
|
||||
descInput.addTextChangedListener(nameEntryWatcher);
|
||||
}
|
||||
|
||||
button = (Button) findViewById(R.id.createBlogButton);
|
||||
if (button != null) {
|
||||
button.setOnClickListener(this);
|
||||
}
|
||||
|
||||
progress = (ProgressBar) findViewById(R.id.createBlogProgressBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void enableOrDisableCreateButton() {
|
||||
if (progress == null) return; // Not created yet
|
||||
button.setEnabled(validateTitle() && validateDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
|
||||
descInput.requestFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean validateTitle() {
|
||||
String name = titleInput.getText().toString();
|
||||
int length = StringUtils.toUtf8(name).length;
|
||||
return length <= MAX_BLOG_TITLE_LENGTH && length > 0;
|
||||
}
|
||||
|
||||
private boolean validateDescription() {
|
||||
String name = descInput.getText().toString();
|
||||
int length = StringUtils.toUtf8(name).length;
|
||||
return length <= MAX_BLOG_DESC_LENGTH && length > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == button) {
|
||||
hideSoftKeyboard(view);
|
||||
if (!validateTitle()) return;
|
||||
button.setVisibility(GONE);
|
||||
progress.setVisibility(VISIBLE);
|
||||
addBlog(titleInput.getText().toString(),
|
||||
descInput.getText().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void addBlog(final String title, final String description) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<LocalAuthor> authors =
|
||||
identityManager.getLocalAuthors();
|
||||
// take first identity, don't support more for now
|
||||
LocalAuthor author = authors.iterator().next();
|
||||
Blog f = blogManager.addBlog(author, title, description);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Storing blog took " + duration + " ms");
|
||||
displayBlog(f);
|
||||
} catch (DbException e) {
|
||||
// TODO show error, e.g. blog with same title exists
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
finishOnUiThread();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayBlog(final Blog b) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Intent i =
|
||||
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,
|
||||
android.R.anim.fade_in,
|
||||
android.R.anim.fade_out);
|
||||
ActivityCompat.startActivity(CreateBlogActivity.this, i,
|
||||
options.toBundle());
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import org.briarproject.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
import org.briarproject.api.blogs.Blog;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface FeedController {
|
||||
|
||||
void onResume();
|
||||
void onPause();
|
||||
|
||||
void loadPosts(
|
||||
final UiResultHandler<Collection<BlogPostItem>> resultHandler);
|
||||
|
||||
void loadPersonalBlog(final UiResultHandler<Blog> resultHandler);
|
||||
|
||||
void setOnBlogPostAddedListener(OnBlogPostAddedListener listener);
|
||||
|
||||
interface OnBlogPostAddedListener {
|
||||
void onBlogPostAdded(final BlogPostItem post);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import org.briarproject.android.controller.DbControllerImpl;
|
||||
import org.briarproject.android.controller.handler.UiResultHandler;
|
||||
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.identity.Author;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
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 FeedControllerImpl extends DbControllerImpl
|
||||
implements FeedController, EventListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(FeedControllerImpl.class.getName());
|
||||
|
||||
@Inject
|
||||
protected volatile BlogManager blogManager;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
protected volatile EventBus eventBus;
|
||||
|
||||
private volatile OnBlogPostAddedListener listener;
|
||||
|
||||
@Inject
|
||||
FeedControllerImpl() {
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
public void onPause() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (!(e instanceof BlogPostAddedEvent)) return;
|
||||
|
||||
LOG.info("New blog post added");
|
||||
if (listener != null) {
|
||||
final BlogPostAddedEvent m = (BlogPostAddedEvent) e;
|
||||
final BlogPostHeader header = m.getHeader();
|
||||
try {
|
||||
final byte[] body = blogManager.getPostBody(header.getId());
|
||||
final BlogPostItem post = new BlogPostItem(header, body);
|
||||
listener.onBlogPostAdded(post);
|
||||
} catch (DbException ex) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, ex.toString(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPosts(
|
||||
final UiResultHandler<Collection<BlogPostItem>> resultHandler) {
|
||||
|
||||
LOG.info("Loading blog posts...");
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Collection<BlogPostItem> posts = new ArrayList<>();
|
||||
try {
|
||||
// load blog posts
|
||||
long now = System.currentTimeMillis();
|
||||
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(h, body));
|
||||
}
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading posts took " + duration + " ms");
|
||||
resultHandler.onResult(posts);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPersonalBlog(final UiResultHandler<Blog> resultHandler) {
|
||||
LOG.info("Loading personal blog...");
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// load blog posts
|
||||
long now = System.currentTimeMillis();
|
||||
Author a =
|
||||
identityManager.getLocalAuthors().iterator().next();
|
||||
Blog b = blogManager.getPersonalBlog(a);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading pers. blog took " + duration + " ms");
|
||||
resultHandler.onResult(b);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
resultHandler.onResult(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
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.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
|
||||
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 java.util.Collection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
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.REQUEST_WRITE_POST;
|
||||
|
||||
public class FeedFragment extends BaseFragment implements
|
||||
OnBlogPostClickListener, FeedController.OnBlogPostAddedListener {
|
||||
|
||||
public final static String TAG = FeedFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
FeedController feedController;
|
||||
|
||||
private BlogPostAdapter adapter;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private BriarRecyclerView list;
|
||||
private Blog personalBlog = null;
|
||||
|
||||
static FeedFragment newInstance() {
|
||||
FeedFragment f = new FeedFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
adapter = new BlogPostAdapter(getActivity(), this);
|
||||
|
||||
layoutManager = new LinearLayoutManager(getActivity());
|
||||
list = (BriarRecyclerView) v.findViewById(R.id.postList);
|
||||
list.setLayoutManager(layoutManager);
|
||||
list.setAdapter(adapter);
|
||||
list.setEmptyText(R.string.blogs_feed_empty_state);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
feedController.setOnBlogPostAddedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
// The BlogPostAddedEvent arrives when the controller is not listening
|
||||
if (requestCode == REQUEST_WRITE_POST && resultCode == RESULT_OK) {
|
||||
showSnackBar(R.string.blogs_blog_post_created);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
feedController
|
||||
.loadPersonalBlog(new UiResultHandler<Blog>(getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(Blog b) {
|
||||
personalBlog = b;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
feedController.onResume();
|
||||
feedController.loadPosts(
|
||||
new UiResultHandler<Collection<BlogPostItem>>(getActivity()) {
|
||||
@Override
|
||||
public void onResultUi(Collection<BlogPostItem> posts) {
|
||||
if (posts == null) {
|
||||
// TODO show error?
|
||||
} else if (posts.isEmpty()) {
|
||||
list.showData();
|
||||
} else {
|
||||
adapter.addAll(posts);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
feedController.onPause();
|
||||
// TODO save list position in database/preferences?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.blogs_feed_actions, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_write_blog_post:
|
||||
if (personalBlog == null) return false;
|
||||
Intent i =
|
||||
new Intent(getActivity(), WriteBlogPostActivity.class);
|
||||
i.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
||||
i.putExtra(BLOG_NAME, personalBlog.getName());
|
||||
ActivityOptionsCompat options =
|
||||
makeCustomAnimation(getActivity(),
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right);
|
||||
startActivityForResult(i, REQUEST_WRITE_POST,
|
||||
options.toBundle());
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostAdded(final BlogPostItem post) {
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.add(post);
|
||||
showSnackBar(R.string.blogs_blog_post_received);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlogPostClick(int position) {
|
||||
// noop
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void showSnackBar(int stringRes) {
|
||||
int firstVisible =
|
||||
layoutManager.findFirstCompletelyVisibleItemPosition();
|
||||
int lastVisible = layoutManager.findLastCompletelyVisibleItemPosition();
|
||||
int count = adapter.getItemCount();
|
||||
boolean scroll = count > (lastVisible - firstVisible + 1);
|
||||
|
||||
Snackbar s = Snackbar.make(list, stringRes, LENGTH_LONG);
|
||||
s.getView().setBackgroundResource(R.color.briar_primary);
|
||||
if (scroll) {
|
||||
OnClickListener onClick = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
list.smoothScrollToPosition(0);
|
||||
}
|
||||
};
|
||||
s.setActionTextColor(ContextCompat
|
||||
.getColor(getContext(),
|
||||
R.color.briar_button_positive));
|
||||
s.setAction(R.string.blogs_blog_post_scroll_to, onClick);
|
||||
}
|
||||
s.show();
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,113 @@
|
||||
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 android.widget.TextView;
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
static MyBlogsFragment newInstance(int num) {
|
||||
MyBlogsFragment f = new MyBlogsFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("num", num);
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blogs_my, container,
|
||||
false);
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
TextView numView = (TextView) v.findViewById(R.id.num);
|
||||
String num = String.valueOf(getArguments().getInt("num"));
|
||||
numView.setText(num);
|
||||
adapter = new BlogListAdapter(getActivity());
|
||||
|
||||
return v;
|
||||
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();
|
||||
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
|
||||
@@ -56,4 +120,49 @@ public class MyBlogsFragment extends BaseFragment {
|
||||
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,200 @@
|
||||
package org.briarproject.android.blogs;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.blogs.BlogManager;
|
||||
import org.briarproject.api.blogs.BlogPost;
|
||||
import org.briarproject.api.blogs.BlogPostFactory;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
|
||||
import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
|
||||
|
||||
public class WriteBlogPostActivity extends BriarActivity
|
||||
implements OnEditorActionListener {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(WriteBlogPostActivity.class.getName());
|
||||
private static final String contentType = "text/plain";
|
||||
|
||||
private TextInputEditText titleInput;
|
||||
private EditText bodyInput;
|
||||
private Button publishButton;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
private volatile GroupId groupId;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
volatile BlogPostFactory blogPostFactory;
|
||||
@Inject
|
||||
volatile BlogManager blogManager;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No Group in intent.");
|
||||
groupId = new GroupId(b);
|
||||
// String blogName = i.getStringExtra(BLOG_NAME);
|
||||
// if (blogName != null) setTitle(blogName);
|
||||
|
||||
setContentView(R.layout.activity_write_blog_post);
|
||||
// String title =
|
||||
// getTitle() + ": " + getString(R.string.blogs_write_blog_post);
|
||||
// setTitle(title);
|
||||
|
||||
TextInputLayout titleLayout =
|
||||
(TextInputLayout) findViewById(R.id.titleLayout);
|
||||
if (titleLayout != null) {
|
||||
titleLayout.setCounterMaxLength(MAX_BLOG_POST_TITLE_LENGTH);
|
||||
}
|
||||
titleInput = (TextInputEditText) findViewById(R.id.titleInput);
|
||||
if (titleInput != null) {
|
||||
titleInput.setOnEditorActionListener(this);
|
||||
}
|
||||
|
||||
bodyInput = (EditText) findViewById(R.id.bodyInput);
|
||||
bodyInput.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
enableOrDisablePublishButton();
|
||||
}
|
||||
});
|
||||
|
||||
publishButton = (Button) findViewById(R.id.publishButton);
|
||||
publishButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
publish();
|
||||
}
|
||||
});
|
||||
|
||||
progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
|
||||
bodyInput.requestFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void enableOrDisablePublishButton() {
|
||||
int bodyLength =
|
||||
StringUtils.toUtf8(bodyInput.getText().toString()).length;
|
||||
if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH &&
|
||||
titleInput.getText().length() <= MAX_BLOG_POST_TITLE_LENGTH)
|
||||
publishButton.setEnabled(true);
|
||||
else
|
||||
publishButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void publish() {
|
||||
// title
|
||||
String title = titleInput.getText().toString();
|
||||
if (title.length() > MAX_BLOG_POST_TITLE_LENGTH) return;
|
||||
if (title.length() == 0) title = null;
|
||||
|
||||
// body
|
||||
byte[] body = StringUtils.toUtf8(bodyInput.getText().toString());
|
||||
|
||||
// hide publish button, show progress bar
|
||||
publishButton.setVisibility(GONE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
|
||||
storePost(title, body);
|
||||
}
|
||||
|
||||
private void storePost(final String title, final byte[] body) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
long now = System.currentTimeMillis();
|
||||
try {
|
||||
Collection<LocalAuthor> authors =
|
||||
identityManager.getLocalAuthors();
|
||||
LocalAuthor author = authors.iterator().next();
|
||||
BlogPost p = blogPostFactory
|
||||
.createBlogPost(groupId, title, now, null, author,
|
||||
contentType, body);
|
||||
blogManager.addLocalPost(p);
|
||||
postPublished();
|
||||
} catch (DbException | GeneralSecurityException | FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
postFailedToPublish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void postPublished() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setResult(RESULT_OK);
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void postFailedToPublish() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// hide progress bar, show publish button
|
||||
progressBar.setVisibility(GONE);
|
||||
publishButton.setVisibility(VISIBLE);
|
||||
// TODO show error
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,16 +104,17 @@ class ForumListAdapter extends
|
||||
// Post Count
|
||||
int postCount = item.getPostCount();
|
||||
if (postCount > 0) {
|
||||
ui.unread.setText(ctx.getResources()
|
||||
.getQuantityString(R.plurals.forum_posts, postCount,
|
||||
ui.avatar.setProblem(false);
|
||||
ui.postCount.setText(ctx.getResources()
|
||||
.getQuantityString(R.plurals.posts, postCount,
|
||||
postCount));
|
||||
ui.unread.setTextColor(
|
||||
ui.postCount.setTextColor(
|
||||
ContextCompat
|
||||
.getColor(ctx, R.color.briar_text_secondary));
|
||||
} else {
|
||||
ui.avatar.setProblem(true);
|
||||
ui.unread.setText(ctx.getString(R.string.forum_no_posts));
|
||||
ui.unread.setTextColor(
|
||||
ui.postCount.setText(ctx.getString(R.string.no_posts));
|
||||
ui.postCount.setTextColor(
|
||||
ContextCompat
|
||||
.getColor(ctx, R.color.briar_text_tertiary));
|
||||
}
|
||||
@@ -187,7 +188,7 @@ class ForumListAdapter extends
|
||||
private final ViewGroup layout;
|
||||
private final TextAvatarView avatar;
|
||||
private final TextView name;
|
||||
private final TextView unread;
|
||||
private final TextView postCount;
|
||||
private final TextView date;
|
||||
|
||||
ForumViewHolder(View v) {
|
||||
@@ -196,7 +197,7 @@ class ForumListAdapter extends
|
||||
layout = (ViewGroup) v;
|
||||
avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
|
||||
name = (TextView) v.findViewById(R.id.forumNameView);
|
||||
unread = (TextView) v.findViewById(R.id.unreadView);
|
||||
postCount = (TextView) v.findViewById(R.id.postCountView);
|
||||
date = (TextView) v.findViewById(R.id.dateView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ public class AndroidUtils {
|
||||
til.setError(null);
|
||||
}
|
||||
|
||||
public static void setError(TextInputLayout til, int res,
|
||||
boolean condition) {
|
||||
setError(til, til.getContext().getString(res), condition);
|
||||
}
|
||||
|
||||
public static String getBluetoothAddress(Context ctx,
|
||||
BluetoothAdapter adapter) {
|
||||
// Return the adapter's address if it's valid and not fake
|
||||
|
||||
@@ -130,6 +130,11 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
emptyView.setText(text);
|
||||
}
|
||||
|
||||
public void setEmptyText(int res) {
|
||||
if (recyclerView == null) initViews();
|
||||
emptyView.setText(res);
|
||||
}
|
||||
|
||||
public void showProgressBar() {
|
||||
if (recyclerView == null) initViews();
|
||||
recyclerView.setVisibility(INVISIBLE);
|
||||
@@ -158,6 +163,11 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
recyclerView.scrollToPosition(position);
|
||||
}
|
||||
|
||||
public void smoothScrollToPosition(int position) {
|
||||
if (recyclerView == null) initViews();
|
||||
recyclerView.smoothScrollToPosition(position);
|
||||
}
|
||||
|
||||
public RecyclerView getRecyclerView() {
|
||||
return this.recyclerView;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class TextAvatarView extends FrameLayout {
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
character.setText(text);
|
||||
character.setText(text.toUpperCase());
|
||||
}
|
||||
|
||||
public void setUnreadCount(int count) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import android.widget.ImageView;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.identity.Author.Status;
|
||||
|
||||
import static org.briarproject.api.identity.Author.Status.OURSELVES;
|
||||
|
||||
public class TrustIndicatorView extends ImageView {
|
||||
|
||||
public TrustIndicatorView(Context context) {
|
||||
@@ -24,6 +26,11 @@ public class TrustIndicatorView extends ImageView {
|
||||
}
|
||||
|
||||
public void setTrustLevel(Status status) {
|
||||
if (status == OURSELVES) {
|
||||
setVisibility(GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
int res;
|
||||
switch (status) {
|
||||
case ANONYMOUS:
|
||||
@@ -39,6 +46,7 @@ public class TrustIndicatorView extends ImageView {
|
||||
res = R.drawable.trust_indicator_unknown;
|
||||
}
|
||||
setImageDrawable(ContextCompat.getDrawable(getContext(), res));
|
||||
setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.io.UnsupportedEncodingException;
|
||||
/** A pseudonym for a user. */
|
||||
public class Author {
|
||||
|
||||
public enum Status { ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED }
|
||||
public enum Status { ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES }
|
||||
|
||||
private final AuthorId id;
|
||||
private final String name;
|
||||
|
||||
@@ -17,6 +17,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.api.identity.Author.Status.OURSELVES;
|
||||
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
|
||||
import static org.briarproject.api.identity.Author.Status.VERIFIED;
|
||||
|
||||
@@ -110,7 +111,7 @@ class IdentityManagerImpl implements IdentityManager {
|
||||
throws DbException {
|
||||
// Compare to the IDs of the user's identities
|
||||
for (LocalAuthor a : db.getLocalAuthors(txn))
|
||||
if (a.getId().equals(authorId)) return VERIFIED;
|
||||
if (a.getId().equals(authorId)) return OURSELVES;
|
||||
// Compare to the IDs of contacts' identities
|
||||
for (Contact c : db.getContacts(txn))
|
||||
if (c.getAuthor().getId().equals(authorId)) return VERIFIED;
|
||||
|
||||
Reference in New Issue
Block a user