Implement new conversation button redesign

This commit is contained in:
Niels Andriesse 2020-03-12 15:57:53 +11:00 committed by gmbnt
parent 8ff4688de1
commit f6a8cd93da
13 changed files with 393 additions and 71 deletions

18
res/drawable/ic_group.xml Normal file
View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="22dp"
android:viewportWidth="32"
android:viewportHeight="22">
<path
android:pathData="M15.625,10.9375C16.6015,10.9375 17.513,10.6933 18.3593,10.2051C19.2057,9.7168 19.873,9.0495 20.3613,8.2031C20.8496,7.3568 21.0937,6.4453 21.0937,5.4687C21.0937,4.4922 20.8496,3.5807 20.3613,2.7344C19.873,1.888 19.2057,1.2207 18.3593,0.7324C17.513,0.2441 16.6015,0 15.625,0C14.6484,0 13.7369,0.2441 12.8906,0.7324C12.0442,1.2207 11.3769,1.888 10.8886,2.7344C10.4004,3.5807 10.1562,4.4922 10.1562,5.4687C10.1562,6.4453 10.4004,7.3568 10.8886,8.2031C11.3769,9.0495 12.0442,9.7168 12.8906,10.2051C13.7369,10.6933 14.6484,10.9375 15.625,10.9375ZM26.5624,9.375C27.6367,9.375 28.5562,8.9925 29.3212,8.2275C30.0862,7.4625 30.4687,6.543 30.4687,5.4687C30.4687,4.3945 30.0862,3.4749 29.3212,2.71C28.5562,1.945 27.6367,1.5625 26.5624,1.5625C25.4882,1.5625 24.5686,1.945 23.8037,2.71C23.0387,3.4749 22.6562,4.3945 22.6562,5.4687C22.6562,6.543 23.0387,7.4625 23.8037,8.2275C24.5686,8.9925 25.4882,9.375 26.5624,9.375ZM4.6875,9.375C5.7617,9.375 6.6813,8.9925 7.4463,8.2275C8.2112,7.4625 8.5937,6.543 8.5937,5.4687C8.5937,4.3945 8.2112,3.4749 7.4463,2.71C6.6813,1.945 5.7617,1.5625 4.6875,1.5625C3.6133,1.5625 2.6937,1.945 1.9287,2.71C1.1637,3.4749 0.7812,4.3945 0.7812,5.4687C0.7812,6.543 1.1637,7.4625 1.9287,8.2275C2.6937,8.9925 3.6133,9.375 4.6875,9.375ZM15.625,8.5937C14.7786,8.5937 14.0462,8.2845 13.4277,7.666C12.8092,7.0475 12.5,6.3151 12.5,5.4687C12.5,4.6224 12.8092,3.89 13.4277,3.2715C14.0462,2.653 14.7786,2.3437 15.625,2.3437C16.4713,2.3437 17.2037,2.653 17.8222,3.2715C18.4407,3.89 18.75,4.6224 18.75,5.4687C18.75,6.3151 18.4407,7.0475 17.8222,7.666C17.2037,8.2845 16.4713,8.5937 15.625,8.5937ZM26.5624,7.0312C26.1393,7.0312 25.7731,6.8766 25.4638,6.5674C25.1546,6.2581 24.9999,5.8919 24.9999,5.4687C24.9999,5.0456 25.1546,4.6794 25.4638,4.3701C25.7731,4.0609 26.1393,3.9062 26.5624,3.9062C26.9856,3.9062 27.3518,4.0609 27.6611,4.3701C27.9703,4.6794 28.1249,5.0456 28.1249,5.4687C28.1249,5.8919 27.9703,6.2581 27.6611,6.5674C27.3518,6.8766 26.9856,7.0312 26.5624,7.0312ZM4.6875,7.0312C4.2643,7.0312 3.8981,6.8766 3.5889,6.5674C3.2796,6.2581 3.125,5.8919 3.125,5.4687C3.125,5.0456 3.2796,4.6794 3.5889,4.3701C3.8981,4.0609 4.2643,3.9062 4.6875,3.9062C5.1107,3.9062 5.4769,4.0609 5.7861,4.3701C6.0954,4.6794 6.25,5.0456 6.25,5.4687C6.25,5.8919 6.0954,6.2581 5.7861,6.5674C5.4769,6.8766 5.1107,7.0312 4.6875,7.0312ZM30.0781,16.2109C30.4036,16.2109 30.6803,16.097 30.9081,15.8691C31.136,15.6412 31.2499,15.3645 31.2499,15.039C31.2499,13.8997 30.8512,12.9313 30.0536,12.1338C29.2561,11.3362 28.2877,10.9375 27.1484,10.9375L27.1484,10.9375L25.9765,10.9375C25.1301,10.9375 24.3652,11.1653 23.6816,11.6211C24.3652,12.1419 24.9348,12.7278 25.3906,13.3789C25.6184,13.3138 25.8137,13.2812 25.9765,13.2812L25.9765,13.2812L27.1484,13.2812C27.6367,13.2812 28.0517,13.4521 28.3935,13.7939C28.7353,14.1357 28.9062,14.5507 28.9062,15.039C28.9062,15.3645 29.0201,15.6412 29.248,15.8691C29.4758,16.097 29.7525,16.2109 30.0781,16.2109ZM1.1719,16.2109C1.4974,16.2109 1.7741,16.097 2.0019,15.8691C2.2298,15.6412 2.3437,15.3645 2.3437,15.039C2.3437,14.5507 2.5146,14.1357 2.8564,13.7939C3.1982,13.4521 3.6133,13.2812 4.1016,13.2812L4.1016,13.2812L5.2734,13.2812C5.4687,13.2812 5.664,13.33 5.8594,13.4277C6.3151,12.7441 6.8847,12.1419 7.5683,11.6211C6.8847,11.1653 6.1198,10.9375 5.2734,10.9375L5.2734,10.9375L4.1016,10.9375C2.9622,10.9375 1.9938,11.3362 1.1963,12.1338C0.3988,12.9313 0,13.8997 0,15.039C0,15.3645 0.1139,15.6412 0.3418,15.8691C0.5697,16.097 0.8464,16.2109 1.1719,16.2109ZM22.6562,21.8749C23.3072,21.8749 23.8606,21.6471 24.3163,21.1914C24.7721,20.7356 24.9999,20.1822 24.9999,19.5312L24.9999,19.5312L24.9999,17.3339C24.9999,16.1946 24.6744,15.153 24.0234,14.209C23.3723,13.2324 22.469,12.5569 21.3134,12.1826C20.1578,11.8082 18.9941,11.8001 17.8222,12.1582C17.0735,12.386 16.333,12.5 15.6005,12.5C14.8681,12.5 14.1438,12.386 13.4277,12.1582C12.2558,11.8001 11.0921,11.8082 9.9365,12.1826C8.7809,12.5569 7.8776,13.2243 7.2265,14.1845C6.5755,15.1448 6.25,16.1946 6.25,17.3339L6.25,17.3339L6.25,19.5312C6.25,20.1822 6.4778,20.7356 6.9336,21.1914C7.3893,21.6471 7.9427,21.8749 8.5937,21.8749L8.5937,21.8749L22.6562,21.8749ZM22.6562,19.5312L8.5937,19.5312L8.5937,17.3339C8.5937,16.455 8.903,15.6982 9.5215,15.0634C10.1399,14.4287 10.8886,14.095 11.7676,14.0625C13.0371,14.5833 14.3229,14.8437 15.625,14.8437C16.927,14.8437 18.2128,14.5833 19.4824,14.0625C20.3613,14.095 21.11,14.4287 21.7285,15.0634C22.347,15.6982 22.6562,16.455 22.6562,17.3339L22.6562,17.3339L22.6562,19.5312Z"
android:strokeWidth="1"
android:fillColor="#ffffff"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M15.625,10.9375C16.6015,10.9375 17.513,10.6933 18.3593,10.2051C19.2057,9.7168 19.873,9.0495 20.3613,8.2031C20.8496,7.3568 21.0937,6.4453 21.0937,5.4687C21.0937,4.4922 20.8496,3.5807 20.3613,2.7344C19.873,1.888 19.2057,1.2207 18.3593,0.7324C17.513,0.2441 16.6015,0 15.625,0C14.6484,0 13.7369,0.2441 12.8906,0.7324C12.0442,1.2207 11.3769,1.888 10.8886,2.7344C10.4004,3.5807 10.1562,4.4922 10.1562,5.4687C10.1562,6.4453 10.4004,7.3568 10.8886,8.2031C11.3769,9.0495 12.0442,9.7168 12.8906,10.2051C13.7369,10.6933 14.6484,10.9375 15.625,10.9375ZM26.5624,9.375C27.6367,9.375 28.5562,8.9925 29.3212,8.2275C30.0862,7.4625 30.4687,6.543 30.4687,5.4687C30.4687,4.3945 30.0862,3.4749 29.3212,2.71C28.5562,1.945 27.6367,1.5625 26.5624,1.5625C25.4882,1.5625 24.5686,1.945 23.8037,2.71C23.0387,3.4749 22.6562,4.3945 22.6562,5.4687C22.6562,6.543 23.0387,7.4625 23.8037,8.2275C24.5686,8.9925 25.4882,9.375 26.5624,9.375ZM4.6875,9.375C5.7617,9.375 6.6813,8.9925 7.4463,8.2275C8.2112,7.4625 8.5937,6.543 8.5937,5.4687C8.5937,4.3945 8.2112,3.4749 7.4463,2.71C6.6813,1.945 5.7617,1.5625 4.6875,1.5625C3.6133,1.5625 2.6937,1.945 1.9287,2.71C1.1637,3.4749 0.7812,4.3945 0.7812,5.4687C0.7812,6.543 1.1637,7.4625 1.9287,8.2275C2.6937,8.9925 3.6133,9.375 4.6875,9.375ZM15.625,8.5937C14.7786,8.5937 14.0462,8.2845 13.4277,7.666C12.8092,7.0475 12.5,6.3151 12.5,5.4687C12.5,4.6224 12.8092,3.89 13.4277,3.2715C14.0462,2.653 14.7786,2.3437 15.625,2.3437C16.4713,2.3437 17.2037,2.653 17.8222,3.2715C18.4407,3.89 18.75,4.6224 18.75,5.4687C18.75,6.3151 18.4407,7.0475 17.8222,7.666C17.2037,8.2845 16.4713,8.5937 15.625,8.5937ZM26.5624,7.0312C26.1393,7.0312 25.7731,6.8766 25.4638,6.5674C25.1546,6.2581 24.9999,5.8919 24.9999,5.4687C24.9999,5.0456 25.1546,4.6794 25.4638,4.3701C25.7731,4.0609 26.1393,3.9062 26.5624,3.9062C26.9856,3.9062 27.3518,4.0609 27.6611,4.3701C27.9703,4.6794 28.1249,5.0456 28.1249,5.4687C28.1249,5.8919 27.9703,6.2581 27.6611,6.5674C27.3518,6.8766 26.9856,7.0312 26.5624,7.0312ZM4.6875,7.0312C4.2643,7.0312 3.8981,6.8766 3.5889,6.5674C3.2796,6.2581 3.125,5.8919 3.125,5.4687C3.125,5.0456 3.2796,4.6794 3.5889,4.3701C3.8981,4.0609 4.2643,3.9062 4.6875,3.9062C5.1107,3.9062 5.4769,4.0609 5.7861,4.3701C6.0954,4.6794 6.25,5.0456 6.25,5.4687C6.25,5.8919 6.0954,6.2581 5.7861,6.5674C5.4769,6.8766 5.1107,7.0312 4.6875,7.0312ZM30.0781,16.2109C30.4036,16.2109 30.6803,16.097 30.9081,15.8691C31.136,15.6412 31.2499,15.3645 31.2499,15.039C31.2499,13.8997 30.8512,12.9313 30.0536,12.1338C29.2561,11.3362 28.2877,10.9375 27.1484,10.9375L27.1484,10.9375L25.9765,10.9375C25.1301,10.9375 24.3652,11.1653 23.6816,11.6211C24.3652,12.1419 24.9348,12.7278 25.3906,13.3789C25.6184,13.3138 25.8137,13.2812 25.9765,13.2812L25.9765,13.2812L27.1484,13.2812C27.6367,13.2812 28.0517,13.4521 28.3935,13.7939C28.7353,14.1357 28.9062,14.5507 28.9062,15.039C28.9062,15.3645 29.0201,15.6412 29.248,15.8691C29.4758,16.097 29.7525,16.2109 30.0781,16.2109ZM1.1719,16.2109C1.4974,16.2109 1.7741,16.097 2.0019,15.8691C2.2298,15.6412 2.3437,15.3645 2.3437,15.039C2.3437,14.5507 2.5146,14.1357 2.8564,13.7939C3.1982,13.4521 3.6133,13.2812 4.1016,13.2812L4.1016,13.2812L5.2734,13.2812C5.4687,13.2812 5.664,13.33 5.8594,13.4277C6.3151,12.7441 6.8847,12.1419 7.5683,11.6211C6.8847,11.1653 6.1198,10.9375 5.2734,10.9375L5.2734,10.9375L4.1016,10.9375C2.9622,10.9375 1.9938,11.3362 1.1963,12.1338C0.3988,12.9313 0,13.8997 0,15.039C0,15.3645 0.1139,15.6412 0.3418,15.8691C0.5697,16.097 0.8464,16.2109 1.1719,16.2109ZM22.6562,21.8749C23.3072,21.8749 23.8606,21.6471 24.3163,21.1914C24.7721,20.7356 24.9999,20.1822 24.9999,19.5312L24.9999,19.5312L24.9999,17.3339C24.9999,16.1946 24.6744,15.153 24.0234,14.209C23.3723,13.2324 22.469,12.5569 21.3134,12.1826C20.1578,11.8082 18.9941,11.8001 17.8222,12.1582C17.0735,12.386 16.333,12.5 15.6005,12.5C14.8681,12.5 14.1438,12.386 13.4277,12.1582C12.2558,11.8001 11.0921,11.8082 9.9365,12.1826C8.7809,12.5569 7.8776,13.2243 7.2265,14.1845C6.5755,15.1448 6.25,16.1946 6.25,17.3339L6.25,17.3339L6.25,19.5312C6.25,20.1822 6.4778,20.7356 6.9336,21.1914C7.3893,21.6471 7.9427,21.8749 8.5937,21.8749L8.5937,21.8749L22.6562,21.8749ZM22.6562,19.5312L8.5937,19.5312L8.5937,17.3339C8.5937,16.455 8.903,15.6982 9.5215,15.0634C10.1399,14.4287 10.8886,14.095 11.7676,14.0625C13.0371,14.5833 14.3229,14.8437 15.625,14.8437C16.927,14.8437 18.2128,14.5833 19.4824,14.0625C20.3613,14.095 21.11,14.4287 21.7285,15.0634C22.347,15.6982 22.6562,16.455 22.6562,17.3339L22.6562,17.3339L22.6562,19.5312Z"
android:strokeWidth="1"
android:fillColor="#ffffff"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M4.2929,14.2929C4.4804,14.1054 4.7348,14 5,14L17,14C17.5523,14 18,13.5523 18,13L18,3C18,2.4477 17.5523,2 17,2L3,2C2.4477,2 2,2.4477 2,3L2,16.5858L4.2929,14.2929ZM5.4142,16L1.7071,19.7071C1.0771,20.3371 0,19.8909 0,19L0,3C0,1.3431 1.3431,0 3,0L17,0C18.6569,0 20,1.3431 20,3L20,13C20,14.6569 18.6569,16 17,16L5.4142,16Z"
android:strokeWidth="1"
android:fillColor="#ffffff"
android:fillType="nonZero"
android:strokeColor="#ffffff"/>
</vector>

18
res/drawable/ic_plus.xml Normal file
View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="23dp"
android:height="23dp"
android:viewportWidth="23"
android:viewportHeight="23">
<path
android:pathData="M11.4167,0.1111L11.4167,0.1111A1.5278,1.5278 0,0 1,12.9444 1.6389L12.9444,20.5833A1.5278,1.5278 0,0 1,11.4167 22.1111L11.4167,22.1111A1.5278,1.5278 0,0 1,9.8889 20.5833L9.8889,1.6389A1.5278,1.5278 0,0 1,11.4167 0.1111z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
<path
android:pathData="M22.4167,11.1111C22.4167,11.9549 21.7327,12.6389 20.8889,12.6389L1.9444,12.6389C1.1007,12.6389 0.4167,11.9549 0.4167,11.1111C0.4167,10.2673 1.1007,9.5833 1.9444,9.5833L20.8889,9.5833C21.7327,9.5833 22.4167,10.2673 22.4167,11.1111Z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@ -1,7 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient android:type="radial" android:gradientRadius="36dp" android:startColor="@color/accent" android:endColor="@android:color/transparent" />
</shape>
android:shape="oval" />

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/accent" />
</shape>

View File

@ -40,28 +40,6 @@
android:layout_centerVertical="true"
android:layout_marginLeft="64dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_alignParentRight="true"
android:layout_centerVertical="true">
<ImageView
android:id="@+id/createClosedGroupButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_group_white_24dp" />
<ImageView
android:id="@+id/joinPublicChatButton"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginLeft="@dimen/medium_spacing"
android:src="@drawable/ic_globe" />
</LinearLayout>
</RelativeLayout>
</android.support.v7.widget.Toolbar>
@ -80,36 +58,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- The button below intentionally uses dp for the text size and not sp -->
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<org.thoughtcrime.securesms.loki.redesign.views.NewConversationButtonSetView
android:id="@+id/newConversationButtonSet"
android:layout_width="252dp"
android:layout_height="156dp"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/new_conversation_button_bottom_offset">
<View
android:layout_width="72dp"
android:layout_height="72dp"
android:background="@drawable/new_conversation_button_background" />
<Button
android:id="@+id/newConversationButton"
android:layout_width="@dimen/new_conversation_button_size"
android:layout_height="@dimen/new_conversation_button_size"
android:layout_centerInParent="true"
android:paddingLeft="1dp"
android:paddingBottom="2dp"
android:background="@drawable/new_conversation_button_foreground"
android:fontFamily="@font/roboto_light"
android:textSize="40dp"
android:textColor="#121212"
android:text="+"
android:elevation="0dp"
android:stateListAnimator="@null" />
</RelativeLayout>
android:layout_marginBottom="@dimen/new_conversation_button_bottom_offset" />
</RelativeLayout>

View File

@ -25,6 +25,7 @@
<color name="received_message_background">#222325</color>
<color name="sent_message_background">#3F4146</color>
<color name="quote_not_found_background">#99FFFFFF</color>
<color name="new_conversation_button_collapsed_background">#1F1F1F</color>
<!-- Session -->
<!-- Loki -->

View File

@ -22,7 +22,8 @@
<dimen name="conversation_view_status_indicator_size">14dp</dimen>
<dimen name="border_thickness">1dp</dimen>
<dimen name="profile_picture_border_thickness">1dp</dimen>
<dimen name="new_conversation_button_size">56dp</dimen>
<dimen name="new_conversation_button_collapsed_size">60dp</dimen>
<dimen name="new_conversation_button_expanded_size">72dp</dimen>
<dimen name="tab_bar_height">36dp</dimen>
<dimen name="text_view_corner_radius">8dp</dimen>
<dimen name="fake_chat_view_bubble_width">224dp</dimen>

View File

@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.loki.getColorWithID
import org.thoughtcrime.securesms.loki.redesign.utilities.push
import org.thoughtcrime.securesms.loki.redesign.utilities.show
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
import org.thoughtcrime.securesms.loki.redesign.views.NewConversationButtonSetViewDelegate
import org.thoughtcrime.securesms.loki.redesign.views.SeedReminderViewDelegate
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
@ -41,7 +42,7 @@ import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import kotlin.math.abs
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate {
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate {
private lateinit var glide: GlideRequests
private val hexEncodedPublicKey: String
@ -87,8 +88,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
profileButton.hexEncodedPublicKey = hexEncodedPublicKey
profileButton.update()
profileButton.setOnClickListener { openSettings() }
createClosedGroupButton.setOnClickListener { createClosedGroup() }
joinPublicChatButton.setOnClickListener { joinPublicChat() }
// Set up seed reminder view
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
@ -125,8 +124,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
homeAdapter.changeCursor(null)
}
})
// Set up new conversation button
newConversationButton.setOnClickListener { createPrivateChat() }
newConversationButtonSet.delegate = this
// Set up typing observer
ApplicationContext.getInstance(this).typingStatusRepository.typingThreads.observe(this, Observer<Set<Long>> { threadIDs ->
val adapter = recyclerView.adapter as HomeAdapter
@ -180,7 +178,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == CreateClosedGroupActivity.createNewPrivateChatResultCode) {
createPrivateChat()
createNewPrivateChat()
}
}
// endregion
@ -215,17 +213,17 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
show(intent)
}
private fun createPrivateChat() {
override fun createNewPrivateChat() {
val intent = Intent(this, CreatePrivateChatActivity::class.java)
show(intent)
}
private fun createClosedGroup() {
override fun createNewClosedGroup() {
val intent = Intent(this, CreateClosedGroupActivity::class.java)
show(intent, true)
}
private fun joinPublicChat() {
override fun joinOpenGroup() {
val intent = Intent(this, JoinPublicChatActivity::class.java)
show(intent)
}

View File

@ -0,0 +1,32 @@
package org.thoughtcrime.securesms.loki.redesign.utilities
import android.graphics.PointF
import android.view.View
fun PointF.distanceTo(other: PointF): Float {
return Math.sqrt(Math.pow(this.x.toDouble() - other.x.toDouble(), 2.toDouble()) + Math.pow(this.y.toDouble() - other.y.toDouble(), 2.toDouble())).toFloat()
}
fun PointF.isLeftOf(view: View, margin: Float = 0.0f): Boolean {
return isContainedVerticallyIn(view, margin) && x < view.hitRect.left
}
fun PointF.isAbove(view: View, margin: Float = 0.0f): Boolean {
return isContainedHorizontallyIn(view, margin) && y < view.hitRect.top
}
fun PointF.isRightOf(view: View, margin: Float = 0.0f): Boolean {
return isContainedVerticallyIn(view, margin) && x > view.hitRect.right
}
fun PointF.isBelow(view: View, margin: Float = 0.0f): Boolean {
return isContainedHorizontallyIn(view, margin) && y > view.hitRect.bottom
}
fun PointF.isContainedHorizontallyIn(view: View, margin: Float = 0.0f): Boolean {
return x >= view.hitRect.left - margin || x <= view.hitRect.right + margin
}
fun PointF.isContainedVerticallyIn(view: View, margin: Float = 0.0f): Boolean {
return y >= view.hitRect.top - margin || x <= view.hitRect.bottom + margin
}

View File

@ -0,0 +1,9 @@
package org.thoughtcrime.securesms.loki.redesign.utilities
import android.view.ViewGroup
fun ViewGroup.disableClipping() {
clipToPadding = false
clipChildren = false
clipToOutline = false
}

View File

@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.loki.redesign.utilities
import android.graphics.PointF
import android.graphics.Rect
import android.view.View
fun View.contains(point: PointF): Boolean {
return hitRect.contains(point.x.toInt(), point.y.toInt())
}
val View.hitRect: Rect
get() {
val rect = Rect()
getHitRect(rect)
return rect
}

View File

@ -0,0 +1,272 @@
package org.thoughtcrime.securesms.loki.redesign.views
import android.animation.ArgbEvaluator
import android.animation.FloatEvaluator
import android.animation.PointFEvaluator
import android.animation.ValueAnimator
import android.content.Context
import android.content.Context.VIBRATOR_SERVICE
import android.content.res.ColorStateList
import android.graphics.PointF
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.os.VibrationEffect
import android.os.VibrationEffect.DEFAULT_AMPLITUDE
import android.os.Vibrator
import android.support.annotation.ColorRes
import android.support.annotation.DimenRes
import android.support.annotation.DrawableRes
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ImageView
import android.widget.RelativeLayout
import network.loki.messenger.R
import org.thoughtcrime.securesms.loki.getColorWithID
import org.thoughtcrime.securesms.loki.redesign.utilities.*
import org.thoughtcrime.securesms.loki.toPx
class NewConversationButtonSetView : RelativeLayout {
private var expandedButton: Button? = null
var delegate: NewConversationButtonSetViewDelegate? = null
// region Convenience
private val sessionButtonExpandedPosition: PointF get() { return PointF(width.toFloat() / 2 - sessionButton.expandedSize / 2, 0.0f) }
private val closedGroupButtonExpandedPosition: PointF get() { return PointF(width.toFloat() - closedGroupButton.expandedSize, height.toFloat() - closedGroupButton.expandedSize) }
private val openGroupButtonExpandedPosition: PointF get() { return PointF(0.0f, height.toFloat() - openGroupButton.expandedSize) }
private val buttonRestPosition: PointF get() { return PointF(width.toFloat() / 2 - mainButton.expandedSize / 2, height.toFloat() - mainButton.expandedSize) }
// endregion
// region Settings
private val maxDragDistance by lazy { toPx(56, resources).toFloat() }
private val dragMargin by lazy { toPx(16, resources).toFloat() }
// endregion
// region Components
private val mainButton by lazy { Button(context, true, R.drawable.ic_plus) }
private val sessionButton by lazy { Button(context, false, R.drawable.ic_message) }
private val closedGroupButton by lazy { Button(context, false, R.drawable.ic_group) }
private val openGroupButton by lazy { Button(context, false, R.drawable.ic_globe) }
// endregion
// region Button
class Button : RelativeLayout {
@DrawableRes private var iconID = 0
private var isMain = false
companion object {
val animationDuration = 250.toLong()
}
val expandedSize by lazy { resources.getDimension(R.dimen.new_conversation_button_expanded_size) }
val collapsedSize by lazy { resources.getDimension(R.dimen.new_conversation_button_collapsed_size) }
private val expandedImageViewPosition by lazy { PointF(0.0f, 0.0f) }
private val collapsedImageViewPosition by lazy { PointF((expandedSize - collapsedSize) / 2, (expandedSize - collapsedSize) / 2) }
private val imageView by lazy {
val result = ImageView(context)
val size = collapsedSize.toInt()
result.layoutParams = LayoutParams(size, size)
result.setBackgroundResource(R.drawable.new_conversation_button_background)
val background = result.background as GradientDrawable
val colorID = if (isMain) R.color.accent else R.color.new_conversation_button_collapsed_background
background.color = ColorStateList.valueOf(resources.getColorWithID(colorID, context.theme))
result.scaleType = ImageView.ScaleType.CENTER
result.setImageResource(iconID)
result
}
constructor(context: Context) : super(context) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { throw IllegalAccessException("Use Button(context:iconID:) instead.") }
constructor(context: Context, isMain: Boolean, @DrawableRes iconID: Int) : super(context) {
this.iconID = iconID
this.isMain = isMain
disableClipping()
val size = resources.getDimension(R.dimen.new_conversation_button_expanded_size).toInt()
val layoutParams = LayoutParams(size, size)
this.layoutParams = layoutParams
addView(imageView)
imageView.x = collapsedImageViewPosition.x
imageView.y = collapsedImageViewPosition.y
}
fun expand() {
animateImageViewColorChange(R.color.new_conversation_button_collapsed_background, R.color.accent)
animateImageViewSizeChange(R.dimen.new_conversation_button_collapsed_size, R.dimen.new_conversation_button_expanded_size)
animateImageViewPositionChange(collapsedImageViewPosition, expandedImageViewPosition)
}
fun collapse() {
animateImageViewColorChange(R.color.accent, R.color.new_conversation_button_collapsed_background)
animateImageViewSizeChange(R.dimen.new_conversation_button_expanded_size, R.dimen.new_conversation_button_collapsed_size)
animateImageViewPositionChange(expandedImageViewPosition, collapsedImageViewPosition)
}
private fun animateImageViewColorChange(@ColorRes startColorID: Int, @ColorRes endColorID: Int) {
val drawable = imageView.background as GradientDrawable
val startColor = resources.getColorWithID(startColorID, context.theme)
val endColor = resources.getColorWithID(endColorID, context.theme)
val animation = ValueAnimator.ofObject(ArgbEvaluator(), startColor, endColor)
animation.duration = animationDuration
animation.addUpdateListener { animator ->
val color = animator.animatedValue as Int
drawable.color = ColorStateList.valueOf(color)
}
animation.start()
}
private fun animateImageViewSizeChange(@DimenRes startSizeID: Int, @DimenRes endSizeID: Int) {
val layoutParams = imageView.layoutParams
val startSize = resources.getDimension(startSizeID)
val endSize = resources.getDimension(endSizeID)
val animation = ValueAnimator.ofObject(FloatEvaluator(), startSize, endSize)
animation.duration = animationDuration
animation.addUpdateListener { animator ->
val size = animator.animatedValue as Float
layoutParams.width = size.toInt()
layoutParams.height = size.toInt()
imageView.layoutParams = layoutParams
}
animation.start()
}
private fun animateImageViewPositionChange(startPosition: PointF, endPosition: PointF) {
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
animation.duration = animationDuration
animation.addUpdateListener { animator ->
val point = animator.animatedValue as PointF
imageView.x = point.x
imageView.y = point.y
}
animation.start()
}
fun animatePositionChange(startPosition: PointF, endPosition: PointF) {
val animation = ValueAnimator.ofObject(PointFEvaluator(), startPosition, endPosition)
animation.duration = animationDuration
animation.addUpdateListener { animator ->
val point = animator.animatedValue as PointF
x = point.x
y = point.y
}
animation.start()
}
fun animateAlphaChange(startAlpha: Float, endAlpha: Float) {
val animation = ValueAnimator.ofObject(FloatEvaluator(), startAlpha, endAlpha)
animation.duration = animationDuration
animation.addUpdateListener { animator ->
alpha = animator.animatedValue as Float
}
animation.start()
}
}
// endregion
// region Lifecycle
constructor(context: Context) : super(context) { setUpViewHierarchy() }
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { setUpViewHierarchy() }
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { setUpViewHierarchy() }
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { setUpViewHierarchy() }
private fun setUpViewHierarchy() {
disableClipping()
// Set up session button
addView(sessionButton)
sessionButton.alpha = 0.0f
val sessionButtonLayoutParams = sessionButton.layoutParams as LayoutParams
sessionButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
sessionButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
// Set up closed group button
addView(closedGroupButton)
closedGroupButton.alpha = 0.0f
val closedGroupButtonLayoutParams = closedGroupButton.layoutParams as LayoutParams
closedGroupButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
closedGroupButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
// Set up open group button
addView(openGroupButton)
openGroupButton.alpha = 0.0f
val openGroupButtonLayoutParams = openGroupButton.layoutParams as LayoutParams
openGroupButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
openGroupButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
// Set up main button
addView(mainButton)
val mainButtonLayoutParams = mainButton.layoutParams as LayoutParams
mainButtonLayoutParams.addRule(CENTER_IN_PARENT, TRUE)
mainButtonLayoutParams.addRule(ALIGN_PARENT_BOTTOM, TRUE)
}
// endregion
// region Interaction
override fun onTouchEvent(event: MotionEvent): Boolean {
val touchX = event.x
val touchY = event.y
val touch = PointF(touchX, touchY)
val expandedButton = expandedButton
val buttonsExcludingMainButton = listOf( sessionButton, closedGroupButton, openGroupButton )
when (event.action) {
MotionEvent.ACTION_DOWN -> {
val vibrator = context.getSystemService(VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(50, DEFAULT_AMPLITUDE))
} else {
vibrator.vibrate(50)
}
sessionButton.animatePositionChange(buttonRestPosition, sessionButtonExpandedPosition)
closedGroupButton.animatePositionChange(buttonRestPosition, closedGroupButtonExpandedPosition)
openGroupButton.animatePositionChange(buttonRestPosition, openGroupButtonExpandedPosition)
buttonsExcludingMainButton.forEach { it.animateAlphaChange(0.0f, 1.0f) }
}
MotionEvent.ACTION_MOVE -> {
mainButton.x = touchX - mainButton.expandedSize / 2
mainButton.y = touchY - mainButton.expandedSize / 2
mainButton.alpha = 1 - (PointF(mainButton.x, mainButton.y).distanceTo(buttonRestPosition) / maxDragDistance)
val buttonToExpand = buttonsExcludingMainButton.firstOrNull { button ->
var hasUserDraggedBeyondButton = false
if (button == openGroupButton && touch.isLeftOf(openGroupButton, dragMargin)) { hasUserDraggedBeyondButton = true }
if (button == sessionButton && touch.isAbove(sessionButton, dragMargin)) { hasUserDraggedBeyondButton = true }
if (button == closedGroupButton && touch.isRightOf(closedGroupButton, dragMargin)) { hasUserDraggedBeyondButton = true }
button.contains(touch) || hasUserDraggedBeyondButton
}
if (buttonToExpand != null) {
if (buttonToExpand == expandedButton) { return true }
expandedButton?.collapse()
buttonToExpand.expand()
this.expandedButton = buttonToExpand
} else {
expandedButton?.collapse()
this.expandedButton = null
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
expandedButton?.collapse()
this.expandedButton = null
val allButtons = listOf( mainButton ) + buttonsExcludingMainButton
allButtons.forEach {
val currentPosition = PointF(it.x, it.y)
it.animatePositionChange(currentPosition, buttonRestPosition)
val endAlpha = if (it == mainButton) 1.0f else 0.0f
it.animateAlphaChange(it.alpha, endAlpha)
}
if (event.action == MotionEvent.ACTION_UP) {
if (openGroupButton.contains(touch) || touch.isLeftOf(openGroupButton, dragMargin)) { delegate?.joinOpenGroup() }
else if (sessionButton.contains(touch) || touch.isAbove(sessionButton, dragMargin)) { delegate?.createNewPrivateChat() }
else if (closedGroupButton.contains(touch) || touch.isRightOf(closedGroupButton, dragMargin)) { delegate?.createNewClosedGroup() }
}
}
}
return true
}
// endregion
}
// region Delegate
interface NewConversationButtonSetViewDelegate {
fun joinOpenGroup()
fun createNewPrivateChat()
fun createNewClosedGroup()
}
// endregion