contact selection reeemix
1) RecyclerView-based, with better long scroller and more material-inspired look. 2) Add badge for Signal users to contact selection list. // FREEBIE
12
build.gradle
@ -59,8 +59,8 @@ dependencies {
|
|||||||
|
|
||||||
compile 'pl.tajchert:waitingdots:0.1.0'
|
compile 'pl.tajchert:waitingdots:0.1.0'
|
||||||
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
|
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
|
||||||
compile 'com.android.support:appcompat-v7:22.1.1'
|
compile 'com.android.support:appcompat-v7:22.2.1'
|
||||||
compile 'com.android.support:recyclerview-v7:21.0.3'
|
compile 'com.android.support:recyclerview-v7:22.2.1'
|
||||||
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
compile 'com.melnykov:floatingactionbutton:1.3.0'
|
||||||
compile 'com.google.zxing:android-integration:3.1.0'
|
compile 'com.google.zxing:android-integration:3.1.0'
|
||||||
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
|
||||||
@ -119,8 +119,8 @@ dependencyVerification {
|
|||||||
'com.afollestad:material-dialogs:624dffff240533ca69414464f416c81c88c5c29689bb169093b9a333104f2471',
|
'com.afollestad:material-dialogs:624dffff240533ca69414464f416c81c88c5c29689bb169093b9a333104f2471',
|
||||||
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
|
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
|
||||||
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
|
||||||
'com.android.support:appcompat-v7:9a2355537c2f01cf0b95523605c18606b8d824017e6e94a05c77b0cfc8f21c96',
|
'com.android.support:appcompat-v7:4b5ccba8c4557ef04f99aa0a80f8aa7d50f05f926a709010a54afd5c878d3618',
|
||||||
'com.android.support:recyclerview-v7:e525ad3f33c84bb12b73d2dc975b55364a53f0f2d0697e043efba59ba73e22d2',
|
'com.android.support:recyclerview-v7:b0f530a5b14334d56ce0de85527ffe93ac419bc928e2884287ce1dddfedfb505',
|
||||||
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
|
'com.melnykov:floatingactionbutton:15d58d4fac0f7a288d0e5301bbaf501a146f5b3f5921277811bf99bd3b397263',
|
||||||
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
|
||||||
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
|
||||||
@ -149,8 +149,8 @@ dependencyVerification {
|
|||||||
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
|
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
|
||||||
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
|
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
|
||||||
'org.whispersystems:curve25519-java:9ccef8f5aba05d9942336f023c589d6278b4f9135bdc34a7bade1f4e7ad65fa3',
|
'org.whispersystems:curve25519-java:9ccef8f5aba05d9942336f023c589d6278b4f9135bdc34a7bade1f4e7ad65fa3',
|
||||||
'com.android.support:support-v4:1e2e4d35ac7fd30db5ce3bc177b92e4d5af86acef2ef93e9221599d733346f56',
|
'com.android.support:support-v4:c62f0d025dafa86f423f48df9185b0d89496adbc5f6a9be5a7c394d84cf91423',
|
||||||
'com.android.support:support-annotations:7bc07519aa613b186001160403bcfd68260fa82c61cc7e83adeedc9b862b94ae',
|
'com.android.support:support-annotations:104f353b53d5dd8d64b2f77eece4b37f6b961de9732eb6b706395e91033ec70a',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
res/drawable-hdpi/ic_badge_24dp.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
res/drawable-hdpi/ic_signal_grey_24dp.png
Normal file
After Width: | Height: | Size: 875 B |
BIN
res/drawable-hdpi/ic_signal_white_48dp.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-mdpi/ic_badge_24dp.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
res/drawable-mdpi/ic_signal_grey_24dp.png
Normal file
After Width: | Height: | Size: 596 B |
BIN
res/drawable-mdpi/ic_signal_white_48dp.png
Normal file
After Width: | Height: | Size: 900 B |
9
res/drawable-v12/recycler_view_fast_scroller_bubble.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<corners android:topLeftRadius="44dp"
|
||||||
|
android:topRightRadius="44dp"
|
||||||
|
android:bottomLeftRadius="44dp"
|
||||||
|
android:bottomRightRadius="0dp"/>
|
||||||
|
<solid android:color="#FF0288D1" />
|
||||||
|
<size android:height="88dp" android:width="88dp" />
|
||||||
|
</shape>
|
BIN
res/drawable-xhdpi/ic_badge_24dp.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
res/drawable-xhdpi/ic_signal_grey_24dp.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
res/drawable-xhdpi/ic_signal_white_48dp.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/drawable-xxhdpi/ic_badge_24dp.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
res/drawable-xxhdpi/ic_signal_grey_24dp.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/drawable-xxhdpi/ic_signal_white_48dp.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
res/drawable-xxxhdpi/ic_badge_24dp.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
res/drawable-xxxhdpi/ic_signal_grey_24dp.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-xxxhdpi/ic_signal_white_48dp.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
6
res/drawable/badge_drawable.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<bitmap android:src="@drawable/ic_badge_24dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:gravity="bottom|right"
|
||||||
|
android:tileMode="disabled" />
|
9
res/drawable/recycler_view_fast_scroller_bubble.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<corners android:topLeftRadius="44dp"
|
||||||
|
android:topRightRadius="44dp"
|
||||||
|
android:bottomLeftRadius="0dp"
|
||||||
|
android:bottomRightRadius="44dp"/>
|
||||||
|
<solid android:color="#FF0288D1" />
|
||||||
|
<size android:height="88dp" android:width="88dp" />
|
||||||
|
</shape>
|
17
res/drawable/recycler_view_fast_scroller_handle.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp"/>
|
||||||
|
<solid android:color="#FF0288D1"/>
|
||||||
|
<size android:height="32dp" android:width="4dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<corners android:radius="2dp"/>
|
||||||
|
<solid android:color="#FF737373"/>
|
||||||
|
<size android:height="32dp" android:width="4dp"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
@ -9,8 +9,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<se.emilsjolander.stickylistheaders.StickyListHeadersListView
|
<android.support.v7.widget.RecyclerView
|
||||||
android:id="@android:id/list"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
@ -23,4 +23,11 @@
|
|||||||
android:textSize="20sp" />
|
android:textSize="20sp" />
|
||||||
|
|
||||||
</android.support.v4.widget.SwipeRefreshLayout>
|
</android.support.v4.widget.SwipeRefreshLayout>
|
||||||
</LinearLayout>
|
|
||||||
|
<org.thoughtcrime.securesms.components.RecyclerViewFastScroller
|
||||||
|
android:id="@+id/fast_scroller"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="right"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
@ -2,30 +2,47 @@
|
|||||||
|
|
||||||
<org.thoughtcrime.securesms.contacts.ContactSelectionListItem
|
<org.thoughtcrime.securesms.contacts.ContactSelectionListItem
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
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"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?android:attr/listPreferredItemHeight"
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
android:paddingRight="50dp">
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@drawable/conversation_list_item_background"
|
||||||
|
android:paddingLeft="48dp"
|
||||||
|
android:paddingRight="20dp">
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AvatarImageView
|
<org.thoughtcrime.securesms.components.AvatarImageView
|
||||||
android:id="@+id/contact_photo_image"
|
android:id="@+id/contact_photo_image"
|
||||||
android:layout_width="@dimen/contact_selection_photo_size"
|
android:layout_width="40dp"
|
||||||
android:layout_height="@dimen/contact_selection_photo_size"
|
android:layout_height="40dp"
|
||||||
android:foreground="@drawable/contact_photo_background"
|
android:foreground="@drawable/contact_photo_background"
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_marginLeft="10dp"
|
|
||||||
android:cropToPadding="true"
|
android:cropToPadding="true"
|
||||||
|
app:showBadge="true"
|
||||||
|
tools:src="@color/md_material_blue_600"
|
||||||
|
android:layout_marginRight="10dp"
|
||||||
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
|
android:contentDescription="@string/SingleContactSelectionActivity_contact_photo" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout android:layout_width="0dp"
|
||||||
android:id="@+id/number_container"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Frieeeeeeedrich Nieeeeeeeeeetzsche" />
|
||||||
|
|
||||||
|
<LinearLayout android:id="@+id/number_container"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:layout_marginBottom="8dip"
|
|
||||||
android:layout_marginLeft="14dip"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:layout_toRightOf="@id/contact_photo_image">
|
|
||||||
|
|
||||||
<TextView android:id="@+id/number"
|
<TextView android:id="@+id/number"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -33,7 +50,9 @@
|
|||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:fontFamily="sans-serif-light" />
|
android:textSize="14sp"
|
||||||
|
android:fontFamily="sans-serif-light"
|
||||||
|
tools:text="+1 (555) 555-5555" />
|
||||||
|
|
||||||
<TextView android:id="@+id/label"
|
<TextView android:id="@+id/label"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -42,30 +61,18 @@
|
|||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
android:fontFamily="sans-serif-light"/>
|
android:fontFamily="sans-serif-light"
|
||||||
|
tools:text="Mobile" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
|
||||||
android:id="@+id/name"
|
</LinearLayout>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_above="@id/number_container"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginBottom="1dip"
|
|
||||||
android:layout_marginLeft="14dip"
|
|
||||||
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:layout_toRightOf="@id/contact_photo_image"
|
|
||||||
android:gravity="center_vertical|left"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
|
||||||
|
|
||||||
<CheckBox android:id="@+id/check_box"
|
<CheckBox android:id="@+id/check_box"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginTop="13dp"
|
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:clickable="false" />
|
android:clickable="false" />
|
||||||
|
|
||||||
|
9
res/layout/contact_selection_recyclerview_header.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="?android:attr/listPreferredItemHeight"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#666"
|
||||||
|
android:textSize="22sp"
|
||||||
|
tools:text="H" />
|
24
res/layout/recycler_view_fast_scroller.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView android:id="@+id/fastscroller_bubble"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/recycler_view_fast_scroller_bubble"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="48sp"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
tools:text="A" />
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/fastscroller_handle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/recycler_view_fast_scroller_handle" />
|
||||||
|
|
||||||
|
</merge>
|
@ -129,7 +129,8 @@
|
|||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="AvatarImageView">
|
<declare-styleable name="AvatarImageView">
|
||||||
<attr name="inverted" format="boolean"/>
|
<attr name="inverted" format="boolean" />
|
||||||
|
<attr name="showBadge" format="boolean" />
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="ThumbnailView">
|
<declare-styleable name="ThumbnailView">
|
||||||
|
@ -18,30 +18,36 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v4.widget.SwipeRefreshLayout;
|
import android.support.v4.widget.SwipeRefreshLayout;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
|
||||||
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fragment for selecting a one or more contacts from a list.
|
* Fragment for selecting a one or more contacts from a list.
|
||||||
*
|
*
|
||||||
@ -65,9 +71,10 @@ public class ContactSelectionListFragment extends Fragment
|
|||||||
|
|
||||||
private Map<Long, String> selectedContacts;
|
private Map<Long, String> selectedContacts;
|
||||||
private OnContactSelectedListener onContactSelectedListener;
|
private OnContactSelectedListener onContactSelectedListener;
|
||||||
private StickyListHeadersListView listView;
|
|
||||||
private SwipeRefreshLayout swipeRefresh;
|
private SwipeRefreshLayout swipeRefresh;
|
||||||
private String cursorFilter;
|
private String cursorFilter;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private RecyclerViewFastScroller fastScroller;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle icicle) {
|
public void onActivityCreated(Bundle icicle) {
|
||||||
@ -89,13 +96,12 @@ public class ContactSelectionListFragment extends Fragment
|
|||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
|
||||||
|
|
||||||
emptyText = (TextView) view.findViewById(android.R.id.empty);
|
emptyText = ViewUtil.findById(view, android.R.id.empty);
|
||||||
swipeRefresh = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh);
|
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
|
||||||
listView = (StickyListHeadersListView) view.findViewById(android.R.id.list);
|
swipeRefresh = ViewUtil.findById(view, R.id.swipe_refresh);
|
||||||
listView.setFocusable(true);
|
fastScroller = ViewUtil.findById(view, R.id.fast_scroller);
|
||||||
listView.setFastScrollEnabled(true);
|
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||||
listView.setDrawingListUnderStickyHeader(false);
|
fastScroller.setRecyclerView(recyclerView);
|
||||||
listView.setOnItemClickListener(new ListClickListener());
|
|
||||||
|
|
||||||
swipeRefresh.setEnabled(getActivity().getIntent().getBooleanExtra(REFRESHABLE, true) &&
|
swipeRefresh.setEnabled(getActivity().getIntent().getBooleanExtra(REFRESHABLE, true) &&
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN);
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN);
|
||||||
@ -117,9 +123,13 @@ public class ContactSelectionListFragment extends Fragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCursor() {
|
private void initializeCursor() {
|
||||||
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(), null, isMulti());
|
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(),
|
||||||
|
null,
|
||||||
|
new ListClickListener(),
|
||||||
|
isMulti());
|
||||||
selectedContacts = adapter.getSelectedContacts();
|
selectedContacts = adapter.getSelectedContacts();
|
||||||
listView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
recyclerView.addItemDecoration(new StickyHeaderDecoration(adapter, true));
|
||||||
this.getLoaderManager().initLoader(0, null, this);
|
this.getLoaderManager().initLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,19 +157,18 @@ public class ContactSelectionListFragment extends Fragment
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||||
((CursorAdapter) listView.getAdapter()).changeCursor(data);
|
((CursorRecyclerViewAdapter) recyclerView.getAdapter()).changeCursor(data);
|
||||||
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
|
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoaderReset(Loader<Cursor> loader) {
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
((CursorAdapter) listView.getAdapter()).changeCursor(null);
|
((CursorRecyclerViewAdapter) recyclerView.getAdapter()).changeCursor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ListClickListener implements AdapterView.OnItemClickListener {
|
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> l, View v, int position, long id) {
|
public void onItemClick(ContactSelectionListItem contact) {
|
||||||
ContactSelectionListItem contact = (ContactSelectionListItem)v;
|
|
||||||
|
|
||||||
if (!isMulti() || !selectedContacts.containsKey(contact.getContactId())) {
|
if (!isMulti() || !selectedContacts.containsKey(contact.getContactId())) {
|
||||||
selectedContacts.put(contact.getContactId(), contact.getNumber());
|
selectedContacts.put(contact.getContactId(), contact.getNumber());
|
||||||
@ -185,4 +194,181 @@ public class ContactSelectionListFragment extends Fragment
|
|||||||
void onContactSelected(String number);
|
void onContactSelected(String number);
|
||||||
void onContactDeselected(String number);
|
void onContactDeselected(String number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sticky header decoration for android's RecyclerView.
|
||||||
|
*/
|
||||||
|
public static class StickyHeaderDecoration extends RecyclerView.ItemDecoration {
|
||||||
|
|
||||||
|
private Map<Long, RecyclerView.ViewHolder> mHeaderCache;
|
||||||
|
|
||||||
|
private StickyHeaderAdapter mAdapter;
|
||||||
|
|
||||||
|
private boolean mRenderInline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param adapter the sticky header adapter to use
|
||||||
|
*/
|
||||||
|
public StickyHeaderDecoration(StickyHeaderAdapter adapter, boolean renderInline) {
|
||||||
|
mAdapter = adapter;
|
||||||
|
mHeaderCache = new HashMap<>();
|
||||||
|
mRenderInline = renderInline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
|
||||||
|
RecyclerView.State state)
|
||||||
|
{
|
||||||
|
int position = parent.getChildAdapterPosition(view);
|
||||||
|
|
||||||
|
int headerHeight = 0;
|
||||||
|
if (position != RecyclerView.NO_POSITION && hasHeader(position)) {
|
||||||
|
View header = getHeader(parent, position).itemView;
|
||||||
|
headerHeight = getHeaderHeightForLayout(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
outRect.set(0, headerHeight, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasHeader(int position) {
|
||||||
|
if (position == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int previous = position - 1;
|
||||||
|
return mAdapter.getHeaderId(position) != mAdapter.getHeaderId(previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecyclerView.ViewHolder getHeader(RecyclerView parent, int position) {
|
||||||
|
final long key = mAdapter.getHeaderId(position);
|
||||||
|
|
||||||
|
if (mHeaderCache.containsKey(key)) {
|
||||||
|
return mHeaderCache.get(key);
|
||||||
|
} else {
|
||||||
|
final RecyclerView.ViewHolder holder = mAdapter.onCreateHeaderViewHolder(parent);
|
||||||
|
final View header = holder.itemView;
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
mAdapter.onBindHeaderViewHolder(holder, position);
|
||||||
|
|
||||||
|
int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
|
||||||
|
int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
|
||||||
|
|
||||||
|
int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
|
||||||
|
parent.getPaddingLeft() + parent.getPaddingRight(), header.getLayoutParams().width);
|
||||||
|
int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
|
||||||
|
parent.getPaddingTop() + parent.getPaddingBottom(), header.getLayoutParams().height);
|
||||||
|
|
||||||
|
header.measure(childWidth, childHeight);
|
||||||
|
header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
|
||||||
|
|
||||||
|
mHeaderCache.put(key, holder);
|
||||||
|
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||||
|
final int count = parent.getChildCount();
|
||||||
|
|
||||||
|
for (int layoutPos = 0; layoutPos < count; layoutPos++) {
|
||||||
|
final View child = parent.getChildAt(layoutPos);
|
||||||
|
|
||||||
|
final int adapterPos = parent.getChildAdapterPosition(child);
|
||||||
|
|
||||||
|
if (adapterPos != RecyclerView.NO_POSITION && (layoutPos == 0 || hasHeader(adapterPos))) {
|
||||||
|
View header = getHeader(parent, adapterPos).itemView;
|
||||||
|
c.save();
|
||||||
|
final int left = child.getLeft();
|
||||||
|
final int top = getHeaderTop(parent, child, header, adapterPos, layoutPos);
|
||||||
|
c.translate(left, top);
|
||||||
|
header.draw(c);
|
||||||
|
c.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getHeaderTop(RecyclerView parent, View child, View header, int adapterPos,
|
||||||
|
int layoutPos)
|
||||||
|
{
|
||||||
|
int headerHeight = getHeaderHeightForLayout(header);
|
||||||
|
int top = getChildY(parent, child) - headerHeight;
|
||||||
|
if (layoutPos == 0) {
|
||||||
|
final int count = parent.getChildCount();
|
||||||
|
final long currentId = mAdapter.getHeaderId(adapterPos);
|
||||||
|
// find next view with header and compute the offscreen push if needed
|
||||||
|
for (int i = 1; i < count; i++) {
|
||||||
|
int adapterPosHere = parent.getChildAdapterPosition(parent.getChildAt(i));
|
||||||
|
if (adapterPosHere != RecyclerView.NO_POSITION) {
|
||||||
|
long nextId = mAdapter.getHeaderId(adapterPosHere);
|
||||||
|
if (nextId != currentId) {
|
||||||
|
final View next = parent.getChildAt(i);
|
||||||
|
final int offset = getChildY(parent, next) - (headerHeight + getHeader(parent, adapterPosHere).itemView.getHeight());
|
||||||
|
if (offset < 0) {
|
||||||
|
return offset;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
top = Math.max(0, top);
|
||||||
|
}
|
||||||
|
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getChildY(RecyclerView parent, View child) {
|
||||||
|
if (VERSION.SDK_INT < 11) {
|
||||||
|
Rect rect = new Rect();
|
||||||
|
parent.getChildVisibleRect(child, rect, null);
|
||||||
|
return rect.top;
|
||||||
|
} else {
|
||||||
|
return (int)ViewCompat.getY(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getHeaderHeightForLayout(View header) {
|
||||||
|
return mRenderInline ? 0 : header.getHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The adapter to assist the {@link StickyHeaderDecoration} in creating and binding the header views.
|
||||||
|
*
|
||||||
|
* @param <T> the header view holder
|
||||||
|
*/
|
||||||
|
public interface StickyHeaderAdapter<T extends RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the header id for the item at the given position.
|
||||||
|
*
|
||||||
|
* @param position the item position
|
||||||
|
* @return the header id
|
||||||
|
*/
|
||||||
|
long getHeaderId(int position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new header ViewHolder.
|
||||||
|
*
|
||||||
|
* @param parent the header's view parent
|
||||||
|
* @return a view holder for the created view
|
||||||
|
*/
|
||||||
|
T onCreateHeaderViewHolder(ViewGroup parent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the header view to reflect the header data for the given position
|
||||||
|
* @param viewHolder the header view holder
|
||||||
|
* @param position the header's item position
|
||||||
|
*/
|
||||||
|
void onBindHeaderViewHolder(T viewHolder, int position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,13 @@ package org.thoughtcrime.securesms.components;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.util.Pair;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
@ -16,10 +21,13 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoFactory;
|
|||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
|
import org.thoughtcrime.securesms.util.DirectoryHelper;
|
||||||
|
import org.thoughtcrime.securesms.util.DirectoryHelper.UserCapabilities.Capability;
|
||||||
|
|
||||||
public class AvatarImageView extends ImageView {
|
public class AvatarImageView extends ImageView {
|
||||||
|
|
||||||
private boolean inverted;
|
private boolean inverted;
|
||||||
|
private boolean showBadge;
|
||||||
|
|
||||||
public AvatarImageView(Context context) {
|
public AvatarImageView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@ -33,18 +41,22 @@ public class AvatarImageView extends ImageView {
|
|||||||
if (attrs != null) {
|
if (attrs != null) {
|
||||||
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
|
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.AvatarImageView, 0, 0);
|
||||||
inverted = typedArray.getBoolean(0, false);
|
inverted = typedArray.getBoolean(0, false);
|
||||||
|
showBadge = typedArray.getBoolean(1, false);
|
||||||
typedArray.recycle();
|
typedArray.recycle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAvatar(@Nullable Recipients recipients, boolean quickContactEnabled) {
|
public void setAvatar(final @Nullable Recipients recipients, boolean quickContactEnabled) {
|
||||||
if (recipients != null) {
|
if (recipients != null) {
|
||||||
MaterialColor backgroundColor = recipients.getColor();
|
MaterialColor backgroundColor = recipients.getColor();
|
||||||
setImageDrawable(recipients.getContactPhoto().asDrawable(getContext(), backgroundColor.toConversationColor(getContext()), inverted));
|
setImageDrawable(recipients.getContactPhoto().asDrawable(getContext(), backgroundColor.toConversationColor(getContext()), inverted));
|
||||||
setAvatarClickHandler(recipients, quickContactEnabled);
|
setAvatarClickHandler(recipients, quickContactEnabled);
|
||||||
|
setTag(recipients);
|
||||||
|
if (showBadge) new BadgeResolutionTask(getContext()).execute(recipients);
|
||||||
} else {
|
} else {
|
||||||
setImageDrawable(ContactPhotoFactory.getDefaultContactPhoto(null).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted));
|
setImageDrawable(ContactPhotoFactory.getDefaultContactPhoto(null).asDrawable(getContext(), ContactColors.UNKNOWN_COLOR.toConversationColor(getContext()), inverted));
|
||||||
setOnClickListener(null);
|
setOnClickListener(null);
|
||||||
|
setTag(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,4 +86,29 @@ public class AvatarImageView extends ImageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BadgeResolutionTask extends AsyncTask<Recipients,Void,Pair<Recipients, Boolean>> {
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public BadgeResolutionTask(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Pair<Recipients, Boolean> doInBackground(Recipients... recipients) {
|
||||||
|
Capability textCapability = DirectoryHelper.getUserCapabilities(context, recipients[0]).getTextCapability();
|
||||||
|
return new Pair<>(recipients[0], textCapability == Capability.SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Pair<Recipients, Boolean> result) {
|
||||||
|
if (getTag() == result.first && result.second) {
|
||||||
|
final Drawable badged = new LayerDrawable(new Drawable[] {
|
||||||
|
getDrawable(),
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.badge_drawable)
|
||||||
|
});
|
||||||
|
|
||||||
|
setImageDrawable(badged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,206 @@
|
|||||||
|
/**
|
||||||
|
* Modified version of
|
||||||
|
* https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller
|
||||||
|
*
|
||||||
|
* Their license:
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms.components;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.view.ViewCompat;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
public class RecyclerViewFastScroller extends LinearLayout {
|
||||||
|
private static final int BUBBLE_ANIMATION_DURATION = 100;
|
||||||
|
private static final int TRACK_SNAP_RANGE = 5;
|
||||||
|
|
||||||
|
private TextView bubble;
|
||||||
|
private View handle;
|
||||||
|
private RecyclerView recyclerView;
|
||||||
|
private int height;
|
||||||
|
private ObjectAnimator currentAnimator;
|
||||||
|
|
||||||
|
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
|
||||||
|
if (bubble == null || handle.isSelected())
|
||||||
|
return;
|
||||||
|
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
|
||||||
|
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
|
||||||
|
float proportion = (float)verticalScrollOffset / ((float)verticalScrollRange - height);
|
||||||
|
setBubbleAndHandlePosition(height * proportion);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public interface FastScrollAdapter {
|
||||||
|
CharSequence getBubbleText(int pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerViewFastScroller(final Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecyclerViewFastScroller(final Context context, final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
setOrientation(HORIZONTAL);
|
||||||
|
setClipChildren(false);
|
||||||
|
inflate(context, R.layout.recycler_view_fast_scroller, this);
|
||||||
|
bubble = ViewUtil.findById(this, R.id.fastscroller_bubble);
|
||||||
|
handle = ViewUtil.findById(this, R.id.fastscroller_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
height = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@TargetApi(11)
|
||||||
|
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||||
|
final int action = event.getAction();
|
||||||
|
switch (action) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
if (event.getX() < ViewUtil.getX(handle) - handle.getPaddingLeft() ||
|
||||||
|
event.getY() < ViewUtil.getY(handle) - handle.getPaddingTop() ||
|
||||||
|
event.getY() > ViewUtil.getY(handle) + handle.getHeight() + handle.getPaddingBottom())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentAnimator != null) {
|
||||||
|
currentAnimator.cancel();
|
||||||
|
}
|
||||||
|
if (bubble.getVisibility() != VISIBLE) {
|
||||||
|
showBubble();
|
||||||
|
}
|
||||||
|
handle.setSelected(true);
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
final float y = event.getY();
|
||||||
|
setBubbleAndHandlePosition(y);
|
||||||
|
setRecyclerViewPosition(y);
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
handle.setSelected(false);
|
||||||
|
hideBubble();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecyclerView(final RecyclerView recyclerView) {
|
||||||
|
this.recyclerView = recyclerView;
|
||||||
|
recyclerView.addOnScrollListener(onScrollListener);
|
||||||
|
recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreDraw() {
|
||||||
|
recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||||
|
if (bubble == null || handle.isSelected())
|
||||||
|
return true;
|
||||||
|
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
|
||||||
|
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
|
||||||
|
float proportion = (float)verticalScrollOffset / ((float)verticalScrollRange - height);
|
||||||
|
setBubbleAndHandlePosition(height * proportion);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
if (recyclerView != null)
|
||||||
|
recyclerView.removeOnScrollListener(onScrollListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setRecyclerViewPosition(float y) {
|
||||||
|
if (recyclerView != null) {
|
||||||
|
final int itemCount = recyclerView.getAdapter().getItemCount();
|
||||||
|
float proportion;
|
||||||
|
if (ViewUtil.getY(handle) == 0)
|
||||||
|
proportion = 0f;
|
||||||
|
else if (ViewUtil.getY(handle) + handle.getHeight() >= height - TRACK_SNAP_RANGE)
|
||||||
|
proportion = 1f;
|
||||||
|
else
|
||||||
|
proportion = y / (float) height;
|
||||||
|
final int targetPos = Util.clamp((int)(proportion * (float)itemCount), 0, itemCount - 1);
|
||||||
|
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
|
||||||
|
final CharSequence bubbleText = ((FastScrollAdapter) recyclerView.getAdapter()).getBubbleText(targetPos);
|
||||||
|
if (bubble != null)
|
||||||
|
bubble.setText(bubbleText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBubbleAndHandlePosition(float y) {
|
||||||
|
final int handleHeight = handle.getHeight();
|
||||||
|
final int bubbleHeight = bubble.getHeight();
|
||||||
|
ViewUtil.setY(handle, Util.clamp((int)(y - handleHeight / 2), 0, height - handleHeight));
|
||||||
|
ViewUtil.setY(bubble, Util.clamp((int)(y - bubbleHeight), 0, height - bubbleHeight - handleHeight / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(11)
|
||||||
|
private void showBubble() {
|
||||||
|
bubble.setVisibility(VISIBLE);
|
||||||
|
if (VERSION.SDK_INT >= 11) {
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(11)
|
||||||
|
private void hideBubble() {
|
||||||
|
if (VERSION.SDK_INT >= 11) {
|
||||||
|
if (currentAnimator != null) currentAnimator.cancel();
|
||||||
|
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||||
|
currentAnimator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
super.onAnimationEnd(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
super.onAnimationCancel(animation);
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
currentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentAnimator.start();
|
||||||
|
} else {
|
||||||
|
bubble.setVisibility(INVISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,27 +20,37 @@ import android.content.Context;
|
|||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.util.Log;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.text.style.ImageSpan;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller.FastScrollAdapter;
|
||||||
|
import org.thoughtcrime.securesms.ContactSelectionListFragment.StickyHeaderAdapter;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.HeaderViewHolder;
|
||||||
|
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.ViewHolder;
|
||||||
|
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List adapter to display all contacts and their related information
|
* List adapter to display all contacts and their related information
|
||||||
*
|
*
|
||||||
* @author Jake McGinty
|
* @author Jake McGinty
|
||||||
*/
|
*/
|
||||||
public class ContactSelectionListAdapter extends CursorAdapter
|
public class ContactSelectionListAdapter extends CursorRecyclerViewAdapter<ViewHolder>
|
||||||
implements StickyListHeadersAdapter
|
implements FastScrollAdapter,
|
||||||
|
StickyHeaderAdapter<HeaderViewHolder>
|
||||||
{
|
{
|
||||||
private final static String TAG = ContactSelectionListAdapter.class.getSimpleName();
|
private final static String TAG = ContactSelectionListAdapter.class.getSimpleName();
|
||||||
|
|
||||||
@ -50,71 +60,134 @@ public class ContactSelectionListAdapter extends CursorAdapter
|
|||||||
private final boolean multiSelect;
|
private final boolean multiSelect;
|
||||||
private final LayoutInflater li;
|
private final LayoutInflater li;
|
||||||
private final TypedArray drawables;
|
private final TypedArray drawables;
|
||||||
|
private final ItemClickListener clickListener;
|
||||||
|
|
||||||
private final HashMap<Long, String> selectedContacts = new HashMap<>();
|
private final HashMap<Long, String> selectedContacts = new HashMap<>();
|
||||||
|
|
||||||
public ContactSelectionListAdapter(Context context, Cursor cursor, boolean multiSelect) {
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
super(context, cursor, 0);
|
public ViewHolder(@NonNull final View itemView,
|
||||||
|
@Nullable final ItemClickListener clickListener)
|
||||||
|
{
|
||||||
|
super(itemView);
|
||||||
|
itemView.setOnClickListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
if (clickListener != null) clickListener.onItemClick(getView());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactSelectionListItem getView() {
|
||||||
|
return (ContactSelectionListItem) itemView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
public HeaderViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactSelectionListAdapter(@NonNull Context context,
|
||||||
|
@Nullable Cursor cursor,
|
||||||
|
@Nullable ItemClickListener clickListener,
|
||||||
|
boolean multiSelect)
|
||||||
|
{
|
||||||
|
super(context, cursor);
|
||||||
this.li = LayoutInflater.from(context);
|
this.li = LayoutInflater.from(context);
|
||||||
this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
|
this.drawables = context.obtainStyledAttributes(STYLE_ATTRIBUTES);
|
||||||
this.multiSelect = multiSelect;
|
this.multiSelect = multiSelect;
|
||||||
|
this.clickListener = clickListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
public long getHeaderId(int i) {
|
||||||
return li.inflate(R.layout.contact_selection_list_item, parent, false);
|
return getHeaderString(i).hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
return new ViewHolder(li.inflate(R.layout.contact_selection_list_item, parent, false), clickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindItemViewHolder(ViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN));
|
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ContactsDatabase.ID_COLUMN));
|
||||||
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
|
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
|
||||||
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
|
String name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN));
|
||||||
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
|
String number = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_COLUMN));
|
||||||
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN));
|
int numberType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.NUMBER_TYPE_COLUMN));
|
||||||
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN));
|
String label = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.LABEL_COLUMN));
|
||||||
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(context.getResources(),
|
String labelText = ContactsContract.CommonDataKinds.Phone.getTypeLabel(getContext().getResources(),
|
||||||
numberType, label).toString();
|
numberType, label).toString();
|
||||||
|
|
||||||
int color = (contactType == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
|
int color = (contactType == ContactsDatabase.PUSH_TYPE) ? drawables.getColor(0, 0xa0000000) :
|
||||||
drawables.getColor(1, 0xff000000);
|
drawables.getColor(1, 0xff000000);
|
||||||
|
|
||||||
|
viewHolder.getView().unbind();
|
||||||
((ContactSelectionListItem)view).unbind();
|
viewHolder.getView().set(id, contactType, name, number, labelText, color, multiSelect);
|
||||||
((ContactSelectionListItem)view).set(id, contactType, name, number, labelText, color, multiSelect);
|
viewHolder.getView().setChecked(selectedContacts.containsKey(id));
|
||||||
((ContactSelectionListItem)view).setChecked(selectedContacts.containsKey(id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getHeaderView(int i, View convertView, ViewGroup viewGroup) {
|
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
|
||||||
Cursor cursor = getCursor();
|
return new HeaderViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
|
||||||
|
|
||||||
final TextView text;
|
|
||||||
if (convertView == null) {
|
|
||||||
text = (TextView)li.inflate(R.layout.contact_selection_list_header, viewGroup, false);
|
|
||||||
} else {
|
|
||||||
text = (TextView)convertView;
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor.moveToPosition(i);
|
|
||||||
|
|
||||||
int contactType = cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
|
|
||||||
|
|
||||||
if (contactType == ContactsDatabase.PUSH_TYPE) text.setText(R.string.contact_selection_list__header_signal_users);
|
|
||||||
else text.setText(R.string.contact_selection_list__header_other);
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getHeaderId(int i) {
|
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
|
||||||
Cursor cursor = getCursor();
|
((TextView)viewHolder.itemView).setText(getSpannedHeaderString(position, R.drawable.ic_signal_grey_24dp));
|
||||||
cursor.moveToPosition(i);
|
}
|
||||||
|
|
||||||
return cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN));
|
@Override
|
||||||
|
public CharSequence getBubbleText(int position) {
|
||||||
|
return getSpannedHeaderString(position, R.drawable.ic_signal_white_48dp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Long, String> getSelectedContacts() {
|
public Map<Long, String> getSelectedContacts() {
|
||||||
return selectedContacts;
|
return selectedContacts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CharSequence getSpannedHeaderString(int position, @DrawableRes int drawable) {
|
||||||
|
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||||
|
|
||||||
|
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN)) == ContactsDatabase.PUSH_TYPE) {
|
||||||
|
SpannableString spannable = new SpannableString(" ");
|
||||||
|
spannable.setSpan(new ImageSpan(getContext(), drawable, ImageSpan.ALIGN_BOTTOM), 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
return spannable;
|
||||||
|
} else {
|
||||||
|
return getHeaderString(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHeaderString(int position) {
|
||||||
|
Cursor cursor = getCursorAtPositionOrThrow(position);
|
||||||
|
|
||||||
|
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactsDatabase.CONTACT_TYPE_COLUMN)) == ContactsDatabase.PUSH_TYPE) {
|
||||||
|
return getContext().getString(R.string.app_name);
|
||||||
|
} else {
|
||||||
|
String letter = cursor.getString(cursor.getColumnIndexOrThrow(ContactsDatabase.NAME_COLUMN))
|
||||||
|
.trim()
|
||||||
|
.substring(0,1)
|
||||||
|
.toUpperCase();
|
||||||
|
if (Character.isLetterOrDigit(letter.codePointAt(0))) {
|
||||||
|
return letter;
|
||||||
|
} else {
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cursor getCursorAtPositionOrThrow(int position) {
|
||||||
|
Cursor cursor = getCursor();
|
||||||
|
if (cursor == null) {
|
||||||
|
throw new IllegalStateException("Cursor should not be null here.");
|
||||||
|
}
|
||||||
|
if (!cursor.moveToPosition(position));
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ItemClickListener {
|
||||||
|
void onItemClick(ContactSelectionListItem item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import android.text.TextUtils;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
|
|
||||||
public class ContactSelectionListItem extends RelativeLayout implements Recipients.RecipientsModifiedListener {
|
public class ContactSelectionListItem extends LinearLayout implements Recipients.RecipientsModifiedListener {
|
||||||
|
|
||||||
private AvatarImageView contactPhotoImage;
|
private AvatarImageView contactPhotoImage;
|
||||||
private TextView numberView;
|
private TextView numberView;
|
||||||
@ -34,10 +34,6 @@ public class ContactSelectionListItem extends RelativeLayout implements Recipien
|
|||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactSelectionListItem(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
@ -24,6 +24,7 @@ import android.support.annotation.IdRes;
|
|||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextUtils.TruncateAt;
|
import android.text.TextUtils.TruncateAt;
|
||||||
@ -33,6 +34,7 @@ import android.view.ViewGroup;
|
|||||||
import android.view.ViewStub;
|
import android.view.ViewStub;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
import android.widget.LinearLayout.LayoutParams;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
public class ViewUtil {
|
public class ViewUtil {
|
||||||
@ -45,6 +47,42 @@ public class ViewUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setY(final @NonNull View v, final int y) {
|
||||||
|
if (VERSION.SDK_INT >= 11) {
|
||||||
|
ViewCompat.setY(v, y);
|
||||||
|
} else {
|
||||||
|
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
|
||||||
|
params.topMargin = y;
|
||||||
|
v.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getY(final @NonNull View v) {
|
||||||
|
if (VERSION.SDK_INT >= 11) {
|
||||||
|
return ViewCompat.getY(v);
|
||||||
|
} else {
|
||||||
|
return ((ViewGroup.MarginLayoutParams)v.getLayoutParams()).topMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setX(final @NonNull View v, final int x) {
|
||||||
|
if (VERSION.SDK_INT >= 11) {
|
||||||
|
ViewCompat.setX(v, x);
|
||||||
|
} else {
|
||||||
|
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
|
||||||
|
params.leftMargin = x;
|
||||||
|
v.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getX(final @NonNull View v) {
|
||||||
|
if (VERSION.SDK_INT >= 11) {
|
||||||
|
return ViewCompat.getX(v);
|
||||||
|
} else {
|
||||||
|
return ((LayoutParams)v.getLayoutParams()).leftMargin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void swapChildInPlace(ViewGroup parent, View toRemove, View toAdd, int defaultIndex) {
|
public static void swapChildInPlace(ViewGroup parent, View toRemove, View toAdd, int defaultIndex) {
|
||||||
int childIndex = parent.indexOfChild(toRemove);
|
int childIndex = parent.indexOfChild(toRemove);
|
||||||
if (childIndex > -1) parent.removeView(toRemove);
|
if (childIndex > -1) parent.removeView(toRemove);
|
||||||
|