add chatkit as module

This commit is contained in:
Yuriy Liskov
2022-07-08 18:03:10 +03:00
parent 8dcfa96709
commit 1cf5d4ce1c
64 changed files with 6172 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
/build
+54
View File
@@ -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
}
+4
View File
@@ -0,0 +1,4 @@
# ViewHolder constructors are resolved by reflection
-keepclassmembers class * extends com.stfalcon.chatkit.commons.ViewHolder {
public <init>(android.view.View);
}
+3
View File
@@ -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();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -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);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 957 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

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" />
+112
View File
@@ -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>
+228
View File
@@ -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>
+31
View File
@@ -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>
+36
View File
@@ -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>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="font_fontFamily_medium">sans-serif</string>
</resources>
+30
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
<resources />
+57
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
include ':smarttubetv', ':common', ':openvpn'
include ':smarttubetv', ':common', ':openvpn', ':chatkit'
def rootDir = settingsDir
+2 -1
View File
@@ -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
}