() {
@Override
public GalleryImage createFromParcel(Parcel source) {
return new GalleryImage(source);
}
@Override
public GalleryImage[] newArray(int size) {
return new GalleryImage[size];
}
};
@Override
public String toString() {
return "Image{" +
"path='" + path + '\'' +
'}';
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/tool/Closeables.java
================================================
/*
* Copyright (C) 2007 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.beetle.bauhinia.gallery.tool;
import androidx.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Utility methods for working with {@link Closeable} objects.
*
* @author Michael Lancaster
* @since 1.0
*/
//@Beta
public final class Closeables {
static final Logger logger = Logger.getLogger(Closeables.class.getName());
private Closeables() {}
/**
* Closes a {@link Closeable}, with control over whether an {@code IOException} may be thrown.
* This is primarily useful in a finally block, where a thrown exception needs to be logged but
* not propagated (otherwise the original exception will be lost).
*
* If {@code swallowIOException} is true then we never throw {@code IOException} but merely log
* it.
*
*
Example:
{@code
*
* public void useStreamNicely() throws IOException {
* SomeStream stream = new SomeStream("foo");
* boolean threw = true;
* try {
* // ... code which does something with the stream ...
* threw = false;
* } finally {
* // If an exception occurs, rethrow it only if threw==false:
* Closeables.close(stream, threw);
* }
* }}
*
* @param closeable the {@code Closeable} object to be closed, or null, in which case this method
* does nothing
* @param swallowIOException if true, don't propagate IO exceptions thrown by the {@code close}
* methods
* @throws IOException if {@code swallowIOException} is false and {@code close} throws an
* {@code IOException}.
*/
public static void close(@Nullable Closeable closeable,
boolean swallowIOException) throws IOException {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (IOException e) {
if (swallowIOException) {
logger.log(Level.WARNING,
"IOException thrown while closing Closeable.", e);
} else {
throw e;
}
}
}
/**
* Closes the given {@link InputStream}, logging any {@code IOException} that's thrown rather
* than propagating it.
*
* While it's not safe in the general case to ignore exceptions that are thrown when closing
* an I/O resource, it should generally be safe in the case of a resource that's being used only
* for reading, such as an {@code InputStream}. Unlike with writable resources, there's no
* chance that a failure that occurs when closing the stream indicates a meaningful problem such
* as a failure to flush all bytes to the underlying resource.
*
* @param inputStream the input stream to be closed, or {@code null} in which case this method
* does nothing
* @since 17.0
*/
public static void closeQuietly(@Nullable InputStream inputStream) {
try {
close(inputStream, true);
} catch (IOException impossible) {
throw new AssertionError(impossible);
}
}
/**
* Closes the given {@link Reader}, logging any {@code IOException} that's thrown rather than
* propagating it.
*
*
While it's not safe in the general case to ignore exceptions that are thrown when closing
* an I/O resource, it should generally be safe in the case of a resource that's being used only
* for reading, such as a {@code Reader}. Unlike with writable resources, there's no chance that
* a failure that occurs when closing the reader indicates a meaningful problem such as a failure
* to flush all bytes to the underlying resource.
*
* @param reader the reader to be closed, or {@code null} in which case this method does nothing
* @since 17.0
*/
public static void closeQuietly(@Nullable Reader reader) {
try {
close(reader, true);
} catch (IOException impossible) {
throw new AssertionError(impossible);
}
}
public static void closeQuietly(@Nullable OutputStream outputStream) {
try {
close(outputStream, true);
} catch (IOException impossible) {
throw new AssertionError(impossible);
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/tool/DisplayUtils.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.tool;
import android.content.Context;
import android.util.TypedValue;
/**
* Created by hillwind
*/
public class DisplayUtils {
public static int getSizeByGivenAbsSize(Context context, int givenAbsSize) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, givenAbsSize, context.getResources().getDisplayMetrics());
}
public static int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
public static int getScreenHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels;
}
public static float getScreenDensity(Context context) {
return context.getResources().getDisplayMetrics().density;
}
public static int getScreenDensityDpi(Context context) {
return context.getResources().getDisplayMetrics().densityDpi;
}
public static int dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
public static int px2dp(Context context, float px) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (px / scale + 0.5f);
}
public static int px2sp(Context context, float pxValue) {
return (int) (pxValue / context.getResources().getDisplayMetrics().scaledDensity + 0.5f);
}
public static int sp2px(Context context, float spValue) {
return (int) (spValue * context.getResources().getDisplayMetrics().scaledDensity + 0.5f);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/tool/FileUtils.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.tool;
import java.io.File;
/**
* Created by hillwind
*/
public class FileUtils {
public static void mkdirIfNeed(File file) {
if (file != null && !file.exists()) {
mkdirIfNeed(file.getParentFile());
file.mkdir();
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/tool/ImageUtils.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.tool;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.text.TextUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by hillwind
*/
public class ImageUtils {
public static String savePNGImage(Context context, String srcPath, Bitmap bitmap) throws IOException {
if (TextUtils.isEmpty(srcPath) || bitmap == null) {
return null;
}
String fileName = Md5FileNameUtils.getMd5FileName(srcPath);
return savePNGImageWithFileName(context, fileName, bitmap);
}
public static String savePNGImageWithFileName(Context context, String fileName, Bitmap bitmap) throws IOException {
if (TextUtils.isEmpty(fileName) || bitmap == null) {
return null;
}
String filePath = null;
FileOutputStream fileOutputStream = null;
try {
File imgFile = new File(StorageUtils.getAlbumDir(context), fileName + ".png");
fileOutputStream = new FileOutputStream(imgFile);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
fileOutputStream.flush();
updateMediaStore(context, imgFile);
filePath = imgFile.getAbsolutePath();
} finally {
Closeables.closeQuietly(fileOutputStream);
}
return filePath;
}
public static void updateMediaStore(Context context, File savedFile) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(savedFile);
mediaScanIntent.setData(contentUri);
context.sendBroadcast(mediaScanIntent);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/tool/Md5FileNameUtils.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.tool;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Created by hillwind
*/
public class Md5FileNameUtils {
private static final String HASH_ALGORITHM = "MD5";
private static final int RADIX = 10 + 26; // 10 digits + 26 letters
public static String getMd5FileName(String url) {
byte[] md5 = getMD5(url.getBytes());
BigInteger bi = new BigInteger(md5).abs();
return bi.toString(RADIX);
}
private static byte[] getMD5(byte[] data) {
byte[] hash = null;
try {
MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
digest.update(data);
hash = digest.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return hash;
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/tool/StorageUtils.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.tool;
import android.content.Context;
import android.os.Environment;
import java.io.File;
/**
* @author hillwind
*/
public class StorageUtils {
public static final String ALBUM_NAME = "ChatAlbum";
public static String getAlbumDir(Context context) {
String dir = getAppDir(context) + File.separator + ALBUM_NAME;
File dirFile = new File(dir);
if (!dirFile.exists()) {
FileUtils.mkdirIfNeed(dirFile);
}
return dir;
}
public static String getAppDir(Context context) {
String dir = getCacheDir(context) + File.separator + context.getPackageName();
File dirFile = new File(dir);
if (!dirFile.exists()) {
FileUtils.mkdirIfNeed(dirFile);
}
return dir;
}
public static String getCacheDir(Context context) {
String cachePath;
if (isSDCardAvailable()) {
cachePath = Environment.getExternalStorageDirectory().getAbsolutePath();
} else {
cachePath = context.getCacheDir().getPath();
}
return cachePath;
}
public static boolean isSDCardAvailable() {
String sdStatus = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(sdStatus);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryAdapter.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.ui;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import androidx.viewpager.widget.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Toast;
import com.beetle.bauhinia.gallery.GalleryImage;
import com.beetle.bauhinia.gallery.tool.ImageUtils;
import com.beetle.imlib.R;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import uk.co.senab.photoview.PhotoView;
import uk.co.senab.photoview.PhotoViewAttacher;
/**
* Created by hillwind
*/
public class GalleryAdapter extends PagerAdapter {
private OnItemClickListener listener;
private final Context context;
private List mPhotos = new ArrayList();
public GalleryAdapter(Context context, List photos) {
this.context = context;
if (photos != null) {
mPhotos = photos;
}
}
@Override
public int getCount() {
return mPhotos.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public View instantiateItem(final ViewGroup container, final int position) {
final PhotoView photoView = new PhotoView(container.getContext());
final String path = mPhotos.get(position).path;
if (path.contains(":/")) {
Picasso.get().load(path).into(photoView);
} else {
Picasso.get().load(new File(path)).into(photoView);
}
container.addView(photoView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
photoView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onItemClick(container, v, position);
}
}
});
photoView.setOnViewTapListener(new PhotoViewAttacher.OnViewTapListener() {
@Override
public void onViewTap(View view, float x, float y) {
if (listener != null) {
listener.onItemClick(container, view, position);
}
}
});
photoView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Dialog dialog = PhotoActionPopup.createDialog(context, new PhotoActionPopup.Listener() {
@Override
public void onSaveToPhone() {
saveImageToPhone(photoView, path);
}
});
dialog.show();
return false;
}
});
return photoView;
}
private void saveImageToPhone(final PhotoView photoView, final String path) {
Observable.create(new Observable.OnSubscribe() {
@Override
public void call(Subscriber super String> subscriber) {
Bitmap bitmap = ((BitmapDrawable) photoView.getDrawable()).getBitmap();
try {
String desImagePath = ImageUtils.savePNGImage(context, path, bitmap);
subscriber.onNext(desImagePath);
subscriber.onCompleted();
} catch (IOException e) {
subscriber.onError(e);
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
Toast.makeText(context, R.string.gallery_image_save_failed, Toast.LENGTH_SHORT).show();
}
@Override
public void onNext(String s) {
Toast.makeText(context, context.getString(R.string.gallery_image_saved_to) + s, Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
public void setOnItemClickListener(OnItemClickListener listener) {
this.listener = listener;
}
public interface OnItemClickListener {
void onItemClick(ViewGroup container, View view, int position);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryGridAdapter.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.ui;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import com.beetle.bauhinia.gallery.GalleryImage;
import com.beetle.imlib.R;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.util.List;
/**
* Created by hillwind
*/
public class GalleryGridAdapter extends BaseAdapter {
private final Context context;
private final int screenWidth;
private final List imagesList;
public GalleryGridAdapter(Context context, List imagesList, int screenWidth) {
this.context = context;
this.imagesList = imagesList;
this.screenWidth = screenWidth;
}
@Override
public int getCount() {
return imagesList == null ? 0 : imagesList.size();
}
@Override
public GalleryImage getItem(int position) {
return imagesList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final GalleryImage ib = getItem(position);
final ViewHolder viewHolder;
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = View.inflate(context, R.layout.gallery_activity_gallery_grid_item, null);
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.child_image);
setConvertViewSize(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
viewHolder.imageView.setImageResource(R.drawable.gallery_picture_place_holder);
}
viewHolder.imageView.setTag(ib.path);
if (ib.path.contains(":/")) {
Picasso.get().load(ib.path).into(viewHolder.imageView);
} else {
Picasso.get().load(new File(ib.path)).into(viewHolder.imageView);
}
return convertView;
}
private void setConvertViewSize(final View convertView) {
int height = screenWidth / 3 - 8;
convertView.setLayoutParams(new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height));
}
public static class ViewHolder {
public ImageView imageView;
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryGridUI.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.GridView;
import com.beetle.bauhinia.activity.BaseActivity;
import com.beetle.bauhinia.gallery.GalleryImage;
import com.beetle.bauhinia.gallery.tool.DisplayUtils;
import com.beetle.imlib.R;
import java.util.ArrayList;
/**
* Created by hillwind
*/
public class GalleryGridUI extends BaseActivity {
private static final String INTENT_EXTRA_KEY_IMAGES = "images";
private static final String INTENT_EXTRA_KEY_POSITION = "position";
private ArrayList imagesList;
private GridView gridView;
private GalleryGridAdapter mGridAdapter;
private int initialPosition = 0;
public static Intent getCallingIntent(Context context, ArrayList imagesList, int position) {
Intent intent = new Intent(context, GalleryGridUI.class);
intent.putExtra(INTENT_EXTRA_KEY_POSITION, position);
intent.putParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES, imagesList);
return intent;
}
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
setContentView(R.layout.gallery_activity_gallery_grid);
handleIntent(getIntent());
initView();
}
private void handleIntent(Intent intent) {
imagesList = intent.getParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES);
initialPosition = intent.getIntExtra(INTENT_EXTRA_KEY_POSITION, 0);
}
private void initView() {
gridView = (GridView) this.findViewById(R.id.child_grid);
mGridAdapter = new GalleryGridAdapter(GalleryGridUI.this, imagesList, DisplayUtils.getScreenWidth(this));
gridView.setAdapter(mGridAdapter);
gridView.setSelection(initialPosition);
gridView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
navigateToViewImageDetail(position);
}
});
}
private void navigateToViewImageDetail(int position) {
Intent intent = GalleryUI.getCallingIntent(this, imagesList, position, true);
startActivity(intent);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/ui/GalleryUI.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import com.beetle.bauhinia.activity.BaseActivity;
import com.beetle.bauhinia.gallery.GalleryImage;
import com.beetle.bauhinia.gallery.view.ScrollViewPager;
import com.beetle.imlib.R;
import java.util.ArrayList;
/**
* Created by hillwind
*/
public class GalleryUI extends BaseActivity {
private static final String INTENT_EXTRA_KEY_POSITION = "position";
private static final String INTENT_EXTRA_KEY_IMAGES = "images";
private static final String INTENT_EXTRA_KEY_IS_ENTER_FROM_GRID = "is_enter_from_grid";
private ScrollViewPager mViewPager;
private GalleryAdapter mPagerAdapter;
private ImageButton ibViewMorePicture;
private int mPosition;
private int mTotal;
private ArrayList imagesList;
private boolean isEnterFromGrid;
public static Intent getCallingIntent(Context context, ArrayList galleryImages, int position, boolean isEnterFromGrid) {
Intent intent = getCallingIntent(context, galleryImages, position);
intent.putExtra(INTENT_EXTRA_KEY_IS_ENTER_FROM_GRID, isEnterFromGrid);
return intent;
}
public static Intent getCallingIntent(Context context, ArrayList galleryImages, int position) {
Intent intent = new Intent(context, GalleryUI.class);
intent.putParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES, galleryImages);
intent.putExtra(INTENT_EXTRA_KEY_POSITION, position);
return intent;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gallery_activity_gallery);
initViews();
init();
}
private void initViews() {
mViewPager = (ScrollViewPager) findViewById(R.id.imagebrowser_svp_pager);
ibViewMorePicture = (ImageButton) findViewById(R.id.ib_view_more_picture);
}
private void navigateToViewMorePicture() {
Intent intent = GalleryGridUI.getCallingIntent(this, imagesList, mViewPager.getCurrentItem());
startActivity(intent);
}
private void init() {
mPosition = getIntent().getIntExtra(INTENT_EXTRA_KEY_POSITION, 0);
imagesList = getIntent().getParcelableArrayListExtra(INTENT_EXTRA_KEY_IMAGES);
isEnterFromGrid = getIntent().getBooleanExtra(INTENT_EXTRA_KEY_IS_ENTER_FROM_GRID, false);
mTotal = imagesList.size();
if (mPosition > mTotal) {
mPosition = mTotal - 1;
}
mPagerAdapter = new GalleryAdapter(this, imagesList);
mPagerAdapter.setOnItemClickListener(new GalleryAdapter.OnItemClickListener() {
@Override
public void onItemClick(ViewGroup container, View view, int position) {
GalleryUI.this.finish();
}
});
mViewPager.setAdapter(mPagerAdapter);
if (mTotal > 0) {
mViewPager.setCurrentItem(mPosition, false);
}
if (isEnterFromGrid) {
hideViewMorePictureButton();
} else {
showViewMorePictureButton();
}
}
private void hideViewMorePictureButton() {
ibViewMorePicture.setVisibility(View.GONE);
}
private void showViewMorePictureButton() {
ibViewMorePicture.setVisibility(View.VISIBLE);
ibViewMorePicture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navigateToViewMorePicture();
}
});
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/ui/PhotoActionPopup.java
================================================
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.beetle.bauhinia.gallery.ui;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import com.beetle.imlib.R;
import java.util.ArrayList;
/**
* Shows a popup asking the user what to do for a photo. The result is passed back to the Listener
*/
public class PhotoActionPopup {
public static final String TAG = PhotoActionPopup.class.getSimpleName();
public static Dialog createDialog(Context context, final Listener listener) {
final ListAdapter adapter = getListAdapter(context, listener);
final AlertDialog dialog = new AlertDialog.Builder(context)
.setAdapter(adapter, null)
.create();
dialog.setCanceledOnTouchOutside(true);
dialog.getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
pickItem(adapter, position);
dialog.dismiss();
}
});
return dialog;
}
private static void pickItem(ListAdapter adapter, int position) {
final ChoiceListItem choice = (ChoiceListItem) adapter.getItem(position);
choice.onPick();
}
private static ListAdapter getListAdapter(final Context context, final Listener listener) {
final ArrayList choices = new ArrayList(1);
// Save photo.
choices.add(new ChoiceListItem(ChoiceListItem.ID_SAVE_TO_PHONE,
context.getString(R.string.gallery_save_to_phone),
new Runnable() {
@Override
public void run() {
listener.onSaveToPhone();
}
}));
return new ArrayAdapter(context, R.layout.gallery_select_dialog_item, choices);
}
private static final class ChoiceListItem {
private final int mId;
private final String mCaption;
private final Runnable action;
public static final int ID_SAVE_TO_PHONE = 0;
public ChoiceListItem(int id, String caption, Runnable action) {
mId = id;
mCaption = caption;
this.action = action;
}
public void onPick() {
if (action != null) {
action.run();
}
}
@Override
public String toString() {
return mCaption;
}
public int getId() {
return mId;
}
}
public interface Listener {
void onSaveToPhone();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/gallery/view/ScrollViewPager.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.gallery.view;
import android.content.Context;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class ScrollViewPager extends ViewPager {
private boolean mIsEnable = true;
public ScrollViewPager(Context context) {
super(context);
}
public ScrollViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mIsEnable) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException e) {
return false;
}
}
return true;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mIsEnable) {
return super.onTouchEvent(ev);
}
return false;
}
@Override
public void setAdapter(PagerAdapter arg0) {
super.setAdapter(arg0);
}
public void setAdapter(PagerAdapter arg0, int index) {
super.setAdapter(arg0);
setCurrentItem(index, false);
}
public void setEnableTouchScroll(boolean isEnable) {
mIsEnable = isEnable;
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/Contact.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.toolbar;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import com.linkedin.android.spyglass.mentions.Mentionable;
public class Contact implements Mentionable {
private final String mName;
private long uid;
public Contact(long uid, String name) {
this.uid = uid;
mName = name;
}
public long getUid() {
return uid;
}
public String getName() {
return mName;
}
// --------------------------------------------------
// Mentionable Implementation
// --------------------------------------------------
@NonNull
@Override
public String getTextForDisplayMode(MentionDisplayMode mode) {
switch (mode) {
case FULL:
return "@" + mName + " ";
case PARTIAL:
case NONE:
default:
return "";
}
}
@Override
public MentionDeleteStyle getDeleteStyle() {
// Note: Cities do not support partial deletion
// i.e. "San Francisco" -> DEL -> ""
return MentionDeleteStyle.PARTIAL_NAME_DELETE;
}
@Override
public int getSuggestibleId() {
return mName.hashCode();
}
@Override
public String getSuggestiblePrimaryText() {
return mName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(uid);
dest.writeString(mName);
}
public Contact(Parcel in) {
uid = in.readLong();
mName = in.readString();
}
public static final Parcelable.Creator CREATOR
= new Parcelable.Creator() {
public Contact createFromParcel(Parcel in) {
return new Contact(in);
}
public Contact[] newArray(int size) {
return new Contact[size];
}
};
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseChatExtendMenu.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.toolbar;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.beetle.imlib.R;
/**
* 按+按钮出来的扩展按钮
*
*/
public class EaseChatExtendMenu extends GridView {
protected Context context;
private List itemModels = new ArrayList();
public EaseChatExtendMenu(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs);
}
public EaseChatExtendMenu(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public EaseChatExtendMenu(Context context) {
super(context);
init(context, null);
}
public static int dip2px(Context context, float dipValue){
final float scale = context.getResources().getDisplayMetrics().density;
return (int)(dipValue * scale + 0.5f);
}
private void init(Context context, AttributeSet attrs){
this.context = context;
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.EaseChatExtendMenu);
int numColumns = ta.getInt(R.styleable.EaseChatExtendMenu_numColumns, 4);
ta.recycle();
setNumColumns(numColumns);
setStretchMode(GridView.STRETCH_COLUMN_WIDTH);
setGravity(Gravity.CENTER_VERTICAL);
setVerticalSpacing(dip2px(context, 8));
}
/**
* 初始化
*/
public void init(){
setAdapter(new ItemAdapter(context, itemModels));
}
/**
* 注册menu item
*
* @param name
* item名字
* @param drawableRes
* item背景
* @param itemId
* id
* @param listener
* item点击事件
*/
public void registerMenuItem(String name, int drawableRes, int itemId, EaseChatExtendMenuItemClickListener listener) {
ChatMenuItemModel item = new ChatMenuItemModel();
item.name = name;
item.image = drawableRes;
item.id = itemId;
item.clickListener = listener;
itemModels.add(item);
}
/**
* 注册menu item
*
* @param nameRes
* item名字的resource id
* @param drawableRes
* item背景
* @param itemId
* id
* @param listener
* item点击事件
*/
public void registerMenuItem(int nameRes, int drawableRes, int itemId, EaseChatExtendMenuItemClickListener listener) {
registerMenuItem(context.getString(nameRes), drawableRes, itemId, listener);
}
private class ItemAdapter extends ArrayAdapter{
private Context context;
public ItemAdapter(Context context, List objects) {
super(context, 1, objects);
this.context = context;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ChatMenuItem menuItem = null;
if(convertView == null){
convertView = new ChatMenuItem(context);
}
menuItem = (ChatMenuItem) convertView;
menuItem.setImage(getItem(position).image);
menuItem.setText(getItem(position).name);
menuItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(getItem(position).clickListener != null){
getItem(position).clickListener.onClick(getItem(position).id, v);
}
}
});
return convertView;
}
}
public interface EaseChatExtendMenuItemClickListener{
void onClick(int itemId, View view);
}
class ChatMenuItemModel{
String name;
int image;
int id;
EaseChatExtendMenuItemClickListener clickListener;
}
class ChatMenuItem extends LinearLayout {
private ImageView imageView;
private TextView textView;
public ChatMenuItem(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs);
}
public ChatMenuItem(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ChatMenuItem(Context context) {
super(context);
init(context, null);
}
private void init(Context context, AttributeSet attrs) {
LayoutInflater.from(context).inflate(R.layout.ease_chat_menu_item, this);
imageView = (ImageView) findViewById(R.id.image);
textView = (TextView) findViewById(R.id.text);
}
public void setImage(int resid) {
imageView.setBackgroundResource(resid);
}
public void setText(int resid) {
textView.setText(resid);
}
public void setText(String text) {
textView.setText(text);
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseChatInputMenu.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.toolbar;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import androidx.annotation.NonNull;
import android.text.Editable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.*;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.beetle.imlib.R;
import com.beetle.bauhinia.toolbar.EaseChatExtendMenu.EaseChatExtendMenuItemClickListener;
import com.beetle.bauhinia.toolbar.emoticon.EmoticonPanel;
import com.linkedin.android.spyglass.mentions.MentionSpan;
import com.linkedin.android.spyglass.mentions.MentionSpanConfig;
import com.linkedin.android.spyglass.tokenization.QueryToken;
import com.linkedin.android.spyglass.tokenization.impl.WordTokenizer;
import com.linkedin.android.spyglass.tokenization.impl.WordTokenizerConfig;
import com.linkedin.android.spyglass.tokenization.interfaces.QueryTokenReceiver;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 聊天页面底部的聊天输入菜单栏
* 主要包含3个控件:EaseChatPrimaryMenu(主菜单栏,包含文字输入、发送等功能),
* EaseChatExtendMenu(扩展栏,点击加号按钮出来的小宫格的菜单栏),
* 以及EaseEmojiconMenu(表情栏)
*/
public class
EaseChatInputMenu extends LinearLayout implements View.OnClickListener, QueryTokenReceiver {
FrameLayout primaryMenuContainer;
protected RelativeLayout emojiconMenuContainer;
protected EaseChatPrimaryMenu chatPrimaryMenu;
private EmoticonPanel mEmoticonPanel;
protected EaseChatExtendMenu chatExtendMenu;
protected FrameLayout chatExtendMenuContainer;
protected LayoutInflater layoutInflater;
private EditText mEtSend;
private Handler handler = new Handler();
private ChatInputMenuListener listener;
public EaseChatInputMenu(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs);
}
public EaseChatInputMenu(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public EaseChatInputMenu(Context context) {
super(context);
init(context, null);
}
private void init(Context context, AttributeSet attrs) {
layoutInflater = LayoutInflater.from(context);
layoutInflater.inflate(R.layout.ease_widget_chat_input_menu, this);
primaryMenuContainer = (FrameLayout) findViewById(R.id.primary_menu_container);
emojiconMenuContainer = (RelativeLayout) findViewById(R.id.menu_container);
chatExtendMenuContainer = (FrameLayout) findViewById(R.id.extend_menu_container);
// 扩展按钮栏
chatExtendMenu = (EaseChatExtendMenu) findViewById(R.id.extend_menu);
chatPrimaryMenu = (EaseChatPrimaryMenu) primaryMenuContainer.findViewById(R.id.primary_menu);
mEtSend = chatPrimaryMenu.editText;
mEmoticonPanel = new EmoticonPanel(context);
emojiconMenuContainer.addView(mEmoticonPanel);
chatPrimaryMenu.buttonSend.setOnClickListener(this);
chatPrimaryMenu.buttonSetModeKeyboard.setOnClickListener(this);
chatPrimaryMenu.buttonSetModeVoice.setOnClickListener(this);
chatPrimaryMenu.buttonMore.setOnClickListener(this);
chatPrimaryMenu.faceLayout.setOnClickListener(this);
chatPrimaryMenu.editText.setOnClickListener(this);
chatPrimaryMenu.editText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
chatPrimaryMenu.edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_active);
} else {
chatPrimaryMenu.edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_normal);
}
if (listener != null) {
listener.onFocusChanged(hasFocus);
}
}
});
// 监听文字框
chatPrimaryMenu.editText.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!TextUtils.isEmpty(s)) {
chatPrimaryMenu.buttonMore.setVisibility(View.GONE);
chatPrimaryMenu.buttonSend.setVisibility(View.VISIBLE);
} else {
chatPrimaryMenu.buttonMore.setVisibility(View.VISIBLE);
chatPrimaryMenu.buttonSend.setVisibility(View.GONE);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
});
WordTokenizerConfig tokenizerConfig = new WordTokenizerConfig.Builder().build();
WordTokenizer tokenizer = new WordTokenizer(tokenizerConfig);
chatPrimaryMenu.editText.setTokenizer(tokenizer);
MentionSpanConfig.Builder configBuilder = new MentionSpanConfig.Builder();
configBuilder.setMentionTextColor(Color.BLACK);
configBuilder.setMentionTextBackgroundColor(Color.TRANSPARENT);
MentionSpanConfig config = configBuilder.build();
chatPrimaryMenu.editText.setMentionSpanConfig(config);
chatPrimaryMenu.editText.setAvoidPrefixOnTap(true);
chatPrimaryMenu.editText.setQueryTokenReceiver(this);
chatPrimaryMenu.buttonPressToSpeak.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(listener != null){
return listener.onPressToSpeakBtnTouch(v, event);
}
return false;
}
});
processEmoticon();
}
/**
* init view 此方法需放在registerExtendMenuItem后面及setCustomEmojiconMenu,
* setCustomPrimaryMenu(如果需要自定义这两个menu)后面
*/
public void init() {
// 初始化extendmenu
chatExtendMenu.init();
}
public void disableSend() {
hideKeyboard();
chatPrimaryMenu.buttonMore.setEnabled(false);
chatPrimaryMenu.buttonSend.setEnabled(false);
chatPrimaryMenu.buttonPressToSpeak.setEnabled(false);
chatPrimaryMenu.buttonSetModeVoice.setEnabled(false);
chatPrimaryMenu.buttonSetModeKeyboard.setEnabled(false);
chatPrimaryMenu.faceLayout.setEnabled(false);
chatPrimaryMenu.editText.setEnabled(false);
}
public void enableSend() {
chatPrimaryMenu.buttonMore.setEnabled(true);
chatPrimaryMenu.buttonSend.setEnabled(true);
chatPrimaryMenu.buttonPressToSpeak.setEnabled(true);
chatPrimaryMenu.buttonSetModeVoice.setEnabled(true);
chatPrimaryMenu.buttonSetModeKeyboard.setEnabled(true);
chatPrimaryMenu.faceLayout.setEnabled(true);
chatPrimaryMenu.editText.setEnabled(true);
}
public void clearFocus() {
chatPrimaryMenu.editText.clearFocus();
}
public void atUser(long uid, String name) {
Contact c = new Contact(uid, name);
chatPrimaryMenu.editText.insertMention(c);
}
/**
* 点击事件
* @param view
*/
@Override
public void onClick(View view){
int id = view.getId();
if (id == R.id.btn_send) {
if(listener != null){
String ss = chatPrimaryMenu.editText.getText().toString();
List mentions = chatPrimaryMenu.editText.getMentionsText().getMentionSpans();
Set atSet = new HashSet<>();
ArrayList atList = new ArrayList<>();
ArrayList atNames = new ArrayList<>();
for(int i = 0; i < mentions.size(); i++) {
Contact c = (Contact)mentions.get(i).getMention();
if (!atSet.contains(c.getUid())) {
atList.add(c.getUid());
atNames.add(c.getName());
atSet.add(c.getUid());
}
}
chatPrimaryMenu.editText.setText("");
listener.onSendMessage(ss, atList, atNames);
}
} else if (id == R.id.btn_set_mode_voice) {
chatPrimaryMenu.setModeVoice();
chatPrimaryMenu.showNormalFaceImage();
hideExtendMenuContainer();
} else if (id == R.id.btn_set_mode_keyboard) {
chatPrimaryMenu.setModeKeyboard();
chatPrimaryMenu.showNormalFaceImage();
hideExtendMenuContainer();
} else if (id == R.id.btn_more) {
chatPrimaryMenu.buttonSetModeVoice.setVisibility(View.VISIBLE);
chatPrimaryMenu.buttonSetModeKeyboard.setVisibility(View.GONE);
chatPrimaryMenu.edittext_layout.setVisibility(View.VISIBLE);
chatPrimaryMenu.buttonPressToSpeak.setVisibility(View.GONE);
chatPrimaryMenu.showNormalFaceImage();
toggleMore();
} else if (id == R.id.et_sendmessage) {
chatPrimaryMenu.edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_active);
chatPrimaryMenu.faceNormal.setVisibility(View.VISIBLE);
chatPrimaryMenu.faceChecked.setVisibility(View.INVISIBLE);
hideExtendMenuContainer();
} else if (id == R.id.rl_face) {
chatPrimaryMenu.toggleFaceImage();
toggleEmojicon();
}
}
/**
* 注册扩展菜单的item
*
* @param name
* item名字
* @param drawableRes
* item背景
* @param itemId
* id
* @param listener
* item点击事件
*/
public void registerExtendMenuItem(String name, int drawableRes, int itemId,
EaseChatExtendMenuItemClickListener listener) {
chatExtendMenu.registerMenuItem(name, drawableRes, itemId, listener);
}
/**
* 注册扩展菜单的item
*
* @param nameRes
* item名字
* @param drawableRes
* item背景
* @param itemId
* id
* @param listener
* item点击事件
*/
public void registerExtendMenuItem(int nameRes, int drawableRes, int itemId,
EaseChatExtendMenuItemClickListener listener) {
chatExtendMenu.registerMenuItem(nameRes, drawableRes, itemId, listener);
}
protected void processEmoticon() {
mEmoticonPanel.setOnItemEmoticonClickListener(new EmoticonPanel.OnItemEmoticonClickListener() {
@Override
public void onEmoticonClick(SpannableString spannableString) {
int index = mEtSend.getSelectionStart();
Editable editable = mEtSend.getEditableText();
editable.insert(index, spannableString);
mEtSend.requestFocus();
}
@Override
public void onEmoticonDeleted() {
if (!TextUtils.isEmpty(mEtSend.getText())) {
KeyEvent event = new KeyEvent(0, 0, 0, KeyEvent.KEYCODE_DEL,
0, 0, 0, 0, KeyEvent.KEYCODE_ENDCALL);
mEtSend.dispatchKeyEvent(event);
}
}
});
}
@Override
public List onQueryReceived(final @NonNull QueryToken queryToken) {
if (listener != null && queryToken.getTokenString().equals("@")) {
listener.onAt();
}
return null;
}
/**
* 显示或隐藏图标按钮页
*
*/
protected void toggleMore() {
if (chatExtendMenuContainer.getVisibility() == View.GONE) {
hideKeyboard();
handler.postDelayed(new Runnable() {
public void run() {
chatExtendMenuContainer.setVisibility(View.VISIBLE);
chatExtendMenu.setVisibility(View.VISIBLE);
emojiconMenuContainer.setVisibility(View.GONE);
}
}, 50);
} else {
if (emojiconMenuContainer.getVisibility() == View.VISIBLE) {
emojiconMenuContainer.setVisibility(View.GONE);
chatExtendMenu.setVisibility(View.VISIBLE);
} else {
chatExtendMenuContainer.setVisibility(View.GONE);
chatPrimaryMenu.showKeyboard();
}
}
}
/**
* 显示或隐藏表情页
*/
protected void toggleEmojicon() {
if (chatExtendMenuContainer.getVisibility() == View.GONE) {
hideKeyboard();
handler.postDelayed(new Runnable() {
public void run() {
chatExtendMenuContainer.setVisibility(View.VISIBLE);
chatExtendMenu.setVisibility(View.GONE);
emojiconMenuContainer.setVisibility(View.VISIBLE);
}
}, 50);
} else {
if (emojiconMenuContainer.getVisibility() == View.VISIBLE) {
chatExtendMenuContainer.setVisibility(View.GONE);
emojiconMenuContainer.setVisibility(View.GONE);
chatPrimaryMenu.showKeyboard();
} else {
chatExtendMenu.setVisibility(View.GONE);
emojiconMenuContainer.setVisibility(View.VISIBLE);
}
}
}
/**
* 隐藏软键盘
*/
public void hideKeyboard() {
chatPrimaryMenu.hideKeyboard();
}
/**
* 隐藏整个扩展按钮栏(包括表情栏)
*/
public void hideExtendMenuContainer() {
chatExtendMenu.setVisibility(View.GONE);
emojiconMenuContainer.setVisibility(View.GONE);
chatExtendMenuContainer.setVisibility(View.GONE);
chatPrimaryMenu.showNormalFaceImage();
}
/**
* 系统返回键被按时调用此方法
*
* @return 返回false表示返回键时扩展菜单栏时打开状态,true则表示按返回键时扩展栏是关闭状态
* 如果返回时打开状态状态,会先关闭扩展栏再返回值
*/
public boolean onBackPressed() {
if (chatExtendMenuContainer.getVisibility() == View.VISIBLE) {
hideExtendMenuContainer();
return false;
} else {
return true;
}
}
public void setChatInputMenuListener(ChatInputMenuListener listener) {
this.listener = listener;
}
public interface ChatInputMenuListener {
/**
* 发送消息按钮点击
*
* @param content
* 文本内容
*/
void onSendMessage(String content, List at, List atNames);
/**
* 长按说话按钮touch事件
* @param v
* @param event
* @return
*/
boolean onPressToSpeakBtnTouch(View v, MotionEvent event);
/**
* edittext 焦点变化
* @param hasFocus
*/
void onFocusChanged(boolean hasFocus);
/**
* 用户输入@
*/
void onAt();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseChatPrimaryMenu.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.toolbar;
import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.beetle.imlib.R;
import com.linkedin.android.spyglass.ui.MentionsEditText;
/**
* 聊天输入栏主菜单栏
*
*/
public class EaseChatPrimaryMenu extends RelativeLayout {
public MentionsEditText editText;
public View buttonSetModeKeyboard;
public RelativeLayout edittext_layout;
public View buttonSetModeVoice;
public View buttonSend;
public View buttonPressToSpeak;
public ImageView faceNormal;
public ImageView faceChecked;
public Button buttonMore;
public RelativeLayout faceLayout;
public Activity activity;
public InputMethodManager inputManager;
public EaseChatPrimaryMenu(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public EaseChatPrimaryMenu(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public EaseChatPrimaryMenu(Context context) {
super(context);
init(context, null);
}
private void init(final Context context, AttributeSet attrs) {
this.activity = (Activity) context;
inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
LayoutInflater.from(context).inflate(R.layout.ease_widget_chat_primary_menu, this);
editText = (MentionsEditText) findViewById(R.id.et_sendmessage);
buttonSetModeKeyboard = findViewById(R.id.btn_set_mode_keyboard);
edittext_layout = (RelativeLayout) findViewById(R.id.edittext_layout);
buttonSetModeVoice = findViewById(R.id.btn_set_mode_voice);
buttonSend = findViewById(R.id.btn_send);
buttonPressToSpeak = findViewById(R.id.btn_press_to_speak);
faceNormal = (ImageView) findViewById(R.id.iv_face_normal);
faceChecked = (ImageView) findViewById(R.id.iv_face_checked);
faceLayout = (RelativeLayout) findViewById(R.id.rl_face);
buttonMore = (Button) findViewById(R.id.btn_more);
edittext_layout.setBackgroundResource(R.drawable.ease_input_bar_bg_normal);
}
/**
* 显示语音图标按钮
*
*/
public void setModeVoice() {
hideKeyboard();
edittext_layout.setVisibility(View.GONE);
buttonSetModeVoice.setVisibility(View.GONE);
buttonSetModeKeyboard.setVisibility(View.VISIBLE);
buttonSend.setVisibility(View.GONE);
buttonMore.setVisibility(View.VISIBLE);
buttonPressToSpeak.setVisibility(View.VISIBLE);
faceNormal.setVisibility(View.VISIBLE);
faceChecked.setVisibility(View.INVISIBLE);
}
/**
* 显示键盘图标
*/
public void setModeKeyboard() {
edittext_layout.setVisibility(View.VISIBLE);
buttonSetModeKeyboard.setVisibility(View.GONE);
buttonSetModeVoice.setVisibility(View.VISIBLE);
showKeyboard();
buttonPressToSpeak.setVisibility(View.GONE);
if (TextUtils.isEmpty(editText.getText())) {
buttonMore.setVisibility(View.VISIBLE);
buttonSend.setVisibility(View.GONE);
} else {
buttonMore.setVisibility(View.GONE);
buttonSend.setVisibility(View.VISIBLE);
}
}
public void toggleFaceImage(){
if(faceNormal.getVisibility() == View.VISIBLE){
showSelectedFaceImage();
}else{
showNormalFaceImage();
}
}
public void showNormalFaceImage(){
faceNormal.setVisibility(View.VISIBLE);
faceChecked.setVisibility(View.INVISIBLE);
}
public void showSelectedFaceImage(){
faceNormal.setVisibility(View.INVISIBLE);
faceChecked.setVisibility(View.VISIBLE);
}
/**
* 隐藏软键盘
*/
public void hideKeyboard() {
if (activity.getWindow().getAttributes().softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) {
if (activity.getCurrentFocus() != null)
inputManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
public void showKeyboard() {
editText.requestFocus();
inputManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/EaseExpandRecylerView.java
================================================
/**
* Copyright (C) 2013-2014 EaseMob Technologies. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.beetle.bauhinia.toolbar;
import android.content.Context;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class EaseExpandRecylerView extends RecyclerView {
public EaseExpandRecylerView(Context context) {
super(context);
}
public EaseExpandRecylerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/Emoticon.java
================================================
package com.beetle.bauhinia.toolbar.emoticon;
import android.graphics.Bitmap;
/**
* Desc.
*
* @author chenxj(陈贤靖)
* @date 2019/3/11
*/
public class Emoticon {
private int id;
private String desc;
private String name;
private Bitmap bitmap;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public Bitmap getBitmap() {
return bitmap;
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonAdapter.java
================================================
package com.beetle.bauhinia.toolbar.emoticon;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.beetle.imlib.R;
import java.util.ArrayList;
import java.util.List;
/**
* Desc.
*
* @author chenxj(陈贤靖)
* @date 2019/3/11
*/
public class EmoticonAdapter extends RecyclerView.Adapter {
private Context mContext;
private List mEmoticonList;
private OnItemClickListener mOnItemClickListener;
public EmoticonAdapter(Context context, @Nullable List data) {
mEmoticonList = data;
if (mEmoticonList == null) {
mEmoticonList = new ArrayList<>();
}
mContext = context;
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_emoticon, parent, false);
return new BaseViewHolder(view);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, final int position) {
Emoticon emoticon = mEmoticonList.get(position);
if (emoticon.getId() == R.drawable.emoji_item_delete) {
holder.ivEmoticon.setImageDrawable(mContext.getResources().getDrawable(R.drawable.emoji_item_delete));
} else {
holder.ivEmoticon.setImageBitmap(emoticon.getBitmap());
}
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
mOnItemClickListener.onClick(position);
}
}
});
}
@Override
public int getItemCount() {
return mEmoticonList.size();
}
public Emoticon getItem(int position) {
return mEmoticonList.get(position);
}
public void setOnClickListener(OnItemClickListener listener) {
if (listener != null) {
this.mOnItemClickListener = listener;
}
}
public interface OnItemClickListener {
void onClick(int position);
}
class BaseViewHolder extends RecyclerView.ViewHolder {
public ImageView ivEmoticon;
public BaseViewHolder(View itemView) {
super(itemView);
ivEmoticon = (ImageView) itemView.findViewById(R.id.iv_emoticon);
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonManager.java
================================================
package com.beetle.bauhinia.toolbar.emoticon;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ImageSpan;
import com.beetle.imlib.R;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.beetle.bauhinia.toolbar.emoticon.EmoticonUtils.FILE_EMOTICON;
/**
* Desc.
*
* @author chenxj(陈贤靖)
* @date 2019/3/11
*/
public class EmoticonManager {
private static final String TAG = "EmoticonManager";
/**
* 每一页表情数量
*/
private static final int PAGE_EMOTICON_SIZE = 31;
/**
* 十六进制数据的正则表达式
*/
private final static String REGEX_HEX = "[0-9a-fA-F]+";
/**
* 微信自定义表情的正则表达式, [text]
*/
private final static String REGEX_CONTAIN_EMOTION = "\\[[^\\]]+\\]";
/**
* emoji表情unicode对应的十六进制的正则表达式
*/
private final static String REGEX_DIVERSE_EMOJI = "[a-fA-F0-9]{5}";
/**
* 表情分页的结果集合
*/
public List> mEmoticonPageList = new ArrayList<>();
/**
* 保存于内存中的表情HashMap
*/
private HashMap mEmoticons = new HashMap<>();
/**
* 保存于内存中的表情列表
*/
private List mEmoticonList = new ArrayList<>();
private Context mContext;
private Set mEmojiSet;
private int mEmoticonSize;
public static EmoticonManager getInstance() {
return InstanceContainer.ISNATNCE;
}
/**
* 初始化
*
* @param context
*/
public void init(Context context) {
mContext = context;
mEmojiSet = EmoticonUtils.getEmojiEncodeSet();
mEmoticonSize = EmoticonUtils.getNormalSize(context);
loadUnicodeEmoji(context);
if (false) {
//disable image emoji
loadImageEmoji(context);
}
int pageCount = (int) Math.ceil((double) mEmoticonList.size() / PAGE_EMOTICON_SIZE);
for (int i = 0; i < pageCount; i++) {
mEmoticonPageList.add(getPageData(i));
}
}
private void loadUnicodeEmoji(Context context) {
Map emojis = EmoticonUtils.getEmojiMap();
for (Map.Entry e : emojis.entrySet()) {
String desc = e.getKey();
String fileName = e.getValue();
int resId = context.getResources().getIdentifier(fileName, "drawable", context.getPackageName());
if (resId != 0) {
Emoticon emoticon = new Emoticon();
emoticon.setId(resId);
emoticon.setName(fileName);
emoticon.setDesc(desc);
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId);
bitmap = Bitmap.createScaledBitmap(bitmap, mEmoticonSize, mEmoticonSize, true);
emoticon.setBitmap(bitmap);
mEmoticonList.add(emoticon);
mEmoticons.put(desc, emoticon);
}
}
}
private void loadImageEmoji(Context context) {
List emoticonStrList = EmoticonUtils.readFile(context, FILE_EMOTICON);
//已经加载过数据,或待解析数据集为空,直接返回
if (emoticonStrList.size() <= 0) {
return;
}
for (String str : emoticonStrList) {
String[] text = str.split(",");
String fileName = text[0].substring(0, text[0].lastIndexOf("."));
String desc = text[1];
int resId = context.getResources().getIdentifier(fileName, "drawable", context.getPackageName());
if (resId != 0) {
Emoticon emoticon = new Emoticon();
emoticon.setId(resId);
emoticon.setName(fileName);
emoticon.setDesc(desc);
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId);
bitmap = Bitmap.createScaledBitmap(bitmap, mEmoticonSize, mEmoticonSize, true);
emoticon.setBitmap(bitmap);
mEmoticonList.add(emoticon);
mEmoticons.put(desc, emoticon);
}
}
}
private List getPageData(int page) {
int startIndex = page * PAGE_EMOTICON_SIZE;
int endIndex = startIndex + PAGE_EMOTICON_SIZE;
if (endIndex > mEmoticonList.size()) {
endIndex = mEmoticonList.size();
}
List subList = mEmoticonList.subList(startIndex, endIndex);
List list = new ArrayList<>(subList);
//追加删除项
Emoticon emoticon = new Emoticon();
emoticon.setId(R.drawable.emoji_item_delete);
emoticon.setDesc(mContext.getString(R.string.desc_emoticon_delete));
emoticon.setName(mContext.getString(R.string.name_emoticon_delete));
list.add(emoticon);
return list;
}
public List> getEmoticonPageList() {
if ((mEmoticonPageList == null || mEmoticonPageList.size() <= 0) && mContext != null) {
init(mContext);
}
return mEmoticonPageList;
}
/**
* 添加表情
*
* @param context
* @param imgId
* @param text
* @return
*/
public SpannableString addEmoticon(Context context, int imgId, String text) {
if (TextUtils.isEmpty(text)) {
return null;
}
if (mEmojiSet != null && mEmojiSet.contains(text)) {
text = EmoticonUtils.EmojiCodeToString(Integer.parseInt(text, 16));
}
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), imgId);
bitmap = Bitmap.createScaledBitmap(bitmap, 64, 64, true);
ImageSpan imageSpan = new ImageSpan(context, bitmap);
SpannableString spannableString = new SpannableString(text + " ");
spannableString.setSpan(imageSpan, 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return spannableString;
}
/**
* 获取包含表情的文本,表情采用自定义大小
*
* @param text
* @return
*/
public SpannableString getEmoticonStr(CharSequence text) {
if (TextUtils.isEmpty(text)) {
return new SpannableString("");
}
String[] regexes = new String[]{REGEX_CONTAIN_EMOTION};
SpannableString spannableString = new SpannableString(text);
for (String regex : regexes) {
dealEmoticon(spannableString, regex);
}
return spannableString;
}
private void dealEmoticon(SpannableString spannableString, String regex) {
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(spannableString);
while (matcher.find()) {
String key = matcher.group();
Emoticon emoticon = mEmoticons.get(key);
if (emoticon == null) {
continue;
}
@SuppressWarnings("deprecation")
ImageSpan imageSpan = new ImageSpan(emoticon.getBitmap());
int end = matcher.start() + key.length();
spannableString.setSpan(imageSpan, matcher.start(), end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private static class InstanceContainer {
private final static EmoticonManager ISNATNCE = new EmoticonManager();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonPanel.java
================================================
package com.beetle.bauhinia.toolbar.emoticon;
import android.content.Context;
import android.content.res.TypedArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.viewpager.widget.ViewPager;
import androidx.recyclerview.widget.GridLayoutManager;
import android.text.SpannableString;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.beetle.imlib.R;
import com.beetle.bauhinia.tools.DisplayUtils;
import com.beetle.bauhinia.toolbar.EaseExpandRecylerView;
import java.util.ArrayList;
import java.util.List;
/**
* Desc.
*
* @author chenxj(陈贤靖)
* @date 2019/3/11
*/
public class EmoticonPanel extends FrameLayout {
private final static int DEFAULT_COLUMNS = 8;
/**
* 指示器圆点半径,选中和未选中,单位:dp
*/
private final static int HALF_SELECTED_DOT_DISTANCE = 4;
private final static int HALF_NORMAL_DOT_DISTANCE = 3;
private Context mContext;
private EmoticonManager mEmoticonManager;
private List> mEmoticonPageList;
private ViewPager mVpEmoticon;
private LinearLayout mLlIndicator;
private ArrayList mEmoticonViewList;
private ArrayList mEmoticonAdapterList;
private ArrayList mIndicatorViewList;
/**
* 列数
*/
private int mColumns;
/**
* 当前所在页数
*/
private int mCurPage;
private OnItemEmoticonClickListener mOnItemEmoticonClickListener;
public EmoticonPanel(@NonNull Context context) {
this(context, null);
}
public EmoticonPanel(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs, 0);
init(context, attrs);
}
public void init(Context context, @Nullable AttributeSet attrs) {
initData(context, attrs);
initView(context);
initViewPager();
//指示器的初始化放在ViewPager初始化之后,因为要根据ViewPager子View数量确定指示器view个数
initIndicator();
}
private void initData(Context context, @Nullable AttributeSet attrs) {
mContext = context;
mEmoticonManager = EmoticonManager.getInstance();
mEmoticonPageList = mEmoticonManager.getEmoticonPageList();
mEmoticonViewList = new ArrayList<>();
mEmoticonAdapterList = new ArrayList();
mIndicatorViewList = new ArrayList<>();
if (attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.EmoticonPanel);
mColumns = typedArray.getInt(R.styleable.EmoticonPanel_emoticonColumns, DEFAULT_COLUMNS);
typedArray.recycle();
} else {
mColumns = DEFAULT_COLUMNS;
}
}
private void initView(Context context) {
LayoutInflater.from(context).inflate(R.layout.emoticon_view, this);
mVpEmoticon = (ViewPager) findViewById(R.id.vp_emoticon);
mLlIndicator = (LinearLayout) findViewById(R.id.ll_emoticon_indicator);
}
private void initViewPager() {
for (int i = 0; i < mEmoticonPageList.size(); i++) {
View view = LayoutInflater.from(mContext).inflate(R.layout.item_emoticon_page, null);
EaseExpandRecylerView recylerView = (EaseExpandRecylerView) view.findViewById(R.id.rv_emoticon);
GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, mColumns);
recylerView.setLayoutManager(gridLayoutManager);
EmoticonAdapter emoticonAdapter = new EmoticonAdapter(mContext, mEmoticonPageList.get(i));
recylerView.setAdapter(emoticonAdapter);
emoticonAdapter.setOnClickListener(new EmoticonAdapter.OnItemClickListener() {
@Override
public void onClick(int position) {
Emoticon emoticon = mEmoticonAdapterList.get(mCurPage).getItem(position);
if (emoticon == null || mOnItemEmoticonClickListener == null) {
return;
}
if (emoticon.getId() == R.drawable.emoji_item_delete) {
mOnItemEmoticonClickListener.onEmoticonDeleted();
} else {
if (!TextUtils.isEmpty(emoticon.getDesc())) {
SpannableString spannableString = mEmoticonManager.addEmoticon(
mContext, emoticon.getId(), emoticon.getDesc());
mOnItemEmoticonClickListener.onEmoticonClick(spannableString);
}
}
}
});
mEmoticonAdapterList.add(emoticonAdapter);
mEmoticonViewList.add(view);
}
mVpEmoticon.setAdapter(new ViewPagerAdapter(mEmoticonViewList));
mVpEmoticon.setCurrentItem(0);
mCurPage = 0;
mVpEmoticon.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
mCurPage = position;
drawIndicatorViews(position);
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void initIndicator() {
View view;
int distance = DisplayUtils.dp2px(mContext, HALF_NORMAL_DOT_DISTANCE);
int length = 2 * distance;
for (int i = 0; i < mEmoticonViewList.size(); i++) {
view = new View(mContext);
view.setBackgroundResource(R.drawable.bg_indicator_dot);
view.setEnabled(false);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(length, length);
layoutParams.leftMargin = distance;
layoutParams.rightMargin = distance;
mLlIndicator.addView(view, layoutParams);
mIndicatorViewList.add(view);
}
drawIndicatorViews(0);
}
private void drawIndicatorViews(int index) {
int selectedDistance = DisplayUtils.dp2px(mContext, HALF_SELECTED_DOT_DISTANCE) * 2;
int normalDistance = DisplayUtils.dp2px(mContext, HALF_NORMAL_DOT_DISTANCE) * 2;
int widthAndHeight;
for (int i = 0; i < mIndicatorViewList.size(); i++) {
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) mIndicatorViewList.get(i).getLayoutParams();
if (index == i) {
mIndicatorViewList.get(i).setEnabled(true);
widthAndHeight = selectedDistance;
} else {
widthAndHeight = normalDistance;
mIndicatorViewList.get(i).setEnabled(false);
}
layoutParams.width = widthAndHeight;
layoutParams.height = widthAndHeight;
mIndicatorViewList.get(i).setLayoutParams(layoutParams);
}
}
public void setOnItemEmoticonClickListener(OnItemEmoticonClickListener emoticonClickListener) {
mOnItemEmoticonClickListener = emoticonClickListener;
}
public interface OnItemEmoticonClickListener {
void onEmoticonClick(SpannableString spannableString);
void onEmoticonDeleted();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/EmoticonUtils.java
================================================
package com.beetle.bauhinia.toolbar.emoticon;
import android.content.Context;
import android.view.WindowManager;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Desc.
*
* @author chenxj(陈贤靖)
* @date 2019/3/11
*/
public class EmoticonUtils {
/**
* 表情配置文件名
*/
public final static String FILE_EMOTICON = "emoticon";
private static final String TAG = "EmoticonUtils";
public static String emojiResources[] = {
"1f60a", "ee_1",
"1f603", "ee_2",
"1f609", "ee_3",
"1f62e", "ee_4",
"1f60b", "ee_5",
"1f60e", "ee_6",
"1f621", "ee_7",
"1f616", "ee_8",
"1f633", "ee_9",
"1f61e", "ee_10",
"1f62d", "ee_11",
"1f610", "ee_12",
"1f607", "ee_13",
"1f62c", "ee_14",
"1f606", "ee_15",
"1f631", "ee_16",
"1f385", "ee_17",
"1f634", "ee_18",
"1f615", "ee_19",
"1f637", "ee_20",
"1f62f", "ee_21",
"1f60f", "ee_22",
"1f611", "ee_23",
"1f496", "ee_24",
"1f494", "ee_25",
"1f319", "ee_26",
"1f31f", "ee_27",
"1f31e", "ee_28",
"1f308", "ee_29",
"1f60d", "ee_30",
"1f61a", "ee_31",
"1f48b", "ee_32",
"1f339", "ee_33",
"1f342", "ee_34",
"1f44d", "ee_35",};
private static final Map emojiMap = new HashMap();
private static final Set emojiSet = new HashSet<>();
static {
for (int i = 0; i < emojiResources.length; i+=2) {
emojiMap.put(emojiResources[i], emojiResources[i+1]);
emojiSet.add(emojiResources[i]);
}
}
private static int EMOJI_CODE_TO_SYMBOL(int x) {
return ((((0x808080F0 | (x & 0x3F000) >> 4) | (x & 0xFC0) << 10) | (x & 0x1C0000) << 18) | (x & 0x3F) << 24);
}
public static String EmojiCodeToString(int x) {
int sym = EMOJI_CODE_TO_SYMBOL(x);
byte data[] = {(byte)(sym&0x00ff), (byte)(sym>>8&0x00ff), (byte)(sym>>16&0x00ff), (byte)(sym>>24)};
try {
return new String(data, 0, 4, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public static List readFile(Context context, String fileName) {
List lineList = new ArrayList<>();
try {
InputStream inputStream = context.getResources().getAssets().open(fileName);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
String str = "";
while ((str = reader.readLine()) != null) {
lineList.add(str);
}
} catch (IOException e) {
e.printStackTrace();
}
return lineList;
}
public static Map getEmojiMap() {
return emojiMap;
}
public static Set getEmojiEncodeSet() {
return emojiSet;
}
private static int getEmoticonSize(Context context) {
if (context == null) {
return 0;
}
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (wm != null) {
return wm.getDefaultDisplay().getHeight() * 200 / 1920;
}
return 0;
}
public static int getNormalSize(Context context) {
return getEmoticonSize(context);
}
public static int getSmallSize(Context context) {
return getEmoticonSize(context) / 4 * 3;
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/toolbar/emoticon/ViewPagerAdapter.java
================================================
package com.beetle.bauhinia.toolbar.emoticon;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.PagerAdapter;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
/**
* Desc.
*
* @author chenxj(陈贤靖)
* @date 2019/3/11
*/
public class ViewPagerAdapter extends PagerAdapter {
private List mPageViewList;
public ViewPagerAdapter(List pageViewList) {
this.mPageViewList = pageViewList;
}
@Override
public int getItemPosition(@NonNull Object object) {
return super.getItemPosition(object);
}
@Override
public int getCount() {
return mPageViewList.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view == object;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView(mPageViewList.get(position));
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
container.addView(mPageViewList.get(position));
return mPageViewList.get(position);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/tools/DisplayUtils.java
================================================
package com.beetle.bauhinia.tools;
import android.content.Context;
import android.util.TypedValue;
/**
* Created by hillwind
*/
public class DisplayUtils {
public static int getSizeByGivenAbsSize(Context context, int givenAbsSize) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, givenAbsSize, context.getResources().getDisplayMetrics());
}
public static int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
public static int getScreenHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels;
}
public static float getScreenDensity(Context context) {
return context.getResources().getDisplayMetrics().density;
}
public static int getScreenDensityDpi(Context context) {
return context.getResources().getDisplayMetrics().densityDpi;
}
public static int dp2px(Context context, float dp) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dp * scale + 0.5f);
}
public static int px2dp(Context context, float px) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (px / scale + 0.5f);
}
public static int px2sp(Context context, float pxValue) {
return (int) (pxValue / context.getResources().getDisplayMetrics().scaledDensity + 0.5f);
}
public static int sp2px(Context context, float spValue) {
return (int) (spValue * context.getResources().getDisplayMetrics().scaledDensity + 0.5f);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageAudioView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.beetle.bauhinia.db.message.Audio;
import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import java.beans.PropertyChangeEvent;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.imlib.R;
public class MessageAudioView extends MessageContentView {
public MessageAudioView(Context context) {
super(context);
inflater.inflate(R.layout.chat_content_audio, this);
}
class AudioHolder {
ImageView control;
TextView duration;
ImageView listen;
AudioHolder(View view) {
control = (ImageView)view.findViewById(R.id.play_control);
duration = (TextView)view.findViewById(R.id.duration);
listen = (ImageView)view.findViewById(R.id.listen);
}
}
@Override
public void setMessage(IMessage msg) {
super.setMessage(msg);
boolean playing = message.getPlaying();
View convertView = this;
final Audio audio = (Audio) msg.content;
AudioHolder audioHolder = new AudioHolder(convertView);
if (playing) {
AnimationDrawable voiceAnimation;
if (!msg.isOutgoing) {
audioHolder.control.setImageResource(R.drawable.voice_from_icon);
} else {
audioHolder.control.setImageResource(R.drawable.voice_to_icon);
}
voiceAnimation = (AnimationDrawable) audioHolder.control.getDrawable();
voiceAnimation.start();
} else {
if (!msg.isOutgoing) {
audioHolder.control.setImageResource(R.drawable.ease_chatfrom_voice_playing);
} else {
audioHolder.control.setImageResource(R.drawable.ease_chatto_voice_playing);
}
}
Period period = new Period().withSeconds((int) audio.duration);
PeriodFormatter periodFormatter = new PeriodFormatterBuilder()
.appendMinutes()
.appendSeparator(":")
.appendSeconds()
.appendSuffix("\"")
.toFormatter();
audioHolder.duration.setText(periodFormatter.print(period));
if (!msg.isListened() && !msg.isOutgoing) {
audioHolder.listen.setVisibility(VISIBLE);
} else {
audioHolder.listen.setVisibility(GONE);
}
convertView.requestLayout();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getPropertyName().equals("playing")) {
Log.i("gobelieve", "playing changed");
boolean playing = this.message.getPlaying();
AudioHolder audioHolder = new AudioHolder(this);
if (playing) {
AnimationDrawable voiceAnimation;
if (!this.message.isOutgoing) {
audioHolder.control.setImageResource(R.drawable.voice_from_icon);
} else {
audioHolder.control.setImageResource(R.drawable.voice_to_icon);
}
voiceAnimation = (AnimationDrawable) audioHolder.control.getDrawable();
voiceAnimation.start();
Log.i("gobelieve", "start animation");
} else {
if (!this.message.isOutgoing) {
audioHolder.control.setImageResource(R.drawable.ease_chatfrom_voice_playing);
} else {
audioHolder.control.setImageResource(R.drawable.ease_chatto_voice_playing);
}
}
} else if (event.getPropertyName().equals("listened")) {
AudioHolder audioHolder = new AudioHolder(this);
if (!this.message.isListened() && !this.message.isOutgoing) {
audioHolder.listen.setVisibility(VISIBLE);
} else {
audioHolder.listen.setVisibility(GONE);
}
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageClassroomView.java
================================================
package com.beetle.bauhinia.view;
import android.content.Context;
import android.widget.ImageView;
import android.widget.TextView;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.MessageContent;
import com.beetle.imlib.R;
//support conference&classroom message
public class MessageClassroomView extends MessageContentView {
public MessageClassroomView(Context context) {
super(context);
inflater.inflate(R.layout.chat_content_voip, this);
}
@Override
public void setMessage(IMessage msg) {
super.setMessage(msg);
ImageView v = findViewById(R.id.phone);
if (msg.getType() == MessageContent.MessageType.MESSAGE_CLASSROOM) {
v.setImageResource(R.drawable.classroom);
String text = "发起了群课堂";
TextView content = (TextView) findViewById(R.id.text);
content.setText(text);
} else {
v.setImageResource(R.drawable.conference);
String text = "发起了视频会议";
TextView content = (TextView) findViewById(R.id.text);
content.setText(text);
}
requestLayout();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageContentView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import com.beetle.bauhinia.db.IMessage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class MessageContentView extends FrameLayout implements PropertyChangeListener {
protected Context context;
LayoutInflater inflater;
protected IMessage message;
public MessageContentView(Context context) {
super(context);
this.context = context;
inflater = LayoutInflater.from(context);
}
public void setMessage(IMessage msg) {
if (this.message != null) {
this.message.removePropertyChangeListener(this);
}
this.message = msg;
this.message.addPropertyChangeListener(this);
}
public IMessage getMessage() {
return message;
}
@Override
public void propertyChange(PropertyChangeEvent event) {
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageFileView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.beetle.bauhinia.db.message.File;
import java.beans.PropertyChangeEvent;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.imlib.R;
public class MessageFileView extends MessageContentView {
protected ProgressBar uploadingProgressBar;
protected View maskView;
public MessageFileView(Context context) {
super(context);
int contentLayout = R.layout.chat_content_file;
inflater.inflate(contentLayout, this);
uploadingProgressBar = (ProgressBar)findViewById(R.id.progress_bar);
maskView = findViewById(R.id.mask);
}
public void setMessage(IMessage msg) {
super.setMessage(msg);
File fileMsg = (File) msg.content;
String filename = fileMsg.filename;
ImageView imageView = (ImageView)findViewById(R.id.image);
if (filename.endsWith(".doc") || filename.endsWith("docx")) {
imageView.setImageResource(R.drawable.word);
} else if (filename.endsWith(".xls") || filename.endsWith(".xlsx")) {
imageView.setImageResource(R.drawable.excel);
} else if (filename.endsWith(".pdf")) {
imageView.setImageResource(R.drawable.pdf);
} else {
imageView.setImageResource(R.drawable.file);
}
TextView titleView = (TextView)findViewById(R.id.title);
titleView.setText(fileMsg.filename);
TextView contentView = (TextView)findViewById(R.id.descreption);
contentView.setText(formatSize(fileMsg.size));
boolean uploading = msg.getUploading();
boolean downloading = msg.getDownloading();
if (uploading || downloading) {
maskView.setVisibility(View.VISIBLE);
uploadingProgressBar.setVisibility(View.VISIBLE);
} else {
maskView.setVisibility(View.GONE);
uploadingProgressBar.setVisibility(View.GONE);
}
requestLayout();
}
private String formatSize(int size) {
if (size < 1024) {
return String.format("%d字节", size);
} else if (size < 1024*1024){
return String.format("%.1fKB", size*1.0/1024);
} else {
return String.format("%.1fMB", size*1.0/(1024*1024));
}
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getPropertyName().equals("uploading") ||
event.getPropertyName().equals("downloading")) {
boolean uploading = this.message.getUploading();
boolean downloading = this.message.getDownloading();
if (uploading || downloading) {
maskView.setVisibility(View.VISIBLE);
uploadingProgressBar.setVisibility(View.VISIBLE);
} else {
maskView.setVisibility(View.GONE);
uploadingProgressBar.setVisibility(View.GONE);
}
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageImageView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.Image;
import com.beetle.imlib.R;
import com.squareup.picasso.Picasso;
import java.beans.PropertyChangeEvent;
public class MessageImageView extends MessageContentView {
protected ProgressBar uploadingProgressBar;
protected View maskView;
public MessageImageView(Context context) {
super(context);
int contentLayout = R.layout.chat_content_image;
inflater.inflate(contentLayout, this);
uploadingProgressBar = (ProgressBar)findViewById(R.id.progress_bar);
maskView = findViewById(R.id.mask);
}
public void setMessage(IMessage msg) {
super.setMessage(msg);
ImageView imageView = (ImageView)findViewById(R.id.image);
String url = ((Image) msg.content).url;
if (msg.secret) {
if (!url.startsWith("file:")) {
url = "";
}
Picasso.get()
.load(url)
.placeholder(R.drawable.image_download_fail)
.into(imageView);
} else {
Picasso.get()
.load(url + "@256w_256h_0c")
.placeholder(R.drawable.image_download_fail)
.into(imageView);
}
boolean uploading = msg.getUploading();
boolean downloading = msg.getDownloading();
if (uploading || downloading) {
if (maskView != null) {
maskView.setVisibility(View.VISIBLE);
}
uploadingProgressBar.setVisibility(View.VISIBLE);
} else {
if (maskView != null) {
maskView.setVisibility(View.GONE);
}
uploadingProgressBar.setVisibility(View.GONE);
}
requestLayout();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getPropertyName().equals("uploading") ||
event.getPropertyName().equals("downloading")) {
boolean uploading = this.message.getUploading();
boolean downloading = this.message.getDownloading();
if (uploading || downloading) {
if (maskView != null) {
maskView.setVisibility(View.VISIBLE);
}
uploadingProgressBar.setVisibility(View.VISIBLE);
} else {
if (maskView != null) {
maskView.setVisibility(View.GONE);
}
uploadingProgressBar.setVisibility(View.GONE);
}
}
ImageView imageView = (ImageView)findViewById(R.id.image);
if (event.getPropertyName().equals("downloading")) {
if (message.secret) {
String url = ((Image) message.content).url;
if (!url.startsWith("file:")) {
url = "";
}
Picasso.get()
.load(url)
.placeholder(R.drawable.image_download_fail)
.into(imageView);
}
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageLinkView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.widget.ImageView;
import android.widget.TextView;
import com.beetle.bauhinia.db.message.Link;
import com.squareup.picasso.Picasso;
import java.beans.PropertyChangeEvent;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.imlib.R;
public class MessageLinkView extends MessageContentView {
public MessageLinkView(Context context) {
super(context);
int contentLayout = R.layout.chat_content_link;
inflater.inflate(contentLayout, this);
}
public void setMessage(IMessage msg) {
super.setMessage(msg);
Link linkMsg = (Link) msg.content;
ImageView imageView = (ImageView)findViewById(R.id.image);
Picasso.get()
.load(linkMsg.image)
.placeholder(R.drawable.image_download_fail)
.into(imageView);
TextView titleView = (TextView)findViewById(R.id.title);
titleView.setText(linkMsg.title);
TextView contentView = (TextView)findViewById(R.id.descreption);
contentView.setText(linkMsg.content);
requestLayout();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageLocationView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.Location;
import com.beetle.imlib.R;
import java.beans.PropertyChangeEvent;
public class MessageLocationView extends MessageContentView {
protected ProgressBar progressBar;
public MessageLocationView(Context context) {
super(context);
int contentLayout = R.layout.chat_content_location;
inflater.inflate(contentLayout, this);
progressBar = (ProgressBar)findViewById(R.id.progress_bar);
}
public void setMessage(IMessage msg) {
super.setMessage(msg);
Location loc = (Location)this.message.content;
if (loc.address != null && loc.address.length() > 0) {
TextView content = (TextView) findViewById(R.id.text);
content.setText(loc.address);
}
boolean geocoding = this.message.getGeocoding();
if (geocoding) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
requestLayout();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getPropertyName().equals("geocoding")) {
boolean geocoding = this.message.getGeocoding();
if (geocoding) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
Location loc = (Location)this.message.content;
if (loc.address != null && loc.address.length() > 0) {
TextView content = (TextView) findViewById(R.id.text);
content.setText(loc.address);
}
}
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageNotificationView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.widget.TextView;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.Notification;
import com.beetle.imlib.R;
import java.beans.PropertyChangeEvent;
public class MessageNotificationView extends MessageContentView {
public MessageNotificationView(Context context) {
super(context);
final int contentLayout;
contentLayout = R.layout.chat_content_small_text;
inflater.inflate(contentLayout, this);
}
@Override
public void setMessage(IMessage msg) {
super.setMessage(msg);
TextView content = (TextView) findViewById(R.id.text);
String text = ((Notification) msg.content).getDescription();
content.setText(text);
requestLayout();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getPropertyName().equals("downloading")) {
TextView content = (TextView) findViewById(R.id.text);
String text = ((Notification) this.message.content).getDescription();
content.setText(text);
}
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageTextView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.widget.TextView;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.MessageContent;
import com.beetle.bauhinia.db.message.Text;
import com.beetle.imlib.R;
public class MessageTextView extends MessageContentView {
private TextView textView;
public MessageTextView(Context context) {
super(context);
inflater.inflate(R.layout.chat_content_text, this);
textView = (TextView)findViewById(R.id.text);
}
@Override
public void setMessage(IMessage msg) {
super.setMessage(msg);
this.textView.setTag(message);
MessageContent.MessageType mediaType = message.content.getType();
if (mediaType == MessageContent.MessageType.MESSAGE_TEXT) {
TextView content = (TextView) findViewById(R.id.text);
Text text = (Text)msg.content;
if (text.spanText != null) {
content.setText(text.spanText);
} else {
content.setText(text.text);
}
}
requestLayout();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageUnknownView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.widget.TextView;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.MessageContent;
import com.beetle.imlib.R;
public class MessageUnknownView extends MessageContentView {
public MessageUnknownView(Context context) {
super(context);
inflater.inflate(R.layout.chat_content_text, this);
}
@Override
public void setMessage(IMessage msg) {
super.setMessage(msg);
TextView content = (TextView) findViewById(R.id.text);
if (msg.getType() == MessageContent.MessageType.MESSAGE_SECRET) {
content.setText("消息未能解密");
} else {
content.setText("未知的消息类型");
}
requestLayout();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageVOIPView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.MessageContent;
import com.beetle.bauhinia.db.message.VOIP;
import com.beetle.imlib.R;
/**
* Created by houxh on 2017/11/14.
*/
public class MessageVOIPView extends MessageContentView {
public MessageVOIPView(Context context) {
super(context);
inflater.inflate(R.layout.chat_content_voip, this);
}
@Override
public void setMessage(IMessage msg) {
super.setMessage(msg);
VOIP voip = (VOIP)msg.content;
int m = voip.duration/60;
int s = voip.duration%60;
String t = String.format("%02d:%02d", m, s);
String text = "未知状态";
if (msg.isOutgoing) {
switch (voip.flag) {
case VOIP.VOIP_FLAG_ACCEPTED:
text = t;
break;
case VOIP.VOIP_FLAG_REFUSED:
text = "对方已拒绝";
break;
case VOIP.VOIP_FLAG_CANCELED:
text = "已取消";
break;
case VOIP.VOIP_FLAG_UNRECEIVED:
text = "对方未接听";
break;
}
} else {
switch (voip.flag) {
case VOIP.VOIP_FLAG_ACCEPTED:
text = t;
break;
case VOIP.VOIP_FLAG_REFUSED:
text = "已拒绝";
break;
case VOIP.VOIP_FLAG_CANCELED:
text = "对方已取消";
break;
case VOIP.VOIP_FLAG_UNRECEIVED:
text = "未接听";
break;
}
}
if (msg.isOutgoing) {
View v = findViewById(R.id.phone);
ViewGroup parent = (ViewGroup)findViewById(R.id.voip);
parent.removeView(v);
parent.addView(v);
}
MessageContent.MessageType mediaType = message.getType();
TextView content = (TextView) findViewById(R.id.text);
content.setText(text);
requestLayout();
}
}
================================================
FILE: imlib/src/main/java/com/beetle/bauhinia/view/MessageVideoView.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.bauhinia.view;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.beetle.bauhinia.db.IMessage;
import com.beetle.bauhinia.db.message.Video;
import com.beetle.imlib.R;
import com.squareup.picasso.Picasso;
import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import java.beans.PropertyChangeEvent;
public class MessageVideoView extends MessageContentView {
protected ProgressBar uploadingProgressBar;
protected View maskView;
protected TextView durationView;
protected View playView;
public MessageVideoView(Context context) {
super(context);
int contentLayout = R.layout.chat_content_video;
inflater.inflate(contentLayout, this);
uploadingProgressBar = (ProgressBar)findViewById(R.id.progress_bar);
maskView = findViewById(R.id.mask);
durationView = (TextView)findViewById(R.id.duration);
playView = findViewById(R.id.play);
}
public void setMessage(IMessage msg) {
super.setMessage(msg);
ImageView imageView = (ImageView)findViewById(R.id.image);
Video video = (Video)msg.content;
String url = video.thumbnail;
if (msg.secret) {
if (!url.startsWith("file:")) {
url = "";
}
Picasso.get()
.load(url)
.placeholder(R.drawable.image_download_fail)
.into(imageView);
} else {
Picasso.get()
.load(url)
.placeholder(R.drawable.image_download_fail)
.into(imageView);
}
Period period = new Period().withSeconds(video.duration);
PeriodFormatter periodFormatter = new PeriodFormatterBuilder()
.appendMinutes()
.appendSeparator(":")
.appendSeconds()
.appendSuffix("\"")
.toFormatter();
durationView.setText(periodFormatter.print(period));
boolean uploading = msg.getUploading();
boolean downloading = msg.getDownloading();
if (uploading || downloading) {
if (maskView != null) {
maskView.setVisibility(View.VISIBLE);
}
uploadingProgressBar.setVisibility(View.VISIBLE);
playView.setVisibility(View.GONE);
} else {
if (maskView != null) {
maskView.setVisibility(View.GONE);
}
uploadingProgressBar.setVisibility(View.GONE);
playView.setVisibility(View.VISIBLE);
}
requestLayout();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
if (event.getPropertyName().equals("uploading") ||
event.getPropertyName().equals("downloading")) {
boolean uploading = this.message.getUploading();
boolean downloading = this.message.getDownloading();
if (uploading || downloading) {
if (maskView != null) {
maskView.setVisibility(View.VISIBLE);
}
uploadingProgressBar.setVisibility(View.VISIBLE);
playView.setVisibility(View.GONE);
} else {
if (maskView != null) {
maskView.setVisibility(View.GONE);
}
uploadingProgressBar.setVisibility(View.GONE);
playView.setVisibility(View.VISIBLE);
}
}
ImageView imageView = (ImageView)findViewById(R.id.image);
if (event.getPropertyName().equals("downloading")) {
if (message.secret) {
String url = ((Video) message.content).thumbnail;
if (!url.startsWith("file:")) {
url = "";
}
Picasso.get()
.load(url)
.placeholder(R.drawable.image_download_fail)
.into(imageView);
}
}
}
}
================================================
FILE: imlib/src/main/res/drawable/bg_indicator_dot.xml
================================================
================================================
FILE: imlib/src/main/res/drawable/bg_indicator_dot_disable.xml
================================================
================================================
FILE: imlib/src/main/res/drawable/bg_indicator_dot_enable.xml
================================================
================================================
FILE: imlib/src/main/res/drawable/chatfrom_bg.xml
================================================
================================================
FILE: imlib/src/main/res/drawable/chatto_bg.xml
================================================
================================================
FILE: imlib/src/main/res/drawable/circle_audio.xml
================================================
================================================
FILE: imlib/src/main/res/drawable/gallery_watch_more_picture_background.xml
================================================
-
-
================================================
FILE: imlib/src/main/res/drawable/rounded_corner.xml
================================================
================================================
FILE: imlib/src/main/res/drawable-mdpi/voice_from_icon.xml
================================================
================================================
FILE: imlib/src/main/res/drawable-mdpi/voice_to_icon.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_audio.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_file.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_image.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_link.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_location.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_small_text.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_text.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_video.xml
================================================
================================================
FILE: imlib/src/main/res/layout/chat_content_voip.xml
================================================
================================================
FILE: imlib/src/main/res/layout/ease_chat_menu_item.xml
================================================
================================================
FILE: imlib/src/main/res/layout/ease_row_expression.xml
================================================
================================================
FILE: imlib/src/main/res/layout/ease_widget_chat_input_menu.xml
================================================
================================================
FILE: imlib/src/main/res/layout/ease_widget_chat_primary_menu.xml
================================================
================================================
FILE: imlib/src/main/res/layout/ease_widget_emojicon.xml
================================================
================================================
FILE: imlib/src/main/res/layout/emoticon_view.xml
================================================
================================================
FILE: imlib/src/main/res/layout/gallery_activity_gallery.xml
================================================
================================================
FILE: imlib/src/main/res/layout/gallery_activity_gallery_grid.xml
================================================
================================================
FILE: imlib/src/main/res/layout/gallery_activity_gallery_grid_item.xml
================================================
================================================
FILE: imlib/src/main/res/layout/gallery_select_dialog_item.xml
================================================
================================================
FILE: imlib/src/main/res/layout/item_emoticon.xml
================================================
================================================
FILE: imlib/src/main/res/layout/item_emoticon_page.xml
================================================
================================================
FILE: imlib/src/main/res/values/attrs.xml
================================================
================================================
FILE: imlib/src/main/res/values/colors.xml
================================================
#000000
#FF0000
#888888
#FFFFFF
#757575
#BDBDBD
================================================
FILE: imlib/src/main/res/values/strings.xml
================================================
Image saved to
Image save failed!
Save to phone
Chat Files
desc_emoticon_delete
name_emoticon_delete
================================================
FILE: imlib/src/main/res/values-zh/strings.xml
================================================
图片已保存至
保存至手机
图片保存失败
聊天文件
desc_emoticon_delete
name_emoticon_delete
================================================
FILE: imsdk/build.gradle
================================================
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':asynctcp')
}
================================================
FILE: imsdk/src/androidTest/java/com/beetle/im/ApplicationTest.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: imsdk/src/main/AndroidManifest.xml
================================================
================================================
FILE: imsdk/src/main/java/com/beetle/im/BytePacket.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Created by houxh on 14-7-21.
*/
public class BytePacket {
static public void writeInt64(long v, byte[] dst, int pos) {
ByteBuffer b = ByteBuffer.allocate(8);
b.order(ByteOrder.BIG_ENDIAN);
b.putLong(v);
byte[] t = b.array();
System.arraycopy(t, 0, dst, pos, t.length);
}
static public void writeInt32(int v, byte[] dst, int pos) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.BIG_ENDIAN);
b.putInt(v);
byte[] t = b.array();
System.arraycopy(t, 0, dst, pos, t.length);
}
static public void writeInt16(short v, byte[] dst, int pos) {
ByteBuffer b = ByteBuffer.allocate(2);
b.order(ByteOrder.BIG_ENDIAN);
b.putShort(v);
byte[] t = b.array();
System.arraycopy(t, 0, dst, pos, t.length);
}
static public long readInt64(byte[] bytes, int pos) {
ByteBuffer b = ByteBuffer.wrap(bytes, pos, 8);
b.order(ByteOrder.BIG_ENDIAN);
return b.getLong();
}
static public int readInt32(byte[] bytes, int pos) {
ByteBuffer b = ByteBuffer.wrap(bytes, pos, 4);
b.order(ByteOrder.BIG_ENDIAN);
return b.getInt();
}
static public short readInt16(byte[] bytes, int pos) {
ByteBuffer b = ByteBuffer.wrap(bytes, pos, 2);
b.order(ByteOrder.BIG_ENDIAN);
return b.getShort();
}
static public int packInetAddress(byte[] bytes) {
ByteBuffer b2 = ByteBuffer.wrap(bytes, 0, 4);
b2.order(ByteOrder.BIG_ENDIAN);
return b2.getInt();
}
static public byte[] unpackInetAddress(int iaddr) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.BIG_ENDIAN);
b.putInt(iaddr);
byte[] t = b.array();
return t;
}
//little->net
static public int ltonl(int v) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.LITTLE_ENDIAN);
b.putInt(v);
byte[] t = b.array();
ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);
b2.order(ByteOrder.BIG_ENDIAN);
return b2.getInt();
}
//net->little
static public int ntoll(int v) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.BIG_ENDIAN);
b.putInt(v);
byte[] t = b.array();
ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);
b2.order(ByteOrder.LITTLE_ENDIAN);
return b2.getInt();
}
//little -> native
static public int ltohl(int v) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.LITTLE_ENDIAN);
b.putInt(v);
byte[] t = b.array();
ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);
b2.order(ByteOrder.nativeOrder());
return b2.getInt();
}
//native -> little
static public int htoll(int v) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.nativeOrder());
b.putInt(v);
byte[] t = b.array();
ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);
b2.order(ByteOrder.LITTLE_ENDIAN);
return b2.getInt();
}
static public int ntohl(int v) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.nativeOrder());
b.putInt(v);
byte[] t = b.array();
ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);
b2.order(ByteOrder.BIG_ENDIAN);
return b2.getInt();
}
static public int htonl(int v) {
ByteBuffer b = ByteBuffer.allocate(4);
b.order(ByteOrder.BIG_ENDIAN);
b.putInt(v);
byte[] t = b.array();
ByteBuffer b2 = ByteBuffer.wrap(t, 0, 4);
b2.order(ByteOrder.nativeOrder());
return b2.getInt();
}
}
================================================
FILE: imsdk/src/main/java/com/beetle/im/CustomerMessage.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
/**
* Created by houxh on 16/1/19.
*/
public class CustomerMessage extends IMMessage {
public long senderAppID;
public long receiverAppID;
}
================================================
FILE: imsdk/src/main/java/com/beetle/im/CustomerMessageHandler.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
/**
* Created by houxh on 16/1/17.
*/
public interface CustomerMessageHandler {
public boolean handleMessage(CustomerMessage msg);
public boolean handleMessageACK(CustomerMessage msg);
public boolean handleMessageFailure(CustomerMessage msg);
}
================================================
FILE: imsdk/src/main/java/com/beetle/im/CustomerMessageObserver.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
/**
* Created by houxh on 16/1/18.
*/
public interface CustomerMessageObserver {
public void onCustomerMessage(CustomerMessage msg);
public void onCustomerMessageACK(CustomerMessage msg);
public void onCustomerMessageFailure(CustomerMessage msg);
}
================================================
FILE: imsdk/src/main/java/com/beetle/im/GroupMessageHandler.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
import java.util.List;
/**
* Created by houxh on 15/3/21.
*/
public interface GroupMessageHandler {
public boolean handleMessages(List msgs);
public boolean handleMessageACK(IMMessage msg, int error);
public boolean handleMessageFailure(IMMessage msg);
}
================================================
FILE: imsdk/src/main/java/com/beetle/im/GroupMessageObserver.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
import java.util.List;
/**
* Created by houxh on 14-7-23.
*/
public interface GroupMessageObserver {
public void onGroupMessages(List msg);
public void onGroupMessageACK(IMMessage msg, int error);
public void onGroupMessageFailure(IMMessage msg);
}
================================================
FILE: imsdk/src/main/java/com/beetle/im/IMMessage.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
/**
* Created by houxh on 14-7-23.
*/
public class IMMessage {
public long msgLocalID;
public boolean secret;//点对点加密消息
public String plainContent;
public long sender;
public long receiver;
public int timestamp;
public String content;
////避免在observer&handler中重复构造content对象
public Object contentObj;
//群组已读消息,通过点对点消息来发送
public long groupID;
//会话未读数减一
public boolean decrementUnread;
//是否由当前用户在当前设备所发出
public boolean isSelf;
//群组通知消息
public boolean isGroupNotification;
}
================================================
FILE: imsdk/src/main/java/com/beetle/im/IMService.java
================================================
/*
Copyright (c) 2014-2019, GoBelieve
All rights reserved.
This source code is licensed under the BSD-style license found in the
LICENSE file in the root directory of this source tree. An additional grant
of patent rights can be found in the PATENTS file in the same directory.
*/
package com.beetle.im;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import com.beetle.AsyncSSLTCP;
import com.beetle.AsyncTCP;
import com.beetle.AsyncTCPInterface;
import com.beetle.TCPConnectCallback;
import com.beetle.TCPReadCallback;
import android.util.Log;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static android.os.SystemClock.uptimeMillis;
/**
* Created by houxh on 14-7-21.
*/
public class IMService {
private static final boolean ENABLE_SSL = false;
private static final String HOST = "imnode2.gobelieve.io";
private static int PORT;
{
if (ENABLE_SSL) {
PORT = 24430;
} else {
PORT = 23000;
}
}
private static final String TAG = "imservice";
private static final int HEARTBEAT = 60*3;
public static final String HEARTBEAT_ACTION = "io.gobelieve.HEARTBEAT";
private static final int CONNECT_TIMEOUT = 60;
public enum ConnectState {
STATE_UNCONNECTED,
STATE_CONNECTING,
STATE_CONNECTED,
STATE_CONNECTFAIL,
}
private AsyncTCPInterface tcp;
private boolean stopped = true;
private boolean suspended = true;
private boolean reachable = true;
private boolean isBackground = false;
private Timer testTimer;
private Timer connectTimer;
private Timer heartbeatTimer;
private int pingTimestamp;
private int connectTimestamp;//发起socket连接的时间戳
private int connectFailCount = 0;
private int seq = 0;
private ConnectState connectState = ConnectState.STATE_UNCONNECTED;
private String hostIP;
private int dnsTimestamp;
//set before call start
private String host;
private int port;
private String token;
private String deviceID;
private int connectTimeout;
private Looper looper;
private Handler handler;
private Handler mainThreadHandler;//调用observer
private boolean keepAlive;//应用在后台,保持socket连接
private PendingIntent alarmIntent;
private PowerManager.WakeLock wakeLock;
private long roomID;
//确保一个时刻只有一个同步过程在运行,以免收到重复的消息
private long syncKey;
//在同步过程中收到新的syncnotify消息
private long pendingSyncKey;
private boolean isSyncing;
private int syncTimestamp;
private static class GroupSync {
public long groupID;
public long syncKey;
//在同步过程中收到新的syncnotify消息
private long pendingSyncKey;
private boolean isSyncing;
private int syncTimestamp;
}
private HashMap groupSyncKeys = new HashMap();
SyncKeyHandler syncKeyHandler;
PeerMessageHandler peerMessageHandler;
GroupMessageHandler groupMessageHandler;
CustomerMessageHandler customerMessageHandler;
ArrayList observers = new ArrayList();
ArrayList groupObservers = new ArrayList();
ArrayList peerObservers = new ArrayList();
ArrayList systemMessageObservers = new ArrayList();
ArrayList customerServiceMessageObservers = new ArrayList();
ArrayList rtMessageObservers = new ArrayList();
ArrayList roomMessageObservers = new ArrayList();
ArrayList messages = new ArrayList();//已发出,等待ack的消息
ArrayList receivedGroupMessages = new ArrayList();
Message metaMessage;
private byte[] data;
private static IMService im = new IMService();
public static IMService getInstance() {
return im;
}
public IMService() {
this.host = HOST;
this.port = PORT;
this.connectTimeout = CONNECT_TIMEOUT;
this.setLooper(Looper.myLooper());
this.mainThreadHandler = new Handler(Looper.getMainLooper());
}
public ConnectState getConnectState() {
return connectState;
}
public void setLooper(Looper looper) {
this.looper = looper;
this.handler = new Handler(looper);
}
public Looper getLooper() {
return looper;
}
private void createTimer() {
if (connectTimer != null && heartbeatTimer != null) {
return;
}
connectTimer = new Timer(looper) {
@Override
protected void fire() {
IMService.this.connect();
}
};
heartbeatTimer = new Timer(looper) {
@Override
protected void fire() {
IMService.this.heartbeat();
}
};
testTimer = new Timer(looper) {
@Override
protected void fire() {
Log.i(TAG, "test timer...");
}
};
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public void setHost(String host) {
this.host = host;
this.hostIP = "";
}
public void setToken(String token) {
this.token = token;
}
public void setDeviceID(String deviceID) {
this.deviceID = deviceID;
}
public void setKeepAlive(boolean keepAlive) {
this.keepAlive = keepAlive;
}
public void setWakeLock(PowerManager.WakeLock wl) {
this.wakeLock = wl;
}
public void setSyncKey(long syncKey) {
this.syncKey = syncKey;
}
public void addSuperGroupSyncKey(long groupID, long syncKey) {
GroupSync s = new GroupSync();
s.groupID = groupID;
s.syncKey = syncKey;
this.groupSyncKeys.put(groupID, s);
this.sendGroupSync(groupID, syncKey);
}
public void removeSuperGroupSyncKey(long groupID) {
this.groupSyncKeys.remove(groupID);
}
public void clearSuperGroupSyncKeys() {
this.groupSyncKeys.clear();
}
public void setSyncKeyHandler(SyncKeyHandler handler) {
this.syncKeyHandler = handler;
}
public void setPeerMessageHandler(PeerMessageHandler handler) {
this.peerMessageHandler = handler;
}
public void setGroupMessageHandler(GroupMessageHandler handler) {
this.groupMessageHandler = handler;
}
public void setCustomerMessageHandler(CustomerMessageHandler handler) {
this.customerMessageHandler = handler;
}
//call on main thread
public void addObserver(IMServiceObserver ob) {
if (observers.contains(ob)) {
return;
}
observers.add(ob);
}
public void removeObserver(IMServiceObserver ob) {
observers.remove(ob);
}
public void addPeerObserver(PeerMessageObserver ob) {
if (peerObservers.contains(ob)) {
return;
}
peerObservers.add(ob);
}
public void removePeerObserver(PeerMessageObserver ob) {
peerObservers.remove(ob);
}
public void addGroupObserver(GroupMessageObserver ob) {
if (groupObservers.contains(ob)) {
return;
}
groupObservers.add(ob);
}
public void removeGroupObserver(GroupMessageObserver ob) {
groupObservers.remove(ob);
}
public void addSystemObserver(SystemMessageObserver ob) {
if (systemMessageObservers.contains(ob)) {
return;
}
systemMessageObservers.add(ob);
}
public void removeSystemObserver(SystemMessageObserver ob) {
systemMessageObservers.remove(ob);
}
public void addCustomerServiceObserver(CustomerMessageObserver ob) {
if (customerServiceMessageObservers.contains(ob)) {
return;
}
customerServiceMessageObservers.add(ob);
}
public void removeCustomerServiceObserver(CustomerMessageObserver ob) {
customerServiceMessageObservers.remove(ob);
}
public void addRTObserver(RTMessageObserver ob) {
if (rtMessageObservers.contains(ob)) {
return;
}
rtMessageObservers.add(ob);
}
public void removeRTObserver(RTMessageObserver ob){
rtMessageObservers.remove(ob);
}
public void addRoomObserver(RoomMessageObserver ob) {
if (roomMessageObservers.contains(ob)) {
return;
}
roomMessageObservers.add(ob);
}
public void removeRoomObserver(RoomMessageObserver ob) {
roomMessageObservers.remove(ob);
}
public void enterBackground() {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
IMService.this._enterBackground();
}
});
}
public void _enterBackground() {
Log.i(TAG, "im service enter background");
this.isBackground = true;
if (!this.stopped) {
suspend();
if (!keepAlive) {
IMService.this.connectState = ConnectState.STATE_UNCONNECTED;
IMService.this.publishConnectState();
this.close();
}
}
}
public void enterForeground() {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
IMService.this._enterForeground();
}
});
}
public void _enterForeground() {
Log.i(TAG, "im service enter foreground");
this.isBackground = false;
if (!this.stopped) {
resume();
}
}
static boolean isNetworkConnected(NetworkCapabilities cap) {
return cap != null && cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
private static boolean isOnNet(Context context) {
if (null == context) {
Log.e("", "context is null");
return false;
}
boolean isOnNet = false;
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
NetworkCapabilities cap = connectivityManager.getNetworkCapabilities(network);
return isNetworkConnected(cap);
} else {
NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
if (null != activeNetInfo) {
isOnNet = activeNetInfo.isConnected();
Log.i(TAG, "active net info:" + activeNetInfo);
}
return isOnNet;
}
}
static class NetworkCallback extends ConnectivityManager.NetworkCallback {
private Context context;
public NetworkCallback(Context context) {
this.context = context;
}
@Override
public void onAvailable(Network network) {
Log.i(TAG, "on network available:" + network.toString());
IMService.im.handler.post(new Runnable() {
@Override
public void run() {
IMService.im.reachable = true;
if (!IMService.im.stopped && !IMService.im.isBackground) {
//todo 优化 可以判断当前连接的socket的localip和当前网络的ip是一样的情况下
//就没有必要重连socket
Log.i(TAG, "reconnect im service");
IMService.im.suspend();
IMService.im.connectState = ConnectState.STATE_UNCONNECTED;
IMService.im.publishConnectState();
IMService.im.close();
IMService.im.resume();
}
}
});
}
@Override
public void onLost(Network network) {
boolean netAvaiable = isOnNet(context);
Log.i(TAG, "on network lost:" + network.toString());
Log.i(TAG, "active network status:" + netAvaiable);
if (netAvaiable) {
IMService.im.handler.post(new Runnable() {
@Override
public void run() {
IMService.im.reachable = true;
if (!IMService.im.stopped && !IMService.im.isBackground) {
//todo 优化 可以判断当前连接的socket的localip和当前网络的ip是一样的情况下
//就没有必要重连socket
Log.i(TAG, "reconnect im service");
IMService.im.suspend();
IMService.im.connectState = ConnectState.STATE_UNCONNECTED;
IMService.im.publishConnectState();
IMService.im.close();
IMService.im.resume();
}
}
});
} else {
IMService.im.handler.post(new Runnable() {
@Override
public void run() {
IMService.im.reachable = false;
if (!IMService.im.stopped) {
IMService.im.suspend();
IMService.im.connectState = ConnectState.STATE_UNCONNECTED;
IMService.im.publishConnectState();
IMService.im.close();
}
}
});
}
}
}
static class HeartbeatReceiver extends BroadcastReceiver {
private final String TAG = "imservice";
@Override
public void onReceive (Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "broadcast receive action:" + action);
if (action.equals(IMService.HEARTBEAT_ACTION)) {
if (!IMService.im.keepAlive) {
Log.w(TAG, "not keepalive, dummy alarm heatbeat action");
return;
}
if (!IMService.im.isBackground) {
Log.w(TAG, "not in background, dummy alarm heatbeat action");
return;
}
if (IMService.im.wakeLock != null) {
IMService.im.wakeLock.acquire(1000);
}
IMService.im.heartbeat();
}
}
};
public void registerConnectivityChangeReceiver(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkCallback cb = new NetworkCallback(context);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.build();
connectivityManager.registerNetworkCallback(request, cb);
this.reachable = isOnNet(context);
Log.i(TAG, "network reachable:" + this.reachable);
HeartbeatReceiver receiver = new HeartbeatReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(HEARTBEAT_ACTION);
context.registerReceiver(receiver, filter, null, handler);
}
//设置了keepalive之后需要创建系统级的定时器
public void startAlarm(Context context) {
if (!keepAlive) {
Log.w(TAG, "keepalive false, can't start alarm");
return;
}
Log.i(TAG, "start alarm");
Context appContext = context;
final int ALARM_INTERVAL = HEARTBEAT*1000;//3 * 60 * 1000;
AlarmManager alarmMgr;
PendingIntent alarmIntent;
alarmMgr = (AlarmManager) appContext.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent();
intent.setAction(HEARTBEAT_ACTION);
intent.setPackage(context.getPackageName());
alarmIntent = PendingIntent.getBroadcast(appContext, 999, intent, 0);
Calendar calendar = Calendar.getInstance();
//从doze模式恢复后,此alarm会失效
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
ALARM_INTERVAL, alarmIntent);
this.alarmIntent = alarmIntent;
}
public void stopAlarm(Context context) {
if (alarmIntent != null) {
Log.i(TAG, "stop alarm");
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(alarmIntent);
alarmIntent = null;
}
}
public void start() {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
IMService.this._start();
}
});
}
private void _start() {
if (this.token.length() == 0) {
throw new RuntimeException("NO TOKEN PROVIDED");
}
if (!this.stopped) {
Log.i(TAG, "already started");
return;
}
Log.i(TAG, "start im service");
this.stopped = false;
createTimer();
this.resume();
testTimer.setTimer(uptimeMillis(), HEARTBEAT*1000);
testTimer.resume();
//应用在后台的情况下基本不太可能调用start
if (this.isBackground) {
Log.w(TAG, "start im service when app is background");
}
}
public void stop() {
runOnWorkerThread(new Runnable() {
@Override
public void run() {
IMService.this._stop();
}
});
}
private void _stop() {
if (this.stopped) {
Log.i(TAG, "already stopped");
return;
}
Log.i(TAG, "stop im service");
stopped = true;
suspend();
testTimer.suspend();
IMService.this.connectState = ConnectState.STATE_UNCONNECTED;
IMService.this.publishConnectState();
this.close();
if (this.isBackground) {
Log.w(TAG, "stop im service when app is background");
}
}
private void suspend() {
if (this.suspended) {
Log.i(TAG, "suspended");
return;
}
heartbeatTimer.suspend();
connectTimer.suspend();
this.suspended = true;
Log.i(TAG, "suspend im service");
}
private void resume() {
if (!this.suspended) {
return;
}
Log.i(TAG, "resume im service");
this.suspended = false;
connectTimer.setTimer(uptimeMillis());
connectTimer.resume();
heartbeatTimer.setTimer(uptimeMillis(), HEARTBEAT*1000);
heartbeatTimer.resume();
refreshHost();
}
public void sendPeerMessageAsync(final IMMessage im) {
handler.post(new Runnable() {
@Override
public void run() {
boolean r = IMService.this.sendPeerMessage(im);
if (!r) {
if (peerMessageHandler != null) {
peerMessageHandler.handleMessageFailure(im);
}
publishPeerMessageFailure(im);
}
}
});
}
public boolean sendPeerMessage(IMMessage im) {
assertLooper();
Message msg = new Message();
msg.cmd = Command.MSG_IM;
msg.body = im;
if (sendMessage(msg)) {
messages.add(msg);
//在发送需要回执的消息时尽快发现socket已经断开的情况
sendHeartbeat();
return true;
} else if (!suspended) {
msg.failCount = 1;
messages.add(msg);
return true;
} else {
return false;
}
}
public void sendGroupMessageAsync(final IMMessage im) {
handler.post(new Runnable() {
@Override
public void run() {
boolean r = IMService.this.sendGroupMessage(im);
if (!r) {
if (groupMessageHandler != null) {
groupMessageHandler.handleMessageFailure(im);
}
publishGroupMessageFailure(im);
}
}
});
}
public boolean sendGroupMessage(IMMessage im) {
assertLooper();
Message msg = new Message();
msg.cmd = Command.MSG_GROUP_IM;
msg.body = im;
if (sendMessage(msg)) {
messages.add(msg);
//在发送需要回执的消息时尽快发现socket已经断开的情况
sendHeartbeat();
return true;
} else if (!suspended) {
msg.failCount = 1;
messages.add(msg);
return true;
} else {
return false;
}
}
public void sendCustomerMessageAsync(final CustomerMessage im) {
handler.post(new Runnable() {
@Override
public void run() {
boolean r = IMService.this.sendCustomerMessage(im);
if (!r) {
if (customerMessageHandler != null) {
customerMessageHandler.handleMessageFailure(im);
}
publishCustomerServiceMessageACK(im);
}
}
});
}
public boolean sendCustomerMessage(CustomerMessage im) {
assertLooper();
Message msg = new Message();
msg.cmd = Command.MSG_CUSTOMER;
msg.body = im;
if (sendMessage(msg)) {
messages.add(msg);
//在发送需要回执的消息时尽快发现socket已经断开的情况
sendHeartbeat();
return true;
} else if (!suspended) {
msg.failCount = 1;
messages.add(msg);
return true;
} else {
return false;
}
}
public void sendRTMessageAsync(final RTMessage rt) {
handler.post(new Runnable() {
@Override
public void run() {
IMService.this.sendRTMessage(rt);
}
});
}
public boolean sendRTMessage(RTMessage rt) {
assertLooper();
Message msg = new Message();
msg.cmd = Command.MSG_RT;
msg.body = rt;
if (!sendMessage(msg)) {
return false;
}
return true;
}
public void sendRoomMessageAsync(final RoomMessage rm) {
handler.post(new Runnable() {
@Override
public void run() {
IMService.this.sendRoomMessage(rm);
}
});
}
public boolean sendRoomMessage(RoomMessage rm) {
assertLooper();
Message msg = new Message();
msg.cmd = Command.MSG_ROOM_IM;
msg.body = rm;
return sendMessage(msg);
}
private void sendEnterRoom(long roomID) {
Message msg = new Message();
msg.cmd = Command.MSG_ENTER_ROOM;
msg.body = new Long(roomID);
sendMessage(msg);
}
private void sendLeaveRoom(long roomID) {
Message msg = new Message();
msg.cmd = Command.MSG_LEAVE_ROOM;
msg.body = new Long(roomID);
sendMessage(msg);
}
public void enterRoom(final long roomID) {
if (roomID == 0) {
return;
}
runOnWorkerThread(new Runnable() {
@Override
public void run() {
IMService.this.roomID = roomID;
sendEnterRoom(roomID);
}
});
}
public void leaveRoom(final long roomID) {
if (roomID == 0) {
return;
}
runOnWorkerThread(new Runnable() {
@Override
public void run() {
if (IMService.this.roomID != roomID) {
return;
}
sendLeaveRoom(roomID);
IMService.this.roomID = 0;
}
});
}
private void close() {
if (receivedGroupMessages.size() > 0) {
Log.i(TAG, "socket closed, received group messages:" + receivedGroupMessages);
receivedGroupMessages.clear();
}
if (metaMessage != null) {
Log.i(TAG, "socket closed, meta message:" + metaMessage);
metaMessage = null;
}
ArrayList peerMessages = new ArrayList();
ArrayList groupMessages = new ArrayList();
ArrayList customerMessages = new ArrayList();
ArrayList resendMessages = new ArrayList();
for (int i = 0; i < messages.size(); i++) {
Message m = messages.get(i);
//消息只会被重发一次
if (m.failCount > 0 || suspended) {
if (m.cmd == Command.MSG_IM) {
peerMessages.add((IMMessage)m.body);
} else if (m.cmd == Command.MSG_GROUP_IM) {
groupMessages.add((IMMessage)m.body);
} else if (m.cmd == Command.MSG_CUSTOMER) {
customerMessages.add((CustomerMessage)m.body);
}
} else {
m.failCount += 1;
resendMessages.add(m);
}
}
for (int i = 0; i < peerMessages.size(); i++) {
IMMessage im = peerMessages.get(i);
if (peerMessageHandler != null) {
peerMessageHandler.handleMessageFailure(im);
}
publishPeerMessageFailure(im);
}
for (int i = 0; i < groupMessages.size(); i++) {
IMMessage im = groupMessages.get(i);
if (groupMessageHandler != null) {
groupMessageHandler.handleMessageFailure(im);
}
publishGroupMessageFailure(im);
}
for (int i= 0; i < customerMessages.size(); i++) {
CustomerMessage im = customerMessages.get(i);
if (customerMessageHandler != null) {
customerMessageHandler.handleMessageFailure(im);
}
publishCustomerServiceMessageFailure(im);
}
messages = resendMessages;
if (this.tcp != null) {
Log.i(TAG, "close tcp");
this.tcp.close();
final AsyncTCPInterface local_tcp = this.tcp;
this.tcp = null;
this.handler.post(new Runnable() {
@Override
public void run() {
local_tcp.release();
}
});
}
}
private static int now() {
Date date = new Date();
long t = date.getTime();
return (int)(t/1000);
}
private void refreshHost() {
new AsyncTask() {
@Override
protected String doInBackground(Void... urls) {
return lookupHost(IMService.this.host);
}
private String lookupHost(String host) {
try {
InetAddress[] inetAddresses = InetAddress.getAllByName(host);
for (int i = 0; i < inetAddresses.length; i++) {
InetAddress inetAddress = inetAddresses[i];
Log.i(TAG, "host address:" + inetAddress.getHostAddress());
if (inetAddress instanceof Inet4Address) {
return inetAddress.getHostAddress();
}
}
return "";
} catch (UnknownHostException exception) {
exception.printStackTrace();
return "";
}
}
@Override
protected void onPostExecute(String result) {
if (result.length() > 0) {
IMService.this.hostIP = result;
IMService.this.dnsTimestamp = now();
}
}
}.execute();
}
private void startConnectTimer() {
if (this.stopped || this.suspended || this.isBackground) {
return;
}
long t;
if (this.connectFailCount > 60) {
t = uptimeMillis() + 60*1000;
} else {
t = uptimeMillis() + this.connectFailCount*1000;
}
connectTimer.setTimer(t);
Log.d(TAG, "start connect timer:" + this.connectFailCount);
}
private void onConnected() {
Log.i(TAG, "tcp connected");
int now = now();
this.data = null;
this.connectFailCount = 0;
this.connectState = ConnectState.STATE_CONNECTED;
this.publishConnectState();
this.sendAuth();
if (this.roomID > 0) {
this.sendEnterRoom(this.roomID);
}
this.sendSync(this.syncKey);
this.isSyncing = true;
this.syncTimestamp = now;
this.pendingSyncKey = 0;
for (Map.Entry e : this.groupSyncKeys.entrySet()) {
GroupSync s = e.getValue();
this.sendGroupSync(e.getKey(), s.syncKey);
s.isSyncing = true;
s.syncTimestamp = now;
s.pendingSyncKey = 0;
}
//重发失败的消息
for (Message m : messages) {
this.sendMessage(m);
}
this.tcp.startRead();
}
private void connect() {
if (this.tcp != null) {
return;
}
if (this.stopped) {
Log.e(TAG, "opps....");
return;
}
if (hostIP == null || hostIP.length() == 0) {
refreshHost();
IMService.this.connectFailCount++;
Log.i(TAG, "host ip is't resolved");
long t;
if (this.connectFailCount > 60) {
t = uptimeMillis() + 60*1000;
} else {
t = uptimeMillis() + this.connectFailCount*1000;
}
connectTimer.setTimer(t);
return;
}
if (now() - dnsTimestamp > 5*60) {
refreshHost();
}
this.pingTimestamp = 0;
this.connectTimestamp = now();
this.connectState = ConnectState.STATE_CONNECTING;
IMService.this.publishConnectState();
if (ENABLE_SSL) {
this.tcp = new AsyncSSLTCP();
} else {
this.tcp = new AsyncTCP();
}
Log.i(TAG, "new tcp...");
this.tcp.setConnectCallback(new TCPConnectCallback() {
@Override
public void onConnect(Object tcp, int status) {
if (status != 0) {
Log.i(TAG, "connect err:" + status);
IMService.this.connectFailCount++;
IMService.this.connectState = ConnectState.STATE_CONNECTFAIL;
IMService.this.publishConnectState();
IMService.this.close();
IMService.this.startConnectTimer();
} else {
IMService.this.onConnected();
}
}
});
this.tcp.setReadCallback(new TCPReadCallback() {
@Override
public void onRead(Object tcp, byte[] data) {
if (data.length == 0) {
Log.i(TAG, "tcp read eof");
IMService.this.connectState = ConnectState.STATE_UNCONNECTED;
IMService.this.publishConnectState();
IMService.this.handleClose();
} else {
IMService.this.pingTimestamp = 0;
boolean b = IMService.this.handleData(data);
if (!b) {
IMService.this.connectState = ConnectState.STATE_UNCONNECTED;
IMService.this.publishConnectState();
IMService.this.handleClose();
}
}
}
});
boolean r = this.tcp.connect(this.hostIP, this.port);
Log.i(TAG, "tcp connect:" + r);
if (!r) {
this.tcp = null;
IMService.this.connectFailCount++;
IMService.this.connectState = ConnectState.STATE_CONNECTFAIL;
publishConnectState();
startConnectTimer();
} else if (connectTimeout > 0){
Timer t = new Timer() {
@Override
protected void fire() {
int now = now();
if (IMService.this.connectState == ConnectState.STATE_CONNECTING &&
now - IMService.this.connectTimestamp >= connectTimeout) {
//connect timeout
Log.i(TAG, "connect timeout");
IMService.this.connectFailCount++;
IMService.this.connectState = ConnectState.STATE_CONNECTFAIL;
IMService.this.publishConnectState();
IMService.this.close();
IMService.this.startConnectTimer();
}
}
};
t.setTimer(uptimeMillis()+1000*connectTimeout+100);
t.resume();
}
}
private void heartbeat() {
IMService.ConnectState state = connectState;
Log.i(TAG, "heartbeat, im connect state:" + state);
if (state == IMService.ConnectState.STATE_CONNECTFAIL || state == IMService.ConnectState.STATE_UNCONNECTED) {
Log.i(TAG, "connect im service");
connect();
} else if (state == IMService.ConnectState.STATE_CONNECTED) {
Log.i(TAG, "send heartbeat");
sendHeartbeat();
} else if (state == IMService.ConnectState.STATE_CONNECTING) {
int t = connectTimestamp;
int n = now();
//90s timeout
if (n - t > 90) {
Log.i(TAG, "im service connect timeout, reconnect");
close();
connect();
}
}
}
private void handleAuthStatus(Message msg) {
Integer status = (Integer)msg.body;
Log.d(TAG, "auth status:" + status);
if (status != 0) {
//失效的accesstoken,2s后重新连接
this.connectFailCount = 2;
this.connectState = ConnectState.STATE_UNCONNECTED;
this.publishConnectState();
this.close();
this.startConnectTimer();
}
}
private void handleIMMessage(Message msg) {
IMMessage im = (IMMessage)msg.body;
Log.d(TAG, "im message sender:" + im.sender + " receiver:" + im.receiver + " content:" + im.content);
im.isSelf = (msg.flag & Flag.MESSAGE_FLAG_SELF) != 0;
if (peerMessageHandler != null && !peerMessageHandler.handleMessage(im)) {
Log.i(TAG, "handle im message fail");
return;
}
if (im.groupID > 0) {
im.receiver = im.groupID;
if ((msg.flag & Flag.MESSAGE_FLAG_PUSH) != 0) {
ArrayList array = new ArrayList();
array.add(im);
if (groupMessageHandler != null && !groupMessageHandler.handleMessages(array)) {
Log.i(TAG, "handle group messages fail");
return;
}
publishGroupMessages(array);
} else {
receivedGroupMessages.add(im);
}
} else if (im.secret) {
publishPeerSecretMessage(im);
} else {
publishPeerMessage(im);
}
sendACK(msg.seq);
}
private void handleGroupIMMessage(Message msg) {
IMMessage im = (IMMessage)msg.body;
Log.d(TAG, "group im message sender:" + im.sender + " receiver:" + im.receiver + " content:" + im.content);
im.isSelf = (msg.flag & Flag.MESSAGE_FLAG_SELF) != 0;
if ((msg.flag & Flag.MESSAGE_FLAG_PUSH) != 0) {
ArrayList array = new ArrayList