add chatkit as module
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,54 @@
|
||||
apply from: gradle.ext.sharedModulesConstants
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
android {
|
||||
compileSdkVersion project.properties.compileSdkVersion
|
||||
buildToolsVersion project.properties.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion project.properties.minSdkVersion
|
||||
targetSdkVersion project.properties.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName '0.4.1'
|
||||
consumerProguardFiles 'proguard.txt'
|
||||
}
|
||||
android {
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
// Creates a Maven publication called "release".
|
||||
release(MavenPublication) {
|
||||
// Applies the component for the release build variant.
|
||||
//from components.release
|
||||
|
||||
// You can then customize attributes of the publication as shown below.
|
||||
groupId = 'com.github.stfalcon'
|
||||
artifactId = 'chatkit'
|
||||
version = '0.4.1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation project(':sharedutils')
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:' + appCompatXLibraryVersion
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
implementation "com.google.android:flexbox:1.0.0"
|
||||
implementation 'androidx.recyclerview:recyclerview:' + recyclerviewXLibraryVersion
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# ViewHolder constructors are resolved by reflection
|
||||
-keepclassmembers class * extends com.stfalcon.chatkit.commons.ViewHolder {
|
||||
public <init>(android.view.View);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest package="com.stfalcon.chatkit">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.commons;
|
||||
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Callback for implementing images loading in message list
|
||||
*/
|
||||
public interface ImageLoader {
|
||||
|
||||
void loadImage(ImageView imageView, @Nullable String url, @Nullable Object payload);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.commons;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.ColorRes;
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.stfalcon.chatkit.R;
|
||||
|
||||
/**
|
||||
* Base class for chat component styles
|
||||
*/
|
||||
public abstract class Style {
|
||||
|
||||
protected Context context;
|
||||
protected Resources resources;
|
||||
protected AttributeSet attrs;
|
||||
|
||||
protected Style(Context context, AttributeSet attrs) {
|
||||
this.context = context;
|
||||
this.resources = context.getResources();
|
||||
this.attrs = attrs;
|
||||
}
|
||||
|
||||
protected final int getSystemAccentColor() {
|
||||
return getSystemColor(R.attr.colorAccent);
|
||||
}
|
||||
|
||||
protected final int getSystemPrimaryColor() {
|
||||
return getSystemColor(R.attr.colorPrimary);
|
||||
}
|
||||
|
||||
protected final int getSystemPrimaryDarkColor() {
|
||||
return getSystemColor(R.attr.colorPrimaryDark);
|
||||
}
|
||||
|
||||
protected final int getSystemPrimaryTextColor() {
|
||||
return getSystemColor(android.R.attr.textColorPrimary);
|
||||
}
|
||||
|
||||
protected final int getSystemHintColor() {
|
||||
return getSystemColor(android.R.attr.textColorHint);
|
||||
}
|
||||
|
||||
protected final int getSystemColor(@AttrRes int attr) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(typedValue.data, new int[]{attr});
|
||||
int color = a.getColor(0, 0);
|
||||
a.recycle();
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
protected final int getDimension(@DimenRes int dimen) {
|
||||
return resources.getDimensionPixelSize(dimen);
|
||||
}
|
||||
|
||||
protected final int getColor(@ColorRes int color) {
|
||||
return ContextCompat.getColor(context, color);
|
||||
}
|
||||
|
||||
protected final Drawable getDrawable(@DrawableRes int drawable) {
|
||||
return ContextCompat.getDrawable(context, drawable);
|
||||
}
|
||||
|
||||
protected final Drawable getVectorDrawable(@DrawableRes int drawable) {
|
||||
return ContextCompat.getDrawable(context, drawable);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.commons;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Base ViewHolder
|
||||
*/
|
||||
public abstract class ViewHolder<DATA> extends RecyclerView.ViewHolder {
|
||||
|
||||
public abstract void onBind(DATA data);
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.commons.models;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* For implementing by real dialog model
|
||||
*/
|
||||
|
||||
public interface IDialog<MESSAGE extends IMessage> {
|
||||
|
||||
String getId();
|
||||
|
||||
String getDialogPhoto();
|
||||
|
||||
String getDialogName();
|
||||
|
||||
List<? extends IUser> getUsers();
|
||||
|
||||
MESSAGE getLastMessage();
|
||||
|
||||
void setLastMessage(MESSAGE message);
|
||||
|
||||
int getUnreadCount();
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.commons.models;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* For implementing by real message model
|
||||
*/
|
||||
public interface IMessage {
|
||||
|
||||
/**
|
||||
* Returns message identifier
|
||||
*
|
||||
* @return the message id
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns message text
|
||||
*
|
||||
* @return the message text
|
||||
*/
|
||||
String getText();
|
||||
|
||||
/**
|
||||
* Returns message author. See the {@link IUser} for more details
|
||||
*
|
||||
* @return the message author
|
||||
*/
|
||||
IUser getUser();
|
||||
|
||||
/**
|
||||
* Returns message creation date
|
||||
*
|
||||
* @return the message creation date
|
||||
*/
|
||||
Date getCreatedAt();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.commons.models;
|
||||
|
||||
/**
|
||||
* For implementing by real user model
|
||||
*/
|
||||
public interface IUser {
|
||||
|
||||
/**
|
||||
* Returns the user's id
|
||||
*
|
||||
* @return the user's id
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns the user's name
|
||||
*
|
||||
* @return the user's name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns the user's avatar image url
|
||||
*
|
||||
* @return the user's avatar image url
|
||||
*/
|
||||
String getAvatar();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.commons.models;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.stfalcon.chatkit.messages.MessageHolders;
|
||||
|
||||
/*
|
||||
* Created by troy379 on 28.03.17.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface used to mark messages as custom content types. For its representation see {@link MessageHolders}
|
||||
*/
|
||||
|
||||
public interface MessageContentType extends IMessage {
|
||||
|
||||
/**
|
||||
* Default media type for image message.
|
||||
*/
|
||||
interface Image extends IMessage {
|
||||
@Nullable
|
||||
String getImageUrl();
|
||||
}
|
||||
|
||||
// other default types will be here
|
||||
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import com.stfalcon.chatkit.R;
|
||||
import com.stfalcon.chatkit.commons.Style;
|
||||
|
||||
/**
|
||||
* Style for DialogList customization by xml attributes
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
class DialogListStyle extends Style {
|
||||
|
||||
private int dialogTitleTextColor;
|
||||
private int dialogTitleTextSize;
|
||||
private int dialogTitleTextStyle;
|
||||
private int dialogUnreadTitleTextColor;
|
||||
private int dialogUnreadTitleTextStyle;
|
||||
|
||||
private int dialogMessageTextColor;
|
||||
private int dialogMessageTextSize;
|
||||
private int dialogMessageTextStyle;
|
||||
private int dialogUnreadMessageTextColor;
|
||||
private int dialogUnreadMessageTextStyle;
|
||||
|
||||
private int dialogDateColor;
|
||||
private int dialogDateSize;
|
||||
private int dialogDateStyle;
|
||||
private int dialogUnreadDateColor;
|
||||
private int dialogUnreadDateStyle;
|
||||
|
||||
private boolean dialogUnreadBubbleEnabled;
|
||||
private int dialogUnreadBubbleTextColor;
|
||||
private int dialogUnreadBubbleTextSize;
|
||||
private int dialogUnreadBubbleTextStyle;
|
||||
private int dialogUnreadBubbleBackgroundColor;
|
||||
|
||||
private int dialogAvatarWidth;
|
||||
private int dialogAvatarHeight;
|
||||
|
||||
private boolean dialogMessageAvatarEnabled;
|
||||
private int dialogMessageAvatarWidth;
|
||||
private int dialogMessageAvatarHeight;
|
||||
|
||||
private boolean dialogDividerEnabled;
|
||||
private int dialogDividerColor;
|
||||
private int dialogDividerLeftPadding;
|
||||
private int dialogDividerRightPadding;
|
||||
|
||||
private int dialogItemBackground;
|
||||
private int dialogUnreadItemBackground;
|
||||
|
||||
static DialogListStyle parse(Context context, AttributeSet attrs) {
|
||||
DialogListStyle style = new DialogListStyle(context, attrs);
|
||||
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DialogsList);
|
||||
|
||||
//Item background
|
||||
style.dialogItemBackground = typedArray.getColor(R.styleable.DialogsList_dialogItemBackground,
|
||||
style.getColor(R.color.transparent));
|
||||
style.dialogUnreadItemBackground = typedArray.getColor(R.styleable.DialogsList_dialogUnreadItemBackground,
|
||||
style.getColor(R.color.transparent));
|
||||
|
||||
//Title text
|
||||
style.dialogTitleTextColor = typedArray.getColor(R.styleable.DialogsList_dialogTitleTextColor,
|
||||
style.getColor(R.color.dialog_title_text));
|
||||
style.dialogTitleTextSize = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogTitleTextSize,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_title_text_size));
|
||||
style.dialogTitleTextStyle = typedArray.getInt(R.styleable.DialogsList_dialogTitleTextStyle, Typeface.NORMAL);
|
||||
|
||||
//Title unread text
|
||||
style.dialogUnreadTitleTextColor = typedArray.getColor(R.styleable.DialogsList_dialogUnreadTitleTextColor,
|
||||
style.getColor(R.color.dialog_title_text));
|
||||
style.dialogUnreadTitleTextStyle = typedArray.getInt(R.styleable.DialogsList_dialogUnreadTitleTextStyle, Typeface.NORMAL);
|
||||
|
||||
//Message text
|
||||
style.dialogMessageTextColor = typedArray.getColor(R.styleable.DialogsList_dialogMessageTextColor,
|
||||
style.getColor(R.color.dialog_message_text));
|
||||
style.dialogMessageTextSize = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogMessageTextSize,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_message_text_size));
|
||||
style.dialogMessageTextStyle = typedArray.getInt(R.styleable.DialogsList_dialogMessageTextStyle, Typeface.NORMAL);
|
||||
|
||||
//Message unread text
|
||||
style.dialogUnreadMessageTextColor = typedArray.getColor(R.styleable.DialogsList_dialogUnreadMessageTextColor,
|
||||
style.getColor(R.color.dialog_message_text));
|
||||
style.dialogUnreadMessageTextStyle = typedArray.getInt(R.styleable.DialogsList_dialogUnreadMessageTextStyle, Typeface.NORMAL);
|
||||
|
||||
//Date text
|
||||
style.dialogDateColor = typedArray.getColor(R.styleable.DialogsList_dialogDateColor,
|
||||
style.getColor(R.color.dialog_date_text));
|
||||
style.dialogDateSize = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogDateSize,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_date_text_size));
|
||||
style.dialogDateStyle = typedArray.getInt(R.styleable.DialogsList_dialogDateStyle, Typeface.NORMAL);
|
||||
|
||||
//Date unread text
|
||||
style.dialogUnreadDateColor = typedArray.getColor(R.styleable.DialogsList_dialogUnreadDateColor,
|
||||
style.getColor(R.color.dialog_date_text));
|
||||
style.dialogUnreadDateStyle = typedArray.getInt(R.styleable.DialogsList_dialogUnreadDateStyle, Typeface.NORMAL);
|
||||
|
||||
//Unread bubble
|
||||
style.dialogUnreadBubbleEnabled = typedArray.getBoolean(R.styleable.DialogsList_dialogUnreadBubbleEnabled, true);
|
||||
style.dialogUnreadBubbleBackgroundColor = typedArray.getColor(R.styleable.DialogsList_dialogUnreadBubbleBackgroundColor,
|
||||
style.getColor(R.color.dialog_unread_bubble));
|
||||
|
||||
//Unread bubble text
|
||||
style.dialogUnreadBubbleTextColor = typedArray.getColor(R.styleable.DialogsList_dialogUnreadBubbleTextColor,
|
||||
style.getColor(R.color.dialog_unread_text));
|
||||
style.dialogUnreadBubbleTextSize = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogUnreadBubbleTextSize,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_unread_bubble_text_size));
|
||||
style.dialogUnreadBubbleTextStyle = typedArray.getInt(R.styleable.DialogsList_dialogUnreadBubbleTextStyle, Typeface.NORMAL);
|
||||
|
||||
//Avatar
|
||||
style.dialogAvatarWidth = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogAvatarWidth,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_avatar_width));
|
||||
style.dialogAvatarHeight = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogAvatarHeight,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_avatar_height));
|
||||
|
||||
//Last message avatar
|
||||
style.dialogMessageAvatarEnabled = typedArray.getBoolean(R.styleable.DialogsList_dialogMessageAvatarEnabled, true);
|
||||
style.dialogMessageAvatarWidth = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogMessageAvatarWidth,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_last_message_avatar_width));
|
||||
style.dialogMessageAvatarHeight = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogMessageAvatarHeight,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_last_message_avatar_height));
|
||||
|
||||
//Divider
|
||||
style.dialogDividerEnabled = typedArray.getBoolean(R.styleable.DialogsList_dialogDividerEnabled, true);
|
||||
style.dialogDividerColor = typedArray.getColor(R.styleable.DialogsList_dialogDividerColor, style.getColor(R.color.dialog_divider));
|
||||
style.dialogDividerLeftPadding = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogDividerLeftPadding,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_divider_margin_left));
|
||||
style.dialogDividerRightPadding = typedArray.getDimensionPixelSize(R.styleable.DialogsList_dialogDividerRightPadding,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.dialog_divider_margin_right));
|
||||
|
||||
typedArray.recycle();
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
private DialogListStyle(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
protected int getDialogTitleTextColor() {
|
||||
return dialogTitleTextColor;
|
||||
}
|
||||
|
||||
protected int getDialogTitleTextSize() {
|
||||
return dialogTitleTextSize;
|
||||
}
|
||||
|
||||
protected int getDialogTitleTextStyle() {
|
||||
return dialogTitleTextStyle;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadTitleTextColor() {
|
||||
return dialogUnreadTitleTextColor;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadTitleTextStyle() {
|
||||
return dialogUnreadTitleTextStyle;
|
||||
}
|
||||
|
||||
protected int getDialogMessageTextColor() {
|
||||
return dialogMessageTextColor;
|
||||
}
|
||||
|
||||
protected int getDialogMessageTextSize() {
|
||||
return dialogMessageTextSize;
|
||||
}
|
||||
|
||||
protected int getDialogMessageTextStyle() {
|
||||
return dialogMessageTextStyle;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadMessageTextColor() {
|
||||
return dialogUnreadMessageTextColor;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadMessageTextStyle() {
|
||||
return dialogUnreadMessageTextStyle;
|
||||
}
|
||||
|
||||
protected int getDialogDateColor() {
|
||||
return dialogDateColor;
|
||||
}
|
||||
|
||||
protected int getDialogDateSize() {
|
||||
return dialogDateSize;
|
||||
}
|
||||
|
||||
protected int getDialogDateStyle() {
|
||||
return dialogDateStyle;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadDateColor() {
|
||||
return dialogUnreadDateColor;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadDateStyle() {
|
||||
return dialogUnreadDateStyle;
|
||||
}
|
||||
|
||||
protected boolean isDialogUnreadBubbleEnabled() {
|
||||
return dialogUnreadBubbleEnabled;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadBubbleTextColor() {
|
||||
return dialogUnreadBubbleTextColor;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadBubbleTextSize() {
|
||||
return dialogUnreadBubbleTextSize;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadBubbleTextStyle() {
|
||||
return dialogUnreadBubbleTextStyle;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadBubbleBackgroundColor() {
|
||||
return dialogUnreadBubbleBackgroundColor;
|
||||
}
|
||||
|
||||
protected int getDialogAvatarWidth() {
|
||||
return dialogAvatarWidth;
|
||||
}
|
||||
|
||||
protected int getDialogAvatarHeight() {
|
||||
return dialogAvatarHeight;
|
||||
}
|
||||
|
||||
protected boolean isDialogDividerEnabled() {
|
||||
return dialogDividerEnabled;
|
||||
}
|
||||
|
||||
protected int getDialogDividerColor() {
|
||||
return dialogDividerColor;
|
||||
}
|
||||
|
||||
protected int getDialogDividerLeftPadding() {
|
||||
return dialogDividerLeftPadding;
|
||||
}
|
||||
|
||||
protected int getDialogDividerRightPadding() {
|
||||
return dialogDividerRightPadding;
|
||||
}
|
||||
|
||||
protected int getDialogItemBackground() {
|
||||
return dialogItemBackground;
|
||||
}
|
||||
|
||||
protected int getDialogUnreadItemBackground() {
|
||||
return dialogUnreadItemBackground;
|
||||
}
|
||||
|
||||
protected boolean isDialogMessageAvatarEnabled() {
|
||||
return dialogMessageAvatarEnabled;
|
||||
}
|
||||
|
||||
protected int getDialogMessageAvatarWidth() {
|
||||
return dialogMessageAvatarWidth;
|
||||
}
|
||||
|
||||
protected int getDialogMessageAvatarHeight() {
|
||||
return dialogMessageAvatarHeight;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
import com.stfalcon.chatkit.commons.models.IDialog;
|
||||
|
||||
/**
|
||||
* Component for displaying list of dialogs
|
||||
*/
|
||||
public class DialogsList extends RecyclerView {
|
||||
|
||||
private DialogListStyle dialogStyle;
|
||||
|
||||
public DialogsList(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public DialogsList(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
parseStyle(context, attrs);
|
||||
}
|
||||
|
||||
public DialogsList(Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
parseStyle(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
LinearLayoutManager layout = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
|
||||
SimpleItemAnimator animator = new DefaultItemAnimator();
|
||||
|
||||
setLayoutManager(layout);
|
||||
setItemAnimator(animator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use this method for setting your adapter, otherwise exception will by thrown.
|
||||
* Call {@link #setAdapter(DialogsListAdapter)} instead.
|
||||
*/
|
||||
@Override
|
||||
public void setAdapter(Adapter adapter) {
|
||||
throw new IllegalArgumentException("You can't set adapter to DialogsList. Use #setAdapter(DialogsListAdapter) instead.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adapter for DialogsList
|
||||
*
|
||||
* @param adapter Adapter. Must extend DialogsListAdapter
|
||||
* @param <DIALOG> Dialog model class
|
||||
*/
|
||||
public <DIALOG extends IDialog<?>>
|
||||
void setAdapter(DialogsListAdapter<DIALOG> adapter) {
|
||||
setAdapter(adapter, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adapter for DialogsList
|
||||
*
|
||||
* @param adapter Adapter. Must extend DialogsListAdapter
|
||||
* @param reverseLayout weather to use reverse layout for layout manager.
|
||||
* @param <DIALOG> Dialog model class
|
||||
*/
|
||||
public <DIALOG extends IDialog<?>>
|
||||
void setAdapter(DialogsListAdapter<DIALOG> adapter, boolean reverseLayout) {
|
||||
SimpleItemAnimator itemAnimator = new DefaultItemAnimator();
|
||||
itemAnimator.setSupportsChangeAnimations(false);
|
||||
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(),
|
||||
LinearLayoutManager.VERTICAL, reverseLayout);
|
||||
|
||||
setItemAnimator(itemAnimator);
|
||||
setLayoutManager(layoutManager);
|
||||
|
||||
adapter.setStyle(dialogStyle);
|
||||
|
||||
super.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResourceType")
|
||||
private void parseStyle(Context context, AttributeSet attrs) {
|
||||
dialogStyle = DialogListStyle.parse(context, attrs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,716 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.dialogs;
|
||||
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.stfalcon.chatkit.R;
|
||||
import com.stfalcon.chatkit.commons.ImageLoader;
|
||||
import com.stfalcon.chatkit.commons.ViewHolder;
|
||||
import com.stfalcon.chatkit.commons.models.IDialog;
|
||||
import com.stfalcon.chatkit.commons.models.IMessage;
|
||||
import com.stfalcon.chatkit.utils.DateFormatter;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
/**
|
||||
* Adapter for {@link DialogsList}
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class DialogsListAdapter<DIALOG extends IDialog>
|
||||
extends RecyclerView.Adapter<DialogsListAdapter.BaseDialogViewHolder> {
|
||||
|
||||
protected List<DIALOG> items = new ArrayList<>();
|
||||
private int itemLayoutId;
|
||||
private Class<? extends BaseDialogViewHolder> holderClass;
|
||||
private ImageLoader imageLoader;
|
||||
private OnDialogClickListener<DIALOG> onDialogClickListener;
|
||||
private OnDialogViewClickListener<DIALOG> onDialogViewClickListener;
|
||||
private OnDialogLongClickListener<DIALOG> onLongItemClickListener;
|
||||
private OnDialogViewLongClickListener<DIALOG> onDialogViewLongClickListener;
|
||||
private DialogListStyle dialogStyle;
|
||||
private DateFormatter.Formatter datesFormatter;
|
||||
|
||||
/**
|
||||
* For default list item layout and view holder
|
||||
*
|
||||
* @param imageLoader image loading method
|
||||
*/
|
||||
public DialogsListAdapter(ImageLoader imageLoader) {
|
||||
this(R.layout.item_dialog, DialogViewHolder.class, imageLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* For custom list item layout and default view holder
|
||||
*
|
||||
* @param itemLayoutId custom list item resource id
|
||||
* @param imageLoader image loading method
|
||||
*/
|
||||
public DialogsListAdapter(@LayoutRes int itemLayoutId, ImageLoader imageLoader) {
|
||||
this(itemLayoutId, DialogViewHolder.class, imageLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* For custom list item layout and custom view holder
|
||||
*
|
||||
* @param itemLayoutId custom list item resource id
|
||||
* @param holderClass custom view holder class
|
||||
* @param imageLoader image loading method
|
||||
*/
|
||||
public DialogsListAdapter(@LayoutRes int itemLayoutId, Class<? extends BaseDialogViewHolder> holderClass,
|
||||
ImageLoader imageLoader) {
|
||||
this.itemLayoutId = itemLayoutId;
|
||||
this.holderClass = holderClass;
|
||||
this.imageLoader = imageLoader;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onBindViewHolder(BaseDialogViewHolder holder, int position) {
|
||||
holder.setImageLoader(imageLoader);
|
||||
holder.setOnDialogClickListener(onDialogClickListener);
|
||||
holder.setOnDialogViewClickListener(onDialogViewClickListener);
|
||||
holder.setOnLongItemClickListener(onLongItemClickListener);
|
||||
holder.setOnDialogViewLongClickListener(onDialogViewLongClickListener);
|
||||
holder.setDatesFormatter(datesFormatter);
|
||||
holder.onBind(items.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseDialogViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(itemLayoutId, parent, false);
|
||||
//create view holder by reflation
|
||||
try {
|
||||
Constructor<? extends BaseDialogViewHolder> constructor = holderClass.getDeclaredConstructor(View.class);
|
||||
constructor.setAccessible(true);
|
||||
BaseDialogViewHolder baseDialogViewHolder = constructor.newInstance(v);
|
||||
if (baseDialogViewHolder instanceof DialogViewHolder) {
|
||||
((DialogViewHolder) baseDialogViewHolder).setDialogStyle(dialogStyle);
|
||||
}
|
||||
return baseDialogViewHolder;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return size of dialogs list
|
||||
*/
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* remove item with id
|
||||
*
|
||||
* @param id dialog i
|
||||
*/
|
||||
public void deleteById(String id) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i).getId().equals(id)) {
|
||||
items.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if, and only if, dialogs count in adapter is non-zero.
|
||||
*
|
||||
* @return {@code true} if size is 0, otherwise {@code false}
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return items.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* clear dialogs list
|
||||
*/
|
||||
public void clear() {
|
||||
if (items != null) {
|
||||
items.clear();
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set dialogs list
|
||||
*
|
||||
* @param items dialogs list
|
||||
*/
|
||||
public void setItems(List<DIALOG> items) {
|
||||
this.items = items;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dialogs items
|
||||
*
|
||||
* @param newItems new dialogs list
|
||||
*/
|
||||
public void addItems(List<DIALOG> newItems) {
|
||||
if (newItems != null) {
|
||||
if (items == null) {
|
||||
items = new ArrayList<>();
|
||||
}
|
||||
int curSize = items.size();
|
||||
items.addAll(newItems);
|
||||
notifyItemRangeInserted(curSize, items.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dialog to the end of dialogs list
|
||||
*
|
||||
* @param dialog dialog item
|
||||
*/
|
||||
public void addItem(DIALOG dialog) {
|
||||
items.add(dialog);
|
||||
notifyItemInserted(items.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add dialog to dialogs list
|
||||
*
|
||||
* @param dialog dialog item
|
||||
* @param position position in dialogs list
|
||||
*/
|
||||
public void addItem(int position, DIALOG dialog) {
|
||||
items.add(position, dialog);
|
||||
notifyItemInserted(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item
|
||||
*
|
||||
* @param fromPosition the actual position of the item
|
||||
* @param toPosition the new position of the item
|
||||
*/
|
||||
public void moveItem(int fromPosition, int toPosition) {
|
||||
DIALOG dialog = items.remove(fromPosition);
|
||||
items.add(toPosition, dialog);
|
||||
notifyItemMoved(fromPosition, toPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dialog by position in dialogs list
|
||||
*
|
||||
* @param position position in dialogs list
|
||||
* @param item new dialog item
|
||||
*/
|
||||
public void updateItem(int position, DIALOG item) {
|
||||
if (items == null) {
|
||||
items = new ArrayList<>();
|
||||
}
|
||||
items.set(position, item);
|
||||
notifyItemChanged(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dialog by dialog id
|
||||
*
|
||||
* @param item new dialog item
|
||||
*/
|
||||
public void updateItemById(DIALOG item) {
|
||||
if (items == null) {
|
||||
items = new ArrayList<>();
|
||||
}
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i).getId().equals(item.getId())) {
|
||||
items.set(i, item);
|
||||
notifyItemChanged(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert dialog in dialogs list or add it to then end of dialogs list
|
||||
*
|
||||
* @param item dialog item
|
||||
*/
|
||||
public void upsertItem(DIALOG item) {
|
||||
boolean updated = false;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i).getId().equals(item.getId())) {
|
||||
items.set(i, item);
|
||||
notifyItemChanged(i);
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an item by its id
|
||||
*
|
||||
* @param id the wanted item's id
|
||||
* @return the found item, or null
|
||||
*/
|
||||
@Nullable
|
||||
public DIALOG getItemById(String id) {
|
||||
if (items == null) {
|
||||
items = new ArrayList<>();
|
||||
}
|
||||
for (DIALOG item : items) {
|
||||
if (item.getId() == null && id == null) {
|
||||
return item;
|
||||
} else if (item.getId() != null && item.getId().equals(id)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last message in dialog and swap item to top of list.
|
||||
*
|
||||
* @param dialogId Dialog ID
|
||||
* @param message New message
|
||||
* @return false if dialog doesn't exist.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean updateDialogWithMessage(String dialogId, IMessage message) {
|
||||
boolean dialogExist = false;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i).getId().equals(dialogId)) {
|
||||
items.get(i).setLastMessage(message);
|
||||
notifyItemChanged(i);
|
||||
if (i != 0) {
|
||||
Collections.swap(items, i, 0);
|
||||
notifyItemMoved(i, 0);
|
||||
}
|
||||
dialogExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return dialogExist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort dialog by last message date
|
||||
*/
|
||||
public void sortByLastMessageDate() {
|
||||
Collections.sort(items, (o1, o2) -> {
|
||||
if (o1.getLastMessage().getCreatedAt().after(o2.getLastMessage().getCreatedAt())) {
|
||||
return -1;
|
||||
} else if (o1.getLastMessage().getCreatedAt().before(o2.getLastMessage().getCreatedAt())) {
|
||||
return 1;
|
||||
} else return 0;
|
||||
});
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort items with rules of comparator
|
||||
*
|
||||
* @param comparator Comparator
|
||||
*/
|
||||
public void sort(Comparator<DIALOG> comparator) {
|
||||
Collections.sort(items, comparator);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return registered image loader
|
||||
*/
|
||||
public ImageLoader getImageLoader() {
|
||||
return imageLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when image need to load.
|
||||
*
|
||||
* @param imageLoader image loading method
|
||||
*/
|
||||
public void setImageLoader(ImageLoader imageLoader) {
|
||||
this.imageLoader = imageLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the item click callback.
|
||||
*/
|
||||
public OnDialogClickListener getOnDialogClickListener() {
|
||||
return onDialogClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when item is clicked.
|
||||
*
|
||||
* @param onDialogClickListener on click item callback
|
||||
*/
|
||||
public void setOnDialogClickListener(OnDialogClickListener<DIALOG> onDialogClickListener) {
|
||||
this.onDialogClickListener = onDialogClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the view click callback.
|
||||
*/
|
||||
public OnDialogViewClickListener getOnDialogViewClickListener() {
|
||||
return onDialogViewClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when dialog view is clicked.
|
||||
*
|
||||
* @param clickListener on click item callback
|
||||
*/
|
||||
public void setOnDialogViewClickListener(OnDialogViewClickListener<DIALOG> clickListener) {
|
||||
this.onDialogViewClickListener = clickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return on long click item callback
|
||||
*/
|
||||
public OnDialogLongClickListener getOnLongItemClickListener() {
|
||||
return onLongItemClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when item is long clicked.
|
||||
*
|
||||
* @param onLongItemClickListener on long click item callback
|
||||
*/
|
||||
public void setOnDialogLongClickListener(OnDialogLongClickListener<DIALOG> onLongItemClickListener) {
|
||||
this.onLongItemClickListener = onLongItemClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return on view long click callback
|
||||
*/
|
||||
public OnDialogViewLongClickListener<DIALOG> getOnDialogViewLongClickListener() {
|
||||
return onDialogViewLongClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when item view is long clicked.
|
||||
*
|
||||
* @param clickListener on long click item callback
|
||||
*/
|
||||
public void setOnDialogViewLongClickListener(OnDialogViewLongClickListener<DIALOG> clickListener) {
|
||||
this.onDialogViewLongClickListener = clickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets custom {@link DateFormatter.Formatter} for text representation of last message date.
|
||||
*/
|
||||
public void setDatesFormatter(DateFormatter.Formatter datesFormatter) {
|
||||
this.datesFormatter = datesFormatter;
|
||||
}
|
||||
|
||||
//TODO ability to set style programmatically
|
||||
void setStyle(DialogListStyle dialogStyle) {
|
||||
this.dialogStyle = dialogStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the position of a dialog in the dialogs list.
|
||||
*/
|
||||
public int getDialogPosition(DIALOG dialog) {
|
||||
return this.items.indexOf(dialog);
|
||||
}
|
||||
|
||||
/*
|
||||
* LISTENERS
|
||||
* */
|
||||
public interface OnDialogClickListener<DIALOG extends IDialog> {
|
||||
void onDialogClick(DIALOG dialog);
|
||||
}
|
||||
|
||||
public interface OnDialogViewClickListener<DIALOG extends IDialog> {
|
||||
void onDialogViewClick(View view, DIALOG dialog);
|
||||
}
|
||||
|
||||
public interface OnDialogLongClickListener<DIALOG extends IDialog> {
|
||||
void onDialogLongClick(DIALOG dialog);
|
||||
}
|
||||
|
||||
public interface OnDialogViewLongClickListener<DIALOG extends IDialog> {
|
||||
void onDialogViewLongClick(View view, DIALOG dialog);
|
||||
}
|
||||
|
||||
/*
|
||||
* HOLDERS
|
||||
* */
|
||||
public abstract static class BaseDialogViewHolder<DIALOG extends IDialog>
|
||||
extends ViewHolder<DIALOG> {
|
||||
|
||||
protected ImageLoader imageLoader;
|
||||
protected OnDialogClickListener<DIALOG> onDialogClickListener;
|
||||
protected OnDialogLongClickListener<DIALOG> onLongItemClickListener;
|
||||
protected OnDialogViewClickListener<DIALOG> onDialogViewClickListener;
|
||||
protected OnDialogViewLongClickListener<DIALOG> onDialogViewLongClickListener;
|
||||
protected DateFormatter.Formatter datesFormatter;
|
||||
|
||||
public BaseDialogViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
void setImageLoader(ImageLoader imageLoader) {
|
||||
this.imageLoader = imageLoader;
|
||||
}
|
||||
|
||||
protected void setOnDialogClickListener(OnDialogClickListener<DIALOG> onDialogClickListener) {
|
||||
this.onDialogClickListener = onDialogClickListener;
|
||||
}
|
||||
|
||||
protected void setOnDialogViewClickListener(OnDialogViewClickListener<DIALOG> onDialogViewClickListener) {
|
||||
this.onDialogViewClickListener = onDialogViewClickListener;
|
||||
}
|
||||
|
||||
protected void setOnLongItemClickListener(OnDialogLongClickListener<DIALOG> onLongItemClickListener) {
|
||||
this.onLongItemClickListener = onLongItemClickListener;
|
||||
}
|
||||
|
||||
protected void setOnDialogViewLongClickListener(OnDialogViewLongClickListener<DIALOG> onDialogViewLongClickListener) {
|
||||
this.onDialogViewLongClickListener = onDialogViewLongClickListener;
|
||||
}
|
||||
|
||||
public void setDatesFormatter(DateFormatter.Formatter dateHeadersFormatter) {
|
||||
this.datesFormatter = dateHeadersFormatter;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DialogViewHolder<DIALOG extends IDialog> extends BaseDialogViewHolder<DIALOG> {
|
||||
protected DialogListStyle dialogStyle;
|
||||
protected ViewGroup container;
|
||||
protected ViewGroup root;
|
||||
protected TextView tvName;
|
||||
protected TextView tvDate;
|
||||
protected ImageView ivAvatar;
|
||||
protected ImageView ivLastMessageUser;
|
||||
protected TextView tvLastMessage;
|
||||
protected TextView tvBubble;
|
||||
protected ViewGroup dividerContainer;
|
||||
protected View divider;
|
||||
|
||||
public DialogViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
root = itemView.findViewById(R.id.dialogRootLayout);
|
||||
container = itemView.findViewById(R.id.dialogContainer);
|
||||
tvName = itemView.findViewById(R.id.dialogName);
|
||||
tvDate = itemView.findViewById(R.id.dialogDate);
|
||||
tvLastMessage = itemView.findViewById(R.id.dialogLastMessage);
|
||||
tvBubble = itemView.findViewById(R.id.dialogUnreadBubble);
|
||||
ivLastMessageUser = itemView.findViewById(R.id.dialogLastMessageUserAvatar);
|
||||
ivAvatar = itemView.findViewById(R.id.dialogAvatar);
|
||||
dividerContainer = itemView.findViewById(R.id.dialogDividerContainer);
|
||||
divider = itemView.findViewById(R.id.dialogDivider);
|
||||
|
||||
}
|
||||
|
||||
private void applyStyle() {
|
||||
if (dialogStyle != null) {
|
||||
//Texts
|
||||
if (tvName != null) {
|
||||
tvName.setTextSize(TypedValue.COMPLEX_UNIT_PX, dialogStyle.getDialogTitleTextSize());
|
||||
}
|
||||
|
||||
if (tvLastMessage != null) {
|
||||
tvLastMessage.setTextSize(TypedValue.COMPLEX_UNIT_PX, dialogStyle.getDialogMessageTextSize());
|
||||
}
|
||||
|
||||
if (tvDate != null) {
|
||||
tvDate.setTextSize(TypedValue.COMPLEX_UNIT_PX, dialogStyle.getDialogDateSize());
|
||||
}
|
||||
|
||||
//Divider
|
||||
if (divider != null)
|
||||
divider.setBackgroundColor(dialogStyle.getDialogDividerColor());
|
||||
if (dividerContainer != null)
|
||||
dividerContainer.setPadding(dialogStyle.getDialogDividerLeftPadding(), 0,
|
||||
dialogStyle.getDialogDividerRightPadding(), 0);
|
||||
//Avatar
|
||||
if (ivAvatar != null) {
|
||||
ivAvatar.getLayoutParams().width = dialogStyle.getDialogAvatarWidth();
|
||||
ivAvatar.getLayoutParams().height = dialogStyle.getDialogAvatarHeight();
|
||||
}
|
||||
|
||||
//Last message user avatar
|
||||
if (ivLastMessageUser != null) {
|
||||
ivLastMessageUser.getLayoutParams().width = dialogStyle.getDialogMessageAvatarWidth();
|
||||
ivLastMessageUser.getLayoutParams().height = dialogStyle.getDialogMessageAvatarHeight();
|
||||
}
|
||||
|
||||
//Unread bubble
|
||||
if (tvBubble != null) {
|
||||
GradientDrawable bgShape = (GradientDrawable) tvBubble.getBackground();
|
||||
bgShape.setColor(dialogStyle.getDialogUnreadBubbleBackgroundColor());
|
||||
tvBubble.setVisibility(dialogStyle.isDialogDividerEnabled() ? VISIBLE : GONE);
|
||||
tvBubble.setTextSize(TypedValue.COMPLEX_UNIT_PX, dialogStyle.getDialogUnreadBubbleTextSize());
|
||||
tvBubble.setTextColor(dialogStyle.getDialogUnreadBubbleTextColor());
|
||||
tvBubble.setTypeface(tvBubble.getTypeface(), dialogStyle.getDialogUnreadBubbleTextStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void applyDefaultStyle() {
|
||||
if (dialogStyle != null) {
|
||||
if (root != null) {
|
||||
root.setBackgroundColor(dialogStyle.getDialogItemBackground());
|
||||
}
|
||||
|
||||
if (tvName != null) {
|
||||
tvName.setTextColor(dialogStyle.getDialogTitleTextColor());
|
||||
tvName.setTypeface(Typeface.DEFAULT, dialogStyle.getDialogTitleTextStyle());
|
||||
}
|
||||
|
||||
if (tvDate != null) {
|
||||
tvDate.setTextColor(dialogStyle.getDialogDateColor());
|
||||
tvDate.setTypeface(Typeface.DEFAULT, dialogStyle.getDialogDateStyle());
|
||||
}
|
||||
|
||||
if (tvLastMessage != null) {
|
||||
tvLastMessage.setTextColor(dialogStyle.getDialogMessageTextColor());
|
||||
tvLastMessage.setTypeface(Typeface.DEFAULT, dialogStyle.getDialogMessageTextStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyUnreadStyle() {
|
||||
if (dialogStyle != null) {
|
||||
if (root != null) {
|
||||
root.setBackgroundColor(dialogStyle.getDialogUnreadItemBackground());
|
||||
}
|
||||
|
||||
if (tvName != null) {
|
||||
tvName.setTextColor(dialogStyle.getDialogUnreadTitleTextColor());
|
||||
tvName.setTypeface(Typeface.DEFAULT, dialogStyle.getDialogUnreadTitleTextStyle());
|
||||
}
|
||||
|
||||
if (tvDate != null) {
|
||||
tvDate.setTextColor(dialogStyle.getDialogUnreadDateColor());
|
||||
tvDate.setTypeface(Typeface.DEFAULT, dialogStyle.getDialogUnreadDateStyle());
|
||||
}
|
||||
|
||||
if (tvLastMessage != null) {
|
||||
tvLastMessage.setTextColor(dialogStyle.getDialogUnreadMessageTextColor());
|
||||
tvLastMessage.setTypeface(Typeface.DEFAULT, dialogStyle.getDialogUnreadMessageTextStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBind(final DIALOG dialog) {
|
||||
if (dialog.getUnreadCount() > 0) {
|
||||
applyUnreadStyle();
|
||||
} else {
|
||||
applyDefaultStyle();
|
||||
}
|
||||
|
||||
//Set Name
|
||||
tvName.setText(dialog.getDialogName());
|
||||
|
||||
//Set Date
|
||||
String formattedDate = null;
|
||||
|
||||
if (dialog.getLastMessage() != null) {
|
||||
Date lastMessageDate = dialog.getLastMessage().getCreatedAt();
|
||||
if (datesFormatter != null) formattedDate = datesFormatter.format(lastMessageDate);
|
||||
tvDate.setText(formattedDate == null
|
||||
? getDateString(lastMessageDate)
|
||||
: formattedDate);
|
||||
} else {
|
||||
tvDate.setText(null);
|
||||
}
|
||||
|
||||
//Set Dialog avatar
|
||||
if (imageLoader != null) {
|
||||
imageLoader.loadImage(ivAvatar, dialog.getDialogPhoto(), null);
|
||||
}
|
||||
|
||||
//Set Last message user avatar with check if there is last message
|
||||
if (imageLoader != null && dialog.getLastMessage() != null) {
|
||||
imageLoader.loadImage(ivLastMessageUser, dialog.getLastMessage().getUser().getAvatar(), null);
|
||||
}
|
||||
ivLastMessageUser.setVisibility(dialogStyle.isDialogMessageAvatarEnabled()
|
||||
&& dialog.getUsers().size() > 1
|
||||
&& dialog.getLastMessage() != null ? VISIBLE : GONE);
|
||||
|
||||
//Set Last message text
|
||||
if (dialog.getLastMessage() != null) {
|
||||
tvLastMessage.setText(dialog.getLastMessage().getText());
|
||||
} else {
|
||||
tvLastMessage.setText(null);
|
||||
}
|
||||
|
||||
//Set Unread message count bubble
|
||||
tvBubble.setText(String.valueOf(dialog.getUnreadCount()));
|
||||
tvBubble.setVisibility(dialogStyle.isDialogUnreadBubbleEnabled() &&
|
||||
dialog.getUnreadCount() > 0 ? VISIBLE : GONE);
|
||||
|
||||
container.setOnClickListener(view -> {
|
||||
if (onDialogClickListener != null) {
|
||||
onDialogClickListener.onDialogClick(dialog);
|
||||
}
|
||||
if (onDialogViewClickListener != null) {
|
||||
onDialogViewClickListener.onDialogViewClick(view, dialog);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
container.setOnLongClickListener(view -> {
|
||||
if (onLongItemClickListener != null) {
|
||||
onLongItemClickListener.onDialogLongClick(dialog);
|
||||
}
|
||||
if (onDialogViewLongClickListener != null) {
|
||||
onDialogViewLongClickListener.onDialogViewLongClick(view, dialog);
|
||||
}
|
||||
return onLongItemClickListener != null || onDialogViewLongClickListener != null;
|
||||
});
|
||||
}
|
||||
|
||||
protected String getDateString(Date date) {
|
||||
return DateFormatter.format(date, DateFormatter.Template.TIME);
|
||||
}
|
||||
|
||||
protected DialogListStyle getDialogStyle() {
|
||||
return dialogStyle;
|
||||
}
|
||||
|
||||
protected void setDialogStyle(DialogListStyle dialogStyle) {
|
||||
this.dialogStyle = dialogStyle;
|
||||
applyStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.messages;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
||||
import com.stfalcon.chatkit.R;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Component for input outcoming messages
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public class MessageInput extends RelativeLayout
|
||||
implements View.OnClickListener, TextWatcher, View.OnFocusChangeListener {
|
||||
|
||||
protected EditText messageInput;
|
||||
protected ImageButton messageSendButton;
|
||||
protected ImageButton attachmentButton;
|
||||
protected Space sendButtonSpace, attachmentButtonSpace;
|
||||
|
||||
private CharSequence input;
|
||||
private InputListener inputListener;
|
||||
private AttachmentsListener attachmentsListener;
|
||||
private boolean isTyping;
|
||||
private TypingListener typingListener;
|
||||
private int delayTypingStatusMillis;
|
||||
private Runnable typingTimerRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isTyping) {
|
||||
isTyping = false;
|
||||
if (typingListener != null) typingListener.onStopTyping();
|
||||
}
|
||||
}
|
||||
};
|
||||
private boolean lastFocus;
|
||||
|
||||
public MessageInput(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public MessageInput(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
public MessageInput(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets callback for 'submit' button.
|
||||
*
|
||||
* @param inputListener input callback
|
||||
*/
|
||||
public void setInputListener(InputListener inputListener) {
|
||||
this.inputListener = inputListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets callback for 'add' button.
|
||||
*
|
||||
* @param attachmentsListener input callback
|
||||
*/
|
||||
public void setAttachmentsListener(AttachmentsListener attachmentsListener) {
|
||||
this.attachmentsListener = attachmentsListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns EditText for messages input
|
||||
*
|
||||
* @return EditText
|
||||
*/
|
||||
public EditText getInputEditText() {
|
||||
return messageInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `submit` button
|
||||
*
|
||||
* @return ImageButton
|
||||
*/
|
||||
public ImageButton getButton() {
|
||||
return messageSendButton;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
int id = view.getId();
|
||||
if (id == R.id.messageSendButton) {
|
||||
boolean isSubmitted = onSubmit();
|
||||
if (isSubmitted) {
|
||||
messageInput.setText("");
|
||||
}
|
||||
removeCallbacks(typingTimerRunnable);
|
||||
post(typingTimerRunnable);
|
||||
} else if (id == R.id.attachmentButton) {
|
||||
onAddAttachments();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to notify you that, within s,
|
||||
* the count characters beginning at start have just replaced old text that had length before
|
||||
*/
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int count, int after) {
|
||||
input = s;
|
||||
messageSendButton.setEnabled(input.length() > 0);
|
||||
if (s.length() > 0) {
|
||||
if (!isTyping) {
|
||||
isTyping = true;
|
||||
if (typingListener != null) typingListener.onStartTyping();
|
||||
}
|
||||
removeCallbacks(typingTimerRunnable);
|
||||
postDelayed(typingTimerRunnable, delayTypingStatusMillis);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to notify you that, within s,
|
||||
* the count characters beginning at start are about to be replaced by new text with length after.
|
||||
*/
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to notify you that, somewhere within s, the text has been changed.
|
||||
*/
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
//do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (lastFocus && !hasFocus && typingListener != null) {
|
||||
typingListener.onStopTyping();
|
||||
}
|
||||
lastFocus = hasFocus;
|
||||
}
|
||||
|
||||
private boolean onSubmit() {
|
||||
return inputListener != null && inputListener.onSubmit(input);
|
||||
}
|
||||
|
||||
private void onAddAttachments() {
|
||||
if (attachmentsListener != null) attachmentsListener.onAddAttachments();
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs) {
|
||||
init(context);
|
||||
MessageInputStyle style = MessageInputStyle.parse(context, attrs);
|
||||
|
||||
this.messageInput.setMaxLines(style.getInputMaxLines());
|
||||
this.messageInput.setHint(style.getInputHint());
|
||||
this.messageInput.setText(style.getInputText());
|
||||
this.messageInput.setTextSize(TypedValue.COMPLEX_UNIT_PX, style.getInputTextSize());
|
||||
this.messageInput.setTextColor(style.getInputTextColor());
|
||||
this.messageInput.setHintTextColor(style.getInputHintColor());
|
||||
ViewCompat.setBackground(this.messageInput, style.getInputBackground());
|
||||
setCursor(style.getInputCursorDrawable());
|
||||
|
||||
this.attachmentButton.setVisibility(style.showAttachmentButton() ? VISIBLE : GONE);
|
||||
this.attachmentButton.setImageDrawable(style.getAttachmentButtonIcon());
|
||||
this.attachmentButton.getLayoutParams().width = style.getAttachmentButtonWidth();
|
||||
this.attachmentButton.getLayoutParams().height = style.getAttachmentButtonHeight();
|
||||
ViewCompat.setBackground(this.attachmentButton, style.getAttachmentButtonBackground());
|
||||
|
||||
this.attachmentButtonSpace.setVisibility(style.showAttachmentButton() ? VISIBLE : GONE);
|
||||
this.attachmentButtonSpace.getLayoutParams().width = style.getAttachmentButtonMargin();
|
||||
|
||||
this.messageSendButton.setImageDrawable(style.getInputButtonIcon());
|
||||
this.messageSendButton.getLayoutParams().width = style.getInputButtonWidth();
|
||||
this.messageSendButton.getLayoutParams().height = style.getInputButtonHeight();
|
||||
ViewCompat.setBackground(messageSendButton, style.getInputButtonBackground());
|
||||
this.sendButtonSpace.getLayoutParams().width = style.getInputButtonMargin();
|
||||
|
||||
if (getPaddingLeft() == 0
|
||||
&& getPaddingRight() == 0
|
||||
&& getPaddingTop() == 0
|
||||
&& getPaddingBottom() == 0) {
|
||||
setPadding(
|
||||
style.getInputDefaultPaddingLeft(),
|
||||
style.getInputDefaultPaddingTop(),
|
||||
style.getInputDefaultPaddingRight(),
|
||||
style.getInputDefaultPaddingBottom()
|
||||
);
|
||||
}
|
||||
this.delayTypingStatusMillis = style.getDelayTypingStatus();
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
inflate(context, R.layout.view_message_input, this);
|
||||
|
||||
messageInput = findViewById(R.id.messageInput);
|
||||
messageSendButton = findViewById(R.id.messageSendButton);
|
||||
attachmentButton = findViewById(R.id.attachmentButton);
|
||||
sendButtonSpace = findViewById(R.id.sendButtonSpace);
|
||||
attachmentButtonSpace = findViewById(R.id.attachmentButtonSpace);
|
||||
|
||||
messageSendButton.setOnClickListener(this);
|
||||
attachmentButton.setOnClickListener(this);
|
||||
messageInput.addTextChangedListener(this);
|
||||
messageInput.setText("");
|
||||
messageInput.setOnFocusChangeListener(this);
|
||||
}
|
||||
|
||||
private void setCursor(Drawable drawable) {
|
||||
if (drawable == null) return;
|
||||
|
||||
try {
|
||||
final Field drawableResField = TextView.class.getDeclaredField("mCursorDrawableRes");
|
||||
drawableResField.setAccessible(true);
|
||||
|
||||
final Object drawableFieldOwner;
|
||||
final Class<?> drawableFieldClass;
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
|
||||
drawableFieldOwner = this.messageInput;
|
||||
drawableFieldClass = TextView.class;
|
||||
} else {
|
||||
final Field editorField = TextView.class.getDeclaredField("mEditor");
|
||||
editorField.setAccessible(true);
|
||||
drawableFieldOwner = editorField.get(this.messageInput);
|
||||
drawableFieldClass = drawableFieldOwner.getClass();
|
||||
}
|
||||
final Field drawableField = drawableFieldClass.getDeclaredField("mCursorDrawable");
|
||||
drawableField.setAccessible(true);
|
||||
drawableField.set(drawableFieldOwner, new Drawable[]{drawable, drawable});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void setTypingListener(TypingListener typingListener) {
|
||||
this.typingListener = typingListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when user pressed 'submit' button
|
||||
*/
|
||||
public interface InputListener {
|
||||
|
||||
/**
|
||||
* Fires when user presses 'send' button.
|
||||
*
|
||||
* @param input input entered by user
|
||||
* @return if input text is valid, you must return {@code true} and input will be cleared, otherwise return false.
|
||||
*/
|
||||
boolean onSubmit(CharSequence input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when user presses 'add' button
|
||||
*/
|
||||
public interface AttachmentsListener {
|
||||
|
||||
/**
|
||||
* Fires when user presses 'add' button.
|
||||
*/
|
||||
void onAddAttachments();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when user typing
|
||||
*/
|
||||
public interface TypingListener {
|
||||
|
||||
/**
|
||||
* Fires when user presses start typing
|
||||
*/
|
||||
void onStartTyping();
|
||||
|
||||
/**
|
||||
* Fires when user presses stop typing
|
||||
*/
|
||||
void onStopTyping();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.messages;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
import com.stfalcon.chatkit.R;
|
||||
import com.stfalcon.chatkit.commons.Style;
|
||||
|
||||
/**
|
||||
* Style for MessageInputStyle customization by xml attributes
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
class MessageInputStyle extends Style {
|
||||
|
||||
private static final int DEFAULT_MAX_LINES = 5;
|
||||
private static final int DEFAULT_DELAY_TYPING_STATUS = 1500;
|
||||
|
||||
private boolean showAttachmentButton;
|
||||
|
||||
private int attachmentButtonBackground;
|
||||
private int attachmentButtonDefaultBgColor;
|
||||
private int attachmentButtonDefaultBgPressedColor;
|
||||
private int attachmentButtonDefaultBgDisabledColor;
|
||||
|
||||
private int attachmentButtonIcon;
|
||||
private int attachmentButtonDefaultIconColor;
|
||||
private int attachmentButtonDefaultIconPressedColor;
|
||||
private int attachmentButtonDefaultIconDisabledColor;
|
||||
|
||||
private int attachmentButtonWidth;
|
||||
private int attachmentButtonHeight;
|
||||
private int attachmentButtonMargin;
|
||||
|
||||
private int inputButtonBackground;
|
||||
private int inputButtonDefaultBgColor;
|
||||
private int inputButtonDefaultBgPressedColor;
|
||||
private int inputButtonDefaultBgDisabledColor;
|
||||
|
||||
private int inputButtonIcon;
|
||||
private int inputButtonDefaultIconColor;
|
||||
private int inputButtonDefaultIconPressedColor;
|
||||
private int inputButtonDefaultIconDisabledColor;
|
||||
|
||||
private int inputButtonWidth;
|
||||
private int inputButtonHeight;
|
||||
private int inputButtonMargin;
|
||||
|
||||
private int inputMaxLines;
|
||||
private String inputHint;
|
||||
private String inputText;
|
||||
|
||||
private int inputTextSize;
|
||||
private int inputTextColor;
|
||||
private int inputHintColor;
|
||||
|
||||
private Drawable inputBackground;
|
||||
private Drawable inputCursorDrawable;
|
||||
|
||||
private int inputDefaultPaddingLeft;
|
||||
private int inputDefaultPaddingRight;
|
||||
private int inputDefaultPaddingTop;
|
||||
private int inputDefaultPaddingBottom;
|
||||
|
||||
private int delayTypingStatus;
|
||||
|
||||
static MessageInputStyle parse(Context context, AttributeSet attrs) {
|
||||
MessageInputStyle style = new MessageInputStyle(context, attrs);
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MessageInput);
|
||||
|
||||
style.showAttachmentButton = typedArray.getBoolean(R.styleable.MessageInput_showAttachmentButton, false);
|
||||
|
||||
style.attachmentButtonBackground = typedArray.getResourceId(R.styleable.MessageInput_attachmentButtonBackground, -1);
|
||||
style.attachmentButtonDefaultBgColor = typedArray.getColor(R.styleable.MessageInput_attachmentButtonDefaultBgColor,
|
||||
style.getColor(R.color.white_four));
|
||||
style.attachmentButtonDefaultBgPressedColor = typedArray.getColor(R.styleable.MessageInput_attachmentButtonDefaultBgPressedColor,
|
||||
style.getColor(R.color.white_five));
|
||||
style.attachmentButtonDefaultBgDisabledColor = typedArray.getColor(R.styleable.MessageInput_attachmentButtonDefaultBgDisabledColor,
|
||||
style.getColor(R.color.transparent));
|
||||
|
||||
style.attachmentButtonIcon = typedArray.getResourceId(R.styleable.MessageInput_attachmentButtonIcon, -1);
|
||||
style.attachmentButtonDefaultIconColor = typedArray.getColor(R.styleable.MessageInput_attachmentButtonDefaultIconColor,
|
||||
style.getColor(R.color.cornflower_blue_two));
|
||||
style.attachmentButtonDefaultIconPressedColor = typedArray.getColor(R.styleable.MessageInput_attachmentButtonDefaultIconPressedColor,
|
||||
style.getColor(R.color.cornflower_blue_two_dark));
|
||||
style.attachmentButtonDefaultIconDisabledColor = typedArray.getColor(R.styleable.MessageInput_attachmentButtonDefaultIconDisabledColor,
|
||||
style.getColor(R.color.cornflower_blue_light_40));
|
||||
|
||||
style.attachmentButtonWidth = typedArray.getDimensionPixelSize(R.styleable.MessageInput_attachmentButtonWidth, style.getDimension(R.dimen.input_button_width));
|
||||
style.attachmentButtonHeight = typedArray.getDimensionPixelSize(R.styleable.MessageInput_attachmentButtonHeight, style.getDimension(R.dimen.input_button_height));
|
||||
style.attachmentButtonMargin = typedArray.getDimensionPixelSize(R.styleable.MessageInput_attachmentButtonMargin, style.getDimension(R.dimen.input_button_margin));
|
||||
|
||||
style.inputButtonBackground = typedArray.getResourceId(R.styleable.MessageInput_inputButtonBackground, -1);
|
||||
style.inputButtonDefaultBgColor = typedArray.getColor(R.styleable.MessageInput_inputButtonDefaultBgColor,
|
||||
style.getColor(R.color.cornflower_blue_two));
|
||||
style.inputButtonDefaultBgPressedColor = typedArray.getColor(R.styleable.MessageInput_inputButtonDefaultBgPressedColor,
|
||||
style.getColor(R.color.cornflower_blue_two_dark));
|
||||
style.inputButtonDefaultBgDisabledColor = typedArray.getColor(R.styleable.MessageInput_inputButtonDefaultBgDisabledColor,
|
||||
style.getColor(R.color.white_four));
|
||||
|
||||
style.inputButtonIcon = typedArray.getResourceId(R.styleable.MessageInput_inputButtonIcon, -1);
|
||||
style.inputButtonDefaultIconColor = typedArray.getColor(R.styleable.MessageInput_inputButtonDefaultIconColor,
|
||||
style.getColor(R.color.white));
|
||||
style.inputButtonDefaultIconPressedColor = typedArray.getColor(R.styleable.MessageInput_inputButtonDefaultIconPressedColor,
|
||||
style.getColor(R.color.white));
|
||||
style.inputButtonDefaultIconDisabledColor = typedArray.getColor(R.styleable.MessageInput_inputButtonDefaultIconDisabledColor,
|
||||
style.getColor(R.color.warm_grey));
|
||||
|
||||
style.inputButtonWidth = typedArray.getDimensionPixelSize(R.styleable.MessageInput_inputButtonWidth, style.getDimension(R.dimen.input_button_width));
|
||||
style.inputButtonHeight = typedArray.getDimensionPixelSize(R.styleable.MessageInput_inputButtonHeight, style.getDimension(R.dimen.input_button_height));
|
||||
style.inputButtonMargin = typedArray.getDimensionPixelSize(R.styleable.MessageInput_inputButtonMargin, style.getDimension(R.dimen.input_button_margin));
|
||||
|
||||
style.inputMaxLines = typedArray.getInt(R.styleable.MessageInput_inputMaxLines, DEFAULT_MAX_LINES);
|
||||
style.inputHint = typedArray.getString(R.styleable.MessageInput_inputHint);
|
||||
style.inputText = typedArray.getString(R.styleable.MessageInput_inputText);
|
||||
|
||||
style.inputTextSize = typedArray.getDimensionPixelSize(R.styleable.MessageInput_inputTextSize, style.getDimension(R.dimen.input_text_size));
|
||||
style.inputTextColor = typedArray.getColor(R.styleable.MessageInput_inputTextColor, style.getColor(R.color.dark_grey_two));
|
||||
style.inputHintColor = typedArray.getColor(R.styleable.MessageInput_inputHintColor, style.getColor(R.color.warm_grey_three));
|
||||
|
||||
style.inputBackground = typedArray.getDrawable(R.styleable.MessageInput_inputBackground);
|
||||
style.inputCursorDrawable = typedArray.getDrawable(R.styleable.MessageInput_inputCursorDrawable);
|
||||
|
||||
style.delayTypingStatus = typedArray.getInt(R.styleable.MessageInput_delayTypingStatus, DEFAULT_DELAY_TYPING_STATUS);
|
||||
|
||||
typedArray.recycle();
|
||||
|
||||
style.inputDefaultPaddingLeft = style.getDimension(R.dimen.input_padding_left);
|
||||
style.inputDefaultPaddingRight = style.getDimension(R.dimen.input_padding_right);
|
||||
style.inputDefaultPaddingTop = style.getDimension(R.dimen.input_padding_top);
|
||||
style.inputDefaultPaddingBottom = style.getDimension(R.dimen.input_padding_bottom);
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
private MessageInputStyle(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
private Drawable getSelector(@ColorInt int normalColor, @ColorInt int pressedColor,
|
||||
@ColorInt int disabledColor, @DrawableRes int shape) {
|
||||
|
||||
Drawable drawable = DrawableCompat.wrap(getVectorDrawable(shape)).mutate();
|
||||
DrawableCompat.setTintList(
|
||||
drawable,
|
||||
new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{android.R.attr.state_enabled, -android.R.attr.state_pressed},
|
||||
new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed},
|
||||
new int[]{-android.R.attr.state_enabled}
|
||||
},
|
||||
new int[]{normalColor, pressedColor, disabledColor}
|
||||
));
|
||||
return drawable;
|
||||
}
|
||||
|
||||
protected boolean showAttachmentButton() {
|
||||
return showAttachmentButton;
|
||||
}
|
||||
|
||||
protected Drawable getAttachmentButtonBackground() {
|
||||
if (attachmentButtonBackground == -1) {
|
||||
return getSelector(attachmentButtonDefaultBgColor, attachmentButtonDefaultBgPressedColor,
|
||||
attachmentButtonDefaultBgDisabledColor, R.drawable.mask);
|
||||
} else {
|
||||
return getDrawable(attachmentButtonBackground);
|
||||
}
|
||||
}
|
||||
|
||||
protected Drawable getAttachmentButtonIcon() {
|
||||
if (attachmentButtonIcon == -1) {
|
||||
return getSelector(attachmentButtonDefaultIconColor, attachmentButtonDefaultIconPressedColor,
|
||||
attachmentButtonDefaultIconDisabledColor, R.drawable.ic_add_attachment);
|
||||
} else {
|
||||
return getDrawable(attachmentButtonIcon);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getAttachmentButtonWidth() {
|
||||
return attachmentButtonWidth;
|
||||
}
|
||||
|
||||
protected int getAttachmentButtonHeight() {
|
||||
return attachmentButtonHeight;
|
||||
}
|
||||
|
||||
protected int getAttachmentButtonMargin() {
|
||||
return attachmentButtonMargin;
|
||||
}
|
||||
|
||||
protected Drawable getInputButtonBackground() {
|
||||
if (inputButtonBackground == -1) {
|
||||
return getSelector(inputButtonDefaultBgColor, inputButtonDefaultBgPressedColor,
|
||||
inputButtonDefaultBgDisabledColor, R.drawable.mask);
|
||||
} else {
|
||||
return getDrawable(inputButtonBackground);
|
||||
}
|
||||
}
|
||||
|
||||
protected Drawable getInputButtonIcon() {
|
||||
if (inputButtonIcon == -1) {
|
||||
return getSelector(inputButtonDefaultIconColor, inputButtonDefaultIconPressedColor,
|
||||
inputButtonDefaultIconDisabledColor, R.drawable.ic_send);
|
||||
} else {
|
||||
return getDrawable(inputButtonIcon);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getInputButtonMargin() {
|
||||
return inputButtonMargin;
|
||||
}
|
||||
|
||||
protected int getInputButtonWidth() {
|
||||
return inputButtonWidth;
|
||||
}
|
||||
|
||||
protected int getInputButtonHeight() {
|
||||
return inputButtonHeight;
|
||||
}
|
||||
|
||||
protected int getInputMaxLines() {
|
||||
return inputMaxLines;
|
||||
}
|
||||
|
||||
protected String getInputHint() {
|
||||
return inputHint;
|
||||
}
|
||||
|
||||
protected String getInputText() {
|
||||
return inputText;
|
||||
}
|
||||
|
||||
protected int getInputTextSize() {
|
||||
return inputTextSize;
|
||||
}
|
||||
|
||||
protected int getInputTextColor() {
|
||||
return inputTextColor;
|
||||
}
|
||||
|
||||
protected int getInputHintColor() {
|
||||
return inputHintColor;
|
||||
}
|
||||
|
||||
protected Drawable getInputBackground() {
|
||||
return inputBackground;
|
||||
}
|
||||
|
||||
protected Drawable getInputCursorDrawable() {
|
||||
return inputCursorDrawable;
|
||||
}
|
||||
|
||||
protected int getInputDefaultPaddingLeft() {
|
||||
return inputDefaultPaddingLeft;
|
||||
}
|
||||
|
||||
protected int getInputDefaultPaddingRight() {
|
||||
return inputDefaultPaddingRight;
|
||||
}
|
||||
|
||||
protected int getInputDefaultPaddingTop() {
|
||||
return inputDefaultPaddingTop;
|
||||
}
|
||||
|
||||
protected int getInputDefaultPaddingBottom() {
|
||||
return inputDefaultPaddingBottom;
|
||||
}
|
||||
|
||||
int getDelayTypingStatus() {
|
||||
return delayTypingStatus;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.messages;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
|
||||
import com.stfalcon.chatkit.commons.models.IMessage;
|
||||
|
||||
/**
|
||||
* Component for displaying list of messages
|
||||
*/
|
||||
public class MessagesList extends RecyclerView {
|
||||
private MessagesListStyle messagesListStyle;
|
||||
|
||||
public MessagesList(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public MessagesList(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
parseStyle(context, attrs);
|
||||
}
|
||||
|
||||
public MessagesList(Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
parseStyle(context, attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't use this method for setting your adapter, otherwise exception will by thrown.
|
||||
* Call {@link #setAdapter(MessagesListAdapter)} instead.
|
||||
*/
|
||||
@Override
|
||||
public void setAdapter(Adapter adapter) {
|
||||
throw new IllegalArgumentException("You can't set adapter to MessagesList. Use #setAdapter(MessagesListAdapter) instead.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adapter for MessagesList
|
||||
*
|
||||
* @param adapter Adapter. Must extend MessagesListAdapter
|
||||
* @param <MESSAGE> Message model class
|
||||
*/
|
||||
public <MESSAGE extends IMessage>
|
||||
void setAdapter(MessagesListAdapter<MESSAGE> adapter) {
|
||||
setAdapter(adapter, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adapter for MessagesList
|
||||
*
|
||||
* @param adapter Adapter. Must extend MessagesListAdapter
|
||||
* @param reverseLayout weather to use reverse layout for layout manager.
|
||||
* @param <MESSAGE> Message model class
|
||||
*/
|
||||
public <MESSAGE extends IMessage>
|
||||
void setAdapter(MessagesListAdapter<MESSAGE> adapter, boolean reverseLayout) {
|
||||
SimpleItemAnimator itemAnimator = new DefaultItemAnimator();
|
||||
itemAnimator.setSupportsChangeAnimations(false);
|
||||
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(),
|
||||
LinearLayoutManager.VERTICAL, reverseLayout);
|
||||
|
||||
setItemAnimator(itemAnimator);
|
||||
setLayoutManager(layoutManager);
|
||||
adapter.setLayoutManager(layoutManager);
|
||||
adapter.setStyle(messagesListStyle);
|
||||
|
||||
addOnScrollListener(new RecyclerScrollMoreListener(layoutManager, adapter));
|
||||
super.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResourceType")
|
||||
private void parseStyle(Context context, AttributeSet attrs) {
|
||||
messagesListStyle = MessagesListStyle.parse(context, attrs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,995 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.messages;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.text.Spannable;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TypedValue;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.stfalcon.chatkit.R;
|
||||
import com.stfalcon.chatkit.commons.ImageLoader;
|
||||
import com.stfalcon.chatkit.commons.ViewHolder;
|
||||
import com.stfalcon.chatkit.commons.models.IMessage;
|
||||
import com.stfalcon.chatkit.utils.DateFormatter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Adapter for {@link MessagesList}.
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class MessagesListAdapter<MESSAGE extends IMessage>
|
||||
extends RecyclerView.Adapter<ViewHolder>
|
||||
implements RecyclerScrollMoreListener.OnLoadMoreListener {
|
||||
|
||||
protected static boolean isSelectionModeEnabled;
|
||||
|
||||
protected List<Wrapper> items;
|
||||
private MessageHolders holders;
|
||||
private String senderId;
|
||||
|
||||
private int selectedItemsCount;
|
||||
private SelectionListener selectionListener;
|
||||
|
||||
private OnLoadMoreListener loadMoreListener;
|
||||
private OnMessageClickListener<MESSAGE> onMessageClickListener;
|
||||
private OnMessageViewClickListener<MESSAGE> onMessageViewClickListener;
|
||||
private OnMessageLongClickListener<MESSAGE> onMessageLongClickListener;
|
||||
private OnMessageViewLongClickListener<MESSAGE> onMessageViewLongClickListener;
|
||||
private ImageLoader imageLoader;
|
||||
private RecyclerView.LayoutManager layoutManager;
|
||||
private MessagesListStyle messagesListStyle;
|
||||
private DateFormatter.Formatter dateHeadersFormatter;
|
||||
private SparseArray<OnMessageViewClickListener> viewClickListenersArray = new SparseArray<>();
|
||||
|
||||
/**
|
||||
* For default list item layout and view holder.
|
||||
*
|
||||
* @param senderId identifier of sender.
|
||||
* @param imageLoader image loading method.
|
||||
*/
|
||||
public MessagesListAdapter(String senderId, ImageLoader imageLoader) {
|
||||
this(senderId, new MessageHolders(), imageLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* For default list item layout and view holder.
|
||||
*
|
||||
* @param senderId identifier of sender.
|
||||
* @param holders custom layouts and view holders. See {@link MessageHolders} documentation for details
|
||||
* @param imageLoader image loading method.
|
||||
*/
|
||||
public MessagesListAdapter(String senderId, MessageHolders holders,
|
||||
ImageLoader imageLoader) {
|
||||
this.senderId = senderId;
|
||||
this.holders = holders;
|
||||
this.imageLoader = imageLoader;
|
||||
this.items = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return holders.getHolder(parent, viewType, messagesListStyle);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Wrapper wrapper = items.get(position);
|
||||
holders.bind(holder, wrapper.item, wrapper.isSelected, imageLoader,
|
||||
getMessageClickListener(wrapper),
|
||||
getMessageLongClickListener(wrapper),
|
||||
dateHeadersFormatter,
|
||||
viewClickListenersArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return holders.getViewType(items.get(position).item, senderId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadMore(int page, int total) {
|
||||
if (loadMoreListener != null) {
|
||||
loadMoreListener.onLoadMore(page, total);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMessagesCount() {
|
||||
int count = 0;
|
||||
for (Wrapper item : items) {
|
||||
if (item.item instanceof IMessage) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* PUBLIC METHODS
|
||||
* */
|
||||
|
||||
/**
|
||||
* Adds message to bottom of list and scroll if needed.
|
||||
*
|
||||
* @param message message to add.
|
||||
* @param scroll {@code true} if need to scroll list to bottom when message added.
|
||||
*/
|
||||
public void addToStart(MESSAGE message, boolean scroll) {
|
||||
boolean isNewMessageToday = !isPreviousSameDate(0, message.getCreatedAt());
|
||||
if (isNewMessageToday) {
|
||||
items.add(0, new Wrapper<>(message.getCreatedAt()));
|
||||
}
|
||||
Wrapper<MESSAGE> element = new Wrapper<>(message);
|
||||
items.add(0, element);
|
||||
notifyItemRangeInserted(0, isNewMessageToday ? 2 : 1);
|
||||
if (layoutManager != null && scroll) {
|
||||
layoutManager.scrollToPosition(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds messages list in chronological order. Use this method to add history.
|
||||
*
|
||||
* @param messages messages from history.
|
||||
* @param reverse {@code true} if need to reverse messages before adding.
|
||||
*/
|
||||
public void addToEnd(List<MESSAGE> messages, boolean reverse) {
|
||||
if (messages.isEmpty()) return;
|
||||
|
||||
if (reverse) Collections.reverse(messages);
|
||||
|
||||
if (!items.isEmpty()) {
|
||||
int lastItemPosition = items.size() - 1;
|
||||
Date lastItem = (Date) items.get(lastItemPosition).item;
|
||||
if (DateFormatter.isSameDay(messages.get(0).getCreatedAt(), lastItem)) {
|
||||
items.remove(lastItemPosition);
|
||||
notifyItemRemoved(lastItemPosition);
|
||||
}
|
||||
}
|
||||
|
||||
int oldSize = items.size();
|
||||
generateDateHeaders(messages);
|
||||
notifyItemRangeInserted(oldSize, items.size() - oldSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates message by its id.
|
||||
*
|
||||
* @param message updated message object.
|
||||
*/
|
||||
public boolean update(MESSAGE message) {
|
||||
return update(message.getId(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates message by old identifier (use this method if id has changed). Otherwise use {@link #update(IMessage)}
|
||||
*
|
||||
* @param oldId an identifier of message to update.
|
||||
* @param newMessage new message object.
|
||||
*/
|
||||
public boolean update(String oldId, MESSAGE newMessage) {
|
||||
int position = getMessagePositionById(oldId);
|
||||
if (position >= 0) {
|
||||
Wrapper<MESSAGE> element = new Wrapper<>(newMessage);
|
||||
items.set(position, element);
|
||||
notifyItemChanged(position);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the elements position from current to start
|
||||
*
|
||||
* @param newMessage new message object.
|
||||
*/
|
||||
public void updateAndMoveToStart(MESSAGE newMessage) {
|
||||
int position = getMessagePositionById(newMessage.getId());
|
||||
if (position >= 0) {
|
||||
Wrapper<MESSAGE> element = new Wrapper<>(newMessage);
|
||||
items.remove(position);
|
||||
items.add(0, element);
|
||||
notifyItemMoved(position, 0);
|
||||
notifyItemChanged(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates message by its id if it exists, add to start if not
|
||||
*
|
||||
* @param message message object to insert or update.
|
||||
*/
|
||||
public void upsert(MESSAGE message) {
|
||||
if (!update(message)) {
|
||||
addToStart(message, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates and moves to start if message by its id exists and if specified move to start, if not
|
||||
* specified the item stays at current position and updated
|
||||
*
|
||||
* @param message message object to insert or update.
|
||||
*/
|
||||
public void upsert(MESSAGE message, boolean moveToStartIfUpdate) {
|
||||
if (moveToStartIfUpdate) {
|
||||
if (getMessagePositionById(message.getId()) > 0) {
|
||||
updateAndMoveToStart(message);
|
||||
} else {
|
||||
upsert(message);
|
||||
}
|
||||
} else {
|
||||
upsert(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes message.
|
||||
*
|
||||
* @param message message to delete.
|
||||
*/
|
||||
public void delete(MESSAGE message) {
|
||||
deleteById(message.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes messages list.
|
||||
*
|
||||
* @param messages messages list to delete.
|
||||
*/
|
||||
public void delete(List<MESSAGE> messages) {
|
||||
boolean result = false;
|
||||
for (MESSAGE message : messages) {
|
||||
int index = getMessagePositionById(message.getId());
|
||||
if (index >= 0) {
|
||||
items.remove(index);
|
||||
notifyItemRemoved(index);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
recountDateHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes message by its identifier.
|
||||
*
|
||||
* @param id identifier of message to delete.
|
||||
*/
|
||||
public void deleteById(String id) {
|
||||
int index = getMessagePositionById(id);
|
||||
if (index >= 0) {
|
||||
items.remove(index);
|
||||
notifyItemRemoved(index);
|
||||
recountDateHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes messages by its identifiers.
|
||||
*
|
||||
* @param ids array of identifiers of messages to delete.
|
||||
*/
|
||||
public void deleteByIds(String[] ids) {
|
||||
boolean result = false;
|
||||
for (String id : ids) {
|
||||
int index = getMessagePositionById(id);
|
||||
if (index >= 0) {
|
||||
items.remove(index);
|
||||
notifyItemRemoved(index);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
if (result) {
|
||||
recountDateHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if, and only if, messages count in adapter is non-zero.
|
||||
*
|
||||
* @return {@code true} if size is 0, otherwise {@code false}
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return items.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the messages list. With notifyDataSetChanged
|
||||
*/
|
||||
public void clear() {
|
||||
clear(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the messages list.
|
||||
*/
|
||||
public void clear(boolean notifyDataSetChanged) {
|
||||
if (items != null) {
|
||||
items.clear();
|
||||
if (notifyDataSetChanged) {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables selection mode.
|
||||
*
|
||||
* @param selectionListener listener for selected items count. To get selected messages use {@link #getSelectedMessages()}.
|
||||
*/
|
||||
public void enableSelectionMode(SelectionListener selectionListener) {
|
||||
if (selectionListener == null) {
|
||||
throw new IllegalArgumentException("SelectionListener must not be null. Use `disableSelectionMode()` if you want tp disable selection mode");
|
||||
} else {
|
||||
this.selectionListener = selectionListener;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables selection mode and removes {@link SelectionListener}.
|
||||
*/
|
||||
public void disableSelectionMode() {
|
||||
this.selectionListener = null;
|
||||
unselectAllItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of selected messages.
|
||||
*
|
||||
* @return list of selected messages. Empty list if nothing was selected or selection mode is disabled.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public ArrayList<MESSAGE> getSelectedMessages() {
|
||||
ArrayList<MESSAGE> selectedMessages = new ArrayList<>();
|
||||
for (Wrapper wrapper : items) {
|
||||
if (wrapper.item instanceof IMessage && wrapper.isSelected) {
|
||||
selectedMessages.add((MESSAGE) wrapper.item);
|
||||
}
|
||||
}
|
||||
return selectedMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns selected messages text and do {@link #unselectAllItems()} for you.
|
||||
*
|
||||
* @param formatter The formatter that allows you to format your message model when copying.
|
||||
* @param reverse Change ordering when copying messages.
|
||||
* @return formatted text by {@link Formatter}. If it's {@code null} - {@code MESSAGE#toString()} will be used.
|
||||
*/
|
||||
public String getSelectedMessagesText(Formatter<MESSAGE> formatter, boolean reverse) {
|
||||
String copiedText = getSelectedText(formatter, reverse);
|
||||
unselectAllItems();
|
||||
return copiedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies text to device clipboard and returns selected messages text. Also it does {@link #unselectAllItems()} for you.
|
||||
*
|
||||
* @param context The context.
|
||||
* @param formatter The formatter that allows you to format your message model when copying.
|
||||
* @param reverse Change ordering when copying messages.
|
||||
* @return formatted text by {@link Formatter}. If it's {@code null} - {@code MESSAGE#toString()} will be used.
|
||||
*/
|
||||
public String copySelectedMessagesText(Context context, Formatter<MESSAGE> formatter, boolean reverse) {
|
||||
String copiedText = getSelectedText(formatter, reverse);
|
||||
copyToClipboard(context, copiedText);
|
||||
unselectAllItems();
|
||||
return copiedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect all of the selected messages. Notifies {@link SelectionListener} with zero count.
|
||||
*/
|
||||
public void unselectAllItems() {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
Wrapper wrapper = items.get(i);
|
||||
if (wrapper.isSelected) {
|
||||
wrapper.isSelected = false;
|
||||
notifyItemChanged(i);
|
||||
}
|
||||
}
|
||||
isSelectionModeEnabled = false;
|
||||
selectedItemsCount = 0;
|
||||
notifySelectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all of the selected messages and disables selection mode.
|
||||
* Call {@link #getSelectedMessages()} before calling this method to delete messages from your data source.
|
||||
*/
|
||||
public void deleteSelectedMessages() {
|
||||
List<MESSAGE> selectedMessages = getSelectedMessages();
|
||||
delete(selectedMessages);
|
||||
unselectAllItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listener for item. Fires ONLY if list is not in selection mode.
|
||||
*
|
||||
* @param onMessageClickListener click listener.
|
||||
*/
|
||||
public void setOnMessageClickListener(OnMessageClickListener<MESSAGE> onMessageClickListener) {
|
||||
this.onMessageClickListener = onMessageClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listener for message view. Fires ONLY if list is not in selection mode.
|
||||
*
|
||||
* @param onMessageViewClickListener click listener.
|
||||
*/
|
||||
public void setOnMessageViewClickListener(OnMessageViewClickListener<MESSAGE> onMessageViewClickListener) {
|
||||
this.onMessageViewClickListener = onMessageViewClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers click listener for view by id
|
||||
*
|
||||
* @param viewId view
|
||||
* @param onMessageViewClickListener click listener.
|
||||
*/
|
||||
public void registerViewClickListener(int viewId, OnMessageViewClickListener<MESSAGE> onMessageViewClickListener) {
|
||||
this.viewClickListenersArray.append(viewId, onMessageViewClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets long click listener for item. Fires only if selection mode is disabled.
|
||||
*
|
||||
* @param onMessageLongClickListener long click listener.
|
||||
*/
|
||||
public void setOnMessageLongClickListener(OnMessageLongClickListener<MESSAGE> onMessageLongClickListener) {
|
||||
this.onMessageLongClickListener = onMessageLongClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets long click listener for message view. Fires ONLY if selection mode is disabled.
|
||||
*
|
||||
* @param onMessageViewLongClickListener long click listener.
|
||||
*/
|
||||
public void setOnMessageViewLongClickListener(OnMessageViewLongClickListener<MESSAGE> onMessageViewLongClickListener) {
|
||||
this.onMessageViewLongClickListener = onMessageViewLongClickListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set callback to be invoked when list scrolled to top.
|
||||
*
|
||||
* @param loadMoreListener listener.
|
||||
*/
|
||||
public void setLoadMoreListener(OnLoadMoreListener loadMoreListener) {
|
||||
this.loadMoreListener = loadMoreListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets custom {@link DateFormatter.Formatter} for text representation of date headers.
|
||||
*/
|
||||
public void setDateHeadersFormatter(DateFormatter.Formatter dateHeadersFormatter) {
|
||||
this.dateHeadersFormatter = dateHeadersFormatter;
|
||||
}
|
||||
|
||||
/*
|
||||
* PRIVATE METHODS
|
||||
* */
|
||||
private void recountDateHeaders() {
|
||||
List<Integer> indicesToDelete = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
Wrapper wrapper = items.get(i);
|
||||
if (wrapper.item instanceof Date) {
|
||||
if (i == 0) {
|
||||
indicesToDelete.add(i);
|
||||
} else {
|
||||
if (items.get(i - 1).item instanceof Date) {
|
||||
indicesToDelete.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.reverse(indicesToDelete);
|
||||
for (int i : indicesToDelete) {
|
||||
items.remove(i);
|
||||
notifyItemRemoved(i);
|
||||
}
|
||||
}
|
||||
|
||||
protected void generateDateHeaders(List<MESSAGE> messages) {
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
MESSAGE message = messages.get(i);
|
||||
this.items.add(new Wrapper<>(message));
|
||||
if (messages.size() > i + 1) {
|
||||
MESSAGE nextMessage = messages.get(i + 1);
|
||||
if (!DateFormatter.isSameDay(message.getCreatedAt(), nextMessage.getCreatedAt())) {
|
||||
this.items.add(new Wrapper<>(message.getCreatedAt()));
|
||||
}
|
||||
} else {
|
||||
this.items.add(new Wrapper<>(message.getCreatedAt()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private int getMessagePositionById(String id) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
Wrapper wrapper = items.get(i);
|
||||
if (wrapper.item instanceof IMessage) {
|
||||
MESSAGE message = (MESSAGE) wrapper.item;
|
||||
if (message.getId().contentEquals(id)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isPreviousSameDate(int position, Date dateToCompare) {
|
||||
if (items.size() <= position) return false;
|
||||
if (items.get(position).item instanceof IMessage) {
|
||||
Date previousPositionDate = ((MESSAGE) items.get(position).item).getCreatedAt();
|
||||
return DateFormatter.isSameDay(dateToCompare, previousPositionDate);
|
||||
} else return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean isPreviousSameAuthor(String id, int position) {
|
||||
int prevPosition = position + 1;
|
||||
if (items.size() <= prevPosition) return false;
|
||||
else return items.get(prevPosition).item instanceof IMessage
|
||||
&& ((MESSAGE) items.get(prevPosition).item).getUser().getId().contentEquals(id);
|
||||
}
|
||||
|
||||
private void incrementSelectedItemsCount() {
|
||||
selectedItemsCount++;
|
||||
notifySelectionChanged();
|
||||
}
|
||||
|
||||
private void decrementSelectedItemsCount() {
|
||||
selectedItemsCount--;
|
||||
isSelectionModeEnabled = selectedItemsCount > 0;
|
||||
|
||||
notifySelectionChanged();
|
||||
}
|
||||
|
||||
private void notifySelectionChanged() {
|
||||
if (selectionListener != null) {
|
||||
selectionListener.onSelectionChanged(selectedItemsCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyMessageClicked(MESSAGE message) {
|
||||
if (onMessageClickListener != null) {
|
||||
onMessageClickListener.onMessageClick(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyMessageViewClicked(View view, MESSAGE message) {
|
||||
if (onMessageViewClickListener != null) {
|
||||
onMessageViewClickListener.onMessageViewClick(view, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyMessageLongClicked(MESSAGE message) {
|
||||
if (onMessageLongClickListener != null) {
|
||||
onMessageLongClickListener.onMessageLongClick(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyMessageViewLongClicked(View view, MESSAGE message) {
|
||||
if (onMessageViewLongClickListener != null) {
|
||||
onMessageViewLongClickListener.onMessageViewLongClick(view, message);
|
||||
}
|
||||
}
|
||||
|
||||
private View.OnClickListener getMessageClickListener(final Wrapper<MESSAGE> wrapper) {
|
||||
return view -> {
|
||||
if (selectionListener != null && isSelectionModeEnabled) {
|
||||
wrapper.isSelected = !wrapper.isSelected;
|
||||
|
||||
if (wrapper.isSelected) incrementSelectedItemsCount();
|
||||
else decrementSelectedItemsCount();
|
||||
|
||||
MESSAGE message = (wrapper.item);
|
||||
notifyItemChanged(getMessagePositionById(message.getId()));
|
||||
} else {
|
||||
notifyMessageClicked(wrapper.item);
|
||||
notifyMessageViewClicked(view, wrapper.item);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private View.OnLongClickListener getMessageLongClickListener(final Wrapper<MESSAGE> wrapper) {
|
||||
return view -> {
|
||||
if (selectionListener == null) {
|
||||
notifyMessageLongClicked(wrapper.item);
|
||||
notifyMessageViewLongClicked(view, wrapper.item);
|
||||
} else {
|
||||
isSelectionModeEnabled = true;
|
||||
view.performClick();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
private String getSelectedText(Formatter<MESSAGE> formatter, boolean reverse) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
ArrayList<MESSAGE> selectedMessages = getSelectedMessages();
|
||||
if (reverse) Collections.reverse(selectedMessages);
|
||||
|
||||
for (MESSAGE message : selectedMessages) {
|
||||
builder.append(formatter == null
|
||||
? message.toString()
|
||||
: formatter.format(message));
|
||||
builder.append("\n\n");
|
||||
}
|
||||
builder.replace(builder.length() - 2, builder.length(), "");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void copyToClipboard(Context context, String copiedText) {
|
||||
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(copiedText, copiedText);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
|
||||
void setLayoutManager(RecyclerView.LayoutManager layoutManager) {
|
||||
this.layoutManager = layoutManager;
|
||||
}
|
||||
|
||||
void setStyle(MessagesListStyle style) {
|
||||
this.messagesListStyle = style;
|
||||
}
|
||||
|
||||
/*
|
||||
* WRAPPER
|
||||
* */
|
||||
public static class Wrapper<DATA> {
|
||||
public DATA item;
|
||||
public boolean isSelected;
|
||||
|
||||
Wrapper(DATA item) {
|
||||
this.item = item;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* LISTENERS
|
||||
* */
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when next part of messages need to be loaded.
|
||||
*/
|
||||
public interface OnLoadMoreListener {
|
||||
|
||||
/**
|
||||
* Fires when user scrolled to the end of list.
|
||||
*
|
||||
* @param page next page to download.
|
||||
* @param totalItemsCount current items count.
|
||||
*/
|
||||
void onLoadMore(int page, int totalItemsCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when selected messages count is changed.
|
||||
*/
|
||||
public interface SelectionListener {
|
||||
|
||||
/**
|
||||
* Fires when selected items count is changed.
|
||||
*
|
||||
* @param count count of selected items.
|
||||
*/
|
||||
void onSelectionChanged(int count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when message item is clicked.
|
||||
*/
|
||||
public interface OnMessageClickListener<MESSAGE extends IMessage> {
|
||||
|
||||
/**
|
||||
* Fires when message is clicked.
|
||||
*
|
||||
* @param message clicked message.
|
||||
*/
|
||||
void onMessageClick(MESSAGE message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when message view is clicked.
|
||||
*/
|
||||
public interface OnMessageViewClickListener<MESSAGE extends IMessage> {
|
||||
|
||||
/**
|
||||
* Fires when message view is clicked.
|
||||
*
|
||||
* @param message clicked message.
|
||||
*/
|
||||
void onMessageViewClick(View view, MESSAGE message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when message item is long clicked.
|
||||
*/
|
||||
public interface OnMessageLongClickListener<MESSAGE extends IMessage> {
|
||||
|
||||
/**
|
||||
* Fires when message is long clicked.
|
||||
*
|
||||
* @param message clicked message.
|
||||
*/
|
||||
void onMessageLongClick(MESSAGE message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be invoked when message view is long clicked.
|
||||
*/
|
||||
public interface OnMessageViewLongClickListener<MESSAGE extends IMessage> {
|
||||
|
||||
/**
|
||||
* Fires when message view is long clicked.
|
||||
*
|
||||
* @param message clicked message.
|
||||
*/
|
||||
void onMessageViewLongClick(View view, MESSAGE message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to format your message model when copying.
|
||||
*/
|
||||
public interface Formatter<MESSAGE> {
|
||||
|
||||
/**
|
||||
* Formats an string representation of the message object.
|
||||
*
|
||||
* @param message The object that should be formatted.
|
||||
* @return Formatted text.
|
||||
*/
|
||||
String format(MESSAGE message);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is deprecated. Use {@link MessageHolders} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class HoldersConfig extends MessageHolders {
|
||||
|
||||
/**
|
||||
* This method is deprecated. Use {@link MessageHolders#setIncomingTextConfig(Class, int)} instead.
|
||||
*
|
||||
* @param holder holder class.
|
||||
* @param layout layout resource.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setIncoming(Class<? extends BaseMessageViewHolder<? extends IMessage>> holder, @LayoutRes int layout) {
|
||||
super.setIncomingTextConfig(holder, layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is deprecated. Use {@link MessageHolders#setIncomingTextHolder(Class)} instead.
|
||||
*
|
||||
* @param holder holder class.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setIncomingHolder(Class<? extends BaseMessageViewHolder<? extends IMessage>> holder) {
|
||||
super.setIncomingTextHolder(holder);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is deprecated. Use {@link MessageHolders#setIncomingTextLayout(int)} instead.
|
||||
*
|
||||
* @param layout layout resource.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setIncomingLayout(@LayoutRes int layout) {
|
||||
super.setIncomingTextLayout(layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is deprecated. Use {@link MessageHolders#setOutcomingTextConfig(Class, int)} instead.
|
||||
*
|
||||
* @param holder holder class.
|
||||
* @param layout layout resource.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setOutcoming(Class<? extends BaseMessageViewHolder<? extends IMessage>> holder, @LayoutRes int layout) {
|
||||
super.setOutcomingTextConfig(holder, layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is deprecated. Use {@link MessageHolders#setOutcomingTextHolder(Class)} instead.
|
||||
*
|
||||
* @param holder holder class.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setOutcomingHolder(Class<? extends BaseMessageViewHolder<? extends IMessage>> holder) {
|
||||
super.setOutcomingTextHolder(holder);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is deprecated. Use {@link MessageHolders#setOutcomingTextLayout(int)} instead.
|
||||
*
|
||||
* @param layout layout resource.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setOutcomingLayout(@LayoutRes int layout) {
|
||||
this.setOutcomingTextLayout(layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is deprecated. Use {@link MessageHolders#setDateHeaderConfig(Class, int)} instead.
|
||||
*
|
||||
* @param holder holder class.
|
||||
* @param layout layout resource.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDateHeader(Class<? extends ViewHolder<Date>> holder, @LayoutRes int layout) {
|
||||
super.setDateHeaderConfig(holder, layout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is deprecated. Use {@link MessageHolders.BaseMessageViewHolder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static abstract class BaseMessageViewHolder<MESSAGE extends IMessage>
|
||||
extends MessageHolders.BaseMessageViewHolder<MESSAGE> {
|
||||
|
||||
private boolean isSelected;
|
||||
|
||||
/**
|
||||
* Callback for implementing images loading in message list
|
||||
*/
|
||||
protected ImageLoader imageLoader;
|
||||
|
||||
public BaseMessageViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether is item selected
|
||||
*
|
||||
* @return weather is item selected.
|
||||
*/
|
||||
public boolean isSelected() {
|
||||
return isSelected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns weather is selection mode enabled
|
||||
*
|
||||
* @return weather is selection mode enabled.
|
||||
*/
|
||||
public boolean isSelectionModeEnabled() {
|
||||
return isSelectionModeEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for {@link #imageLoader}
|
||||
*
|
||||
* @return image loader interface.
|
||||
*/
|
||||
public ImageLoader getImageLoader() {
|
||||
return imageLoader;
|
||||
}
|
||||
|
||||
protected void configureLinksBehavior(final TextView text) {
|
||||
text.setLinksClickable(false);
|
||||
text.setMovementMethod(new LinkMovementMethod() {
|
||||
@Override
|
||||
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
|
||||
boolean result = false;
|
||||
if (!isSelectionModeEnabled) {
|
||||
result = super.onTouchEvent(widget, buffer, event);
|
||||
}
|
||||
itemView.onTouchEvent(event);
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is deprecated. Use {@link MessageHolders.DefaultDateHeaderViewHolder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class DefaultDateHeaderViewHolder extends ViewHolder<Date>
|
||||
implements MessageHolders.DefaultMessageViewHolder {
|
||||
|
||||
protected TextView text;
|
||||
protected String dateFormat;
|
||||
protected DateFormatter.Formatter dateHeadersFormatter;
|
||||
|
||||
public DefaultDateHeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
text = itemView.findViewById(R.id.messageText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(Date date) {
|
||||
if (text != null) {
|
||||
String formattedDate = null;
|
||||
if (dateHeadersFormatter != null) formattedDate = dateHeadersFormatter.format(date);
|
||||
text.setText(formattedDate == null ? DateFormatter.format(date, dateFormat) : formattedDate);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(MessagesListStyle style) {
|
||||
if (text != null) {
|
||||
text.setTextColor(style.getDateHeaderTextColor());
|
||||
text.setTextSize(TypedValue.COMPLEX_UNIT_PX, style.getDateHeaderTextSize());
|
||||
text.setTypeface(text.getTypeface(), style.getDateHeaderTextStyle());
|
||||
text.setPadding(style.getDateHeaderPadding(), style.getDateHeaderPadding(),
|
||||
style.getDateHeaderPadding(), style.getDateHeaderPadding());
|
||||
}
|
||||
dateFormat = style.getDateHeaderFormat();
|
||||
dateFormat = dateFormat == null ? DateFormatter.Template.STRING_DAY_MONTH_YEAR.get() : dateFormat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is deprecated. Use {@link MessageHolders.IncomingTextMessageViewHolder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class IncomingMessageViewHolder<MESSAGE extends IMessage>
|
||||
extends MessageHolders.IncomingTextMessageViewHolder<MESSAGE>
|
||||
implements MessageHolders.DefaultMessageViewHolder {
|
||||
|
||||
public IncomingMessageViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is deprecated. Use {@link MessageHolders.OutcomingTextMessageViewHolder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class OutcomingMessageViewHolder<MESSAGE extends IMessage>
|
||||
extends MessageHolders.OutcomingTextMessageViewHolder<MESSAGE> {
|
||||
|
||||
public OutcomingMessageViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.messages;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
|
||||
import com.stfalcon.chatkit.R;
|
||||
import com.stfalcon.chatkit.commons.Style;
|
||||
|
||||
/**
|
||||
* Style for MessagesListStyle customization by xml attributes
|
||||
*/
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
class MessagesListStyle extends Style {
|
||||
|
||||
private int textAutoLinkMask;
|
||||
private int incomingTextLinkColor;
|
||||
private int outcomingTextLinkColor;
|
||||
|
||||
private int incomingAvatarWidth;
|
||||
private int incomingAvatarHeight;
|
||||
|
||||
private int incomingBubbleDrawable;
|
||||
private int incomingDefaultBubbleColor;
|
||||
private int incomingDefaultBubblePressedColor;
|
||||
private int incomingDefaultBubbleSelectedColor;
|
||||
|
||||
private int incomingImageOverlayDrawable;
|
||||
private int incomingDefaultImageOverlayPressedColor;
|
||||
private int incomingDefaultImageOverlaySelectedColor;
|
||||
|
||||
private int incomingDefaultBubblePaddingLeft;
|
||||
private int incomingDefaultBubblePaddingRight;
|
||||
private int incomingDefaultBubblePaddingTop;
|
||||
private int incomingDefaultBubblePaddingBottom;
|
||||
|
||||
private int incomingTextColor;
|
||||
private int incomingTextSize;
|
||||
private int incomingTextStyle;
|
||||
|
||||
private int incomingTimeTextColor;
|
||||
private int incomingTimeTextSize;
|
||||
private int incomingTimeTextStyle;
|
||||
|
||||
private int incomingImageTimeTextColor;
|
||||
private int incomingImageTimeTextSize;
|
||||
private int incomingImageTimeTextStyle;
|
||||
|
||||
private int outcomingBubbleDrawable;
|
||||
private int outcomingDefaultBubbleColor;
|
||||
private int outcomingDefaultBubblePressedColor;
|
||||
private int outcomingDefaultBubbleSelectedColor;
|
||||
|
||||
private int outcomingImageOverlayDrawable;
|
||||
private int outcomingDefaultImageOverlayPressedColor;
|
||||
private int outcomingDefaultImageOverlaySelectedColor;
|
||||
|
||||
private int outcomingDefaultBubblePaddingLeft;
|
||||
private int outcomingDefaultBubblePaddingRight;
|
||||
private int outcomingDefaultBubblePaddingTop;
|
||||
private int outcomingDefaultBubblePaddingBottom;
|
||||
|
||||
private int outcomingTextColor;
|
||||
private int outcomingTextSize;
|
||||
private int outcomingTextStyle;
|
||||
|
||||
private int outcomingTimeTextColor;
|
||||
private int outcomingTimeTextSize;
|
||||
private int outcomingTimeTextStyle;
|
||||
|
||||
private int outcomingImageTimeTextColor;
|
||||
private int outcomingImageTimeTextSize;
|
||||
private int outcomingImageTimeTextStyle;
|
||||
|
||||
private int dateHeaderPadding;
|
||||
private String dateHeaderFormat;
|
||||
private int dateHeaderTextColor;
|
||||
private int dateHeaderTextSize;
|
||||
private int dateHeaderTextStyle;
|
||||
|
||||
static MessagesListStyle parse(Context context, AttributeSet attrs) {
|
||||
MessagesListStyle style = new MessagesListStyle(context, attrs);
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MessagesList);
|
||||
|
||||
style.textAutoLinkMask = typedArray.getInt(R.styleable.MessagesList_textAutoLink, 0);
|
||||
style.incomingTextLinkColor = typedArray.getColor(R.styleable.MessagesList_incomingTextLinkColor,
|
||||
style.getSystemAccentColor());
|
||||
style.outcomingTextLinkColor = typedArray.getColor(R.styleable.MessagesList_outcomingTextLinkColor,
|
||||
style.getSystemAccentColor());
|
||||
|
||||
style.incomingAvatarWidth = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingAvatarWidth,
|
||||
style.getDimension(R.dimen.message_avatar_width));
|
||||
style.incomingAvatarHeight = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingAvatarHeight,
|
||||
style.getDimension(R.dimen.message_avatar_height));
|
||||
|
||||
style.incomingBubbleDrawable = typedArray.getResourceId(R.styleable.MessagesList_incomingBubbleDrawable, -1);
|
||||
style.incomingDefaultBubbleColor = typedArray.getColor(R.styleable.MessagesList_incomingDefaultBubbleColor,
|
||||
style.getColor(R.color.white_two));
|
||||
style.incomingDefaultBubblePressedColor = typedArray.getColor(R.styleable.MessagesList_incomingDefaultBubblePressedColor,
|
||||
style.getColor(R.color.white_two));
|
||||
style.incomingDefaultBubbleSelectedColor = typedArray.getColor(R.styleable.MessagesList_incomingDefaultBubbleSelectedColor,
|
||||
style.getColor(R.color.cornflower_blue_two_24));
|
||||
|
||||
style.incomingImageOverlayDrawable = typedArray.getResourceId(R.styleable.MessagesList_incomingImageOverlayDrawable, -1);
|
||||
style.incomingDefaultImageOverlayPressedColor = typedArray.getColor(R.styleable.MessagesList_incomingDefaultImageOverlayPressedColor,
|
||||
style.getColor(R.color.transparent));
|
||||
style.incomingDefaultImageOverlaySelectedColor = typedArray.getColor(R.styleable.MessagesList_incomingDefaultImageOverlaySelectedColor,
|
||||
style.getColor(R.color.cornflower_blue_light_40));
|
||||
|
||||
style.incomingDefaultBubblePaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingBubblePaddingLeft,
|
||||
style.getDimension(R.dimen.message_padding_left));
|
||||
style.incomingDefaultBubblePaddingRight = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingBubblePaddingRight,
|
||||
style.getDimension(R.dimen.message_padding_right));
|
||||
style.incomingDefaultBubblePaddingTop = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingBubblePaddingTop,
|
||||
style.getDimension(R.dimen.message_padding_top));
|
||||
style.incomingDefaultBubblePaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingBubblePaddingBottom,
|
||||
style.getDimension(R.dimen.message_padding_bottom));
|
||||
style.incomingTextColor = typedArray.getColor(R.styleable.MessagesList_incomingTextColor,
|
||||
style.getColor(R.color.dark_grey_two));
|
||||
style.incomingTextSize = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingTextSize,
|
||||
style.getDimension(R.dimen.message_text_size));
|
||||
style.incomingTextStyle = typedArray.getInt(R.styleable.MessagesList_incomingTextStyle, Typeface.NORMAL);
|
||||
|
||||
style.incomingTimeTextColor = typedArray.getColor(R.styleable.MessagesList_incomingTimeTextColor,
|
||||
style.getColor(R.color.warm_grey_four));
|
||||
style.incomingTimeTextSize = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingTimeTextSize,
|
||||
style.getDimension(R.dimen.message_time_text_size));
|
||||
style.incomingTimeTextStyle = typedArray.getInt(R.styleable.MessagesList_incomingTimeTextStyle, Typeface.NORMAL);
|
||||
|
||||
style.incomingImageTimeTextColor = typedArray.getColor(R.styleable.MessagesList_incomingImageTimeTextColor,
|
||||
style.getColor(R.color.warm_grey_four));
|
||||
style.incomingImageTimeTextSize = typedArray.getDimensionPixelSize(R.styleable.MessagesList_incomingImageTimeTextSize,
|
||||
style.getDimension(R.dimen.message_time_text_size));
|
||||
style.incomingImageTimeTextStyle = typedArray.getInt(R.styleable.MessagesList_incomingImageTimeTextStyle, Typeface.NORMAL);
|
||||
|
||||
style.outcomingBubbleDrawable = typedArray.getResourceId(R.styleable.MessagesList_outcomingBubbleDrawable, -1);
|
||||
style.outcomingDefaultBubbleColor = typedArray.getColor(R.styleable.MessagesList_outcomingDefaultBubbleColor,
|
||||
style.getColor(R.color.cornflower_blue_two));
|
||||
style.outcomingDefaultBubblePressedColor = typedArray.getColor(R.styleable.MessagesList_outcomingDefaultBubblePressedColor,
|
||||
style.getColor(R.color.cornflower_blue_two));
|
||||
style.outcomingDefaultBubbleSelectedColor = typedArray.getColor(R.styleable.MessagesList_outcomingDefaultBubbleSelectedColor,
|
||||
style.getColor(R.color.cornflower_blue_two_24));
|
||||
|
||||
style.outcomingImageOverlayDrawable = typedArray.getResourceId(R.styleable.MessagesList_outcomingImageOverlayDrawable, -1);
|
||||
style.outcomingDefaultImageOverlayPressedColor = typedArray.getColor(R.styleable.MessagesList_outcomingDefaultImageOverlayPressedColor,
|
||||
style.getColor(R.color.transparent));
|
||||
style.outcomingDefaultImageOverlaySelectedColor = typedArray.getColor(R.styleable.MessagesList_outcomingDefaultImageOverlaySelectedColor,
|
||||
style.getColor(R.color.cornflower_blue_light_40));
|
||||
|
||||
style.outcomingDefaultBubblePaddingLeft = typedArray.getDimensionPixelSize(R.styleable.MessagesList_outcomingBubblePaddingLeft,
|
||||
style.getDimension(R.dimen.message_padding_left));
|
||||
style.outcomingDefaultBubblePaddingRight = typedArray.getDimensionPixelSize(R.styleable.MessagesList_outcomingBubblePaddingRight,
|
||||
style.getDimension(R.dimen.message_padding_right));
|
||||
style.outcomingDefaultBubblePaddingTop = typedArray.getDimensionPixelSize(R.styleable.MessagesList_outcomingBubblePaddingTop,
|
||||
style.getDimension(R.dimen.message_padding_top));
|
||||
style.outcomingDefaultBubblePaddingBottom = typedArray.getDimensionPixelSize(R.styleable.MessagesList_outcomingBubblePaddingBottom,
|
||||
style.getDimension(R.dimen.message_padding_bottom));
|
||||
style.outcomingTextColor = typedArray.getColor(R.styleable.MessagesList_outcomingTextColor,
|
||||
style.getColor(R.color.white));
|
||||
style.outcomingTextSize = typedArray.getDimensionPixelSize(R.styleable.MessagesList_outcomingTextSize,
|
||||
style.getDimension(R.dimen.message_text_size));
|
||||
style.outcomingTextStyle = typedArray.getInt(R.styleable.MessagesList_outcomingTextStyle, Typeface.NORMAL);
|
||||
|
||||
style.outcomingTimeTextColor = typedArray.getColor(R.styleable.MessagesList_outcomingTimeTextColor,
|
||||
style.getColor(R.color.white60));
|
||||
style.outcomingTimeTextSize = typedArray.getDimensionPixelSize(R.styleable.MessagesList_outcomingTimeTextSize,
|
||||
style.getDimension(R.dimen.message_time_text_size));
|
||||
style.outcomingTimeTextStyle = typedArray.getInt(R.styleable.MessagesList_outcomingTimeTextStyle, Typeface.NORMAL);
|
||||
|
||||
style.outcomingImageTimeTextColor = typedArray.getColor(R.styleable.MessagesList_outcomingImageTimeTextColor,
|
||||
style.getColor(R.color.warm_grey_four));
|
||||
style.outcomingImageTimeTextSize = typedArray.getDimensionPixelSize(R.styleable.MessagesList_outcomingImageTimeTextSize,
|
||||
style.getDimension(R.dimen.message_time_text_size));
|
||||
style.outcomingImageTimeTextStyle = typedArray.getInt(R.styleable.MessagesList_outcomingImageTimeTextStyle, Typeface.NORMAL);
|
||||
|
||||
style.dateHeaderPadding = typedArray.getDimensionPixelSize(R.styleable.MessagesList_dateHeaderPadding,
|
||||
style.getDimension(R.dimen.message_date_header_padding));
|
||||
style.dateHeaderFormat = typedArray.getString(R.styleable.MessagesList_dateHeaderFormat);
|
||||
style.dateHeaderTextColor = typedArray.getColor(R.styleable.MessagesList_dateHeaderTextColor,
|
||||
style.getColor(R.color.warm_grey_two));
|
||||
style.dateHeaderTextSize = typedArray.getDimensionPixelSize(R.styleable.MessagesList_dateHeaderTextSize,
|
||||
style.getDimension(R.dimen.message_date_header_text_size));
|
||||
style.dateHeaderTextStyle = typedArray.getInt(R.styleable.MessagesList_dateHeaderTextStyle, Typeface.NORMAL);
|
||||
|
||||
typedArray.recycle();
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
private MessagesListStyle(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
private Drawable getMessageSelector(@ColorInt int normalColor, @ColorInt int selectedColor,
|
||||
@ColorInt int pressedColor, @DrawableRes int shape) {
|
||||
|
||||
Drawable drawable = DrawableCompat.wrap(getVectorDrawable(shape)).mutate();
|
||||
DrawableCompat.setTintList(
|
||||
drawable,
|
||||
new ColorStateList(
|
||||
new int[][]{
|
||||
new int[]{android.R.attr.state_selected},
|
||||
new int[]{android.R.attr.state_pressed},
|
||||
new int[]{-android.R.attr.state_pressed, -android.R.attr.state_selected}
|
||||
},
|
||||
new int[]{selectedColor, pressedColor, normalColor}
|
||||
));
|
||||
return drawable;
|
||||
}
|
||||
|
||||
protected int getTextAutoLinkMask() {
|
||||
return textAutoLinkMask;
|
||||
}
|
||||
|
||||
protected int getIncomingTextLinkColor() {
|
||||
return incomingTextLinkColor;
|
||||
}
|
||||
|
||||
protected int getOutcomingTextLinkColor() {
|
||||
return outcomingTextLinkColor;
|
||||
}
|
||||
|
||||
protected int getIncomingAvatarWidth() {
|
||||
return incomingAvatarWidth;
|
||||
}
|
||||
|
||||
protected int getIncomingAvatarHeight() {
|
||||
return incomingAvatarHeight;
|
||||
}
|
||||
|
||||
protected int getIncomingDefaultBubblePaddingLeft() {
|
||||
return incomingDefaultBubblePaddingLeft;
|
||||
}
|
||||
|
||||
protected int getIncomingDefaultBubblePaddingRight() {
|
||||
return incomingDefaultBubblePaddingRight;
|
||||
}
|
||||
|
||||
protected int getIncomingDefaultBubblePaddingTop() {
|
||||
return incomingDefaultBubblePaddingTop;
|
||||
}
|
||||
|
||||
protected int getIncomingDefaultBubblePaddingBottom() {
|
||||
return incomingDefaultBubblePaddingBottom;
|
||||
}
|
||||
|
||||
protected int getIncomingTextColor() {
|
||||
return incomingTextColor;
|
||||
}
|
||||
|
||||
protected int getIncomingTextSize() {
|
||||
return incomingTextSize;
|
||||
}
|
||||
|
||||
protected int getIncomingTextStyle() {
|
||||
return incomingTextStyle;
|
||||
}
|
||||
|
||||
protected Drawable getOutcomingBubbleDrawable() {
|
||||
if (outcomingBubbleDrawable == -1) {
|
||||
return getMessageSelector(outcomingDefaultBubbleColor, outcomingDefaultBubbleSelectedColor,
|
||||
outcomingDefaultBubblePressedColor, R.drawable.shape_outcoming_message);
|
||||
} else {
|
||||
return getDrawable(outcomingBubbleDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
protected Drawable getOutcomingImageOverlayDrawable() {
|
||||
if (outcomingImageOverlayDrawable == -1) {
|
||||
return getMessageSelector(Color.TRANSPARENT, outcomingDefaultImageOverlaySelectedColor,
|
||||
outcomingDefaultImageOverlayPressedColor, R.drawable.shape_outcoming_message);
|
||||
} else {
|
||||
return getDrawable(outcomingImageOverlayDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getOutcomingDefaultBubblePaddingLeft() {
|
||||
return outcomingDefaultBubblePaddingLeft;
|
||||
}
|
||||
|
||||
protected int getOutcomingDefaultBubblePaddingRight() {
|
||||
return outcomingDefaultBubblePaddingRight;
|
||||
}
|
||||
|
||||
protected int getOutcomingDefaultBubblePaddingTop() {
|
||||
return outcomingDefaultBubblePaddingTop;
|
||||
}
|
||||
|
||||
protected int getOutcomingDefaultBubblePaddingBottom() {
|
||||
return outcomingDefaultBubblePaddingBottom;
|
||||
}
|
||||
|
||||
protected int getOutcomingTextColor() {
|
||||
return outcomingTextColor;
|
||||
}
|
||||
|
||||
protected int getOutcomingTextSize() {
|
||||
return outcomingTextSize;
|
||||
}
|
||||
|
||||
protected int getOutcomingTextStyle() {
|
||||
return outcomingTextStyle;
|
||||
}
|
||||
|
||||
protected int getOutcomingTimeTextColor() {
|
||||
return outcomingTimeTextColor;
|
||||
}
|
||||
|
||||
protected int getOutcomingTimeTextSize() {
|
||||
return outcomingTimeTextSize;
|
||||
}
|
||||
|
||||
protected int getOutcomingTimeTextStyle() {
|
||||
return outcomingTimeTextStyle;
|
||||
}
|
||||
|
||||
protected int getOutcomingImageTimeTextColor() {
|
||||
return outcomingImageTimeTextColor;
|
||||
}
|
||||
|
||||
protected int getOutcomingImageTimeTextSize() {
|
||||
return outcomingImageTimeTextSize;
|
||||
}
|
||||
|
||||
protected int getOutcomingImageTimeTextStyle() {
|
||||
return outcomingImageTimeTextStyle;
|
||||
}
|
||||
|
||||
protected int getDateHeaderTextColor() {
|
||||
return dateHeaderTextColor;
|
||||
}
|
||||
|
||||
protected int getDateHeaderTextSize() {
|
||||
return dateHeaderTextSize;
|
||||
}
|
||||
|
||||
protected int getDateHeaderTextStyle() {
|
||||
return dateHeaderTextStyle;
|
||||
}
|
||||
|
||||
protected int getDateHeaderPadding() {
|
||||
return dateHeaderPadding;
|
||||
}
|
||||
|
||||
protected String getDateHeaderFormat() {
|
||||
return dateHeaderFormat;
|
||||
}
|
||||
|
||||
protected int getIncomingTimeTextSize() {
|
||||
return incomingTimeTextSize;
|
||||
}
|
||||
|
||||
protected int getIncomingTimeTextStyle() {
|
||||
return incomingTimeTextStyle;
|
||||
}
|
||||
|
||||
protected int getIncomingTimeTextColor() {
|
||||
return incomingTimeTextColor;
|
||||
}
|
||||
|
||||
protected int getIncomingImageTimeTextColor() {
|
||||
return incomingImageTimeTextColor;
|
||||
}
|
||||
|
||||
protected int getIncomingImageTimeTextSize() {
|
||||
return incomingImageTimeTextSize;
|
||||
}
|
||||
|
||||
protected int getIncomingImageTimeTextStyle() {
|
||||
return incomingImageTimeTextStyle;
|
||||
}
|
||||
|
||||
protected Drawable getIncomingBubbleDrawable() {
|
||||
if (incomingBubbleDrawable == -1) {
|
||||
return getMessageSelector(incomingDefaultBubbleColor, incomingDefaultBubbleSelectedColor,
|
||||
incomingDefaultBubblePressedColor, R.drawable.shape_incoming_message);
|
||||
} else {
|
||||
return getDrawable(incomingBubbleDrawable);
|
||||
}
|
||||
}
|
||||
|
||||
protected Drawable getIncomingImageOverlayDrawable() {
|
||||
if (incomingImageOverlayDrawable == -1) {
|
||||
return getMessageSelector(Color.TRANSPARENT, incomingDefaultImageOverlaySelectedColor,
|
||||
incomingDefaultImageOverlayPressedColor, R.drawable.shape_incoming_message);
|
||||
} else {
|
||||
return getDrawable(incomingImageOverlayDrawable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.messages;
|
||||
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
class RecyclerScrollMoreListener
|
||||
extends RecyclerView.OnScrollListener {
|
||||
|
||||
private OnLoadMoreListener loadMoreListener;
|
||||
private int currentPage = 0;
|
||||
private int previousTotalItemCount = 0;
|
||||
private boolean loading = true;
|
||||
|
||||
private RecyclerView.LayoutManager mLayoutManager;
|
||||
|
||||
RecyclerScrollMoreListener(LinearLayoutManager layoutManager, OnLoadMoreListener loadMoreListener) {
|
||||
this.mLayoutManager = layoutManager;
|
||||
this.loadMoreListener = loadMoreListener;
|
||||
}
|
||||
|
||||
private int getLastVisibleItem(int[] lastVisibleItemPositions) {
|
||||
int maxSize = 0;
|
||||
for (int i = 0; i < lastVisibleItemPositions.length; i++) {
|
||||
if (i == 0) {
|
||||
maxSize = lastVisibleItemPositions[i];
|
||||
} else if (lastVisibleItemPositions[i] > maxSize) {
|
||||
maxSize = lastVisibleItemPositions[i];
|
||||
}
|
||||
}
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView view, int dx, int dy) {
|
||||
if (loadMoreListener != null) {
|
||||
int lastVisibleItemPosition = 0;
|
||||
int totalItemCount = mLayoutManager.getItemCount();
|
||||
|
||||
if (mLayoutManager instanceof StaggeredGridLayoutManager) {
|
||||
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findLastVisibleItemPositions(null);
|
||||
lastVisibleItemPosition = getLastVisibleItem(lastVisibleItemPositions);
|
||||
} else if (mLayoutManager instanceof LinearLayoutManager) {
|
||||
lastVisibleItemPosition = ((LinearLayoutManager) mLayoutManager).findLastVisibleItemPosition();
|
||||
} else if (mLayoutManager instanceof GridLayoutManager) {
|
||||
lastVisibleItemPosition = ((GridLayoutManager) mLayoutManager).findLastVisibleItemPosition();
|
||||
}
|
||||
|
||||
if (totalItemCount < previousTotalItemCount) {
|
||||
this.currentPage = 0;
|
||||
this.previousTotalItemCount = totalItemCount;
|
||||
if (totalItemCount == 0) {
|
||||
this.loading = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (loading && (totalItemCount > previousTotalItemCount)) {
|
||||
loading = false;
|
||||
previousTotalItemCount = totalItemCount;
|
||||
}
|
||||
|
||||
int visibleThreshold = 5;
|
||||
if (!loading && (lastVisibleItemPosition + visibleThreshold) > totalItemCount) {
|
||||
currentPage++;
|
||||
loadMoreListener.onLoadMore(loadMoreListener.getMessagesCount(), totalItemCount);
|
||||
loading = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface OnLoadMoreListener {
|
||||
void onLoadMore(int page, int total);
|
||||
|
||||
int getMessagesCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class DateFormatter {
|
||||
private DateFormatter() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static String format(Date date, Template template) {
|
||||
return format(date, template.get());
|
||||
}
|
||||
|
||||
public static String format(Date date, String format) {
|
||||
if (date == null) return "";
|
||||
return new SimpleDateFormat(format, Locale.getDefault())
|
||||
.format(date);
|
||||
}
|
||||
|
||||
public static boolean isSameDay(Date date1, Date date2) {
|
||||
if (date1 == null || date2 == null) {
|
||||
throw new IllegalArgumentException("Dates must not be null");
|
||||
}
|
||||
Calendar cal1 = Calendar.getInstance();
|
||||
cal1.setTime(date1);
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
cal2.setTime(date2);
|
||||
return isSameDay(cal1, cal2);
|
||||
}
|
||||
|
||||
public static boolean isSameDay(Calendar cal1, Calendar cal2) {
|
||||
if (cal1 == null || cal2 == null) {
|
||||
throw new IllegalArgumentException("Dates must not be null");
|
||||
}
|
||||
return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
|
||||
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
|
||||
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR));
|
||||
}
|
||||
|
||||
public static boolean isSameYear(Date date1, Date date2) {
|
||||
if (date1 == null || date2 == null) {
|
||||
throw new IllegalArgumentException("Dates must not be null");
|
||||
}
|
||||
Calendar cal1 = Calendar.getInstance();
|
||||
cal1.setTime(date1);
|
||||
Calendar cal2 = Calendar.getInstance();
|
||||
cal2.setTime(date2);
|
||||
return isSameYear(cal1, cal2);
|
||||
}
|
||||
|
||||
public static boolean isSameYear(Calendar cal1, Calendar cal2) {
|
||||
if (cal1 == null || cal2 == null) {
|
||||
throw new IllegalArgumentException("Dates must not be null");
|
||||
}
|
||||
return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
|
||||
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR));
|
||||
}
|
||||
|
||||
public static boolean isToday(Calendar calendar) {
|
||||
return isSameDay(calendar, Calendar.getInstance());
|
||||
}
|
||||
|
||||
public static boolean isToday(Date date) {
|
||||
return isSameDay(date, Calendar.getInstance().getTime());
|
||||
}
|
||||
|
||||
public static boolean isYesterday(Calendar calendar) {
|
||||
Calendar yesterday = Calendar.getInstance();
|
||||
yesterday.add(Calendar.DAY_OF_MONTH, -1);
|
||||
return isSameDay(calendar, yesterday);
|
||||
}
|
||||
|
||||
public static boolean isYesterday(Date date) {
|
||||
Calendar yesterday = Calendar.getInstance();
|
||||
yesterday.add(Calendar.DAY_OF_MONTH, -1);
|
||||
return isSameDay(date, yesterday.getTime());
|
||||
}
|
||||
|
||||
public static boolean isCurrentYear(Date date) {
|
||||
return isSameYear(date, Calendar.getInstance().getTime());
|
||||
}
|
||||
|
||||
public static boolean isCurrentYear(Calendar calendar) {
|
||||
return isSameYear(calendar, Calendar.getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to format dates before they were displayed (e.g. dialogs time, messages date headers etc.).
|
||||
*/
|
||||
public interface Formatter {
|
||||
|
||||
/**
|
||||
* Formats an string representation of the date object.
|
||||
*
|
||||
* @param date The date that should be formatted.
|
||||
* @return Formatted text.
|
||||
*/
|
||||
String format(Date date);
|
||||
}
|
||||
|
||||
public enum Template {
|
||||
STRING_DAY_MONTH_YEAR("d MMMM yyyy"),
|
||||
STRING_DAY_MONTH("d MMMM"),
|
||||
TIME("HH:mm");
|
||||
|
||||
private String template;
|
||||
|
||||
Template(String template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
public String get() {
|
||||
return template;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
package com.stfalcon.chatkit.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.BitmapShader;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Shader;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.net.Uri;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.DimenRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
/**
|
||||
* Thanks to Joonho Kim (https://github.com/pungrue26) for his lightweight SelectableRoundedImageView,
|
||||
* that was used as default image message representation
|
||||
*/
|
||||
public class RoundedImageView extends AppCompatImageView {
|
||||
|
||||
private int mResource = 0;
|
||||
private Drawable mDrawable;
|
||||
|
||||
private float[] mRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
public RoundedImageView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public RoundedImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageDrawable(Drawable drawable) {
|
||||
mResource = 0;
|
||||
mDrawable = RoundedCornerDrawable.fromDrawable(drawable, getResources());
|
||||
super.setImageDrawable(mDrawable);
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageBitmap(Bitmap bm) {
|
||||
mResource = 0;
|
||||
mDrawable = RoundedCornerDrawable.fromBitmap(bm, getResources());
|
||||
super.setImageDrawable(mDrawable);
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageResource(int resId) {
|
||||
if (mResource != resId) {
|
||||
mResource = resId;
|
||||
mDrawable = resolveResource();
|
||||
super.setImageDrawable(mDrawable);
|
||||
updateDrawable();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImageURI(Uri uri) {
|
||||
super.setImageURI(uri);
|
||||
setImageDrawable(getDrawable());
|
||||
}
|
||||
|
||||
public void setCorners(@DimenRes int leftTop, @DimenRes int rightTop,
|
||||
@DimenRes int rightBottom, @DimenRes int leftBottom) {
|
||||
setCorners(
|
||||
leftTop == 0 ? 0 : getResources().getDimension(leftTop),
|
||||
rightTop == 0 ? 0 : getResources().getDimension(rightTop),
|
||||
rightBottom == 0 ? 0 : getResources().getDimension(rightBottom),
|
||||
leftBottom == 0 ? 0 : getResources().getDimension(leftBottom)
|
||||
);
|
||||
}
|
||||
|
||||
public void setCorners(float leftTop, float rightTop, float rightBottom, float leftBottom) {
|
||||
mRadii = new float[]{
|
||||
leftTop, leftTop,
|
||||
rightTop, rightTop,
|
||||
rightBottom, rightBottom,
|
||||
leftBottom, leftBottom};
|
||||
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
private Drawable resolveResource() {
|
||||
Drawable d = null;
|
||||
|
||||
if (mResource != 0) {
|
||||
try {
|
||||
d = ContextCompat.getDrawable(getContext(), mResource);
|
||||
} catch (NotFoundException e) {
|
||||
mResource = 0;
|
||||
}
|
||||
}
|
||||
return RoundedCornerDrawable.fromDrawable(d, getResources());
|
||||
}
|
||||
|
||||
private void updateDrawable() {
|
||||
if (mDrawable == null) return;
|
||||
|
||||
((RoundedCornerDrawable) mDrawable).setCornerRadii(mRadii);
|
||||
}
|
||||
|
||||
private static class RoundedCornerDrawable extends Drawable {
|
||||
private RectF mBounds = new RectF();
|
||||
|
||||
private final RectF mBitmapRect = new RectF();
|
||||
private final int mBitmapWidth;
|
||||
private final int mBitmapHeight;
|
||||
|
||||
private final Paint mBitmapPaint;
|
||||
|
||||
private float[] mRadii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
private Path mPath = new Path();
|
||||
private Bitmap mBitmap;
|
||||
private boolean mBoundsConfigured = false;
|
||||
|
||||
private RoundedCornerDrawable(Bitmap bitmap, Resources r) {
|
||||
mBitmap = bitmap;
|
||||
BitmapShader mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
||||
|
||||
mBitmapWidth = bitmap.getScaledWidth(r.getDisplayMetrics());
|
||||
mBitmapHeight = bitmap.getScaledHeight(r.getDisplayMetrics());
|
||||
|
||||
mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight);
|
||||
|
||||
mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mBitmapPaint.setStyle(Paint.Style.FILL);
|
||||
mBitmapPaint.setShader(mBitmapShader);
|
||||
}
|
||||
|
||||
private static RoundedCornerDrawable fromBitmap(Bitmap bitmap, Resources r) {
|
||||
if (bitmap != null) return new RoundedCornerDrawable(bitmap, r);
|
||||
else return null;
|
||||
}
|
||||
|
||||
private static Drawable fromDrawable(Drawable drawable, Resources r) {
|
||||
if (drawable != null) {
|
||||
if (drawable instanceof RoundedCornerDrawable) {
|
||||
return drawable;
|
||||
} else if (drawable instanceof LayerDrawable) {
|
||||
LayerDrawable ld = (LayerDrawable) drawable;
|
||||
final int num = ld.getNumberOfLayers();
|
||||
for (int i = 0; i < num; i++) {
|
||||
Drawable d = ld.getDrawable(i);
|
||||
ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d, r));
|
||||
}
|
||||
return ld;
|
||||
}
|
||||
|
||||
Bitmap bm = drawableToBitmap(drawable);
|
||||
if (bm != null) return new RoundedCornerDrawable(bm, r);
|
||||
}
|
||||
return drawable;
|
||||
}
|
||||
|
||||
private static Bitmap drawableToBitmap(Drawable drawable) {
|
||||
if (drawable == null) return null;
|
||||
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return ((BitmapDrawable) drawable).getBitmap();
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
int width = Math.max(drawable.getIntrinsicWidth(), 2);
|
||||
int height = Math.max(drawable.getIntrinsicHeight(), 2);
|
||||
try {
|
||||
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
drawable.draw(canvas);
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
bitmap = null;
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private void configureBounds(Canvas canvas) {
|
||||
Matrix canvasMatrix = canvas.getMatrix();
|
||||
|
||||
applyScaleToRadii(canvasMatrix);
|
||||
mBounds.set(mBitmapRect);
|
||||
}
|
||||
|
||||
private void applyScaleToRadii(Matrix m) {
|
||||
float[] values = new float[9];
|
||||
m.getValues(values);
|
||||
for (int i = 0; i < mRadii.length; i++) {
|
||||
mRadii[i] = mRadii[i] / values[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
canvas.save();
|
||||
if (!mBoundsConfigured) {
|
||||
configureBounds(canvas);
|
||||
mBoundsConfigured = true;
|
||||
}
|
||||
mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW);
|
||||
canvas.drawPath(mPath, mBitmapPaint);
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
void setCornerRadii(float[] radii) {
|
||||
if (radii == null) return;
|
||||
if (radii.length != 8)
|
||||
throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
|
||||
|
||||
System.arraycopy(radii, 0, mRadii, 0, radii.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
return (mBitmap == null || mBitmap.hasAlpha() || mBitmapPaint.getAlpha() < 255)
|
||||
? PixelFormat.TRANSLUCENT
|
||||
: PixelFormat.OPAQUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mBitmapPaint.setAlpha(alpha);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter cf) {
|
||||
mBitmapPaint.setColorFilter(cf);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDither(boolean dither) {
|
||||
mBitmapPaint.setDither(dither);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterBitmap(boolean filter) {
|
||||
mBitmapPaint.setFilterBitmap(filter);
|
||||
invalidateSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicWidth() {
|
||||
return mBitmapWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntrinsicHeight() {
|
||||
return mBitmapHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2016 stfalcon.com
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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 com.stfalcon.chatkit.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Path;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* ImageView with mask what described with Bézier Curves
|
||||
*/
|
||||
|
||||
public class ShapeImageView extends androidx.appcompat.widget.AppCompatImageView {
|
||||
private Path path;
|
||||
|
||||
public ShapeImageView(Context context) {
|
||||
super(context);
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
|
||||
public ShapeImageView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
path = new Path();
|
||||
float halfWidth = (float) w / 2f;
|
||||
float firstParam = (float) w * 0.1f;
|
||||
float secondParam = (float) w * 0.8875f;
|
||||
|
||||
//Bézier Curves
|
||||
path.moveTo(halfWidth, (float) w);
|
||||
path.cubicTo(firstParam, (float) w, 0, secondParam, 0, halfWidth);
|
||||
path.cubicTo(0, firstParam, firstParam, 0, halfWidth, 0);
|
||||
path.cubicTo(secondParam, 0, (float) w, firstParam, (float) w, halfWidth);
|
||||
path.cubicTo((float) w, secondParam, secondParam, (float) w, halfWidth, (float) w);
|
||||
path.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (path.isEmpty()) {
|
||||
super.onDraw(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
int saveCount = canvas.save();
|
||||
canvas.clipPath(path);
|
||||
super.onDraw(canvas);
|
||||
canvas.restoreToCount(saveCount);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 957 B |
|
After Width: | Height: | Size: 980 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 105 B |
|
After Width: | Height: | Size: 661 B |
|
After Width: | Height: | Size: 642 B |
|
After Width: | Height: | Size: 717 B |
|
After Width: | Height: | Size: 140 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 247 B |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/white" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/white" />
|
||||
<size
|
||||
android:width="20dp"
|
||||
android:height="20dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,12 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners
|
||||
android:bottomLeftRadius="0dp"
|
||||
android:bottomRightRadius="@dimen/message_bubble_corners_radius"
|
||||
android:topLeftRadius="@dimen/message_bubble_corners_radius"
|
||||
android:topRightRadius="@dimen/message_bubble_corners_radius" />
|
||||
|
||||
<solid android:color="@color/white" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,12 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners
|
||||
android:bottomLeftRadius="@dimen/message_bubble_corners_radius"
|
||||
android:bottomRightRadius="0dp"
|
||||
android:topLeftRadius="@dimen/message_bubble_corners_radius"
|
||||
android:topRightRadius="@dimen/message_bubble_corners_radius" />
|
||||
|
||||
<solid android:color="@color/white" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@id/messageText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:padding="16dp" />
|
||||
@@ -0,0 +1,112 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@id/dialogRootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@id/dialogContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<com.stfalcon.chatkit.utils.ShapeImageView
|
||||
android:id="@id/dialogAvatar"
|
||||
android:layout_width="@dimen/dialog_avatar_width"
|
||||
android:layout_height="@dimen/dialog_avatar_height"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/dialogName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="19dp"
|
||||
android:layout_toEndOf="@id/dialogAvatar"
|
||||
android:layout_toLeftOf="@id/dialogDate"
|
||||
android:layout_toRightOf="@id/dialogAvatar"
|
||||
android:layout_toStartOf="@id/dialogDate"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@string/font_fontFamily_medium"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLines="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/dialogDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/dialogName"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="7dp"
|
||||
android:layout_toEndOf="@id/dialogAvatar"
|
||||
android:layout_toRightOf="@id/dialogAvatar">
|
||||
|
||||
<com.stfalcon.chatkit.utils.ShapeImageView
|
||||
android:id="@id/dialogLastMessageUserAvatar"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="7dp"
|
||||
android:layout_marginRight="7dp" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@id/dialogLastMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@id/dialogLastMessageUserAvatar"
|
||||
android:layout_toRightOf="@id/dialogLastMessageUserAvatar"
|
||||
android:ellipsize="end"
|
||||
android:gravity="top"
|
||||
android:maxLines="1" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@id/dialogUnreadBubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignEnd="@id/dialogAvatar"
|
||||
android:layout_alignRight="@id/dialogAvatar"
|
||||
android:layout_alignTop="@id/dialogAvatar"
|
||||
android:layout_marginEnd="-5dp"
|
||||
android:layout_marginRight="-5dp"
|
||||
android:layout_marginTop="-5dp"
|
||||
android:background="@drawable/bubble_circle"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@string/font_fontFamily_medium"
|
||||
android:gravity="center"
|
||||
android:lines="1" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@id/dialogDividerContainer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingLeft="@dimen/dialog_divider_margin_left"
|
||||
android:paddingStart="@dimen/dialog_divider_margin_left">
|
||||
|
||||
<View
|
||||
android:id="@id/dialogDivider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/dialog_divider" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,46 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<com.stfalcon.chatkit.utils.ShapeImageView
|
||||
android:id="@id/messageUserAvatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_above="@id/messageTime"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp" />
|
||||
|
||||
<com.stfalcon.chatkit.utils.RoundedImageView
|
||||
android:id="@id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_marginEnd="@dimen/message_incoming_bubble_margin_right"
|
||||
android:layout_marginRight="@dimen/message_incoming_bubble_margin_right"
|
||||
android:layout_toEndOf="@id/messageUserAvatar"
|
||||
android:layout_toRightOf="@id/messageUserAvatar" />
|
||||
|
||||
<View
|
||||
android:id="@id/imageOverlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/image"
|
||||
android:layout_alignEnd="@id/image"
|
||||
android:layout_alignLeft="@id/image"
|
||||
android:layout_alignRight="@id/image"
|
||||
android:layout_alignStart="@id/image"
|
||||
android:layout_alignTop="@id/image" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignEnd="@id/image"
|
||||
android:layout_alignRight="@id/image"
|
||||
android:layout_below="@id/image" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,48 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<com.stfalcon.chatkit.utils.ShapeImageView
|
||||
android:id="@id/messageUserAvatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp" />
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@id/bubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/message_incoming_bubble_margin_right"
|
||||
android:layout_marginRight="@dimen/message_incoming_bubble_margin_right"
|
||||
android:layout_toEndOf="@id/messageUserAvatar"
|
||||
android:layout_toRightOf="@id/messageUserAvatar"
|
||||
android:orientation="vertical"
|
||||
app:alignContent="stretch"
|
||||
app:alignItems="stretch"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,36 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<com.stfalcon.chatkit.utils.RoundedImageView
|
||||
android:id="@id/image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:layout_marginLeft="@dimen/message_outcoming_bubble_margin_left"
|
||||
android:layout_marginStart="@dimen/message_outcoming_bubble_margin_left" />
|
||||
|
||||
<View
|
||||
android:id="@id/imageOverlay"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/image"
|
||||
android:layout_alignEnd="@id/image"
|
||||
android:layout_alignLeft="@id/image"
|
||||
android:layout_alignRight="@id/image"
|
||||
android:layout_alignStart="@id/image"
|
||||
android:layout_alignTop="@id/image" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignEnd="@id/image"
|
||||
android:layout_alignRight="@id/image"
|
||||
android:layout_below="@id/image" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,41 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
android:id="@id/bubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginLeft="@dimen/message_outcoming_bubble_margin_left"
|
||||
android:layout_marginStart="@dimen/message_outcoming_bubble_margin_left"
|
||||
app:alignContent="stretch"
|
||||
app:alignItems="stretch"
|
||||
app:flexWrap="wrap"
|
||||
app:justifyContent="flex_end">
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignWithParentIfMissing="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
app:layout_alignSelf="center"
|
||||
app:layout_order="1" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,45 @@
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@id/attachmentButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<Space
|
||||
android:id="@id/attachmentButtonSpace"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_toEndOf="@id/attachmentButton"
|
||||
android:layout_toRightOf="@id/attachmentButton" />
|
||||
|
||||
<EditText
|
||||
android:id="@id/messageInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toEndOf="@id/attachmentButtonSpace"
|
||||
android:layout_toLeftOf="@id/sendButtonSpace"
|
||||
android:layout_toRightOf="@id/attachmentButtonSpace"
|
||||
android:layout_toStartOf="@id/sendButtonSpace"
|
||||
android:inputType="textAutoCorrect|textAutoComplete|textMultiLine|textCapSentences" />
|
||||
|
||||
<Space
|
||||
android:id="@id/sendButtonSpace"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_toLeftOf="@id/messageSendButton"
|
||||
android:layout_toStartOf="@id/messageSendButton" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@id/messageSendButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
</merge>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="font_fontFamily_medium">sans-serif-medium</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,228 @@
|
||||
<resources>
|
||||
|
||||
<attr name="MessageInput" format="reference" />
|
||||
|
||||
<declare-styleable name="MessageInput">
|
||||
<attr name="inputButtonBackground" format="reference" />
|
||||
<attr name="inputButtonDefaultBgColor" format="color|reference" />
|
||||
<attr name="inputButtonDefaultBgPressedColor" format="color|reference" />
|
||||
<attr name="inputButtonDefaultBgDisabledColor" format="color|reference" />
|
||||
|
||||
<attr name="inputButtonIcon" format="reference" />
|
||||
<attr name="inputButtonDefaultIconColor" format="color|reference" />
|
||||
<attr name="inputButtonDefaultIconPressedColor" format="color|reference" />
|
||||
<attr name="inputButtonDefaultIconDisabledColor" format="color|reference" />
|
||||
|
||||
<attr name="inputButtonMargin" format="dimension|reference" />
|
||||
<attr name="inputButtonWidth" format="dimension|reference" />
|
||||
<attr name="inputButtonHeight" format="dimension|reference" />
|
||||
|
||||
<attr name="showAttachmentButton" format="boolean" />
|
||||
<attr name="attachmentButtonBackground" format="reference" />
|
||||
<attr name="attachmentButtonDefaultBgColor" format="color|reference" />
|
||||
<attr name="attachmentButtonDefaultBgPressedColor" format="color|reference" />
|
||||
<attr name="attachmentButtonDefaultBgDisabledColor" format="color|reference" />
|
||||
|
||||
<attr name="attachmentButtonIcon" format="reference" />
|
||||
<attr name="attachmentButtonDefaultIconColor" format="color|reference" />
|
||||
<attr name="attachmentButtonDefaultIconPressedColor" format="color|reference" />
|
||||
<attr name="attachmentButtonDefaultIconDisabledColor" format="color|reference" />
|
||||
|
||||
<attr name="attachmentButtonMargin" format="dimension|reference" />
|
||||
<attr name="attachmentButtonWidth" format="dimension|reference" />
|
||||
<attr name="attachmentButtonHeight" format="dimension|reference" />
|
||||
|
||||
<attr name="inputMaxLines" format="integer" />
|
||||
<attr name="inputHint" format="string" />
|
||||
|
||||
<attr name="inputText" format="string" />
|
||||
<attr name="inputTextSize" format="dimension|reference" />
|
||||
<attr name="inputTextColor" format="color|reference" />
|
||||
<attr name="inputHintColor" format="color|reference" />
|
||||
|
||||
<attr name="inputBackground" format="reference" />
|
||||
<attr name="inputCursorDrawable" format="reference" />
|
||||
|
||||
<attr name="delayTypingStatus" format="integer" />
|
||||
</declare-styleable>
|
||||
|
||||
<attr name="MessagesList" format="reference" />
|
||||
|
||||
<declare-styleable name="MessagesList">
|
||||
|
||||
<attr name="textAutoLink">
|
||||
<flag name="all" value="15" />
|
||||
<flag name="email" value="2" />
|
||||
<flag name="map" value="8" />
|
||||
<flag name="none" value="0" />
|
||||
<flag name="phone" value="4" />
|
||||
<flag name="web" value="1" />
|
||||
</attr>
|
||||
|
||||
<attr name="incomingTextLinkColor" format="color|reference" />
|
||||
<attr name="outcomingTextLinkColor" format="color|reference" />
|
||||
|
||||
<attr name="incomingAvatarWidth" format="dimension|reference" />
|
||||
<attr name="incomingAvatarHeight" format="dimension|reference" />
|
||||
|
||||
<attr name="incomingBubbleDrawable" format="reference" />
|
||||
<attr name="incomingDefaultBubbleColor" format="color|reference" />
|
||||
<attr name="incomingDefaultBubblePressedColor" format="color|reference" />
|
||||
<attr name="incomingDefaultBubbleSelectedColor" format="color|reference" />
|
||||
|
||||
<attr name="incomingImageOverlayDrawable" format="reference" />
|
||||
<attr name="incomingDefaultImageOverlayPressedColor" format="color|reference" />
|
||||
<attr name="incomingDefaultImageOverlaySelectedColor" format="color|reference" />
|
||||
|
||||
<attr name="incomingBubblePaddingLeft" format="dimension|reference" />
|
||||
<attr name="incomingBubblePaddingRight" format="dimension|reference" />
|
||||
<attr name="incomingBubblePaddingTop" format="dimension|reference" />
|
||||
<attr name="incomingBubblePaddingBottom" format="dimension|reference" />
|
||||
|
||||
<attr name="incomingTextColor" format="color|reference" />
|
||||
<attr name="incomingTextSize" format="dimension|reference" />
|
||||
<attr name="incomingTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="incomingTimeTextColor" format="color|reference" />
|
||||
<attr name="incomingTimeTextSize" format="dimension|reference" />
|
||||
<attr name="incomingTimeTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="incomingImageTimeTextColor" format="color|reference" />
|
||||
<attr name="incomingImageTimeTextSize" format="dimension|reference" />
|
||||
<attr name="incomingImageTimeTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="outcomingBubbleDrawable" format="reference" />
|
||||
<attr name="outcomingDefaultBubbleColor" format="color|reference" />
|
||||
<attr name="outcomingDefaultBubblePressedColor" format="color|reference" />
|
||||
<attr name="outcomingDefaultBubbleSelectedColor" format="color|reference" />
|
||||
|
||||
<attr name="outcomingImageOverlayDrawable" format="reference" />
|
||||
<attr name="outcomingDefaultImageOverlayPressedColor" format="color|reference" />
|
||||
<attr name="outcomingDefaultImageOverlaySelectedColor" format="color|reference" />
|
||||
|
||||
<attr name="outcomingBubblePaddingLeft" format="dimension|reference" />
|
||||
<attr name="outcomingBubblePaddingRight" format="dimension|reference" />
|
||||
<attr name="outcomingBubblePaddingTop" format="dimension|reference" />
|
||||
<attr name="outcomingBubblePaddingBottom" format="dimension|reference" />
|
||||
|
||||
<attr name="outcomingTextColor" format="color|reference" />
|
||||
<attr name="outcomingTextSize" format="dimension|reference" />
|
||||
<attr name="outcomingTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="outcomingTimeTextColor" format="color|reference" />
|
||||
<attr name="outcomingTimeTextSize" format="dimension|reference" />
|
||||
<attr name="outcomingTimeTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="outcomingImageTimeTextColor" format="color|reference" />
|
||||
<attr name="outcomingImageTimeTextSize" format="dimension|reference" />
|
||||
<attr name="outcomingImageTimeTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="dateHeaderPadding" format="dimension|reference" />
|
||||
<attr name="dateHeaderFormat" format="string|reference" />
|
||||
<attr name="dateHeaderTextColor" format="color|reference" />
|
||||
<attr name="dateHeaderTextSize" format="dimension|reference" />
|
||||
<attr name="dateHeaderTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
<attr name="DialogsListView" format="reference" />
|
||||
|
||||
<declare-styleable name="DialogsList">
|
||||
<attr name="dialogTitleTextColor" format="color|reference" />
|
||||
<attr name="dialogTitleTextSize" format="dimension|reference" />
|
||||
<attr name="dialogTitleTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
<attr name="dialogUnreadTitleTextColor" format="color|reference" />
|
||||
<attr name="dialogUnreadTitleTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="dialogMessageTextColor" format="color|reference" />
|
||||
<attr name="dialogMessageTextSize" format="dimension|reference" />
|
||||
<attr name="dialogMessageTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
<attr name="dialogUnreadMessageTextColor" format="color|reference" />
|
||||
<attr name="dialogUnreadMessageTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="dialogDateColor" format="color|reference" />
|
||||
<attr name="dialogDateSize" format="dimension|reference" />
|
||||
<attr name="dialogDateStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
<attr name="dialogUnreadDateColor" format="color|reference" />
|
||||
<attr name="dialogUnreadDateStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
|
||||
<attr name="dialogUnreadBubbleEnabled" format="boolean" />
|
||||
<attr name="dialogUnreadBubbleTextColor" format="color|reference" />
|
||||
<attr name="dialogUnreadBubbleTextSize" format="dimension|reference" />
|
||||
<attr name="dialogUnreadBubbleTextStyle">
|
||||
<flag name="normal" value="0" />
|
||||
<flag name="bold" value="1" />
|
||||
<flag name="italic" value="2" />
|
||||
</attr>
|
||||
<attr name="dialogUnreadBubbleBackgroundColor" format="color|reference" />
|
||||
|
||||
<attr name="dialogAvatarWidth" format="dimension|reference" />
|
||||
<attr name="dialogAvatarHeight" format="dimension|reference" />
|
||||
|
||||
<attr name="dialogMessageAvatarEnabled" format="boolean" />
|
||||
<attr name="dialogMessageAvatarWidth" format="dimension|reference" />
|
||||
<attr name="dialogMessageAvatarHeight" format="dimension|reference" />
|
||||
|
||||
<attr name="dialogDividerEnabled" format="boolean" />
|
||||
<attr name="dialogDividerColor" format="color|reference" />
|
||||
<attr name="dialogDividerLeftPadding" format="dimension|reference" />
|
||||
<attr name="dialogDividerRightPadding" format="dimension|reference" />
|
||||
|
||||
<attr name="dialogItemBackground" format="color|reference" />
|
||||
<attr name="dialogUnreadItemBackground" format="color|reference" />
|
||||
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,31 @@
|
||||
<resources>
|
||||
<color name="white">#ffffff</color>
|
||||
<color name="white60">#a0ffffff</color>
|
||||
<color name="white_two">#efefef</color>
|
||||
<color name="white_three">#ebebeb</color>
|
||||
<color name="white_four">#e0e0e0</color>
|
||||
<color name="white_five">#dadada</color>
|
||||
<color name="blue">#031cd7</color>
|
||||
<color name="black">#000000</color>
|
||||
<color name="transparent">#00000000</color>
|
||||
<color name="gray">#333333</color>
|
||||
<color name="dark_gray">#282a2b</color>
|
||||
<color name="dark_grey_two">#191a1b</color>
|
||||
<color name="warm_grey">#7f7f7f</color>
|
||||
<color name="warm_grey_two">#9c9c9c</color>
|
||||
<color name="warm_grey_three">#8b8b8b</color>
|
||||
<color name="warm_grey_four">#979797</color>
|
||||
<color name="dark_mint">#51c05c</color>
|
||||
<color name="cornflower_blue_two">#4f62d7</color>
|
||||
<color name="cornflower_blue_two_24">#3d4f62d7</color>
|
||||
<color name="cornflower_blue_two_dark">#475bd4</color>
|
||||
<color name="cornflower_blue_light_40">#64bec5f7</color>
|
||||
|
||||
<color name="dialog_divider">@color/white_three</color>
|
||||
<color name="dialog_title_text">@color/dark_gray</color>
|
||||
<color name="dialog_message_text">@color/warm_grey</color>
|
||||
<color name="dialog_date_text">@color/warm_grey_two</color>
|
||||
<color name="dialog_unread_text">@color/white</color>
|
||||
<color name="dialog_unread_bubble">@color/dark_mint</color>
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<resources>
|
||||
<dimen name="input_button_width">36dp</dimen>
|
||||
<dimen name="input_button_height">36dp</dimen>
|
||||
<dimen name="input_button_margin">16dp</dimen>
|
||||
<dimen name="input_text_size">17sp</dimen>
|
||||
<dimen name="input_padding_left">16sp</dimen>
|
||||
<dimen name="input_padding_right">16sp</dimen>
|
||||
<dimen name="input_padding_top">8sp</dimen>
|
||||
<dimen name="input_padding_bottom">8sp</dimen>
|
||||
|
||||
<dimen name="message_bubble_corners_radius">15dp</dimen>
|
||||
|
||||
<dimen name="dialog_avatar_width">56dp</dimen>
|
||||
<dimen name="dialog_avatar_height">56dp</dimen>
|
||||
<dimen name="dialog_last_message_avatar_width">24dp</dimen>
|
||||
<dimen name="dialog_last_message_avatar_height">24dp</dimen>
|
||||
<dimen name="dialog_title_text_size">18sp</dimen>
|
||||
<dimen name="dialog_message_text_size">16sp</dimen>
|
||||
<dimen name="dialog_date_text_size">14sp</dimen>
|
||||
<dimen name="dialog_unread_bubble_text_size">13sp</dimen>
|
||||
<dimen name="dialog_divider_margin_left">88dp</dimen>
|
||||
<dimen name="dialog_divider_margin_right">0dp</dimen>
|
||||
|
||||
<dimen name="message_avatar_width">40dp</dimen>
|
||||
<dimen name="message_avatar_height">40dp</dimen>
|
||||
<dimen name="message_padding_left">16dp</dimen>
|
||||
<dimen name="message_padding_right">16dp</dimen>
|
||||
<dimen name="message_padding_top">16dp</dimen>
|
||||
<dimen name="message_padding_bottom">16dp</dimen>
|
||||
<dimen name="message_text_size">17sp</dimen>
|
||||
<dimen name="message_time_text_size">14sp</dimen>
|
||||
<dimen name="message_date_header_text_size">16sp</dimen>
|
||||
<dimen name="message_date_header_padding">16dp</dimen>
|
||||
<dimen name="message_incoming_bubble_margin_right">40dp</dimen>
|
||||
<dimen name="message_outcoming_bubble_margin_left">88dp</dimen>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="font_fontFamily_medium">sans-serif</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,30 @@
|
||||
<resources>
|
||||
|
||||
<!-- MESSAGE LIST ITEM -->
|
||||
<item name="bubble" type="id" />
|
||||
<item name="messageText" type="id" />
|
||||
<item name="messageUserAvatar" type="id" />
|
||||
<item name="messageTime" type="id" />
|
||||
<item name="image" type="id" />
|
||||
<item name="imageOverlay" type="id" />
|
||||
|
||||
<!-- MESSAGE INPUT -->
|
||||
<item name="attachmentButton" type="id" />
|
||||
<item name="messageSendButton" type="id" />
|
||||
<item name="attachmentButtonSpace" type="id" />
|
||||
<item name="sendButtonSpace" type="id" />
|
||||
<item name="messageInput" type="id" />
|
||||
|
||||
<!-- DIALOGS LIST ITEM -->
|
||||
<item name="dialogRootLayout" type="id" />
|
||||
<item name="dialogContainer" type="id" />
|
||||
<item name="dialogName" type="id" />
|
||||
<item name="dialogDate" type="id" />
|
||||
<item name="dialogLastMessage" type="id" />
|
||||
<item name="dialogUnreadBubble" type="id" />
|
||||
<item name="dialogAvatar" type="id" />
|
||||
<item name="dialogLastMessageUserAvatar" type="id" />
|
||||
<item name="dialogDividerContainer" type="id" />
|
||||
<item name="dialogDivider" type="id" />
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1 @@
|
||||
<resources />
|
||||
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="ChatTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatMessageInfo">
|
||||
<item name="android:textSize">11sp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatHeaderTitle">
|
||||
<item name="android:textStyle">bold</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatWhoIsTyping">
|
||||
<item name="android:lines">1</item>
|
||||
<item name="android:ellipsize">end</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatSendButton">
|
||||
<item name="android:maxLines">5</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatInputEditText">
|
||||
<item name="android:background">@null</item>
|
||||
<item name="android:textAllCaps">true</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatMessageLayoutOutcoming">
|
||||
<item name="android:paddingLeft">8dp</item>
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingRight">16dp</item>
|
||||
<item name="android:paddingBottom">4dp</item>
|
||||
<item name="android:gravity">end|right</item>
|
||||
<item name="android:layout_marginTop">4dp</item>
|
||||
<item name="android:layout_marginBottom">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatMessageLayoutIncoming">
|
||||
<item name="android:paddingLeft">16dp</item>
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingRight">8dp</item>
|
||||
<item name="android:paddingBottom">4dp</item>
|
||||
<item name="android:gravity">end|right</item>
|
||||
<item name="android:layout_marginTop">4dp</item>
|
||||
<item name="android:layout_marginBottom">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ChatHeaderLayout">
|
||||
<item name="android:padding">8dp</item>
|
||||
<item name="android:layout_marginTop">4dp</item>
|
||||
<item name="android:layout_marginBottom">4dp</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
@@ -1,4 +1,4 @@
|
||||
include ':smarttubetv', ':common', ':openvpn'
|
||||
include ':smarttubetv', ':common', ':openvpn', ':chatkit'
|
||||
|
||||
def rootDir = settingsDir
|
||||
|
||||
|
||||
@@ -190,6 +190,7 @@ dependencies {
|
||||
implementation project(':sharedutils')
|
||||
implementation project(':mediaserviceinterfaces')
|
||||
implementation project(':youtubeapi')
|
||||
implementation project(':chatkit')
|
||||
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:' + rxAndroidVersion
|
||||
implementation 'io.reactivex.rxjava2:rxjava:' + rxJavaVersion
|
||||
@@ -204,5 +205,5 @@ dependencies {
|
||||
|
||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
|
||||
|
||||
implementation 'com.github.stfalcon-studio:Chatkit:' + chatkitVersion
|
||||
//implementation 'com.github.stfalcon-studio:Chatkit:' + chatkitVersion
|
||||
}
|
||||
|
||||