UI for introducing two contacts to each other
Show system notification for successful introductions
@@ -90,6 +90,7 @@
|
||||
<activity
|
||||
android:name=".android.contact.ConversationActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/BriarThemeNoActionBar.Default"
|
||||
android:parentActivityName=".android.NavDrawerActivity"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
<meta-data
|
||||
@@ -174,7 +175,16 @@
|
||||
android:parentActivityName=".android.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.NavDrawerActivity"
|
||||
android:value=".android.NavDrawerActivity"/>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".android.introduction.IntroductionActivity"
|
||||
android:label="@string/introduction_activity_title"
|
||||
android:parentActivityName=".android.contact.ConversationActivity"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.contact.ConversationActivity"
|
||||
/>
|
||||
</activity>
|
||||
<activity
|
||||
|
||||
|
Before Width: | Height: | Size: 957 B After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 28 KiB |
BIN
briar-android/res/drawable-hdpi/notice_in.9.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
briar-android/res/drawable-hdpi/notice_out.9.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 669 B After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 27 KiB |
BIN
briar-android/res/drawable-mdpi/notice_in.9.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
briar-android/res/drawable-mdpi/notice_out.9.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
16
briar-android/res/drawable-v21/round_button.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A FAB does not work, because even with fabSize="mini" it will be too big due to shadow drawing
|
||||
on lower API levels
|
||||
-->
|
||||
<ripple
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/briar_primary_dark">
|
||||
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/briar_primary"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</ripple>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 20 KiB |
BIN
briar-android/res/drawable-xhdpi/notice_in.9.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
briar-android/res/drawable-xhdpi/notice_out.9.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 31 KiB |
BIN
briar-android/res/drawable-xxhdpi/notice_in.9.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
briar-android/res/drawable-xxhdpi/notice_out.9.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
21
briar-android/res/drawable/contact_offline.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#2D3E50"
|
||||
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
|
||||
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
|
||||
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295
|
||||
C20.0698,10.7495,20.1616,12.4612,19.777,13.9758
|
||||
C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771
|
||||
C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1"/>
|
||||
|
||||
</vector>
|
||||
21
briar-android/res/drawable/contact_online.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#95D220"
|
||||
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
|
||||
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
|
||||
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295
|
||||
C20.0698,10.7495,20.1616,12.4612,19.777,13.9758
|
||||
C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771
|
||||
C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.5"/>
|
||||
|
||||
</vector>
|
||||
5
briar-android/res/drawable/ic_contact_introduction.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:alpha="0.56" android:height="48dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/>
|
||||
</vector>
|
||||
9
briar-android/res/drawable/introduction_notification.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M21,8V7l-3,2 -3,-2v1l3,2 3,-2zm1,-5H2C0.9,3 0,3.9 0,5v14c0,1.1 0.9,2 2,2h20c1.1,0 1.99,-0.9 1.99,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM8,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H2v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1zm8,-6h-8V6h8v6z"/>
|
||||
</vector>
|
||||
9
briar-android/res/drawable/introduction_white.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M21,8V7l-3,2 -3,-2v1l3,2 3,-2zm1,-5H2C0.9,3 0,3.9 0,5v14c0,1.1 0.9,2 2,2h20c1.1,0 1.99,-0.9 1.99,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM8,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H2v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1zm8,-6h-8V6h8v6z"/>
|
||||
</vector>
|
||||
5
briar-android/res/drawable/message_delivered_white.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="16dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFFFF" android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zm4.24,-1.41L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
|
||||
</vector>
|
||||
5
briar-android/res/drawable/message_sent_white.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="16dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFFFF" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
||||
5
briar-android/res/drawable/message_stored_white.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<vector android:height="16dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillAlpha=".9" android:fillColor="#FFFFFF" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/>
|
||||
</vector>
|
||||
13
briar-android/res/drawable/round_button.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A FAB does not work, because even with fabSize="mini" it will be too big due to shadow drawing
|
||||
on lower API levels
|
||||
-->
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="@color/briar_primary"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
9
briar-android/res/drawable/social_send_now_white.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
|
||||
</vector>
|
||||
@@ -7,7 +7,6 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.briarproject.android.util.BriarRecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/contactList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
@@ -1,46 +1,79 @@
|
||||
<?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:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".android.contact.ConversationActivity">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/BriarToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/contact_avatar_status"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contactName"
|
||||
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:gravity="center"
|
||||
tools:text="Contact Name"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.Toolbar>
|
||||
|
||||
<org.briarproject.android.util.BriarRecyclerView
|
||||
android:id="@+id/conversationView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_weight="1"
|
||||
android:background="@color/conversation_background"/>
|
||||
|
||||
<View style="@style/Divider.Horizontal"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/button_bar_background"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/margin_medium"
|
||||
android:paddingStart="@dimen/margin_medium">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/contentView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/private_message_hint"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:hint="@string/private_message_hint"
|
||||
android:inputType="text|textMultiLine|textCapSentences"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/sendButton"
|
||||
android:layout_width="38dp"
|
||||
android:layout_height="38dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:src="@drawable/social_send_now"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:scaleType="fitEnd"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
android:background="@drawable/round_button"
|
||||
android:src="@drawable/social_send_now_white"
|
||||
android:contentDescription="@string/send"
|
||||
android:paddingRight="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:paddingBottom="@dimen/margin_medium"/>
|
||||
android:elevation="@dimen/margin_tiny"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
6
briar-android/res/layout/activity_introduction.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
android:id="@+id/introductionContainer"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
28
briar-android/res/layout/contact_avatar_status.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
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="32dp"
|
||||
android:layout_height="32dp"
|
||||
tools:showIn="@layout/activity_conversation">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/contactAvatar"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:transitionName="avatar"
|
||||
app:civ_border_color="@color/action_bar_text"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/contactStatus"
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:scaleType="fitCenter"
|
||||
tools:src="@drawable/contact_online"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.briarproject.android.util.BriarRecyclerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/contactList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/list_item_contact"/>
|
||||
99
briar-android/res/layout/introduction_message.xml
Normal file
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v4.widget.NestedScrollView
|
||||
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:fillViewport="true">
|
||||
|
||||
<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:padding="@dimen/margin_activity_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/introductionHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_medium">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarContact1"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:layout_toLeftOf="@+id/introductionIcon"
|
||||
android:layout_toStartOf="@+id/introductionIcon"
|
||||
app:civ_border_color="@color/briar_text_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/introductionIcon"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:src="@drawable/ic_contact_introduction"
|
||||
tools:ignore="ContentDescription"/>
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarContact2"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:layout_toEndOf="@+id/introductionIcon"
|
||||
android:layout_toRightOf="@+id/introductionIcon"
|
||||
android:transitionName="avatar"
|
||||
app:civ_border_color="@color/briar_text_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
tools:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introductionText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:layout_weight="1"
|
||||
android:gravity="top"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="@string/introduction_message_text"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/introductionMessageView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:gravity="bottom"
|
||||
android:hint="@string/introduction_message_hint"
|
||||
android:inputType="text|textMultiLine|textCapSentences"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/makeIntroductionButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/introduction_button"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v4.widget.NestedScrollView>
|
||||
@@ -1,16 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/listitem_height_one_line_avatar"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/listitem_horizontal_margin"
|
||||
android:paddingBottom="@dimen/listitem_horizontal_margin"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
>
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarView"
|
||||
@@ -21,56 +24,62 @@
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:transitionName="avatar"
|
||||
app:civ_border_color="@color/briar_text_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
app:civ_border_color="@color/briar_text_primary"/>
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bulbHolder"
|
||||
android:id="@+id/textViews"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:orientation="vertical"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:gravity="right"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:layout_toLeftOf="@+id/bulbView"
|
||||
android:layout_toRightOf="@+id/avatarView"
|
||||
android:layout_toEndOf="@+id/avatarView">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bulbView"
|
||||
<TextView
|
||||
android:id="@+id/nameView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:src="@drawable/contact_disconnected"/>
|
||||
android:maxLines="2"
|
||||
android:textColor="@android:color/primary_text_light"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="This is a name of a contact"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dateView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/no_private_messages"
|
||||
android:textColor="@android:color/secondary_text_light"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Dec 24"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/identityView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@android:color/tertiary_text_light"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="My Identity"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nameView"
|
||||
<ImageView
|
||||
android:id="@+id/bulbView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:layout_marginLeft="@dimen/listitem_text_left_margin"
|
||||
android:layout_marginRight="@dimen/margin_small"
|
||||
android:layout_marginStart="@dimen/listitem_text_left_margin"
|
||||
android:layout_toLeftOf="@id/bulbHolder"
|
||||
android:layout_toStartOf="@id/bulbHolder"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="2"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="This is a name of a contact. It can be quite long."/>
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
tools:src="@drawable/contact_connected"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<View style="@style/Divider.Horizontal"/>
|
||||
<View style="@style/Divider.ContactListDevider"/>
|
||||
|
||||
</LinearLayout>
|
||||
67
briar-android/res/layout/list_item_introduction_in.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/messageLayout"
|
||||
layout="@layout/list_item_msg_in"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/introductionLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left|start"
|
||||
android:background="@drawable/notice_in"
|
||||
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
|
||||
android:layout_marginRight="@dimen/message_bubble_margin_non_tail">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introductionText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="80dp"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="italic"
|
||||
tools:text="@string/introduction_request_received"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introductionTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||
android:layout_alignEnd="@+id/acceptButton"
|
||||
android:layout_alignRight="@+id/acceptButton"
|
||||
android:layout_below="@+id/acceptButton"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/acceptButton"
|
||||
style="@style/BriarButtonFlat.Positive"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="-15dp"
|
||||
android:layout_alignEnd="@+id/introductionText"
|
||||
android:layout_alignRight="@+id/introductionText"
|
||||
android:layout_below="@+id/introductionText"
|
||||
android:text="@string/dialog_button_accept"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/declineButton"
|
||||
style="@style/BriarButtonFlat.Negative"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/introductionText"
|
||||
android:layout_toLeftOf="@+id/acceptButton"
|
||||
android:layout_toStartOf="@+id/acceptButton"
|
||||
android:text="@string/dialog_button_decline"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
57
briar-android/res/layout/list_item_introduction_out.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/messageLayout"
|
||||
layout="@layout/list_item_msg_out"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/introductionLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right|end"
|
||||
android:background="@drawable/notice_out"
|
||||
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
|
||||
android:layout_marginRight="@dimen/message_bubble_margin_tail">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introductionText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="175dp"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="italic"
|
||||
tools:text="@string/introduction_request_received"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/introductionTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/introductionText"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/introductionStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/introductionTime"
|
||||
android:layout_toRightOf="@+id/introductionTime"
|
||||
android:layout_alignBottom="@+id/introductionTime"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/message_delivered"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,53 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingRight="@dimen/margin_medium"
|
||||
android:paddingEnd="@dimen/margin_medium"
|
||||
android:paddingTop="@dimen/margin_small"
|
||||
android:paddingBottom="@dimen/margin_small">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/msgAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_marginStart="@dimen/margin_medium"
|
||||
android:visibility="gone"
|
||||
app:civ_border_color="@color/briar_text_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
app:civ_border_color="@color/briar_text_primary"/>
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<RelativeLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/msgLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left|start"
|
||||
android:background="@drawable/msg_in"
|
||||
android:paddingLeft="17dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="7dp"
|
||||
android:paddingBottom="5dp">
|
||||
android:orientation="vertical"
|
||||
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
|
||||
android:layout_marginRight="@dimen/message_bubble_margin_non_tail">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="80dp"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="Short message"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="10sp"
|
||||
android:layout_gravity="right|end"
|
||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:layout_below="@+id/msgBody"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -4,11 +4,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="@dimen/margin_medium"
|
||||
android:paddingStart="@dimen/margin_medium"
|
||||
android:paddingTop="@dimen/margin_small"
|
||||
android:paddingBottom="@dimen/margin_small">
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/msgLayout"
|
||||
@@ -16,28 +12,29 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right|end"
|
||||
android:background="@drawable/msg_out"
|
||||
android:paddingLeft="7dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="17dp"
|
||||
android:paddingBottom="5dp">
|
||||
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
|
||||
android:layout_marginRight="@dimen/message_bubble_margin_tail">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgBody"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:textIsSelectable="true"
|
||||
android:minWidth="80dp"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/msgTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/msgBody"
|
||||
android:layout_toLeftOf="@+id/msgStatus"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/private_message_date_inverse"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
<ImageView
|
||||
@@ -45,10 +42,11 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/msgTime"
|
||||
android:layout_alignRight="@+id/msgBody"
|
||||
android:layout_alignEnd="@+id/msgBody"
|
||||
android:layout_marginLeft="3dp"
|
||||
tools:src="@drawable/message_delivered"/>
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_toEndOf="@+id/msgTime"
|
||||
android:layout_toRightOf="@+id/msgTime"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/message_delivered_white"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
34
briar-android/res/layout/list_item_notice_in.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
android:id="@+id/noticeLayout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/notice_in"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
|
||||
android:layout_marginRight="@dimen/message_bubble_margin_non_tail">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noticeText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="80dp"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="italic"
|
||||
tools:text="@string/introduction_response_accepted_received"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noticeTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right|end"
|
||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
</LinearLayout>
|
||||
52
briar-android/res/layout/list_item_notice_out.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/noticeLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right|end"
|
||||
android:background="@drawable/notice_out"
|
||||
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
|
||||
android:layout_marginRight="@dimen/message_bubble_margin_tail">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noticeText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="italic"
|
||||
tools:text="@string/introduction_response_accepted_sent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/noticeTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/noticeText"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="Dec 24, 13:37"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/noticeStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/noticeTime"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_toEndOf="@+id/noticeTime"
|
||||
android:layout_toRightOf="@+id/noticeTime"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/message_delivered"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -3,10 +3,16 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_introduction"
|
||||
android:icon="@drawable/introduction_white"
|
||||
android:title="@string/make_introduction"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_social_remove_person"
|
||||
android:icon="@drawable/social_remove_person"
|
||||
app:showAsAction="always"
|
||||
android:title="@string/delete_contact"/>
|
||||
android:title="@string/delete_contact"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
</menu>
|
||||
@@ -8,11 +8,13 @@
|
||||
<color name="briar_red">#C1392B</color>
|
||||
|
||||
<color name="window_background">#EEEEEE</color>
|
||||
<color name="conversation_background">#efebe9</color>
|
||||
<color name="action_bar_text">#FFFFFF</color>
|
||||
<color name="action_bar_background">@color/briar_blue</color>
|
||||
<color name="button_bar_background">#FFFFFF</color>
|
||||
<color name="dashboard_background">#FFFFFF</color>
|
||||
<color name="private_message_date">#AAAAAA</color>
|
||||
<color name="private_message_date_inverse">#e0e0e0</color>
|
||||
<color name="unread_background">#FFFFFF</color>
|
||||
<color name="horizontal_border">#CCCCCC</color>
|
||||
<color name="forums_available_background">@color/briar_gold</color>
|
||||
@@ -28,6 +30,8 @@
|
||||
<color name="briar_text_link">@color/briar_green_dark</color>
|
||||
<color name="briar_text_primary">@color/briar_primary</color>
|
||||
<color name="briar_text_primary_inverse">#ffffff</color>
|
||||
<color name="briar_text_secondary">#333333</color>
|
||||
<color name="briar_text_tertiary">#333333</color>
|
||||
|
||||
<!-- this is needed as preference_category_material layout uses this color as the text color -->
|
||||
<color name="preference_fallback_accent_color">@color/briar_accent</color>
|
||||
|
||||
@@ -23,8 +23,12 @@
|
||||
<dimen name="listitem_horizontal_margin">16dp</dimen>
|
||||
<dimen name="listitem_text_left_margin">72dp</dimen>
|
||||
<dimen name="listitem_height_one_line_avatar">56dp</dimen>
|
||||
<dimen name="listitem_picture_size">40dp</dimen>
|
||||
<dimen name="listitem_picture_size">48dp</dimen>
|
||||
<dimen name="dropdown_picture_size">32dp</dimen>
|
||||
<dimen name="avatar_border_width">1dp</dimen>
|
||||
|
||||
<dimen name="message_bubble_margin_tail">14dp</dimen>
|
||||
<dimen name="message_bubble_margin_non_tail">51dp</dimen>
|
||||
<dimen name="message_bubble_timestamp_margin">15dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -141,6 +141,26 @@
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<string name="no_data">No data</string>
|
||||
<string name="unknown_app">an unknown app</string>
|
||||
<string name="make_introduction">Make Introduction</string>
|
||||
<string name="introduction_activity_title">Select contact</string>
|
||||
<string name="introduction_message_title">Introduce Contacts</string>
|
||||
<string name="introduction_message_text">You can compose a message that will be sent to %1$s and %2$s along with your introduction:</string>
|
||||
<string name="introduction_message_hint">Type message (optional)</string>
|
||||
<string name="introduction_button">Make Introduction</string>
|
||||
<string name="introduction_error">There was an error making the introduction.</string>
|
||||
<string name="introduction_response_error">Error when responding to introduction</string>
|
||||
<string name="introduction_warn_different_identities_title">Warning: Different Identities</string>
|
||||
<string name="introduction_warn_different_identities_text">You are trying to introduce two contacts that you have added with different identities. This might reveal that both identities are yours.</string>
|
||||
<string name="introduction_request_sent">You have introduced %1$s to %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s introduced you to %2$s. Do you want to add %2$s to your contact list?</string>
|
||||
<string name="introduction_request_exists_received">%1$s introduced you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string>
|
||||
<string name="introduction_request_answered_received">%1$s introduced you to %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string>
|
||||
<string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string>
|
||||
<string name="introduction_response_accepted_received">%1$s accepted to be introduced to %2$s.</string>
|
||||
<string name="introduction_response_declined_received">%1$s declined to be introduced to %2$s.</string>
|
||||
<string name="introduction_success_title">Introduced contact was added</string>
|
||||
<string name="introduction_success_text">You have been successfully introduced to %1$s who was now added to your contact list.</string>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<string name="dialog_title_lost_password">Lost Password</string>
|
||||
@@ -152,6 +172,9 @@
|
||||
<string name="dialog_title_welcome">Welcome to Briar</string>
|
||||
<string name="dialog_welcome_message">Add a contact to start communicating securely or press the icon in the upper left corner of the screen for more options.</string>
|
||||
<string name="dialog_button_ok">OK</string>
|
||||
<string name="dialog_button_introduce">Introduce</string>
|
||||
<string name="dialog_button_accept">Accept</string>
|
||||
<string name="dialog_button_decline">Decline</string>
|
||||
<!-- Toolbar headers -->
|
||||
<string name="dashboard_toolbar_header">Briar</string>
|
||||
<string name="settings_toolbar_header">Settings</string>
|
||||
|
||||
@@ -51,13 +51,31 @@
|
||||
<item name="elevation">1dp</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarButton">
|
||||
<style name="BriarDialogTheme" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="colorPrimary">@color/briar_primary</item>
|
||||
<item name="colorPrimaryDark">@color/briar_primary_dark</item>
|
||||
<item name="colorAccent">@color/briar_accent</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarButton" parent="Widget.AppCompat.Button.Colored">
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:padding">@dimen/margin_large</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarButton.Default"/>
|
||||
|
||||
<style name="BriarButtonFlat.Negative" parent="Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:textColor">#ff0000</item>
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:padding">@dimen/margin_large</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarButtonFlat.Positive" parent="Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:textColor">#06b9ff</item>
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:padding">@dimen/margin_large</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarTextTitle">
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:textColor">@android:color/primary_text_light</item>
|
||||
@@ -76,11 +94,17 @@
|
||||
<item name="android:background">?android:attr/listDivider</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider.Horizontal">
|
||||
<style name="Divider.Horizontal" parent="Divider">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">1px</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider.ContactListDevider" parent="Divider">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">2dp</item>
|
||||
<item name="android:layout_marginLeft">@dimen/margin_large</item>
|
||||
</style>
|
||||
|
||||
<style name="NavMenuButton" parent="Widget.AppCompat.Button.Borderless.Colored">
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:textColor">@android:color/tertiary_text_light</item>
|
||||
|
||||
@@ -12,6 +12,9 @@ import org.briarproject.android.forum.ReadForumPostActivity;
|
||||
import org.briarproject.android.forum.ShareForumActivity;
|
||||
import org.briarproject.android.forum.WriteForumPostActivity;
|
||||
import org.briarproject.android.identity.CreateIdentityActivity;
|
||||
import org.briarproject.android.introduction.ContactChooserFragment;
|
||||
import org.briarproject.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.android.introduction.IntroductionMessageFragment;
|
||||
import org.briarproject.android.invitation.AddContactActivity;
|
||||
import org.briarproject.android.keyagreement.ChooseIdentityFragment;
|
||||
import org.briarproject.android.keyagreement.KeyAgreementActivity;
|
||||
@@ -80,6 +83,12 @@ public interface AndroidComponent extends CoreEagerSingletons {
|
||||
|
||||
void inject(ShowQrCodeFragment fragment);
|
||||
|
||||
void inject(IntroductionActivity activity);
|
||||
|
||||
void inject(ContactChooserFragment fragment);
|
||||
|
||||
void inject(IntroductionMessageFragment fragment);
|
||||
|
||||
// Eager singleton load
|
||||
void inject(AppModule.EagerSingletons init);
|
||||
}
|
||||
|
||||
@@ -14,10 +14,14 @@ import org.briarproject.android.api.AndroidExecutor;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.contact.ConversationActivity;
|
||||
import org.briarproject.android.forum.ForumActivity;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.db.DatabaseExecutor;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
|
||||
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.api.event.IntroductionSucceededEvent;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.event.SettingsUpdatedEvent;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
@@ -57,6 +61,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
|
||||
private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
|
||||
private static final int FORUM_POST_NOTIFICATION_ID = 4;
|
||||
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 5;
|
||||
private static final String CONTACT_URI =
|
||||
"content://org.briarproject/contact";
|
||||
private static final String FORUM_URI =
|
||||
@@ -111,6 +116,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
public Void call() {
|
||||
clearPrivateMessageNotification();
|
||||
clearForumPostNotification();
|
||||
clearIntroductionSuccessNotification();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
@@ -135,6 +141,12 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
nm.cancel(FORUM_POST_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
private void clearIntroductionSuccessNotification() {
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.cancel(INTRODUCTION_SUCCESS_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
|
||||
@@ -148,6 +160,25 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
else if (c.equals(forumManager.getClientId()))
|
||||
showForumPostNotification(m.getMessage().getGroupId());
|
||||
}
|
||||
} else if (e instanceof IntroductionRequestReceivedEvent) {
|
||||
try {
|
||||
GroupId group = messagingManager.getConversationId(
|
||||
((IntroductionRequestReceivedEvent) e).getContactId());
|
||||
showPrivateMessageNotification(group);
|
||||
} catch (DbException ex) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, ex.toString(), ex);
|
||||
}
|
||||
} else if (e instanceof IntroductionResponseReceivedEvent) {
|
||||
try {
|
||||
GroupId group = messagingManager.getConversationId(
|
||||
((IntroductionResponseReceivedEvent) e).getContactId());
|
||||
showPrivateMessageNotification(group);
|
||||
} catch (DbException ex) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, ex.toString(), ex);
|
||||
}
|
||||
} else if (e instanceof IntroductionSucceededEvent) {
|
||||
Contact c = ((IntroductionSucceededEvent) e).getContact();
|
||||
showIntroductionSucceededNotification(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,4 +366,35 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showIntroductionSucceededNotification(final Contact c) {
|
||||
androidExecutor.execute(new Runnable() {
|
||||
public void run() {
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(appContext);
|
||||
b.setSmallIcon(R.drawable.introduction_notification);
|
||||
|
||||
b.setContentTitle(appContext
|
||||
.getString(R.string.introduction_success_title));
|
||||
b.setContentText(appContext
|
||||
.getString(R.string.introduction_success_text,
|
||||
c.getAuthor().getName()));
|
||||
b.setDefaults(getDefaults());
|
||||
b.setAutoCancel(true);
|
||||
|
||||
Intent i = new Intent(appContext, NavDrawerActivity.class);
|
||||
i.putExtra(NavDrawerActivity.INTENT_CONTACTS, true);
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
||||
TaskStackBuilder t = TaskStackBuilder.create(appContext);
|
||||
t.addParentStack(NavDrawerActivity.class);
|
||||
t.addNextIntent(i);
|
||||
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
|
||||
|
||||
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) o;
|
||||
nm.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, b.build());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||
((InputMethodManager) o).showSoftInput(view, SHOW_IMPLICIT);
|
||||
}
|
||||
|
||||
protected void hideSoftKeyboard(View view) {
|
||||
public void hideSoftKeyboard(View view) {
|
||||
IBinder token = view.getWindowToken();
|
||||
Object o = getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
@@ -14,9 +18,8 @@ import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -53,13 +56,23 @@ public class ContactListAdapter
|
||||
@Override
|
||||
public int compare(ContactListItem c1,
|
||||
ContactListItem c2) {
|
||||
// sort items by time
|
||||
// and do not take unread messages into account
|
||||
long time1 = c1.getTimestamp();
|
||||
long time2 = c2.getTimestamp();
|
||||
if (time1 < time2) return 1;
|
||||
if (time1 > time2) return -1;
|
||||
return 0;
|
||||
int authorCompare = 0;
|
||||
if (chooser) {
|
||||
authorCompare = c1.getLocalAuthor().getName()
|
||||
.compareTo(
|
||||
c2.getLocalAuthor().getName());
|
||||
}
|
||||
if (authorCompare == 0) {
|
||||
// sort items by time
|
||||
// and do not take unread messages into account
|
||||
long time1 = c1.getTimestamp();
|
||||
long time2 = c2.getTimestamp();
|
||||
if (time1 < time2) return 1;
|
||||
if (time1 > time2) return -1;
|
||||
return 0;
|
||||
} else {
|
||||
return authorCompare;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,10 +99,16 @@ public class ContactListAdapter
|
||||
return true;
|
||||
}
|
||||
});
|
||||
private final OnItemClickListener listener;
|
||||
private final boolean chooser;
|
||||
private Context ctx;
|
||||
private AuthorId localAuthorId;
|
||||
|
||||
public ContactListAdapter(Context context) {
|
||||
public ContactListAdapter(Context context, OnItemClickListener listener,
|
||||
boolean chooser) {
|
||||
ctx = context;
|
||||
this.listener = listener;
|
||||
this.chooser = chooser;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,12 +122,11 @@ public class ContactListAdapter
|
||||
@Override
|
||||
public void onBindViewHolder(final ContactHolder ui, final int position) {
|
||||
final ContactListItem item = getItem(position);
|
||||
Resources res = ctx.getResources();
|
||||
|
||||
int unread = item.getUnreadCount();
|
||||
if (unread > 0) {
|
||||
if (!chooser && unread > 0) {
|
||||
ui.layout.setBackgroundColor(
|
||||
res.getColor(R.color.unread_background));
|
||||
ContextCompat.getColor(ctx, R.color.unread_background));
|
||||
}
|
||||
|
||||
if (item.isConnected()) {
|
||||
@@ -121,27 +139,37 @@ public class ContactListAdapter
|
||||
ui.avatar.setImageDrawable(
|
||||
new IdenticonDrawable(author.getId().getBytes()));
|
||||
String contactName = author.getName();
|
||||
if (unread > 0) {
|
||||
|
||||
if (!chooser && unread > 0) {
|
||||
// TODO show these in a bubble on top of the avatar
|
||||
ui.name.setText(contactName + " (" + unread + ")");
|
||||
} else {
|
||||
ui.name.setText(contactName);
|
||||
}
|
||||
|
||||
if (chooser) {
|
||||
ui.identity.setText(item.getLocalAuthor().getName());
|
||||
} else {
|
||||
ui.identity.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (item.isEmpty()) {
|
||||
ui.date.setText(R.string.no_private_messages);
|
||||
} else {
|
||||
// TODO show this as X units ago
|
||||
long timestamp = item.getTimestamp();
|
||||
ui.date.setText(
|
||||
DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
||||
}
|
||||
|
||||
if (chooser && !item.getLocalAuthor().getId().equals(localAuthorId)) {
|
||||
grayOutItem(ui);
|
||||
}
|
||||
|
||||
ui.layout.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
GroupId groupId = item.getGroupId();
|
||||
Intent i = new Intent(ctx, ConversationActivity.class);
|
||||
i.putExtra("briar.GROUP_ID", groupId.getBytes());
|
||||
ctx.startActivity(i);
|
||||
listener.onItemClick(ui.avatar, item);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -151,6 +179,34 @@ public class ContactListAdapter
|
||||
return contacts.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the identity from whose perspective the contact shall be chosen.
|
||||
* This is only used if chooser is true.
|
||||
* @param authorId The ID of the local Author
|
||||
*/
|
||||
public void setLocalAuthor(AuthorId authorId) {
|
||||
localAuthorId = authorId;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void grayOutItem(final ContactHolder ui) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
float alpha = 0.25f;
|
||||
ui.bulb.setAlpha(alpha);
|
||||
ui.avatar.setAlpha(alpha);
|
||||
ui.name.setAlpha(alpha);
|
||||
ui.date.setAlpha(alpha);
|
||||
ui.identity.setAlpha(alpha);
|
||||
} else {
|
||||
ColorFilter colorFilter = new PorterDuffColorFilter(Color.GRAY,
|
||||
PorterDuff.Mode.MULTIPLY);
|
||||
ui.bulb.setColorFilter(colorFilter);
|
||||
ui.avatar.setColorFilter(colorFilter);
|
||||
ui.name.setEnabled(false);
|
||||
ui.date.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
public ContactListItem getItem(int position) {
|
||||
if (position == INVALID_POSITION || contacts.size() <= position) {
|
||||
return null; // Not found
|
||||
@@ -162,10 +218,6 @@ public class ContactListAdapter
|
||||
contacts.updateItemAt(position, item);
|
||||
}
|
||||
|
||||
public int findItemPosition(ContactListItem item) {
|
||||
return contacts.indexOf(item);
|
||||
}
|
||||
|
||||
public int findItemPosition(ContactId c) {
|
||||
int count = getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -202,6 +254,7 @@ public class ContactListAdapter
|
||||
public ImageView bulb;
|
||||
public ImageView avatar;
|
||||
public TextView name;
|
||||
public TextView identity;
|
||||
public TextView date;
|
||||
|
||||
public ContactHolder(View v) {
|
||||
@@ -211,7 +264,13 @@ public class ContactListAdapter
|
||||
bulb = (ImageView) v.findViewById(R.id.bulbView);
|
||||
avatar = (ImageView) v.findViewById(R.id.avatarView);
|
||||
name = (TextView) v.findViewById(R.id.nameView);
|
||||
identity = (TextView) v.findViewById(R.id.identityView);
|
||||
date = (TextView) v.findViewById(R.id.dateView);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickListener {
|
||||
void onItemClick(View view, ContactListItem item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -11,23 +14,26 @@ import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.BriarApplication;
|
||||
import org.briarproject.android.fragment.BaseEventFragment;
|
||||
import org.briarproject.android.keyagreement.KeyAgreementActivity;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.event.ContactAddedEvent;
|
||||
import org.briarproject.api.event.ContactConnectedEvent;
|
||||
import org.briarproject.api.event.ContactDisconnectedEvent;
|
||||
import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.ContactStatusChangedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.introduction.IntroductionManager;
|
||||
import org.briarproject.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.api.messaging.MessagingManager;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.api.plugins.ConnectionRegistry;
|
||||
@@ -74,8 +80,12 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
@Inject
|
||||
protected volatile ContactManager contactManager;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
protected volatile MessagingManager messagingManager;
|
||||
@Inject
|
||||
protected volatile IntroductionManager introductionManager;
|
||||
@Inject
|
||||
protected volatile EventBus eventBus;
|
||||
|
||||
@Override
|
||||
@@ -91,7 +101,31 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
inflater.inflate(R.layout.activity_contact_list, container,
|
||||
false);
|
||||
|
||||
adapter = new ContactListAdapter(getContext());
|
||||
ContactListAdapter.OnItemClickListener onItemClickListener =
|
||||
new ContactListAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(View view, ContactListItem item) {
|
||||
|
||||
GroupId groupId = item.getGroupId();
|
||||
Intent i = new Intent(getActivity(),
|
||||
ConversationActivity.class);
|
||||
i.putExtra("briar.GROUP_ID", groupId.getBytes());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
ActivityOptionsCompat options =
|
||||
ActivityOptionsCompat.
|
||||
makeSceneTransitionAnimation(
|
||||
getActivity(),
|
||||
view, "avatar");
|
||||
getActivity().startActivity(i, options.toBundle());
|
||||
} else {
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
adapter = new ContactListAdapter(getContext(), onItemClickListener,
|
||||
false);
|
||||
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
list.setAdapter(adapter);
|
||||
@@ -135,12 +169,14 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
ContactId id = c.getId();
|
||||
GroupId groupId =
|
||||
messagingManager.getConversationId(id);
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
messagingManager.getMessageHeaders(id);
|
||||
Collection<ConversationItem> messages =
|
||||
getMessages(id);
|
||||
boolean connected =
|
||||
connectionRegistry.isConnected(c.getId());
|
||||
contacts.add(new ContactListItem(c, connected,
|
||||
groupId, headers));
|
||||
LocalAuthor localAuthor = identityManager
|
||||
.getLocalAuthor(c.getLocalAuthorId());
|
||||
contacts.add(new ContactListItem(c, localAuthor,
|
||||
connected, groupId, messages));
|
||||
} catch (NoSuchContactException e) {
|
||||
// Continue
|
||||
}
|
||||
@@ -169,7 +205,12 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactAddedEvent) {
|
||||
LOG.info("Contact added, reloading");
|
||||
if(((ContactAddedEvent) e).isActive()) {
|
||||
LOG.info("Contact added as active, reloading");
|
||||
loadContacts();
|
||||
}
|
||||
} else if (e instanceof ContactStatusChangedEvent) {
|
||||
LOG.info("Contact Status changed, reloading");
|
||||
loadContacts();
|
||||
} else if (e instanceof ContactConnectedEvent) {
|
||||
setConnected(((ContactConnectedEvent) e).getContactId(), true);
|
||||
@@ -181,7 +222,8 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
} else if (e instanceof MessageValidatedEvent) {
|
||||
MessageValidatedEvent m = (MessageValidatedEvent) e;
|
||||
ClientId c = m.getClientId();
|
||||
if (m.isValid() && c.equals(messagingManager.getClientId())) {
|
||||
if (m.isValid() && (c.equals(messagingManager.getClientId()) ||
|
||||
c.equals(introductionManager.getClientId()))) {
|
||||
LOG.info("Message added, reloading");
|
||||
reloadConversation(m.getMessage().getGroupId());
|
||||
}
|
||||
@@ -192,14 +234,10 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
listener.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
ContactId c = messagingManager.getContactId(g);
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
messagingManager.getMessageHeaders(c);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Partial load took " + duration + " ms");
|
||||
updateItem(c, headers);
|
||||
Collection<ConversationItem> messages =
|
||||
getMessages(c);
|
||||
updateItem(c, messages);
|
||||
} catch (NoSuchContactException e) {
|
||||
LOG.info("Contact removed");
|
||||
} catch (DbException e) {
|
||||
@@ -211,13 +249,13 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
}
|
||||
|
||||
private void updateItem(final ContactId c,
|
||||
final Collection<PrivateMessageHeader> headers) {
|
||||
final Collection<ConversationItem> messages) {
|
||||
listener.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
int position = adapter.findItemPosition(c);
|
||||
ContactListItem item = adapter.getItem(position);
|
||||
if (item != null) {
|
||||
item.setHeaders(headers);
|
||||
item.setMessages(messages);
|
||||
adapter.updateItem(position, item);
|
||||
}
|
||||
}
|
||||
@@ -246,4 +284,35 @@ public class ContactListFragment extends BaseEventFragment {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** This needs to be called from the DbThread */
|
||||
private Collection<ConversationItem> getMessages(ContactId id)
|
||||
throws DbException {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
Collection<ConversationItem> messages =
|
||||
new ArrayList<ConversationItem>();
|
||||
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
messagingManager.getMessageHeaders(id);
|
||||
for (PrivateMessageHeader h : headers) {
|
||||
messages.add(new ConversationMessageItem(h));
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading message headers took " + duration + " ms");
|
||||
|
||||
Collection<IntroductionMessage> introductions =
|
||||
introductionManager
|
||||
.getIntroductionMessages(id);
|
||||
for (IntroductionMessage m : introductions) {
|
||||
messages.add(ConversationItem.from(m));
|
||||
}
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading introduction messages took " + duration + " ms");
|
||||
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,55 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
|
||||
|
||||
// This class is not thread-safe
|
||||
class ContactListItem {
|
||||
public class ContactListItem {
|
||||
|
||||
private final Contact contact;
|
||||
private final LocalAuthor localAuthor;
|
||||
private final GroupId groupId;
|
||||
private boolean connected, empty;
|
||||
private long timestamp;
|
||||
private int unread;
|
||||
|
||||
ContactListItem(Contact contact, boolean connected, GroupId groupId,
|
||||
Collection<PrivateMessageHeader> headers) {
|
||||
public ContactListItem(Contact contact, LocalAuthor localAuthor,
|
||||
boolean connected,
|
||||
GroupId groupId,
|
||||
Collection<ConversationItem> messages) {
|
||||
this.contact = contact;
|
||||
this.localAuthor = localAuthor;
|
||||
this.groupId = groupId;
|
||||
this.connected = connected;
|
||||
setHeaders(headers);
|
||||
setMessages(messages);
|
||||
}
|
||||
|
||||
void setHeaders(Collection<PrivateMessageHeader> headers) {
|
||||
empty = headers.isEmpty();
|
||||
void setMessages(Collection<ConversationItem> messages) {
|
||||
empty = messages.isEmpty();
|
||||
timestamp = 0;
|
||||
unread = 0;
|
||||
if (!empty) {
|
||||
for (PrivateMessageHeader h : headers) {
|
||||
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp();
|
||||
if (!h.isRead()) unread++;
|
||||
for (ConversationItem i : messages) {
|
||||
if (i.getTime() > timestamp) timestamp = i.getTime();
|
||||
if (i instanceof IncomingItem && !((IncomingItem) i).isRead())
|
||||
unread++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Contact getContact() {
|
||||
public Contact getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public LocalAuthor getLocalAuthor() {
|
||||
return localAuthor;
|
||||
}
|
||||
|
||||
GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@ package org.briarproject.android.contact;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -14,14 +18,17 @@ import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.android.api.AndroidNotificationManager;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
@@ -35,9 +42,16 @@ import org.briarproject.api.event.ContactRemovedEvent;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.EventBus;
|
||||
import org.briarproject.api.event.EventListener;
|
||||
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
|
||||
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.api.event.MessageValidatedEvent;
|
||||
import org.briarproject.api.event.MessagesAckedEvent;
|
||||
import org.briarproject.api.event.MessagesSentEvent;
|
||||
import org.briarproject.api.introduction.IntroductionManager;
|
||||
import org.briarproject.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.api.introduction.SessionId;
|
||||
import org.briarproject.api.messaging.MessagingManager;
|
||||
import org.briarproject.api.messaging.PrivateMessage;
|
||||
import org.briarproject.api.messaging.PrivateMessageFactory;
|
||||
@@ -61,21 +75,31 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
|
||||
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
|
||||
|
||||
public class ConversationActivity extends BriarActivity
|
||||
implements EventListener, OnClickListener {
|
||||
implements EventListener, OnClickListener,
|
||||
ConversationAdapter.IntroductionHandler {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConversationActivity.class.getName());
|
||||
private static final int INTRODUCTION_REQUEST_CODE = 0;
|
||||
|
||||
@Inject protected AndroidNotificationManager notificationManager;
|
||||
@Inject protected ConnectionRegistry connectionRegistry;
|
||||
@Inject @CryptoExecutor protected Executor cryptoExecutor;
|
||||
private Map<MessageId, byte[]> bodyCache = new HashMap<MessageId, byte[]>();
|
||||
private ConversationAdapter adapter = null;
|
||||
private CircleImageView toolbarAvatar;
|
||||
private ImageView toolbarStatus;
|
||||
private TextView toolbarTitle;
|
||||
private BriarRecyclerView list = null;
|
||||
private EditText content = null;
|
||||
private ImageButton sendButton = null;
|
||||
@@ -85,6 +109,7 @@ public class ConversationActivity extends BriarActivity
|
||||
@Inject protected volatile MessagingManager messagingManager;
|
||||
@Inject protected volatile EventBus eventBus;
|
||||
@Inject protected volatile PrivateMessageFactory privateMessageFactory;
|
||||
@Inject protected volatile IntroductionManager introductionManager;
|
||||
private volatile GroupId groupId = null;
|
||||
private volatile ContactId contactId = null;
|
||||
private volatile String contactName = null;
|
||||
@@ -102,7 +127,21 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
setContentView(R.layout.activity_conversation);
|
||||
|
||||
adapter = new ConversationAdapter(this);
|
||||
// Custom Toolbar
|
||||
final Toolbar tb = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbarAvatar = (CircleImageView) tb.findViewById(R.id.contactAvatar);
|
||||
toolbarStatus = (ImageView) tb.findViewById(R.id.contactStatus);
|
||||
toolbarTitle = (TextView) tb.findViewById(R.id.contactName);
|
||||
setSupportActionBar(tb);
|
||||
final ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayShowHomeEnabled(true);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
ab.setDisplayShowCustomEnabled(true);
|
||||
ab.setDisplayShowTitleEnabled(false);
|
||||
}
|
||||
|
||||
adapter = new ConversationAdapter(this, this);
|
||||
list = (BriarRecyclerView) findViewById(R.id.conversationView);
|
||||
list.setLayoutManager(new LinearLayoutManager(this));
|
||||
list.setAdapter(adapter);
|
||||
@@ -125,8 +164,7 @@ public class ConversationActivity extends BriarActivity
|
||||
eventBus.addListener(this);
|
||||
notificationManager.blockNotification(groupId);
|
||||
notificationManager.clearPrivateMessageNotification(groupId);
|
||||
loadContactDetails();
|
||||
loadHeaders();
|
||||
loadData();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,12 +179,10 @@ public class ConversationActivity extends BriarActivity
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// Inflate the menu items for use in the action bar
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.contact_actions, menu);
|
||||
inflater.inflate(R.menu.conversation_actions, menu);
|
||||
|
||||
// Adapt icon color to dark action bar
|
||||
menu.findItem(R.id.action_social_remove_person).getIcon().setColorFilter(
|
||||
getResources().getColor(R.color.action_bar_text),
|
||||
PorterDuff.Mode.SRC_IN);
|
||||
hideIntroductionActionWhenOneContact(
|
||||
menu.findItem(R.id.action_introduction));
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -155,6 +191,20 @@ public class ConversationActivity extends BriarActivity
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
supportFinishAfterTransition();
|
||||
return true;
|
||||
case R.id.action_introduction:
|
||||
if (contactId == null) return false;
|
||||
Intent intent = new Intent(this, IntroductionActivity.class);
|
||||
intent.putExtra(IntroductionActivity.CONTACT_ID,
|
||||
contactId.getInt());
|
||||
ActivityOptionsCompat options = ActivityOptionsCompat
|
||||
.makeCustomAnimation(this, android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right);
|
||||
ActivityCompat.startActivityForResult(this, intent,
|
||||
INTRODUCTION_REQUEST_CODE, options.toBundle());
|
||||
return true;
|
||||
case R.id.action_social_remove_person:
|
||||
askToRemoveContact();
|
||||
return true;
|
||||
@@ -163,20 +213,37 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void loadContactDetails() {
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
|
||||
if (requestCode == INTRODUCTION_REQUEST_CODE) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
contactId = messagingManager.getContactId(groupId);
|
||||
Contact contact = contactManager.getContact(contactId);
|
||||
contactName = contact.getAuthor().getName();
|
||||
contactIdenticonKey = contact.getAuthor().getId().getBytes();
|
||||
if (contactId == null)
|
||||
contactId = messagingManager.getContactId(groupId);
|
||||
if (contactName == null || contactIdenticonKey == null) {
|
||||
Contact contact = contactManager.getContact(contactId);
|
||||
contactName = contact.getAuthor().getName();
|
||||
contactIdenticonKey =
|
||||
contact.getAuthor().getId().getBytes();
|
||||
}
|
||||
connected = connectionRegistry.isConnected(contactId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading contact took " + duration + " ms");
|
||||
displayContactDetails();
|
||||
// Load the messages here to make sure we have a contactId
|
||||
loadMessages();
|
||||
} catch (NoSuchContactException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
@@ -190,31 +257,42 @@ public class ConversationActivity extends BriarActivity
|
||||
private void displayContactDetails() {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(contactName);
|
||||
if (connected) {
|
||||
actionBar.setSubtitle(getString(R.string.online));
|
||||
} else {
|
||||
actionBar.setSubtitle(getString(R.string.offline));
|
||||
}
|
||||
toolbarAvatar.setImageDrawable(
|
||||
new IdenticonDrawable(contactIdenticonKey));
|
||||
toolbarTitle.setText(contactName);
|
||||
|
||||
if (connected) {
|
||||
toolbarStatus.setImageDrawable(ContextCompat
|
||||
.getDrawable(ConversationActivity.this,
|
||||
R.drawable.contact_online));
|
||||
toolbarStatus
|
||||
.setContentDescription(getString(R.string.online));
|
||||
} else {
|
||||
toolbarStatus.setImageDrawable(ContextCompat
|
||||
.getDrawable(ConversationActivity.this,
|
||||
R.drawable.contact_offline));
|
||||
toolbarStatus
|
||||
.setContentDescription(getString(R.string.offline));
|
||||
}
|
||||
adapter.setIdenticonKey(contactIdenticonKey);
|
||||
adapter.setIdenticonKey(contactIdenticonKey, contactName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadHeaders() {
|
||||
private void loadMessages() {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
messagingManager.getMessageHeaders(contactId);
|
||||
Collection<IntroductionMessage> introductions =
|
||||
introductionManager
|
||||
.getIntroductionMessages(contactId);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading headers took " + duration + " ms");
|
||||
displayHeaders(headers);
|
||||
displayMessages(headers, introductions);
|
||||
} catch (NoSuchContactException e) {
|
||||
finishOnUiThread();
|
||||
} catch (DbException e) {
|
||||
@@ -225,23 +303,37 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void displayHeaders(
|
||||
final Collection<PrivateMessageHeader> headers) {
|
||||
private void displayMessages(final Collection<PrivateMessageHeader> headers,
|
||||
final Collection<IntroductionMessage> introductions) {
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
sendButton.setEnabled(true);
|
||||
if (headers.isEmpty()) {
|
||||
if (headers.isEmpty() && introductions.isEmpty()) {
|
||||
// we have no messages,
|
||||
// so let the list know to hide progress bar
|
||||
list.showData();
|
||||
} else {
|
||||
for (PrivateMessageHeader h : headers) {
|
||||
ConversationItem item = new ConversationItem(h);
|
||||
ConversationMessageItem item =
|
||||
new ConversationMessageItem(h);
|
||||
byte[] body = bodyCache.get(h.getId());
|
||||
if (body == null) loadMessageBody(h);
|
||||
else item.setBody(body);
|
||||
adapter.add(item);
|
||||
}
|
||||
for (IntroductionMessage m : introductions) {
|
||||
ConversationItem item;
|
||||
if (m instanceof IntroductionRequest) {
|
||||
item = ConversationItem
|
||||
.from((IntroductionRequest) m);
|
||||
} else {
|
||||
item = ConversationItem
|
||||
.from(ConversationActivity.this,
|
||||
contactName,
|
||||
(IntroductionResponse) m);
|
||||
}
|
||||
adapter.add(item);
|
||||
}
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
@@ -273,27 +365,42 @@ public class ConversationActivity extends BriarActivity
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
bodyCache.put(m, body);
|
||||
int count = adapter.getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ConversationItem item = adapter.getItem(i);
|
||||
if (item.getHeader().getId().equals(m)) {
|
||||
SparseArray<ConversationMessageItem> messages =
|
||||
adapter.getPrivateMessages();
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
ConversationMessageItem item = messages.valueAt(i);
|
||||
if (item.getId().equals(m)) {
|
||||
item.setBody(body);
|
||||
adapter.notifyItemChanged(i);
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(count - 1);
|
||||
adapter.notifyItemChanged(messages.keyAt(i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addIntroduction(final ConversationItem item) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (adapter != null) {
|
||||
adapter.add(item);
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markMessagesRead() {
|
||||
List<MessageId> unread = new ArrayList<MessageId>();
|
||||
int count = adapter.getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
PrivateMessageHeader h = adapter.getItem(i).getHeader();
|
||||
if (!h.isRead()) unread.add(h.getId());
|
||||
SparseArray<IncomingItem> list =
|
||||
adapter.getIncomingMessages();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
IncomingItem item = list.valueAt(i);
|
||||
if (!item.isRead()) unread.add(item.getId());
|
||||
}
|
||||
if (unread.isEmpty()) return;
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -307,6 +414,8 @@ public class ConversationActivity extends BriarActivity
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
for (MessageId m : unread)
|
||||
// not really clean, but the messaging manager can
|
||||
// handle introduction messages as well
|
||||
messagingManager.setReadFlag(m, true);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -331,7 +440,7 @@ public class ConversationActivity extends BriarActivity
|
||||
if (m.isValid() && m.getMessage().getGroupId().equals(groupId)) {
|
||||
LOG.info("Message added, reloading");
|
||||
// Mark new incoming messages as read directly
|
||||
if (m.isLocal()) loadHeaders();
|
||||
if (m.isLocal()) loadMessages();
|
||||
else markMessageReadIfNew(m.getMessage());
|
||||
}
|
||||
} else if (e instanceof MessagesSentEvent) {
|
||||
@@ -360,6 +469,23 @@ public class ConversationActivity extends BriarActivity
|
||||
connected = false;
|
||||
displayContactDetails();
|
||||
}
|
||||
} else if (e instanceof IntroductionRequestReceivedEvent) {
|
||||
IntroductionRequestReceivedEvent event =
|
||||
(IntroductionRequestReceivedEvent) e;
|
||||
if (event.getContactId().equals(contactId)) {
|
||||
IntroductionRequest ir = event.getIntroductionRequest();
|
||||
ConversationItem item = new ConversationIntroductionInItem(ir);
|
||||
addIntroduction(item);
|
||||
}
|
||||
} else if (e instanceof IntroductionResponseReceivedEvent) {
|
||||
IntroductionResponseReceivedEvent event =
|
||||
(IntroductionResponseReceivedEvent) e;
|
||||
if (event.getContactId().equals(contactId)) {
|
||||
IntroductionResponse ir = event.getIntroductionResponse();
|
||||
ConversationItem item =
|
||||
ConversationItem.from(this, contactName, ir);
|
||||
addIntroduction(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,10 +495,13 @@ public class ConversationActivity extends BriarActivity
|
||||
ConversationItem item = adapter.getLastItem();
|
||||
if (item != null) {
|
||||
// Mark the message read if it's the newest message
|
||||
long lastMsgTime = item.getHeader().getTimestamp();
|
||||
long lastMsgTime = item.getTime();
|
||||
long newMsgTime = m.getTimestamp();
|
||||
if (newMsgTime > lastMsgTime) markNewMessageRead(m);
|
||||
else loadHeaders();
|
||||
else loadMessages();
|
||||
} else {
|
||||
// mark the message as read as well if it is the first one
|
||||
markNewMessageRead(m);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -383,7 +512,7 @@ public class ConversationActivity extends BriarActivity
|
||||
public void run() {
|
||||
try {
|
||||
messagingManager.setReadFlag(m.getId(), true);
|
||||
loadHeaders();
|
||||
loadMessages();
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
@@ -397,13 +526,14 @@ public class ConversationActivity extends BriarActivity
|
||||
runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
Set<MessageId> messages = new HashSet<MessageId>(messageIds);
|
||||
int count = adapter.getItemCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ConversationItem item = adapter.getItem(i);
|
||||
if (messages.contains(item.getHeader().getId())) {
|
||||
SparseArray<OutgoingItem> list =
|
||||
adapter.getOutgoingMessages();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
OutgoingItem item = list.valueAt(i);
|
||||
if (messages.contains(item.getId())) {
|
||||
item.setSent(sent);
|
||||
item.setSeen(seen);
|
||||
adapter.notifyItemChanged(i);
|
||||
adapter.notifyItemChanged(list.keyAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,7 +554,7 @@ public class ConversationActivity extends BriarActivity
|
||||
private long getMinTimestampForNewMessage() {
|
||||
// Don't use an earlier timestamp than the newest message
|
||||
ConversationItem item = adapter.getLastItem();
|
||||
return item == null ? 0 : item.getHeader().getTimestamp() + 1;
|
||||
return item == null ? 0 : item.getTime() + 1;
|
||||
}
|
||||
|
||||
private void createMessage(final byte[] body, final long timestamp) {
|
||||
@@ -466,7 +596,8 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
};
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(ConversationActivity.this);
|
||||
new AlertDialog.Builder(ConversationActivity.this,
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(R.string.dialog_title_delete_contact));
|
||||
builder.setMessage(getString(R.string.dialog_message_delete_contact));
|
||||
builder.setPositiveButton(android.R.string.ok, okListener);
|
||||
@@ -500,4 +631,66 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideIntroductionActionWhenOneContact(final MenuItem item) {
|
||||
runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
if (contactManager.getActiveContacts().size() < 2) {
|
||||
hideIntroductionAction(item);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideIntroductionAction(final MenuItem item) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
item.setVisible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respondToIntroduction(final SessionId sessionId, final boolean accept) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (accept) {
|
||||
introductionManager.acceptIntroduction(sessionId);
|
||||
} else {
|
||||
introductionManager.declineIntroduction(sessionId);
|
||||
}
|
||||
loadMessages();
|
||||
} catch (DbException e) {
|
||||
introductionResponseError();
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
} catch (FormatException e) {
|
||||
introductionResponseError();
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void introductionResponseError() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(ConversationActivity.this,
|
||||
R.string.introduction_response_error,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,29 +4,37 @@ import android.content.Context;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.SparseArray;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.api.introduction.SessionId;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||
import static android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_IN;
|
||||
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_OUT;
|
||||
import static org.briarproject.android.contact.ConversationItem.MSG_IN;
|
||||
import static org.briarproject.android.contact.ConversationItem.MSG_IN_UNREAD;
|
||||
import static org.briarproject.android.contact.ConversationItem.MSG_OUT;
|
||||
import static org.briarproject.android.contact.ConversationItem.NOTICE_IN;
|
||||
import static org.briarproject.android.contact.ConversationItem.NOTICE_OUT;
|
||||
import static org.briarproject.android.contact.ConversationItem.OutgoingItem;
|
||||
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
|
||||
|
||||
class ConversationAdapter extends
|
||||
RecyclerView.Adapter<ConversationAdapter.MessageHolder> {
|
||||
class ConversationAdapter extends RecyclerView.Adapter {
|
||||
|
||||
private static final int MSG_OUT = 0;
|
||||
private static final int MSG_IN = 1;
|
||||
private static final int MSG_IN_UNREAD = 2;
|
||||
|
||||
private final SortedList<ConversationItem> messages =
|
||||
private final SortedList<ConversationItem> items =
|
||||
new SortedList<ConversationItem>(ConversationItem.class,
|
||||
new SortedList.Callback<ConversationItem>() {
|
||||
@Override
|
||||
@@ -52,8 +60,8 @@ class ConversationAdapter extends
|
||||
@Override
|
||||
public int compare(ConversationItem c1,
|
||||
ConversationItem c2) {
|
||||
long time1 = c1.getHeader().getTimestamp();
|
||||
long time2 = c2.getHeader().getTimestamp();
|
||||
long time1 = c1.getTime();
|
||||
long time2 = c2.getTime();
|
||||
if (time1 < time2) return -1;
|
||||
if (time1 > time2) return 1;
|
||||
return 0;
|
||||
@@ -62,8 +70,7 @@ class ConversationAdapter extends
|
||||
@Override
|
||||
public boolean areItemsTheSame(ConversationItem c1,
|
||||
ConversationItem c2) {
|
||||
return c1.getHeader().getId()
|
||||
.equals(c2.getHeader().getId());
|
||||
return c1.getId().equals(c2.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,67 +80,113 @@ class ConversationAdapter extends
|
||||
}
|
||||
});
|
||||
private Context ctx;
|
||||
private IntroductionHandler intro;
|
||||
private byte[] identiconKey;
|
||||
private String contactName;
|
||||
|
||||
public ConversationAdapter(Context context) {
|
||||
public ConversationAdapter(Context context,
|
||||
IntroductionHandler introductionHandler) {
|
||||
ctx = context;
|
||||
intro = introductionHandler;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
public void setIdenticonKey(byte[] key) {
|
||||
public void setIdenticonKey(byte[] key, String contactName) {
|
||||
this.identiconKey = key;
|
||||
this.contactName = contactName;
|
||||
// FIXME this breaks the progress animation because it is called early before data is loaded
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
// return different type for incoming and outgoing (local) messages
|
||||
PrivateMessageHeader header = getItem(position).getHeader();
|
||||
if (header.isLocal()) {
|
||||
return MSG_OUT;
|
||||
} else if (header.isRead()) {
|
||||
return MSG_IN;
|
||||
} else {
|
||||
return MSG_IN_UNREAD;
|
||||
}
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).getId().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
|
||||
public int getItemViewType(int position) {
|
||||
return getItem(position).getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
|
||||
View v;
|
||||
|
||||
// outgoing message (local)
|
||||
if (type == MSG_OUT) {
|
||||
v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.list_item_msg_out, viewGroup, false);
|
||||
return new MessageHolder(v, type);
|
||||
}
|
||||
else if (type == INTRODUCTION_IN) {
|
||||
v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.list_item_introduction_in, viewGroup, false);
|
||||
return new IntroductionHolder(v, type);
|
||||
}
|
||||
else if (type == INTRODUCTION_OUT) {
|
||||
v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.list_item_introduction_out, viewGroup, false);
|
||||
return new IntroductionHolder(v, type);
|
||||
}
|
||||
else if (type == NOTICE_IN) {
|
||||
v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.list_item_notice_in, viewGroup, false);
|
||||
return new NoticeHolder(v, type);
|
||||
}
|
||||
else if (type == NOTICE_OUT) {
|
||||
v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.list_item_notice_out, viewGroup, false);
|
||||
return new NoticeHolder(v, type);
|
||||
}
|
||||
// incoming message (non-local)
|
||||
else {
|
||||
v = LayoutInflater.from(viewGroup.getContext())
|
||||
.inflate(R.layout.list_item_msg_in, viewGroup, false);
|
||||
return new MessageHolder(v, type);
|
||||
}
|
||||
|
||||
return new MessageHolder(v, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final MessageHolder ui, final int position) {
|
||||
public void onBindViewHolder(final ViewHolder ui, final int position) {
|
||||
ConversationItem item = getItem(position);
|
||||
if (item instanceof ConversationMessageItem) {
|
||||
bindMessage((MessageHolder) ui, (ConversationMessageItem) item,
|
||||
position);
|
||||
} else if (item instanceof ConversationIntroductionOutItem) {
|
||||
bindIntroduction((IntroductionHolder) ui,
|
||||
(ConversationIntroductionOutItem) item, position);
|
||||
} else if (item instanceof ConversationIntroductionInItem) {
|
||||
bindIntroduction((IntroductionHolder) ui,
|
||||
(ConversationIntroductionInItem) item, position);
|
||||
} else if (item instanceof ConversationNoticeOutItem) {
|
||||
bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item,
|
||||
position);
|
||||
} else if (item instanceof ConversationNoticeInItem) {
|
||||
bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item,
|
||||
position);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unhandled Conversation Item");
|
||||
}
|
||||
}
|
||||
|
||||
private void bindMessage(final MessageHolder ui,
|
||||
ConversationMessageItem item, final int position) {
|
||||
PrivateMessageHeader header = item.getHeader();
|
||||
|
||||
if (header.isLocal()) {
|
||||
if (item.getType() == MSG_OUT) {
|
||||
if (item.isSeen()) {
|
||||
ui.status.setImageResource(R.drawable.message_delivered);
|
||||
ui.status.setImageResource(R.drawable.message_delivered_white);
|
||||
} else if (item.isSent()) {
|
||||
ui.status.setImageResource(R.drawable.message_sent);
|
||||
ui.status.setImageResource(R.drawable.message_sent_white);
|
||||
} else {
|
||||
ui.status.setImageResource(R.drawable.message_stored);
|
||||
ui.status.setImageResource(R.drawable.message_stored_white);
|
||||
}
|
||||
} else {
|
||||
if (identiconKey != null)
|
||||
ui.avatar.setImageDrawable(
|
||||
new IdenticonDrawable(identiconKey));
|
||||
if (!header.isRead()) {
|
||||
int left = ui.layout.getPaddingLeft();
|
||||
ui.avatar.setImageDrawable(new IdenticonDrawable(identiconKey));
|
||||
if (item.getType() == MSG_IN_UNREAD) {
|
||||
// TODO implement new unread message highlight according to #232
|
||||
/* int left = ui.layout.getPaddingLeft();
|
||||
int top = ui.layout.getPaddingTop();
|
||||
int right = ui.layout.getPaddingRight();
|
||||
int bottom = ui.layout.getPaddingBottom();
|
||||
@@ -144,6 +197,7 @@ class ConversationAdapter extends
|
||||
// re-apply the previous padding due to bug in some Android versions
|
||||
// see: https://code.google.com/p/android/issues/detail?id=17885
|
||||
ui.layout.setPadding(left, top, right, bottom);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,42 +213,178 @@ class ConversationAdapter extends
|
||||
ui.date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
|
||||
}
|
||||
|
||||
private void bindIntroduction(final IntroductionHolder ui,
|
||||
final ConversationIntroductionInItem item, final int position) {
|
||||
|
||||
final IntroductionRequest ir = item.getIntroductionRequest();
|
||||
|
||||
final String message = ir.getMessage();
|
||||
if (StringUtils.isNullOrEmpty(message)) {
|
||||
ui.messageLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
ui.messageLayout.setVisibility(View.VISIBLE);
|
||||
if (item.getType() == INTRODUCTION_IN && identiconKey != null) {
|
||||
ui.message.avatar.setImageDrawable(
|
||||
new IdenticonDrawable(identiconKey));
|
||||
}
|
||||
ui.message.body.setText(message);
|
||||
ui.message.date.setText(
|
||||
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
|
||||
}
|
||||
|
||||
// Outgoing Introduction Request
|
||||
if (item instanceof ConversationIntroductionOutItem) {
|
||||
ui.text.setText(ctx.getString(R.string.introduction_request_sent,
|
||||
contactName, ir.getName()));
|
||||
ConversationIntroductionOutItem i =
|
||||
(ConversationIntroductionOutItem) item;
|
||||
if (i.isSeen()) {
|
||||
ui.status.setImageResource(R.drawable.message_delivered);
|
||||
ui.message.status.setImageResource(R.drawable.message_delivered_white);
|
||||
} else if (i.isSent()) {
|
||||
ui.status.setImageResource(R.drawable.message_sent);
|
||||
ui.message.status.setImageResource(R.drawable.message_sent_white);
|
||||
} else {
|
||||
ui.status.setImageResource(R.drawable.message_stored);
|
||||
ui.message.status.setImageResource(R.drawable.message_stored_white);
|
||||
}
|
||||
}
|
||||
// Incoming Introduction Request (Answered)
|
||||
else if (item.wasAnswered()) {
|
||||
ui.text.setText(ctx.getString(
|
||||
R.string.introduction_request_answered_received,
|
||||
contactName, ir.getName()));
|
||||
ui.acceptButton.setVisibility(View.GONE);
|
||||
ui.declineButton.setVisibility(View.GONE);
|
||||
}
|
||||
// Incoming Introduction Request (Not Answered)
|
||||
else {
|
||||
if (item.getIntroductionRequest().doesExist()) {
|
||||
ui.text.setText(ctx.getString(
|
||||
R.string.introduction_request_exists_received,
|
||||
contactName, ir.getName()));
|
||||
} else {
|
||||
ui.text.setText(
|
||||
ctx.getString(R.string.introduction_request_received,
|
||||
contactName, ir.getName()));
|
||||
}
|
||||
|
||||
ui.acceptButton.setVisibility(View.VISIBLE);
|
||||
ui.acceptButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
intro.respondToIntroduction(ir.getSessionId(), true);
|
||||
item.setAnswered(true);
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
});
|
||||
ui.declineButton.setVisibility(View.VISIBLE);
|
||||
ui.declineButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
intro.respondToIntroduction(ir.getSessionId(), false);
|
||||
item.setAnswered(true);
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.date.setText(
|
||||
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
|
||||
}
|
||||
|
||||
private void bindNotice(final NoticeHolder ui,
|
||||
final ConversationNoticeItem item, final int position) {
|
||||
|
||||
ui.text.setText(item.getText());
|
||||
ui.date.setText(
|
||||
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
|
||||
|
||||
if (item instanceof ConversationNoticeOutItem) {
|
||||
ConversationNoticeOutItem n = (ConversationNoticeOutItem) item;
|
||||
if (n.isSeen()) {
|
||||
ui.status.setImageResource(R.drawable.message_delivered);
|
||||
} else if (n.isSent()) {
|
||||
ui.status.setImageResource(R.drawable.message_sent);
|
||||
} else {
|
||||
ui.status.setImageResource(R.drawable.message_stored);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return messages.size();
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public ConversationItem getItem(int position) {
|
||||
if (position == INVALID_POSITION || messages.size() <= position) {
|
||||
if (position == INVALID_POSITION || items.size() <= position) {
|
||||
return null; // Not found
|
||||
}
|
||||
return messages.get(position);
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
public ConversationItem getLastItem() {
|
||||
if (messages.size() > 0) {
|
||||
return messages.get(messages.size() - 1);
|
||||
if (items.size() > 0) {
|
||||
return items.get(items.size() - 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public SparseArray<IncomingItem> getIncomingMessages() {
|
||||
SparseArray<IncomingItem> messages =
|
||||
new SparseArray<IncomingItem>();
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ConversationItem item = items.get(i);
|
||||
if (item instanceof IncomingItem) {
|
||||
messages.put(i, (IncomingItem) item);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public SparseArray<OutgoingItem> getOutgoingMessages() {
|
||||
SparseArray<OutgoingItem> messages =
|
||||
new SparseArray<OutgoingItem>();
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ConversationItem item = items.get(i);
|
||||
if (item instanceof OutgoingItem) {
|
||||
messages.put(i, (OutgoingItem) item);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public SparseArray<ConversationMessageItem> getPrivateMessages() {
|
||||
SparseArray<ConversationMessageItem> messages =
|
||||
new SparseArray<ConversationMessageItem>();
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
ConversationItem item = items.get(i);
|
||||
if (item instanceof ConversationMessageItem) {
|
||||
messages.put(i, (ConversationMessageItem) item);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void add(final ConversationItem message) {
|
||||
this.messages.add(message);
|
||||
this.items.add(message);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.messages.beginBatchedUpdates();
|
||||
this.items.beginBatchedUpdates();
|
||||
|
||||
while(messages.size() != 0) {
|
||||
messages.removeItemAt(0);
|
||||
while(items.size() != 0) {
|
||||
items.removeItemAt(0);
|
||||
}
|
||||
|
||||
this.messages.endBatchedUpdates();
|
||||
this.items.endBatchedUpdates();
|
||||
}
|
||||
|
||||
// TODO: Does this class need to be public?
|
||||
public static class MessageHolder extends RecyclerView.ViewHolder {
|
||||
protected class MessageHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public ViewGroup layout;
|
||||
public TextView body;
|
||||
@@ -217,4 +407,59 @@ class ConversationAdapter extends
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class IntroductionHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public ViewGroup layout;
|
||||
public View messageLayout;
|
||||
public MessageHolder message;
|
||||
public TextView text;
|
||||
public Button acceptButton;
|
||||
public Button declineButton;
|
||||
public TextView date;
|
||||
public ImageView status;
|
||||
|
||||
public IntroductionHolder(View v, int type) {
|
||||
super(v);
|
||||
|
||||
layout = (ViewGroup) v.findViewById(R.id.introductionLayout);
|
||||
messageLayout = v.findViewById(R.id.messageLayout);
|
||||
message = new MessageHolder(messageLayout,
|
||||
type == INTRODUCTION_IN ? MSG_IN : MSG_OUT);
|
||||
text = (TextView) v.findViewById(R.id.introductionText);
|
||||
acceptButton = (Button) v.findViewById(R.id.acceptButton);
|
||||
declineButton = (Button) v.findViewById(R.id.declineButton);
|
||||
date = (TextView) v.findViewById(R.id.introductionTime);
|
||||
|
||||
if (type == INTRODUCTION_OUT) {
|
||||
status = (ImageView) v.findViewById(R.id.introductionStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected class NoticeHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public ViewGroup layout;
|
||||
public TextView text;
|
||||
public TextView date;
|
||||
public ImageView status;
|
||||
|
||||
public NoticeHolder(View v, int type) {
|
||||
super(v);
|
||||
|
||||
layout = (ViewGroup) v.findViewById(R.id.noticeLayout);
|
||||
text = (TextView) v.findViewById(R.id.noticeText);
|
||||
date = (TextView) v.findViewById(R.id.noticeTime);
|
||||
|
||||
if (type == NOTICE_OUT) {
|
||||
status = (ImageView) v.findViewById(R.id.noticeStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IntroductionHandler {
|
||||
void respondToIntroduction(final SessionId sessionId,
|
||||
final boolean accept);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
public class ConversationIntroductionInItem extends ConversationItem implements
|
||||
ConversationItem.IncomingItem {
|
||||
|
||||
private IntroductionRequest ir;
|
||||
private boolean answered, read;
|
||||
|
||||
public ConversationIntroductionInItem(IntroductionRequest ir) {
|
||||
super(ir.getMessageId(), ir.getTime());
|
||||
|
||||
this.ir = ir;
|
||||
this.answered = ir.wasAnswered();
|
||||
this.read = ir.isRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
int getType() {
|
||||
return INTRODUCTION_IN;
|
||||
}
|
||||
|
||||
public IntroductionRequest getIntroductionRequest() {
|
||||
return ir;
|
||||
}
|
||||
|
||||
public boolean wasAnswered() {
|
||||
return answered;
|
||||
}
|
||||
|
||||
public void setAnswered(boolean answered) {
|
||||
this.answered = answered;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRead(boolean read) {
|
||||
this.read = read;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
/**
|
||||
* This class is needed and can not be replaced by an ConversationNoticeOutItem,
|
||||
* because it carries the optional introduction message
|
||||
* to be displayed as a regular private message.
|
||||
*/
|
||||
public class ConversationIntroductionOutItem
|
||||
extends ConversationIntroductionInItem
|
||||
implements ConversationItem.OutgoingItem {
|
||||
|
||||
private boolean sent, seen;
|
||||
|
||||
public ConversationIntroductionOutItem(IntroductionRequest ir) {
|
||||
super(ir);
|
||||
this.sent = ir.isSent();
|
||||
this.seen = ir.isSeen();
|
||||
}
|
||||
|
||||
@Override
|
||||
int getType() {
|
||||
return INTRODUCTION_OUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSent() {
|
||||
return sent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSent(boolean sent) {
|
||||
this.sent = sent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSeen() {
|
||||
return seen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeen(boolean seen) {
|
||||
this.seen = seen;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +1,105 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import android.content.Context;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.api.introduction.IntroductionResponse;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
// This class is not thread-safe
|
||||
class ConversationItem {
|
||||
public abstract class ConversationItem {
|
||||
|
||||
private final PrivateMessageHeader header;
|
||||
private byte[] body;
|
||||
private boolean sent, seen;
|
||||
final static int MSG_IN = 0;
|
||||
final static int MSG_IN_UNREAD = 1;
|
||||
final static int MSG_OUT = 2;
|
||||
final static int INTRODUCTION_IN = 3;
|
||||
final static int INTRODUCTION_OUT = 4;
|
||||
final static int NOTICE_IN = 5;
|
||||
final static int NOTICE_OUT = 6;
|
||||
|
||||
ConversationItem(PrivateMessageHeader header) {
|
||||
this.header = header;
|
||||
body = null;
|
||||
sent = header.isSent();
|
||||
seen = header.isSeen();
|
||||
private MessageId id;
|
||||
private long time;
|
||||
|
||||
public ConversationItem(MessageId id, long time) {
|
||||
this.id = id;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
PrivateMessageHeader getHeader() {
|
||||
return header;
|
||||
abstract int getType();
|
||||
|
||||
public MessageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
byte[] getBody() {
|
||||
return body;
|
||||
long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
void setBody(byte[] body) {
|
||||
this.body = body;
|
||||
public static ConversationItem from(IntroductionRequest ir) {
|
||||
if (ir.isLocal()) {
|
||||
return new ConversationIntroductionOutItem(ir);
|
||||
} else {
|
||||
return new ConversationIntroductionInItem(ir);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isSent() {
|
||||
return sent;
|
||||
public static ConversationItem from(Context ctx, String contactName,
|
||||
IntroductionResponse ir) {
|
||||
|
||||
if (ir.isLocal()) {
|
||||
String text;
|
||||
if (ir.wasAccepted()) {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_accepted_sent,
|
||||
ir.getName());
|
||||
} else {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_declined_sent,
|
||||
ir.getName());
|
||||
}
|
||||
return new ConversationNoticeOutItem(ir.getMessageId(), text,
|
||||
ir.getTime(), ir.isSent(), ir.isSeen());
|
||||
} else {
|
||||
String text;
|
||||
if (ir.wasAccepted()) {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_accepted_received,
|
||||
contactName, ir.getName());
|
||||
} else {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_response_declined_received,
|
||||
contactName, ir.getName());
|
||||
}
|
||||
return new ConversationNoticeInItem(ir.getMessageId(), text,
|
||||
ir.getTime(), ir.isRead());
|
||||
}
|
||||
}
|
||||
|
||||
void setSent(boolean sent) {
|
||||
this.sent = sent;
|
||||
/** This method should not be used to get user-facing objects,
|
||||
* Its purpose is to provider data for the contact list.
|
||||
*/
|
||||
public static ConversationItem from(IntroductionMessage im) {
|
||||
if (im.isLocal())
|
||||
return new ConversationNoticeOutItem(im.getMessageId(), "",
|
||||
im.getTime(), false, false);
|
||||
return new ConversationNoticeInItem(im.getMessageId(), "", im.getTime(),
|
||||
im.isRead());
|
||||
}
|
||||
|
||||
boolean isSeen() {
|
||||
return seen;
|
||||
protected interface OutgoingItem {
|
||||
MessageId getId();
|
||||
boolean isSent();
|
||||
void setSent(boolean sent);
|
||||
boolean isSeen();
|
||||
void setSeen(boolean seen);
|
||||
}
|
||||
|
||||
void setSeen(boolean seen) {
|
||||
this.seen = seen;
|
||||
protected interface IncomingItem {
|
||||
MessageId getId();
|
||||
boolean isRead();
|
||||
void setRead(boolean read);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
// This class is not thread-safe
|
||||
public class ConversationMessageItem extends ConversationItem implements
|
||||
ConversationItem.OutgoingItem, ConversationItem.IncomingItem {
|
||||
|
||||
private final PrivateMessageHeader header;
|
||||
private byte[] body;
|
||||
private boolean sent, seen, read;
|
||||
|
||||
public ConversationMessageItem(PrivateMessageHeader header) {
|
||||
super(header.getId(), header.getTimestamp());
|
||||
|
||||
this.header = header;
|
||||
body = null;
|
||||
sent = header.isSent();
|
||||
seen = header.isSeen();
|
||||
read = header.isRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
int getType() {
|
||||
if (getHeader().isLocal()) return MSG_OUT;
|
||||
if (getHeader().isRead()) return MSG_IN;
|
||||
return MSG_IN_UNREAD;
|
||||
}
|
||||
|
||||
PrivateMessageHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
void setBody(byte[] body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSent() {
|
||||
return sent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSent(boolean sent) {
|
||||
this.sent = sent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSeen() {
|
||||
return seen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeen(boolean seen) {
|
||||
this.seen = seen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRead(boolean read) {
|
||||
this.read = read;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
public class ConversationNoticeInItem extends ConversationNoticeItem implements
|
||||
ConversationItem.IncomingItem {
|
||||
|
||||
private boolean read;
|
||||
|
||||
public ConversationNoticeInItem(MessageId id, String text, long time,
|
||||
boolean read) {
|
||||
super(id, text, time);
|
||||
|
||||
this.read = read;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getType() {
|
||||
return NOTICE_IN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRead() {
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRead(boolean read) {
|
||||
this.read = read;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
abstract class ConversationNoticeItem extends ConversationItem {
|
||||
|
||||
private String text;
|
||||
|
||||
public ConversationNoticeItem(MessageId id, String text, long time) {
|
||||
super(id, time);
|
||||
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
public class ConversationNoticeOutItem extends ConversationNoticeItem implements
|
||||
ConversationItem.OutgoingItem {
|
||||
|
||||
private boolean sent, seen;
|
||||
|
||||
public ConversationNoticeOutItem(MessageId id, String text, long time,
|
||||
boolean sent, boolean seen) {
|
||||
|
||||
super(id, text, time);
|
||||
|
||||
this.sent = sent;
|
||||
this.seen = seen;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getType() {
|
||||
return NOTICE_OUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSent() {
|
||||
return sent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSent(boolean sent) {
|
||||
this.sent = sent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSeen() {
|
||||
return seen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSeen(boolean seen) {
|
||||
this.seen = seen;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package org.briarproject.android.introduction;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.Fade;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.contact.ContactListAdapter;
|
||||
import org.briarproject.android.contact.ContactListItem;
|
||||
import org.briarproject.android.contact.ConversationItem;
|
||||
import org.briarproject.android.contact.ConversationMessageItem;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.introduction.IntroductionManager;
|
||||
import org.briarproject.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.api.messaging.MessagingManager;
|
||||
import org.briarproject.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.api.plugins.ConnectionRegistry;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
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 ContactChooserFragment extends BaseFragment {
|
||||
|
||||
public final static String TAG = "ContactChooserFragment";
|
||||
private IntroductionActivity introductionActivity;
|
||||
private BriarRecyclerView list;
|
||||
private ContactListAdapter adapter;
|
||||
private int contactId;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ContactChooserFragment.class.getName());
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
protected volatile Contact c1;
|
||||
@Inject
|
||||
protected volatile ContactManager contactManager;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
@Inject
|
||||
protected volatile MessagingManager messagingManager;
|
||||
@Inject
|
||||
protected volatile IntroductionManager introductionManager;
|
||||
@Inject
|
||||
protected volatile ConnectionRegistry connectionRegistry;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
introductionActivity = (IntroductionActivity) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new java.lang.InstantiationError(
|
||||
"This fragment is only meant to be attached to the IntroductionActivity");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(AndroidComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
View contentView =
|
||||
inflater.inflate(R.layout.introduction_contact_chooser,
|
||||
container, false);
|
||||
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
setExitTransition(new Fade());
|
||||
}
|
||||
|
||||
ContactListAdapter.OnItemClickListener onItemClickListener =
|
||||
new ContactListAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(View view, ContactListItem item) {
|
||||
if (c1 == null) {
|
||||
Toast.makeText(getActivity(),
|
||||
R.string.introduction_error,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
Contact c2 = item.getContact();
|
||||
if (!c1.getLocalAuthorId()
|
||||
.equals(c2.getLocalAuthorId())) {
|
||||
warnAboutDifferentIdentities(view, c1, c2);
|
||||
} else {
|
||||
introductionActivity.showMessageScreen(view, c1, c2);
|
||||
}
|
||||
}
|
||||
};
|
||||
adapter =
|
||||
new ContactListAdapter(getActivity(), onItemClickListener, true);
|
||||
|
||||
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
|
||||
list.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
list.setAdapter(adapter);
|
||||
list.setEmptyText(getString(R.string.no_contacts));
|
||||
|
||||
contactId = introductionActivity.getContactId();
|
||||
|
||||
return contentView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
loadContacts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void loadContacts() {
|
||||
introductionActivity.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
List<ContactListItem> contacts =
|
||||
new ArrayList<ContactListItem>();
|
||||
AuthorId localAuthorId= null;
|
||||
for (Contact c : contactManager.getActiveContacts()) {
|
||||
if (c.getId().getInt() == contactId) {
|
||||
c1 = c;
|
||||
localAuthorId = c1.getLocalAuthorId();
|
||||
} else {
|
||||
ContactId id = c.getId();
|
||||
GroupId groupId =
|
||||
messagingManager.getConversationId(id);
|
||||
Collection<ConversationItem> messages =
|
||||
getMessages(id);
|
||||
boolean connected =
|
||||
connectionRegistry.isConnected(c.getId());
|
||||
LocalAuthor localAuthor = identityManager
|
||||
.getLocalAuthor(c.getLocalAuthorId());
|
||||
contacts.add(new ContactListItem(c, localAuthor,
|
||||
connected, groupId, messages));
|
||||
}
|
||||
}
|
||||
displayContacts(localAuthorId, contacts);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayContacts(final AuthorId localAuthorId,
|
||||
final List<ContactListItem> contacts) {
|
||||
introductionActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
adapter.setLocalAuthor(localAuthorId);
|
||||
adapter.clear();
|
||||
if (contacts.size() == 0) list.showData();
|
||||
else adapter.addAll(contacts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void warnAboutDifferentIdentities(final View view, final Contact c1,
|
||||
final Contact c2) {
|
||||
|
||||
DialogInterface.OnClickListener okListener =
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
introductionActivity.showMessageScreen(view, c1, c2);
|
||||
}
|
||||
};
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setTitle(getString(
|
||||
R.string.introduction_warn_different_identities_title));
|
||||
builder.setMessage(getString(
|
||||
R.string.introduction_warn_different_identities_text));
|
||||
builder.setPositiveButton(R.string.dialog_button_introduce, okListener);
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
/** This needs to be called from the DbThread */
|
||||
private Collection<ConversationItem> getMessages(ContactId id)
|
||||
throws DbException {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
Collection<ConversationItem> messages =
|
||||
new ArrayList<ConversationItem>();
|
||||
|
||||
Collection<PrivateMessageHeader> headers =
|
||||
messagingManager.getMessageHeaders(id);
|
||||
for (PrivateMessageHeader h : headers) {
|
||||
messages.add(new ConversationMessageItem(h));
|
||||
}
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading message headers took " + duration + " ms");
|
||||
|
||||
Collection<IntroductionMessage> introductions =
|
||||
introductionManager
|
||||
.getIntroductionMessages(id);
|
||||
for (IntroductionMessage m : introductions) {
|
||||
messages.add(ConversationItem.from(m));
|
||||
}
|
||||
duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading introduction messages took " + duration + " ms");
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.briarproject.android.introduction;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.transition.ChangeBounds;
|
||||
import android.transition.Fade;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
|
||||
public class IntroductionActivity extends BriarActivity implements
|
||||
BaseFragment.BaseFragmentListener {
|
||||
|
||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||
private int contactId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
contactId = intent.getIntExtra(CONTACT_ID, -1);
|
||||
|
||||
setContentView(R.layout.activity_introduction);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
ContactChooserFragment chooserFragment =
|
||||
new ContactChooserFragment();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.introductionContainer, chooserFragment).commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(AndroidComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showLoadingScreen(boolean isBlocking, int stringId) {
|
||||
// this is handled by the recycler view in ContactChooserFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideLoadingScreen() {
|
||||
// this is handled by the recycler view in ContactChooserFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
if (fm.getBackStackEntryCount() == 1) {
|
||||
fm.popBackStack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
public int getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public void showMessageScreen(final View view, final Contact c1,
|
||||
final Contact c2) {
|
||||
|
||||
IntroductionMessageFragment messageFragment =
|
||||
IntroductionMessageFragment
|
||||
.newInstance(c1.getId().getInt(), c2.getId().getInt());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
messageFragment.setSharedElementEnterTransition(new ChangeBounds());
|
||||
messageFragment.setEnterTransition(new Fade());
|
||||
messageFragment.setSharedElementReturnTransition(new ChangeBounds());
|
||||
}
|
||||
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(android.R.anim.fade_in,
|
||||
android.R.anim.fade_out,
|
||||
android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right)
|
||||
.addSharedElement(view, "avatar")
|
||||
.replace(R.id.introductionContainer, messageFragment,
|
||||
ContactChooserFragment.TAG)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package org.briarproject.android.introduction;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
import org.briarproject.android.fragment.BaseFragment;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.contact.ContactManager;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.introduction.IntroductionManager;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import de.hdodenhof.circleimageview.CircleImageView;
|
||||
import im.delight.android.identicons.IdenticonDrawable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class IntroductionMessageFragment extends BaseFragment {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroductionMessageFragment.class.getName());
|
||||
|
||||
public final static String TAG = "ContactChooserFragment";
|
||||
private IntroductionActivity introductionActivity;
|
||||
private ViewHolder ui;
|
||||
|
||||
private final static String CONTACT_ID_1 = "contact1";
|
||||
private final static String CONTACT_ID_2 = "contact2";
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
private volatile boolean introductionWasMade = false;
|
||||
@Inject
|
||||
protected volatile ContactManager contactManager;
|
||||
@Inject
|
||||
protected volatile IntroductionManager introductionManager;
|
||||
|
||||
public static IntroductionMessageFragment newInstance(int contactId1,
|
||||
int contactId2) {
|
||||
IntroductionMessageFragment f = new IntroductionMessageFragment();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(CONTACT_ID_1, contactId1);
|
||||
args.putInt(CONTACT_ID_2, contactId2);
|
||||
f.setArguments(args);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
introductionActivity = (IntroductionActivity) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new java.lang.InstantiationError(
|
||||
"This fragment is only meant to be attached to the IntroductionActivity");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(AndroidComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
// change toolbar text
|
||||
ActionBar actionBar = introductionActivity.getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(R.string.introduction_message_title);
|
||||
}
|
||||
|
||||
// inflate view
|
||||
View v =
|
||||
inflater.inflate(R.layout.introduction_message, container,
|
||||
false);
|
||||
|
||||
// show progress bar until contacts have been loaded
|
||||
ui = new ViewHolder(v);
|
||||
ui.text.setVisibility(View.GONE);
|
||||
ui.button.setEnabled(false);
|
||||
|
||||
// get contact IDs from fragment arguments
|
||||
int contactId1 = getArguments().getInt(CONTACT_ID_1, -1);
|
||||
int contactId2 = getArguments().getInt(CONTACT_ID_2, -1);
|
||||
if (contactId1 == -1 || contactId2 == -1) {
|
||||
throw new java.lang.InstantiationError(
|
||||
"You need to use newInstance() to instantiate");
|
||||
}
|
||||
|
||||
// get contacts and then show view
|
||||
prepareToSetUpViews(contactId1, contactId2);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
private void prepareToSetUpViews(final int contactId1,
|
||||
final int contactId2) {
|
||||
introductionActivity.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Contact c1 = contactManager
|
||||
.getContact(new ContactId(contactId1));
|
||||
Contact c2 = contactManager
|
||||
.getContact(new ContactId(contactId2));
|
||||
setUpViews(c1, c2);
|
||||
} catch (DbException e) {
|
||||
// TODO
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpViews(final Contact c1, final Contact c2) {
|
||||
introductionActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
// set avatars
|
||||
ui.avatar1.setImageDrawable(new IdenticonDrawable(
|
||||
c1.getAuthor().getId().getBytes()));
|
||||
ui.avatar2.setImageDrawable(new IdenticonDrawable(
|
||||
c2.getAuthor().getId().getBytes()));
|
||||
|
||||
// set introduction text
|
||||
ui.text.setText(String.format(
|
||||
getString(R.string.introduction_message_text),
|
||||
c1.getAuthor().getName(), c2.getAuthor().getName()));
|
||||
|
||||
// set button action
|
||||
ui.button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onButtonClick(c1, c2);
|
||||
}
|
||||
});
|
||||
|
||||
// hide progress bar and show views
|
||||
ui.progressBar.setVisibility(View.GONE);
|
||||
ui.text.setVisibility(View.VISIBLE);
|
||||
ui.button.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void onButtonClick(final Contact c1, final Contact c2) {
|
||||
String msg = ui.message.getText().toString();
|
||||
makeIntroduction(c1, c2, msg);
|
||||
}
|
||||
|
||||
private void makeIntroduction(final Contact c1, final Contact c2,
|
||||
final String msg) {
|
||||
introductionActivity.runOnDbThread(new Runnable() {
|
||||
public void run() {
|
||||
// prevent double introductions
|
||||
if (introductionWasMade) return;
|
||||
|
||||
// actually make the introduction
|
||||
try {
|
||||
introductionManager.makeIntroduction(c1, c2, msg);
|
||||
introductionWasMade = true;
|
||||
postIntroduction(false);
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
postIntroduction(true);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
postIntroduction(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void postIntroduction(final boolean error) {
|
||||
introductionActivity.runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
introductionActivity.hideSoftKeyboard(ui.message);
|
||||
if (error) {
|
||||
Toast.makeText(introductionActivity,
|
||||
R.string.introduction_error, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
introductionActivity.setResult(Activity.RESULT_CANCELED);
|
||||
} else {
|
||||
introductionActivity.setResult(Activity.RESULT_OK);
|
||||
}
|
||||
introductionActivity.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
ProgressBar progressBar;
|
||||
ViewGroup header;
|
||||
CircleImageView avatar1;
|
||||
CircleImageView avatar2;
|
||||
TextView text;
|
||||
EditText message;
|
||||
Button button;
|
||||
|
||||
ViewHolder(View v) {
|
||||
progressBar = (ProgressBar) v.findViewById(R.id.progressBar);
|
||||
header = (ViewGroup) v.findViewById(R.id.introductionHeader);
|
||||
avatar1 = (CircleImageView) v.findViewById(R.id.avatarContact1);
|
||||
avatar2 = (CircleImageView) v.findViewById(R.id.avatarContact2);
|
||||
text = (TextView) v.findViewById(R.id.introductionText);
|
||||
message = (EditText) v.findViewById(R.id.introductionMessageView);
|
||||
button = (Button) v.findViewById(R.id.makeIntroductionButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,15 +73,10 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
}
|
||||
|
||||
emptyObserver = new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
showData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
super.onItemRangeInserted(positionStart, itemCount);
|
||||
onChanged();
|
||||
if (itemCount > 0) showData();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||