diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..96cc43e --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..e7bedf3 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..1e1352c --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..3b31283 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d518b8c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + C:\Users\DwayneQ\AppData\Roaming\Subversion + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..88d7145 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..c29437e --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'com.android.application' +apply plugin: 'org.greenrobot.greendao' // apply plugin + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.3" + defaultConfig { + applicationId "com.example.administrator.chatdemo" + minSdkVersion 16 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:25.3.1' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.android.support:recyclerview-v7:23.1.1' + testCompile 'junit:junit:4.12' + // greenDao + compile 'org.greenrobot:greendao:3.2.2' + compile 'com.github.LuckSiege.PictureSelector:picture_library:v2.1.0' + + + compile 'io.github.leibnik:chatimageview:1.0.1' + compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3' + compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final' + +} +greendao { + schemaVersion 1 +// daoPackage 'com.fengpi.gen' +// targetGenDir 'src/main/java' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..ce3149c --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/example/administrator/chatdemo/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/administrator/chatdemo/ExampleInstrumentedTest.java new file mode 100644 index 0000000..06ce7bd --- /dev/null +++ b/app/src/androidTest/java/com/example/administrator/chatdemo/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.administrator.chatdemo; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.example.administrator.chatdemo", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..feb99a1 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/administrator/chatdemo/App.java b/app/src/main/java/com/example/administrator/chatdemo/App.java new file mode 100644 index 0000000..626aa5e --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/App.java @@ -0,0 +1,55 @@ +package com.example.administrator.chatdemo; + +import android.app.Application; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import com.example.administrator.chatdemo.bean.DaoMaster; +import com.example.administrator.chatdemo.bean.DaoSession; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.core.assist.QueueProcessingType; + +/** + * Created by dwq on 2017/7/20/020. + * e-mail:lomapa@163.com + */ + +public class App extends Application { + + private static App instance; + + public DaoSession getDaoSession() { + return daoSession; + } + + private DaoSession daoSession; + + public static App getInstance() { + return instance; + } + + @Override + public void onCreate() { + super.onCreate(); + DaoMaster.DevOpenHelper mHelper = new DaoMaster.DevOpenHelper(this, "chat-message", null); + SQLiteDatabase db = mHelper.getWritableDatabase(); + daoSession = new DaoMaster(db).newSession(); + initImageLoader(this); + instance = this; + } + + /** + * 初始化ImageLoader + */ + public static void initImageLoader(Context context) { + ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder( + context) + .threadPoolSize(3).threadPriority(Thread.NORM_PRIORITY - 2) + //.memoryCache(new WeakMemoryCache()) + .denyCacheImageMultipleSizesInMemory() + .tasksProcessingOrder(QueueProcessingType.LIFO) + .build(); + ImageLoader.getInstance().init(config); + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/AudioHelper.java b/app/src/main/java/com/example/administrator/chatdemo/AudioHelper.java new file mode 100644 index 0000000..04031ec --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/AudioHelper.java @@ -0,0 +1,92 @@ +package com.example.administrator.chatdemo; + +import android.media.MediaPlayer; +import android.util.Log; + +import java.io.IOException; + +/** + * Created by lzw on 14/12/19. + */ +public class AudioHelper { + private static AudioHelper audioHelper; + private MediaPlayer mediaPlayer; + private Runnable finishCallback; + private String audioPath; + private boolean onceStart = false; + private boolean isPlaying = false; + + private AudioHelper() { + + } + + public static AudioHelper getInstance() { + if (audioHelper == null) { + audioHelper = new AudioHelper(); + } + return audioHelper; + } + + public String getAudioPath() { + return audioPath; + } + + public void stopPlayer() { + if (mediaPlayer != null) { + mediaPlayer.stop(); + mediaPlayer.release(); + } + isPlaying = false; + mediaPlayer = null; + } + + public void pausePlayer() { + if (mediaPlayer != null) { + mediaPlayer.pause(); + isPlaying = false; + } + } + + public boolean isPlaying() { + return isPlaying; + } + + public void restartPlayer() { + if (mediaPlayer != null && !mediaPlayer.isPlaying()) { + mediaPlayer.start(); + } + } + + public synchronized void playAudio(String path, Runnable finishCallback) { + if (mediaPlayer != null && onceStart) { + mediaPlayer.reset(); + } else { + mediaPlayer = new MediaPlayer(); + } +// tryRunFinishCallback(); + audioPath = path; + AudioHelper.this.finishCallback = finishCallback; + try { + mediaPlayer.setDataSource(path); + mediaPlayer.prepare(); + mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + tryRunFinishCallback(); + } + }); + isPlaying = true; + mediaPlayer.start(); + onceStart = true; + } catch (IOException e) { + Log.e("AudioHelper", e.toString()); + } + } + + public void tryRunFinishCallback() { + if (finishCallback != null) { + finishCallback.run(); + finishCallback = null; + } + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/ImageBrowserActivity.java b/app/src/main/java/com/example/administrator/chatdemo/ImageBrowserActivity.java new file mode 100644 index 0000000..99334ad --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/ImageBrowserActivity.java @@ -0,0 +1,38 @@ +package com.example.administrator.chatdemo; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.example.administrator.chatdemo.utils.Constants; +import com.example.administrator.chatdemo.utils.PhotoUtils; + + +/** + * Created by lzw on 14-9-21. + */ +public class ImageBrowserActivity extends Activity { + + private ImageView imageView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.chat_image_brower_layout); + imageView = (ImageView) findViewById(R.id.imageView); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + Intent intent = getIntent(); + String path = intent.getStringExtra(Constants.IMAGE_LOCAL_PATH); + String url = intent.getStringExtra(Constants.IMAGE_URL); + PhotoUtils.displayImageCacheElseNetwork(imageView, path, url); + findViewById(R.id.lly_image_browser).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/PlayButtonClickListener.java b/app/src/main/java/com/example/administrator/chatdemo/PlayButtonClickListener.java new file mode 100644 index 0000000..4b21051 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/PlayButtonClickListener.java @@ -0,0 +1,102 @@ +package com.example.administrator.chatdemo; + +import android.content.Context; +import android.graphics.drawable.AnimationDrawable; +import android.media.MediaPlayer; +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; + +import com.example.administrator.chatdemo.bean.ChatMessageBean; +import com.example.administrator.chatdemo.widget.PlayButton; + +import java.io.File; + +/** + * Created by dwq on 2017/7/24/024. + * e-mail:lomapa@163.com + */ + +public class PlayButtonClickListener implements View.OnClickListener { + private PlayButton playButton; + private boolean leftSide; + private Context context; + private String path; + + private AnimationDrawable anim; + public static PlayButtonClickListener mCurrentPlayButtonClickListner = null; + private MediaPlayer mediaPlayer = null; + public static boolean isPlaying = false; + // 将MediaPlayer播放地址, + public static String audioPath; + private ImageView ivIsListened; + private ChatMessageBean chatMessageBean; + + public PlayButtonClickListener(PlayButton playButton, ImageView ivIsListened, ChatMessageBean chatMessageBean, boolean leftSide, Context context, String path) { + this.playButton = playButton; + this.leftSide = leftSide; + this.context = context; + this.path = path; + this.ivIsListened = ivIsListened; + this.chatMessageBean = chatMessageBean; + } + + @Override + public void onClick(View v) { + if (isPlaying) { + if (TextUtils.equals(audioPath, path)) { + mCurrentPlayButtonClickListner.stopPlayVoice(); + return; + } + mCurrentPlayButtonClickListner.stopPlayVoice(); + } + playButton.startRecordAnimation(); + playVoice(path); + if (leftSide && ivIsListened != null) { + ivIsListened.setVisibility(View.INVISIBLE); + chatMessageBean.setIsListened(true); +// context + App.getInstance().getDaoSession().update(chatMessageBean); + } + } + + public void stopPlayVoice() { + playButton.stopRecordAnimation(); + if (mediaPlayer != null) { + mediaPlayer.stop(); + mediaPlayer.release(); + } + isPlaying = false; + } + + public void playVoice(String filePath) { + if (!(new File(filePath).exists())) { + return; + } + mediaPlayer = new MediaPlayer(); + this.audioPath = path; + try { + mediaPlayer.setDataSource(filePath); + mediaPlayer.prepare(); + mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + + @Override + public void onCompletion(MediaPlayer mp) { + // TODO Auto-generated method stub + mediaPlayer.release(); + mediaPlayer = null; + stopPlayVoice(); // stop animation + } + + }); + isPlaying = true; + mCurrentPlayButtonClickListner = this; + mediaPlayer.start(); + playButton.startRecordAnimation(); + } catch (Exception e) { + System.out.println(); + } + } + + +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/ServiceChatActivity.java b/app/src/main/java/com/example/administrator/chatdemo/ServiceChatActivity.java new file mode 100644 index 0000000..0784f70 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/ServiceChatActivity.java @@ -0,0 +1,482 @@ +package com.example.administrator.chatdemo; + +import android.Manifest; +import android.annotation.TargetApi; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.provider.MediaStore; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Toast; + +import com.example.administrator.chatdemo.adapter.ChatRecyclerAdapter; +import com.example.administrator.chatdemo.bean.ChatConst; +import com.example.administrator.chatdemo.bean.ChatMessageBean; +import com.example.administrator.chatdemo.bean.ChatMessageType; +import com.example.administrator.chatdemo.bean.DaoSession; +import com.example.administrator.chatdemo.utils.ImageCheckoutUtil; +import com.example.administrator.chatdemo.utils.PathUtils; +import com.example.administrator.chatdemo.widget.ChatBottomView; +import com.example.administrator.chatdemo.widget.InputBarLayout; +import com.luck.picture.lib.PictureSelector; +import com.luck.picture.lib.config.PictureConfig; +import com.luck.picture.lib.config.PictureMimeType; +import com.luck.picture.lib.entity.LocalMedia; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ServiceChatActivity extends AppCompatActivity { + + private View activityRootView; + + private static final int SDK_PERMISSION_REQUEST = 127; + private static final int IMAGE_SIZE = 100 * 1024;// 300kb + private String permissionInfo; + private boolean CAN_WRITE_EXTERNAL_STORAGE = true; + private boolean CAN_RECORD_AUDIO = true; + private String camPicPath; + private InputBarLayout inputbarLayout; + private RecyclerView rvChat; + private LinearLayoutManager layoutManager; + + public List messageList = new ArrayList<>(); + private ChatRecyclerAdapter chatRecyclerAdapter; + + public String userName = "Dwq";//聊天对象昵称 + + public static final int SEND_OK = 0x1110; + public static final int REFRESH = 0x0011; + public static final int RECERIVE_OK = 0x1111; + public static final int PULL_TO_REFRESH_DOWN = 0x0111; + private SendMessageHandler sendMessageHandler; + private String content; + private DaoSession daoSession; + private String voiceFilePath; + private SwipeRefreshLayout swipeRefresh; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_service_chat); + initView(); + initListener(); + initData(); + getPersimmions(); + } + + + private void initView() { + activityRootView = findViewById(R.id.layout_tongbao_rl); + inputbarLayout = (InputBarLayout) findViewById(R.id.input_bar); + swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh); + rvChat = (RecyclerView) findViewById(R.id.rv_chat); + layoutManager = new LinearLayoutManager(this); + rvChat.setLayoutManager(layoutManager); + } + + private void initListener() { + inputbarLayout.setOnBottomIconClickListener(new InputBarLayout.OnBottomIconClickListener() { + @Override + public void onIconClickListener(int from) { + switch (from) { + case ChatBottomView.FROM_CAMERA:// 相机 + if (!CAN_WRITE_EXTERNAL_STORAGE) { + Toast.makeText(ServiceChatActivity.this, "权限未开通\n请到设置中开通相册权限", Toast.LENGTH_SHORT).show(); + } else { + camPicPath = PathUtils.getSavePicPath(ServiceChatActivity.this); + Intent openCameraIntent = new Intent( + MediaStore.ACTION_IMAGE_CAPTURE); + Uri uri = Uri.fromFile(new File(camPicPath)); + openCameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + startActivityForResult(openCameraIntent, + ChatBottomView.FROM_CAMERA); + } + break; + case ChatBottomView.FROM_GALLERY:// 相册 + PictureSelector.create(ServiceChatActivity.this) + .openGallery(PictureMimeType.ofImage()) + .imageSpanCount(4)// 每行显示个数 + .selectionMode(PictureConfig.SINGLE) + .previewImage(true) + .compress(true) + .isCamera(false) + .forResult(PictureConfig.CHOOSE_REQUEST); + break; + } + } + }); + + inputbarLayout.setOnMessageSendListener(new InputBarLayout.OnMessageSendListener() { + @Override + public void sendMessage(final String msg) {// 发送文字消息 + new Thread(new Runnable() { + @Override + public void run() { +// messageList.add(getTbub(userName, ChatMessageType.TextMessageType.getType(), msg, null, null, +// null, null, null, 0f, ChatConst.COMPLETED)); + chatRecyclerAdapter.addMessage(getTbub(userName, 0, ChatMessageType.TextMessageType.getType(), + msg, null, null, null, null, null, 0f, ChatConst.COMPLETED)); + sendMessageHandler.sendEmptyMessage(SEND_OK); + ServiceChatActivity.this.content = msg; + receriveHandler.sendEmptyMessageDelayed(0, 1000); + } + }).start(); + } + + @Override + public void sendVoice(float seconds, String voicePath) {// 发送语音消息 + sendAudio(seconds, voicePath); + } + + @Override + public void onVoiceRecordStart() { + chatRecyclerAdapter.pausePlayer(); + } + }); + + rvChat.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + + inputbarLayout.hideBottomLayout(); + return false; + } + }); + } + + private void initData() { + + daoSession = ((App) getApplication()).getDaoSession(); + loadLocalMessage(); + chatRecyclerAdapter = new ChatRecyclerAdapter(this, messageList); + chatRecyclerAdapter.resetRecycledViewPoolSize(rvChat); + + rvChat.setAdapter(chatRecyclerAdapter); + rvChat.scrollToPosition(messageList.size() - 1); + sendMessageHandler = new SendMessageHandler(this); + + } + + private void loadLocalMessage() { + if (messageList != null) { + messageList.clear(); + } + List chatMessageBeen = daoSession.loadAll(ChatMessageBean.class); + messageList.addAll(chatMessageBeen); + } + + + @TargetApi(23) + protected void getPersimmions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ArrayList permissions = new ArrayList(); + // 读写权限 + if (addPermission(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + permissionInfo += "Manifest.permission.WRITE_EXTERNAL_STORAGE Deny \n"; + } + // 麦克风权限 + if (addPermission(permissions, Manifest.permission.RECORD_AUDIO)) { + permissionInfo += "Manifest.permission.WRITE_EXTERNAL_STORAGE Deny \n"; + } + if (permissions.size() > 0) { + requestPermissions(permissions.toArray(new String[permissions.size()]), SDK_PERMISSION_REQUEST); + } + } + } + + @TargetApi(23) + private boolean addPermission(ArrayList permissionsList, String permission) { + if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { // 如果应用没有获得对应权限,则添加到列表中,准备批量申请 + if (shouldShowRequestPermissionRationale(permission)) { + return true; + } else { + permissionsList.add(permission); + return false; + } + + } else { + return true; + } + } + + @TargetApi(23) + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + // TODO Auto-generated method stub + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + switch (requestCode) { + case SDK_PERMISSION_REQUEST: + Map perms = new HashMap(); + // Initial + perms.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PackageManager.PERMISSION_GRANTED); + perms.put(Manifest.permission.RECORD_AUDIO, PackageManager.PERMISSION_GRANTED); + // Fill with results + for (int i = 0; i < permissions.length; i++) + perms.put(permissions[i], grantResults[i]); + // Check for ACCESS_FINE_LOCATION + if (perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + // Permission Denied + CAN_WRITE_EXTERNAL_STORAGE = false; + Toast.makeText(this, "禁用图片权限将导致发送图片功能无法使用!", Toast.LENGTH_SHORT) + .show(); + } + if (perms.get(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + CAN_RECORD_AUDIO = false; + Toast.makeText(this, "禁用录制音频权限将导致语音功能无法使用!", Toast.LENGTH_SHORT) + .show(); + } + break; + default: + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + switch (requestCode) { + case ChatBottomView.FROM_CAMERA: + FileInputStream is = null; + try { + is = new FileInputStream(camPicPath); + File camFile = new File(camPicPath); // 图片文件路径 + if (camFile.exists()) { + int size = ImageCheckoutUtil + .getImageSize(ImageCheckoutUtil + .getLoacalBitmap(camPicPath)); +// if (size > IMAGE_SIZE) { +// showDialog(camPicPath); +// Toast.makeText(this, "ImgPath=" + camPicPath, Toast.LENGTH_SHORT).show(); +// } else { + sendImage(camPicPath); +// Toast.makeText(this, "ImgPath=" + camPicPath, Toast.LENGTH_SHORT).show(); +// } + } else { + Toast.makeText(this, "该文件不存在", Toast.LENGTH_SHORT).show(); + } + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + // 关闭流 + try { + is.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + break; + case PictureConfig.CHOOSE_REQUEST: + // 图片选择结果回调 + List selectList = PictureSelector.obtainMultipleResult(data); + LocalMedia media = selectList.get(0); + String path = media.getPath(); + sendImage(path); + // 例如 LocalMedia 里面返回三种path + // 1.media.getPath(); 为原图path + // 2.media.getCutPath();为裁剪后path,需判断media.isCut();是否为true + // 3.media.getCompressPath();为压缩后path,需判断media.isCompressed();是否为true + // 如果裁剪并压缩了,已取压缩路径为准,因为是先裁剪后压缩的 +// mDesignCenterView.refreshSelectedPicture(selectList); + break; + } + } else if (resultCode == RESULT_CANCELED) { +// Toast.makeText(this, "操作取消", Toast.LENGTH_SHORT).show(); + } + } + + public ChatMessageBean getTbub(String username, int type, int messageType, + String Content, String imageIconUrl, String imageUrl, + String imageLocal, String userVoicePath, String userVoiceUrl, + Float userVoiceTime, @ChatConst.SendState int sendState) { + ChatMessageBean tbub = new ChatMessageBean(); + tbub.setUserName(username); + long time = System.currentTimeMillis(); + Log.i("serviceChat", "currentTime:" + time); + tbub.setTime(time); + tbub.setType(type); + tbub.setMessagetype(messageType); + tbub.setUserContent(Content); + tbub.setImageIconUrl(imageIconUrl); + tbub.setImageUrl(imageUrl); + tbub.setUserVoicePath(userVoicePath); + tbub.setUserVoiceUrl(userVoiceUrl); + tbub.setUserVoiceTime(userVoiceTime); + tbub.setSendState(sendState); + tbub.setImageLocal(imageLocal); + daoSession.insert(tbub); + + return tbub; + } + + private String filePath = ""; + + protected void sendImage(final String filePath) { + new Thread(new Runnable() { + @Override + public void run() { + chatRecyclerAdapter.addMessage(getTbub(userName, 0, ChatMessageType.ImageMessageType.getType(), null, null, null, filePath, null, null, + 0f, ChatConst.COMPLETED)); +// imageList.add(tblist.get(tblist.size() - 1).getImageLocal()); +// imagePosition.put(tblist.size() - 1, imageList.size() - 1); + sendMessageHandler.sendEmptyMessage(SEND_OK); + ServiceChatActivity.this.filePath = filePath; + receriveHandler.sendEmptyMessageDelayed(1, 3000); + } + }).start(); + } + + + /** + * 发送语音 + */ + protected void sendAudio(final float seconds, final String filePath) { + new Thread(new Runnable() { + @Override + public void run() { + chatRecyclerAdapter.addMessage(getTbub(userName, 0, ChatMessageType.AudioMessageType.getType(), null, null, null, null, filePath, + null, seconds, ChatConst.COMPLETED)); + sendMessageHandler.sendEmptyMessage(SEND_OK); + ServiceChatActivity.this.seconds = seconds; + voiceFilePath = filePath; + receriveHandler.sendEmptyMessageDelayed(2, 3000); + } + }).start(); + } + + + /** + * 为了模拟接收延迟 + */ + private Handler receriveHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case 0: + receriveMsgText(content); + break; +// case 1: +// receriveImageText(filePath); +// break; + case 2: + receriveVoiceText(seconds, voiceFilePath); + break; + default: + break; + } + } + }; + + private void receriveMsgText(final String content) { + new Thread(new Runnable() { + @Override + public void run() { + String message = "回复:" + content; + ChatMessageBean tbub = new ChatMessageBean(); + tbub.setUserName(userName); + long time = System.currentTimeMillis(); + tbub.setUserContent(message); + tbub.setMessagetype(ChatMessageType.TextMessageType.getType()); + tbub.setTime(time); + tbub.setType(1); + tbub.setSendState(ChatConst.COMPLETED); + chatRecyclerAdapter.addMessage(tbub); + sendMessageHandler.sendEmptyMessage(RECERIVE_OK); + daoSession.insert(tbub); + } + }).start(); + } + + /** + * 接收语音 + */ + float seconds = 0.0f; + + private void receriveVoiceText(final float seconds, final String filePath) { + new Thread(new Runnable() { + @Override + public void run() { + ChatMessageBean tbub = new ChatMessageBean(); + tbub.setUserName(userName); + long time = System.currentTimeMillis(); + tbub.setTime(time); + tbub.setUserVoiceTime(seconds); + tbub.setUserVoicePath(filePath); + tbub.setType(1); + tbub.setMessagetype(ChatMessageType.AudioMessageType.getType()); + tbub.setSendState(ChatConst.COMPLETED); + chatRecyclerAdapter.addMessage(tbub); + sendMessageHandler.sendEmptyMessage(RECERIVE_OK); + daoSession.insert(tbub); + } + }).start(); + } + + static class SendMessageHandler extends Handler { + WeakReference mActivity; + + SendMessageHandler(ServiceChatActivity activity) { + mActivity = new WeakReference(activity); + } + + @Override + public void handleMessage(Message msg) { + // TODO Auto-generated method stub + ServiceChatActivity theActivity = mActivity.get(); + if (theActivity != null) { + switch (msg.what) { +// case REFRESH: +// theActivity.tbAdapter.isPicRefresh = true; +// theActivity.tbAdapter.notifyDataSetChanged(); +// int position = theActivity.tbAdapter.getItemCount() - 1 < 0 ? 0 : theActivity.tbAdapter.getItemCount() - 1; +// theActivity.myList.smoothScrollToPosition(position); +// break; + case SEND_OK: +// theActivity.chatRecyclerAdapter.isPicRefresh = true; +// theActivity.chatRecyclerAdapter.addMessage; + theActivity.chatRecyclerAdapter.notifyItemInserted(theActivity.messageList + .size() - 1); + theActivity.rvChat.smoothScrollToPosition(theActivity.chatRecyclerAdapter.getItemCount() - 1); + break; + case RECERIVE_OK: +// theActivity.chatRecyclerAdapter.isPicRefresh = true; + theActivity.chatRecyclerAdapter.notifyItemInserted(theActivity.messageList + .size() - 1); + theActivity.rvChat.smoothScrollToPosition(theActivity.chatRecyclerAdapter.getItemCount() - 1); + break; +// case PULL_TO_REFRESH_DOWN: +// theActivity.pullList.refreshComplete(); +// theActivity.tbAdapter.notifyDataSetChanged(); +// theActivity.rvChat.smoothScrollToPosition(theActivity.position - 1); +// theActivity.isDown = false; +// break; + default: + break; + } + } + } + + } +} + diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionGridAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionGridAdapter.java new file mode 100644 index 0000000..fee615d --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionGridAdapter.java @@ -0,0 +1,59 @@ +package com.example.administrator.chatdemo.adapter; + +import android.content.Context; +import android.graphics.Bitmap; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; + +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.utils.EmotionHelper; +import com.example.administrator.chatdemo.viewholder.ViewHolder; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by lzw on 14-9-25. + */ +public class ChatEmotionGridAdapter extends BaseAdapter { + private Context context; + private List datas = new ArrayList<>(); + + public ChatEmotionGridAdapter(Context ctx) { + this.context = ctx; + } + + public void setDatas(List datas) { + this.datas = datas; + } + + @Override + public int getCount() { + return datas.size(); + } + + @Override + public Object getItem(int i) { + return datas.get(i); + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public View getView(int position, View conView, ViewGroup parent) { + if (conView == null) { + conView = View.inflate(context, R.layout.chat_emotion_item, null); + } + ImageView emotionImageView = ViewHolder.findViewById(conView, R.id.emotionImageView); + String emotion = (String) getItem(position); + emotion = emotion.substring(1, emotion.length() - 1); + Bitmap bitmap = EmotionHelper.getEmojiDrawable(context, emotion); + emotionImageView.setImageBitmap(bitmap); + return conView; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionPagerAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionPagerAdapter.java new file mode 100644 index 0000000..1d71d96 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatEmotionPagerAdapter.java @@ -0,0 +1,41 @@ +package com.example.administrator.chatdemo.adapter; + +import android.support.v4.view.PagerAdapter; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by lzw on 14-9-25. + */ +public class ChatEmotionPagerAdapter extends PagerAdapter { + + private List views = new ArrayList(); + + public ChatEmotionPagerAdapter(List views) { + this.views = views; + } + + @Override + public int getCount() { + return views.size(); + } + + @Override + public boolean isViewFromObject(View view, Object o) { + return view == o; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + container.addView(views.get(position)); + return views.get(position); + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView(views.get(position)); + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatRecyclerAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatRecyclerAdapter.java new file mode 100644 index 0000000..c4f4f77 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/ChatRecyclerAdapter.java @@ -0,0 +1,174 @@ +package com.example.administrator.chatdemo.adapter; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.ViewGroup; + +import com.example.administrator.chatdemo.PlayButtonClickListener; +import com.example.administrator.chatdemo.bean.ChatMessageBean; +import com.example.administrator.chatdemo.bean.ChatMessageType; +import com.example.administrator.chatdemo.viewholder.ChatItemAudioHolder; +import com.example.administrator.chatdemo.viewholder.ChatItemHolder; +import com.example.administrator.chatdemo.viewholder.ChatItemImageHolder; +import com.example.administrator.chatdemo.viewholder.ChatItemTextHolder; +import com.example.administrator.chatdemo.viewholder.CommonViewHolder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Created by dwq on 2017/7/20/020. + * e-mail:lomapa@163.com + */ + +public class ChatRecyclerAdapter extends RecyclerView.Adapter { + + private final int ITEM_LEFT = 100; + private final int ITEM_LEFT_TEXT = 101;// 左侧文字 + private final int ITEM_LEFT_IMAGE = 102;// 左侧图片 + private final int ITEM_LEFT_AUDIO = 103;// 左侧语音 + + private final int ITEM_RIGHT = 200; + private final int ITEM_RIGHT_TEXT = 201;// 右侧文字 + private final int ITEM_RIGHT_IMAGE = 202;// 右侧图片 + private final int ITEM_RIGHT_AUDIO = 203;// 右侧语音 + + // 时间间隔最小为十分钟 + private final static long TIME_INTERVAL = 1000 * 60 * 3; + private boolean isShowUserName = false; + + private List chatMessageList = new ArrayList<>(); + private Context context; + private int playButtonId = -1; + private boolean isMe; + + public ChatRecyclerAdapter(Context context, List chatMessageList) { + this.context = context; + this.chatMessageList = chatMessageList; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + Log.i("MsgFrom", "viewType=" + viewType); + switch (viewType) { + case ITEM_LEFT_TEXT: + return new ChatItemTextHolder(parent.getContext(), parent, true); + case ITEM_LEFT_AUDIO: + return new ChatItemAudioHolder(parent.getContext(), parent, true); + case ITEM_LEFT_IMAGE: + return new ChatItemImageHolder(parent.getContext(), parent, true); + case ITEM_RIGHT_TEXT: + return new ChatItemTextHolder(parent.getContext(), parent, false); + case ITEM_RIGHT_AUDIO: + return new ChatItemAudioHolder(parent.getContext(), parent, false); + case ITEM_RIGHT_IMAGE: + return new ChatItemImageHolder(parent.getContext(), parent, false); + default: + } + return null; + } + + @Override + public int getItemViewType(int position) { + ChatMessageBean chatMessageBean = chatMessageList.get(position); + if (null != chatMessageBean) { + isMe = fromMe(chatMessageBean); + int messagetype = chatMessageBean.getMessagetype(); + if (messagetype == ChatMessageType.TextMessageType.getType()) { + return isMe ? ITEM_LEFT_TEXT : ITEM_RIGHT_TEXT; + } else if (messagetype == ChatMessageType.ImageMessageType.getType()) { + return isMe ? ITEM_LEFT_IMAGE : ITEM_RIGHT_IMAGE; + } else if (messagetype == ChatMessageType.AudioMessageType.getType()) { + return isMe ? ITEM_LEFT_AUDIO : ITEM_RIGHT_AUDIO; + } else { + return isMe ? ITEM_LEFT : ITEM_RIGHT; + } + + } + return 8888; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + ((CommonViewHolder) holder).bindData(chatMessageList.get(position)); + if (holder instanceof ChatItemHolder) { + ((ChatItemHolder) holder).showTimeView(shouldShowTime(position)); + ((ChatItemHolder) holder).showUserName(isShowUserName); + } + if (getItemViewType(position) == ITEM_LEFT_AUDIO || getItemViewType(position) == ITEM_RIGHT_AUDIO) { + playButtonId = ((ChatItemAudioHolder) holder).playButton.getId(); + } + } + + @Override + public int getItemCount() { + return chatMessageList.size(); + } + + public void setChatMessageList(List chatMessageList) { + this.chatMessageList.clear(); + if (null != chatMessageList) { + this.chatMessageList.addAll(chatMessageList); + } + } + + public void addMessageListAll(List chatMessageList) { + this.chatMessageList.addAll(0, chatMessageList); + } + + public void addMessage(ChatMessageBean message) { + this.chatMessageList.addAll(Collections.singletonList(message)); + } + + public ChatMessageBean getFirstMessage() { + if (null != chatMessageList && chatMessageList.size() > 0) { + return this.chatMessageList.get(0); + } else { + return null; + } + } + + + private boolean fromMe(ChatMessageBean chatMessageBean) { + int type = chatMessageBean.getType(); + if (type == 0) { + return false; + } else { + return true; + } + } + + private boolean shouldShowTime(int position) { + if (position == 0) { + return true; + } + long lastTime = chatMessageList.get(position - 1).getTime(); + long curTime = chatMessageList.get(position).getTime(); + return curTime - lastTime > TIME_INTERVAL; + } + + + /** + * 因为 RecyclerView 中的 item 缓存默认最大为 5,造成会重复的 create item 而卡顿 + * 所以这里根据不同的类型设置不同的缓存值,经验值,不同 app 可以根据自己的场景进行更改 + */ + public void resetRecycledViewPoolSize(RecyclerView recyclerView) { + recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_LEFT_TEXT, 25); + recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_LEFT_IMAGE, 10); + recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_LEFT_AUDIO, 15); + + recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_RIGHT_TEXT, 25); + recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_RIGHT_IMAGE, 10); + recyclerView.getRecycledViewPool().setMaxRecycledViews(ITEM_RIGHT_AUDIO, 15); + } + + public void pausePlayer() { + if (PlayButtonClickListener.isPlaying) + PlayButtonClickListener.mCurrentPlayButtonClickListner.stopPlayVoice(); +// } + + } + +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/adapter/DataAdapter.java b/app/src/main/java/com/example/administrator/chatdemo/adapter/DataAdapter.java new file mode 100644 index 0000000..b4b426d --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/adapter/DataAdapter.java @@ -0,0 +1,63 @@ +package com.example.administrator.chatdemo.adapter; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.example.administrator.chatdemo.R; + + +public class DataAdapter extends BaseAdapter { + + private String[] title; + private Context context; + public DataAdapter(Context context, String[] title) { + super(); + this.context = context; + this.title = title; + } + + @Override + public int getCount() { + return title.length; + } + + @Override + public Object getItem(int position) { + return title[position]; + } + + @Override + public long getItemId(int position) { + return position; + } + + @SuppressLint("InflateParams") + @Override + public View getView(final int position, View convertView, + ViewGroup parent) { + ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(); + convertView = LayoutInflater.from(context) + .inflate(R.layout.layout_mess_iv_listitem, null); + holder.money_Tv = (TextView) convertView + .findViewById(R.id.title); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + + holder.money_Tv.setText(title[position]); + return convertView; + } + + class ViewHolder { + + TextView money_Tv; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/bean/ChatConst.java b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatConst.java new file mode 100644 index 0000000..a857fd3 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatConst.java @@ -0,0 +1,23 @@ +package com.example.administrator.chatdemo.bean; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Created by Mao Jiqing on 2016/10/15. + */ + +public class ChatConst { + public static final String LISTVIEW_DATABASE_NAME = "listview.db"; + public static final String RECYCLER_DATABASE_NAME = "recycler.db"; + public static final int SENDING = 0; + public static final int COMPLETED = 1; + public static final int SENDERROR = 2; + + @IntDef({SENDING, COMPLETED, SENDERROR}) + @Retention(RetentionPolicy.SOURCE) + public @interface SendState { + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageBean.java b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageBean.java new file mode 100644 index 0000000..2dfe918 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageBean.java @@ -0,0 +1,174 @@ +package com.example.administrator.chatdemo.bean; + +import org.greenrobot.greendao.annotation.Entity; +import org.greenrobot.greendao.annotation.Id; +import org.greenrobot.greendao.annotation.Property; +import org.greenrobot.greendao.annotation.Generated; + +/** + * Created by dwq on 2017/7/20/020. + * e-mail:lomapa@163.com + */ +@Entity +public class ChatMessageBean { + @Id + private Long id; + @Property(nameInDb = "UserId") + private String UserId; + @Property(nameInDb = "UserName") + private String UserName; + @Property(nameInDb = "UserHeadIcon") + private String UserHeadIcon; + @Property(nameInDb = "UserContent") + private String UserContent; + @Property(nameInDb = "time") + private long time; + @Property(nameInDb = "type") + private int type; + @Property(nameInDb = "messagetype") + private int messagetype; + @Property(nameInDb = "UserVoiceTime") + private float UserVoiceTime; + @Property(nameInDb = "UserVoicePath") + private String UserVoicePath; + @Property(nameInDb = "UserVoiceUrl") + private String UserVoiceUrl; + @Property(nameInDb = "sendState") + private + @ChatConst.SendState + int sendState; + @Property(nameInDb = "imageUrl") + private String imageUrl; + @Property(nameInDb = "imageIconUrl") + private String imageIconUrl; + @Property(nameInDb = "imageLocal") + private String imageLocal; + @Property(nameInDb = "isListened") + private boolean isListened; + @Generated(hash = 595418362) + public ChatMessageBean(Long id, String UserId, String UserName, + String UserHeadIcon, String UserContent, long time, int type, + int messagetype, float UserVoiceTime, String UserVoicePath, + String UserVoiceUrl, int sendState, String imageUrl, + String imageIconUrl, String imageLocal, boolean isListened) { + this.id = id; + this.UserId = UserId; + this.UserName = UserName; + this.UserHeadIcon = UserHeadIcon; + this.UserContent = UserContent; + this.time = time; + this.type = type; + this.messagetype = messagetype; + this.UserVoiceTime = UserVoiceTime; + this.UserVoicePath = UserVoicePath; + this.UserVoiceUrl = UserVoiceUrl; + this.sendState = sendState; + this.imageUrl = imageUrl; + this.imageIconUrl = imageIconUrl; + this.imageLocal = imageLocal; + this.isListened = isListened; + } + @Generated(hash = 1557449535) + public ChatMessageBean() { + } + public Long getId() { + return this.id; + } + public void setId(Long id) { + this.id = id; + } + public String getUserId() { + return this.UserId; + } + public void setUserId(String UserId) { + this.UserId = UserId; + } + public String getUserName() { + return this.UserName; + } + public void setUserName(String UserName) { + this.UserName = UserName; + } + public String getUserHeadIcon() { + return this.UserHeadIcon; + } + public void setUserHeadIcon(String UserHeadIcon) { + this.UserHeadIcon = UserHeadIcon; + } + public String getUserContent() { + return this.UserContent; + } + public void setUserContent(String UserContent) { + this.UserContent = UserContent; + } + public long getTime() { + return this.time; + } + public void setTime(long time) { + this.time = time; + } + public int getType() { + return this.type; + } + public void setType(int type) { + this.type = type; + } + public int getMessagetype() { + return this.messagetype; + } + public void setMessagetype(int messagetype) { + this.messagetype = messagetype; + } + public float getUserVoiceTime() { + return this.UserVoiceTime; + } + public void setUserVoiceTime(float UserVoiceTime) { + this.UserVoiceTime = UserVoiceTime; + } + public String getUserVoicePath() { + return this.UserVoicePath; + } + public void setUserVoicePath(String UserVoicePath) { + this.UserVoicePath = UserVoicePath; + } + public String getUserVoiceUrl() { + return this.UserVoiceUrl; + } + public void setUserVoiceUrl(String UserVoiceUrl) { + this.UserVoiceUrl = UserVoiceUrl; + } + public int getSendState() { + return this.sendState; + } + public void setSendState(int sendState) { + this.sendState = sendState; + } + public String getImageUrl() { + return this.imageUrl; + } + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + public String getImageIconUrl() { + return this.imageIconUrl; + } + public void setImageIconUrl(String imageIconUrl) { + this.imageIconUrl = imageIconUrl; + } + public String getImageLocal() { + return this.imageLocal; + } + public void setImageLocal(String imageLocal) { + this.imageLocal = imageLocal; + } + public boolean getIsListened() { + return this.isListened; + } + public void setIsListened(boolean isListened) { + this.isListened = isListened; + } + + + + +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageType.java b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageType.java new file mode 100644 index 0000000..bf5a709 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/bean/ChatMessageType.java @@ -0,0 +1,36 @@ +package com.example.administrator.chatdemo.bean; + +/** + * Created by dwq on 2017/7/20/020. + * e-mail:lomapa@163.com + */ + +public enum ChatMessageType { + UnsupportedMessageType(0), + TextMessageType(-1), + ImageMessageType(-2), + AudioMessageType(-3); + + int type; + + private ChatMessageType(int type) { + this.type = type; + } + + public int getType() { + return this.type; + } + + public static ChatMessageType getAVIMReservedMessageType(int type) { + switch (type) { + case -3: + return AudioMessageType; + case -2: + return ImageMessageType; + case -1: + return TextMessageType; + default: + return UnsupportedMessageType; + } + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/AudioManager.java b/app/src/main/java/com/example/administrator/chatdemo/utils/AudioManager.java new file mode 100644 index 0000000..eff9bf3 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/AudioManager.java @@ -0,0 +1,212 @@ +package com.example.administrator.chatdemo.utils; + +import android.media.MediaRecorder; +import android.os.Handler; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +public class AudioManager { + /** + * 录音的时候出错 + */ + public static final int MSG_ERROR_AUDIO_RECORD = -4; + private MediaRecorder mRecorder; + private String mDirString; + private String mCurrentFilePathString; + private Handler handler; + private boolean isPrepared;// 是否准备好了 + + /** + * 单例化的方法 1 先声明一个static 类型的变量a 2 在声明默认的构造函数 3 再用public synchronized static + * 类名 getInstance() { if(a==null) { a=new 类();} return a; } 或者用以下的方法 + */ + + /** + * 单例化这个类 + */ + private static AudioManager mInstance; + + private AudioManager(String dir) { + mDirString = dir; + } + + public static AudioManager getInstance(String dir) { + if (mInstance == null) { + synchronized (AudioManager.class) { + if (mInstance == null) { + mInstance = new AudioManager(dir); + + } + } + } + return mInstance; + + } + + public void setHandle(Handler handler) { + this.handler = handler; + } + + /** + * 回调函数,准备完毕,准备好后,button才会开始显示录音框 + */ + public interface AudioStageListener { + void wellPrepared(); + } + + public AudioStageListener mListener; + + public void setOnAudioStageListener(AudioStageListener listener) { + mListener = listener; + } + + public void setVocDir(String dir) { + mDirString = dir; + } + + // 准备方法 + @SuppressWarnings("deprecation") + public void prepareAudio() { + try { + // 一开始应该是false的 + isPrepared = false; + + File dir = new File(mDirString); +// if (!dir.exists()) { +// dir.mkdirs(); +// } +// + String fileNameString = generalFileName(); + File file = new File(dir, fileNameString); + mCurrentFilePathString = file.getAbsolutePath(); + mRecorder = new MediaRecorder(); + // 设置输出文件 + mRecorder.setOutputFile(file.getAbsolutePath()); + // 设置meidaRecorder的音频源是麦克风 + mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + // 设置文件音频的输出格式为amr + mRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); + // 设置音频的编码格式为amr + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + + // 严格遵守google官方api给出的mediaRecorder的状态流程图 + mRecorder.prepare(); + + mRecorder.start(); + // 准备结束 + // 已经准备好了,可以录制了 + if (mListener != null) { + mListener.wellPrepared(); + } + isPrepared = true; + + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + if (handler != null) { + handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + if (handler != null) { + handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD); + } + } catch (Exception e) { + e.printStackTrace(); + if (handler != null) { + handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD); + } + } + + } + + /** + * 随机生成文件的名称 + * + * @return + */ + private String generalFileName() { + // TODO Auto-generated method stub + +// return UUID.randomUUID().toString() + ".amr"; + return String.valueOf(System.currentTimeMillis()); + } + + private int vocAuthority[] = new int[10]; + private int vocNum = 0; + private boolean check = true; + + // 获得声音的level + public int getVoiceLevel(int maxLevel) { + // mRecorder.getMaxAmplitude()这个是音频的振幅范围,值域是0-32767 + if (isPrepared) { + try { + int vocLevel = mRecorder.getMaxAmplitude(); + if (check) { + if (vocNum >= 10) { + Set set = new HashSet(); + for (int i = 0; i < vocNum; i++) { + set.add(vocAuthority[i]); + } + if (set.size() == 1) { + if (handler != null) + handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD); + vocNum = 0; + vocAuthority = null; + vocAuthority = new int[10]; + } else { + check = false; + } + } else { + vocAuthority[vocNum] = vocLevel; + vocNum++; + } + } + return maxLevel * vocLevel / 32768 + 1; + } catch (Exception e) { + // TODO Auto-generated catch block + if (handler != null) + handler.sendEmptyMessage(MSG_ERROR_AUDIO_RECORD); + } + } + + return 1; + } + + // 释放资源 + public void release() { + // 严格按照api流程进行 + if (null != mRecorder) { + isPrepared = false; + try { + mRecorder.stop(); + mRecorder.release(); + } catch (Exception e) { + e.printStackTrace(); + } + mRecorder = null; + } + } + + // 取消,因为prepare时产生了一个文件,所以cancel方法应该要删除这个文件, + // 这是与release的方法的区别 + public void cancel() { + release(); + if (mCurrentFilePathString != null) { + File file = new File(mCurrentFilePathString); + file.delete(); + mCurrentFilePathString = null; + } + + } + + public String getCurrentFilePath() { + // TODO Auto-generated method stub + return mCurrentFilePathString; + } + +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/Constants.java b/app/src/main/java/com/example/administrator/chatdemo/utils/Constants.java new file mode 100644 index 0000000..54addf1 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/Constants.java @@ -0,0 +1,47 @@ +package com.example.administrator.chatdemo.utils; + +/** + * Created by wli on 15/8/23. + * 用来存放各种 static final 值 + */ +public class Constants { + + public static final String OBJECT_ID = "objectId"; + public static final int PAGE_SIZE = 10; + public static final String CREATED_AT = "createdAt"; + public static final String UPDATED_AT = "updatedAt"; + + + //TODO 还不知道这俩货是干嘛的 + public static final int ORDER_UPDATED_AT = 1; + public static final int ORDER_DISTANCE = 0; + + private static final String LEANMESSAGE_CONSTANTS_PREFIX = "com.avoscloud.leanchatlib."; + + public static final String MEMBER_ID = getPrefixConstant("member_id"); + public static final String CONVERSATION_ID = getPrefixConstant("conversation_id"); + + public static final String LEANCHAT_USER_ID = getPrefixConstant("leanchat_user_id"); + + public static final String ACTIVITY_TITLE = getPrefixConstant("activity_title"); + + public static final String INTENT_KEY = getPrefixConstant("intent_key"); + public static final String INTENT_VALUE = getPrefixConstant("intent_value"); + public static final String INTENT_DATA = getPrefixConstant("intent_data"); + + + + // ImageBrowserActivity + public static final String IMAGE_LOCAL_PATH = getPrefixConstant("image_local_path"); + public static final String IMAGE_URL = getPrefixConstant("image_url"); + + //Notification + public static final String NOTOFICATION_TAG = getPrefixConstant("notification_tag"); + public static final String NOTIFICATION_SINGLE_CHAT = Constants.getPrefixConstant("notification_single_chat"); + public static final String NOTIFICATION_GROUP_CHAT = Constants.getPrefixConstant("notification_group_chat"); + public static final String NOTIFICATION_SYSTEM = Constants.getPrefixConstant("notification_system_chat"); + + public static String getPrefixConstant(String str) { + return LEANMESSAGE_CONSTANTS_PREFIX + str; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/EmotionHelper.java b/app/src/main/java/com/example/administrator/chatdemo/utils/EmotionHelper.java new file mode 100644 index 0000000..61eedfe --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/EmotionHelper.java @@ -0,0 +1,205 @@ +package com.example.administrator.chatdemo.utils; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by lzw on 14-9-25. + */ +public class EmotionHelper { + private static final int ONE_PAGE_SIZE = 24; + public static List> emojiGroups; + private static Pattern pattern; + private static String[] emojiCodes = new String[]{ + ":smile:", + ":laughing:", + ":blush:", + ":smiley:", + ":relaxed:", + ":smirk:", + ":heart_eyes:", + ":kissing_heart:", + ":kissing_closed_eyes:", + ":flushed:", + ":relieved:", + ":satisfied:", + ":grin:", + ":wink:", + ":stuck_out_tongue_winking_eye:", + ":stuck_out_tongue_closed_eyes:", + ":grinning:", + ":kissing:", + ":kissing_smiling_eyes:", + ":stuck_out_tongue:", + ":sleeping:", + ":worried:", + ":frowning:", + ":anguished:", + ":open_mouth:", + ":grimacing:", + ":confused:", + ":hushed:", + ":expressionless:", + ":unamused:", + ":sweat_smile:", + ":sweat:", + ":disappointed_relieved:", + ":weary:", + ":pensive:", + ":disappointed:", + ":confounded:", + ":fearful:", + ":cold_sweat:", + ":persevere:", + ":cry:", + ":sob:", + ":joy:", + ":astonished:", + ":scream:", + ":tired_face:", + ":angry:", + ":rage:", + ":triumph:", + ":sleepy:", + ":yum:", + ":mask:", + ":sunglasses:", + ":dizzy_face:", + ":neutral_face:", + ":no_mouth:", + ":innocent:", + ":thumbsup:", + ":thumbsdown:", + ":clap:", + ":point_right:", + ":point_left:"}; + + static { + int pages = emojiCodes.length / ONE_PAGE_SIZE + (emojiCodes.length % ONE_PAGE_SIZE == 0 ? 0 : 1); + emojiGroups = new ArrayList<>(); + for (int page = 0; page < pages; page++) { + List onePageEmojis = new ArrayList<>(); + int start = page * ONE_PAGE_SIZE; + int end = Math.min(page * ONE_PAGE_SIZE + ONE_PAGE_SIZE, emojiCodes.length); + for (int i = start; i < end; i++) { + onePageEmojis.add(emojiCodes[i]); + } + emojiGroups.add(onePageEmojis); + } + pattern = Pattern.compile("\\:[a-z0-9-_]*\\:"); + } + + public static boolean contain(String[] strings, String string) { + for (String s : strings) { + if (s.equals(string)) { + return true; + } + } + return false; + } + + public static CharSequence replace(Context context, String text) { + if (TextUtils.isEmpty(text)) { + return text; + } + SpannableString spannableString = new SpannableString(text); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + String factText = matcher.group(); + String key = factText.substring(1, factText.length() - 1); + if (contain(emojiCodes, factText)) { + Bitmap bitmap = getEmojiDrawableInText(context, key); + ImageSpan image = new ImageSpan(context, bitmap); + int start = matcher.start(); + int end = matcher.end(); + spannableString.setSpan(image, start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + return spannableString; + } + + public static SpannableString replaceEmoj(Context context, String text) { + if (TextUtils.isEmpty(text)) { + return null; + } + SpannableString spannableString = new SpannableString(text); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + String factText = matcher.group(); + String key = factText.substring(1, factText.length() - 1); + if (contain(emojiCodes, factText)) { + Bitmap bitmap = getEmojiDrawable(context, key); + ImageSpan image = new ImageSpan(context, bitmap); + int start = matcher.start(); + int end = matcher.end(); + spannableString.setSpan(image, start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + return spannableString; + } + + public static void isEmojiDrawableAvailable(Context context) { + for (String emojiCode : emojiCodes) { + String code = emojiCode.substring(1, emojiCode.length() - 1); + Bitmap bitmap = getDrawableByName(context, code); + if (bitmap == null) { + Log.i("EmotionHelper", "not available test " + code); + } + } + } + + public static Bitmap getEmojiDrawableInText(Context context, String name) { + return getDrawableByNameInText(context, "emoji_" + name); + } + + public static Bitmap getEmojiDrawable(Context context, String name) { + return getDrawableByName(context, "emoji_" + name); + } + + public static Bitmap getDrawableByName(Context ctx, String name) { + + BitmapFactory.Options options = new BitmapFactory.Options(); + Matrix matrix = new Matrix(); + matrix.postScale(0.8f, 0.8f); + Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), + ctx.getResources().getIdentifier(name, "drawable", + ctx.getPackageName()), options); + Bitmap BitmapOrg = bitmap; + int width = BitmapOrg.getWidth(); + int height = BitmapOrg.getHeight(); + Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width, + height, matrix, true); + return resizedBitmap; + } + + public static Bitmap getDrawableByNameInText(Context ctx, String name) { + + BitmapFactory.Options options = new BitmapFactory.Options(); + Matrix matrix = new Matrix(); + matrix.postScale(0.58f, 0.58f); + Bitmap bitmap = BitmapFactory.decodeResource(ctx.getResources(), + ctx.getResources().getIdentifier(name, "drawable", + ctx.getPackageName()), options); + Bitmap BitmapOrg = bitmap; + int width = BitmapOrg.getWidth(); + int height = BitmapOrg.getHeight(); + Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width, + height, matrix, true); + return resizedBitmap; + } +} + diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/FileSaveUtil.java b/app/src/main/java/com/example/administrator/chatdemo/utils/FileSaveUtil.java new file mode 100644 index 0000000..04523af --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/FileSaveUtil.java @@ -0,0 +1,372 @@ +package com.example.administrator.chatdemo.utils; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.util.Base64; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +public class FileSaveUtil { + public static final String SD_CARD_PATH = Environment.getExternalStorageDirectory().toString() + "/MAXI/"; + + // public static final String saveFn = SD_CARD_PATH +// + "/user_chat_data/"; +// public static final String savelistFn = SD_CARD_PATH +// + "/user_chat_data/chatList/"; +// public static final String savechannelFn = SD_CARD_PATH +// + "/user_chat_data/channel_id/"; +// public static final String saveUnReadFn = SD_CARD_PATH +// + "/user_chat_data/UnRead/"; + public static final String voice_dir = SD_CARD_PATH + + "/voice_data/"; + + /** + * SD卡是否存在 + **/ + private boolean hasSD = false; + /** + * 当前程序包的路径 + **/ + private String FILESPATH; + + public static boolean isFileExists(File file) { + if (!file.exists()) { + return false; + } + return true; + } + + /** + * 获取文件夹下的所有文件名 + */ + public static List getFileName(String fileName) { + List fileList = new ArrayList(); + String path = fileName; // 路径 + File f = new File(path); + if (!f.exists()) { + System.out.println(path + " not exists"); + return null; + } + + File fa[] = f.listFiles(); + for (int i = 0; i < fa.length; i++) { + File fs = fa[i]; + if (!fs.isDirectory()) { + fileList.add(fs.getName()); + } + } + return fileList; + } + + /** + * 在SD卡上创建文件 + * + * @throws IOException + */ + public static File createSDFile(String fileName) throws IOException { + File file = new File(fileName); + if (!isFileExists(file)) + if (file.isDirectory()) { + file.mkdirs(); + } else { + file.createNewFile(); + } + return file; + } + + /** + * 在SD卡上创建文件夹 + * + * @throws IOException + */ + public static File createSDDirectory(String fileName) throws IOException { + File file = new File(fileName); + if (!isFileExists(file)) + file.mkdirs(); + return file; + } + +// /** +// * @content 存储内容 +// * @file 文件目录 +// * @isAppend 是否追加 +// */ +// public synchronized static void writeString(String content, String file, boolean isAppend) { +// try { +// createSDDirectory(saveFn); +// createSDDirectory(savelistFn); +// createSDDirectory(savechannelFn); +// byte[] data = content.getBytes("utf-8"); +// writeBytes(file, data, isAppend); +// } catch (Exception e) { +// System.out.println(e.getMessage()); +// } +// +// } + + public synchronized static boolean writeBytes(String filePath, byte[] data, + boolean isAppend) { + try { + FileOutputStream fos; + if (isAppend) + fos = new FileOutputStream(filePath, true); + else + fos = new FileOutputStream(filePath); + fos.write(data); + fos.close(); + return true; + } catch (Exception e) { + System.out.println(e.getMessage()); + } + return false; + } + + /** + * 读取SD卡中文本文件 + * + * @param fileName + * @return + */ + public synchronized static String readSDFile(String fileName) { + StringBuffer sb = new StringBuffer(); + File f1 = new File(fileName); + String str = null; + try { + InputStream is = new FileInputStream(f1); + InputStreamReader input = new InputStreamReader(is, "UTF-8"); + @SuppressWarnings("resource") + BufferedReader reader = new BufferedReader(input); + while ((str = reader.readLine()) != null) { + sb.append(str); + } + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return sb.toString(); + } + + public String getFILESPATH() { + return FILESPATH; + } + + public boolean hasSD() { + return hasSD; + } + + /** + * 删除单个文件 + * + * @param filePath 被删除文件的文件名 + * @return 文件删除成功返回true,否则返回false + */ + public static boolean deleteFile(String filePath) { + File file = new File(filePath); + if (file.isFile() && file.exists()) { + return file.delete(); + } + return false; + } + + /** + * 删除文件夹以及目录下的文件 + * + * @param filePath 被删除目录的文件路径 + * @return 目录删除成功返回true,否则返回false + */ + public static boolean deleteDirectory(String filePath) { + boolean flag = false; + // 如果filePath不以文件分隔符结尾,自动添加文件分隔符 + if (!filePath.endsWith(File.separator)) { + filePath = filePath + File.separator; + } + File dirFile = new File(filePath); + if (!dirFile.exists() || !dirFile.isDirectory()) { + return false; + } + flag = true; + File[] files = dirFile.listFiles(); + // 遍历删除文件夹下的所有文件(包括子目录) + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) { + // 删除子文件 + flag = deleteFile(files[i].getAbsolutePath()); + if (!flag) + break; + } else { + // 删除子目录 + flag = deleteDirectory(files[i].getAbsolutePath()); + if (!flag) + break; + } + } + if (!flag) + return false; + // 删除当前空目录 + return dirFile.delete(); + } + + public static boolean saveBitmap(Bitmap bm, String picName) { + try { + File f = new File(picName); + if (f.exists()) { + f.delete(); + } + FileOutputStream out = new FileOutputStream(f); + bm.compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + out.close(); + return true; + } catch (FileNotFoundException e) { + e.printStackTrace(); + return false; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 把文件转换成base64 + * + * @param path + * @return + */ + public static String encodeBase64File(String path) throws Exception { + byte[] videoBytes; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + @SuppressWarnings("resource") + FileInputStream fis = new FileInputStream(new File(path)); + byte[] buf = new byte[1024]; + int n; + while (-1 != (n = fis.read(buf))) + baos.write(buf, 0, n); + videoBytes = baos.toByteArray(); + return Base64.encodeToString(videoBytes, Base64.NO_WRAP); + } + + /** + * 根据相册媒体库路径转换成sd卡路径 + * + * @param context + * @param uri + * @return + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static String getPath(final Context context, final Uri uri) { + final boolean isOverKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + // DocumentProvider + if (isOverKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + + split[1]; + } + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), + Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + return getDataColumn(context, contentUri, selection, + selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + return null; + } + + @SuppressLint("NewApi") + public static String getDataColumn(Context context, Uri uri, + String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; + try { + cursor = context.getContentResolver().query(uri, projection, + selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri + .getAuthority()); + } + + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri + .getAuthority()); + } + + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri + .getAuthority()); + } + + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri + .getAuthority()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/ImageCheckoutUtil.java b/app/src/main/java/com/example/administrator/chatdemo/utils/ImageCheckoutUtil.java new file mode 100644 index 0000000..58997ae --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/ImageCheckoutUtil.java @@ -0,0 +1,61 @@ +package com.example.administrator.chatdemo.utils; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Build; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class ImageCheckoutUtil { + /** + * 检测图片内存大小 + * + * @param data + * @return + */ + @SuppressLint("NewApi") + public static int getImageSize(Bitmap data) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) { + return data.getRowBytes() * data.getHeight(); + } else { + return data.getByteCount(); + } + } + + public static Bitmap getLoacalBitmap(String url) { + try { + ByteArrayOutputStream out; + FileInputStream fis = new FileInputStream(url); + BufferedInputStream bis = new BufferedInputStream(fis); + out = new ByteArrayOutputStream(); + @SuppressWarnings("unused") + int hasRead = 0; + byte[] buffer = new byte[1024 * 2]; + while ((hasRead = bis.read(buffer)) > 0) { + // 读出多少数据,向输出流中写入多少 + out.write(buffer); + out.flush(); + } + out.close(); + fis.close(); + bis.close(); + byte[] data = out.toByteArray(); + // 长宽减半 + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = 3; + return BitmapFactory.decodeByteArray(data, 0, data.length, opts); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/KeyBoardUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/KeyBoardUtils.java new file mode 100644 index 0000000..664f5fe --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/KeyBoardUtils.java @@ -0,0 +1,26 @@ +package com.example.administrator.chatdemo.utils; + +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +/** + * Created by Mao Jiqing on 2016/9/28. + */ +public class KeyBoardUtils { + public static void hideKeyBoard(Context context, View view) { + InputMethodManager imm = (InputMethodManager) context + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); // 强制隐藏键盘 + } + + public static void showKeyBoard(Context context, View view) { + view.setFocusable(true); + view.setFocusableInTouchMode(true); + view.requestFocus(); + view.findFocus(); + InputMethodManager imm = (InputMethodManager) context + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/LogUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/LogUtils.java new file mode 100644 index 0000000..38aabff --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/LogUtils.java @@ -0,0 +1,67 @@ +package com.example.administrator.chatdemo.utils; + +import android.util.Log; + +/** + * Created by lzw on 15/7/14. + */ +public class LogUtils { + public static final String LOGTAG = "leanchat"; + public static boolean debugEnabled; + + public LogUtils() { + } + + private static String getDebugInfo() { + Throwable stack = new Throwable().fillInStackTrace(); + StackTraceElement[] trace = stack.getStackTrace(); + int n = 2; + return trace[n].getClassName() + " " + trace[n].getMethodName() + "()" + ":" + trace[n].getLineNumber() + + " "; + } + + private static String getLogInfoByArray(String[] infos) { + StringBuilder sb = new StringBuilder(); + for (String info : infos) { + sb.append(info); + sb.append(" "); + } + return sb.toString(); + } + + public static void i(String... s) { + if (debugEnabled) { + Log.i(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void e(String... s) { + if (debugEnabled) { + Log.e(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void d(String... s) { + if (debugEnabled) { + Log.d(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void v(String... s) { + if (debugEnabled) { + Log.v(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void w(String... s) { + if (debugEnabled) { + Log.w(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void logException(Throwable tr) { + if (debugEnabled) { + Log.e(LOGTAG, getDebugInfo(), tr); + } + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/PathUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/PathUtils.java new file mode 100644 index 0000000..fba9602 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/PathUtils.java @@ -0,0 +1,94 @@ +package com.example.administrator.chatdemo.utils; + +import android.content.Context; +import android.os.Environment; +import android.text.TextUtils; + +import java.io.File; + +/** + * Created by lzw on 15/4/26. + */ +public class PathUtils { + private static File checkAndMkdirs(File file) { + if (!file.exists()) { + file.mkdirs(); + } + return file; + } + + private static boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + return Environment.MEDIA_MOUNTED.equals(state); + } + + /** + * 有 sdcard 的时候,小米是 /storage/sdcard0/Android/data/com.avoscloud.chat/cache/ + * 无 sdcard 的时候,小米是 /data/data/com.avoscloud.chat/cache + * 依赖于包名。所以不同应用使用该库也没问题,要有点理想。 + * + * @return + */ + private static File getAvailableCacheDir(Context context) { + if (isExternalStorageWritable()) { + return context.getExternalCacheDir(); + } else { + // 只有此应用才能访问。拍照的时候有问题,因为拍照的应用写入不了该文件 + return context.getCacheDir(); + } + } + + /** + * 可能文件会被清除掉,需要检查是否存在 + * + * @param id + * @return + */ + public static String getChatFilePath(Context context, String id) { + return (TextUtils.isEmpty(id) ? null : new File(getAvailableCacheDir(context), id).getAbsolutePath()); + } + + /** + * 录音保存的地址 + * + * @return + */ + public static String getRecordPathByCurrentTime(Context context) { + return new File(getAvailableCacheDir(context), "record_" + System.currentTimeMillis()).getAbsolutePath(); + } + + /** + * 拍照保存的地址 + * + * @return + */ + public static String getPicturePathByCurrentTime(Context context) { + String path = new File(getAvailableCacheDir(context), "picture_" + System.currentTimeMillis()).getAbsolutePath(); +// LogUtils.d("picture path ", path); + return path; + } + + public static String getSavePicPath(Context context) { + final String dir = getAvailableCacheDir(context) + "/image_data/"; + File file = new File(dir); + if (!isFileExists(file)) + file.mkdirs(); + String fileName = String.valueOf(System.currentTimeMillis() + ".png"); + return dir + fileName; + } + + public static String getSaveVoicePath(Context context) { + final String dir = getAvailableCacheDir(context) + "/voice_data/"; + File file = new File(dir); + if (!isFileExists(file)) + file.mkdirs(); + return dir; + } + + public static boolean isFileExists(File file) { + if (!file.exists()) { + return false; + } + return true; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/PhotoUtils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/PhotoUtils.java new file mode 100644 index 0000000..c63755c --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/PhotoUtils.java @@ -0,0 +1,372 @@ +package com.example.administrator.chatdemo.utils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.media.ExifInterface; +import android.media.ThumbnailUtils; +import android.widget.ImageView; + +import com.example.administrator.chatdemo.R; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.ImageScaleType; +import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Created by lzw on 15/4/24. + * TODO Util 部分还需要再优化 + */ +public class PhotoUtils { + public static DisplayImageOptions avatarImageOptions = new DisplayImageOptions.Builder() + .showImageOnLoading(R.drawable.chat_default_user_avatar) + .showImageForEmptyUri(R.drawable.chat_default_user_avatar) + .showImageOnFail(R.drawable.chat_default_user_avatar) + .cacheInMemory(true) + .cacheOnDisc(true) + .considerExifParams(true) + .imageScaleType(ImageScaleType.EXACTLY) + .bitmapConfig(Bitmap.Config.RGB_565) + .resetViewBeforeLoading(true)// 设置图片在下载前是否重置,复位 + //.displayer(new RoundedBitmapDisplayer(20)) + //.displayer(new FadeInBitmapDisplayer(100))// 淡入 + .build(); + + private static DisplayImageOptions normalImageOptions = new DisplayImageOptions.Builder() + .showImageOnLoading(R.drawable.chat_common_empty_photo) + .showImageForEmptyUri(R.drawable.chat_common_empty_photo) + .showImageOnFail(R.drawable.chat_common_image_load_fail) + .cacheInMemory(true) + .cacheOnDisc(true) + .considerExifParams(true) + .imageScaleType(ImageScaleType.EXACTLY_STRETCHED) + .bitmapConfig(Bitmap.Config.RGB_565) + .resetViewBeforeLoading(true)// 设置图片在下载前是否重置,复位 + //.displayer(new RoundedBitmapDisplayer(20)) + .displayer(new FadeInBitmapDisplayer(100))// 淡入 + .build(); + + public static void displayImageCacheElseNetwork(ImageView imageView, + String path, String url) { + ImageLoader imageLoader = ImageLoader.getInstance(); + if (path != null) { + File file = new File(path); + if (file.exists()) { + imageLoader.displayImage("file://" + path, imageView, normalImageOptions); + return; + } + } + imageLoader.displayImage(url, imageView, normalImageOptions); + } + + public static String compressImage(String path, String newPath) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, options); + int inSampleSize = 1; + int maxSize = 3000; + if (options.outWidth > maxSize || options.outHeight > maxSize) { + int widthScale = (int) Math.ceil(options.outWidth * 1.0 / maxSize); + int heightScale = (int) Math.ceil(options.outHeight * 1.0 / maxSize); + inSampleSize = Math.max(widthScale, heightScale); + } + options.inJustDecodeBounds = false; + options.inSampleSize = inSampleSize; + Bitmap bitmap = BitmapFactory.decodeFile(path, options); + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + int newW = w; + int newH = h; + if (w > maxSize || h > maxSize) { + if (w > h) { + newW = maxSize; + newH = (int) (newW * h * 1.0 / w); + } else { + newH = maxSize; + newW = (int) (newH * w * 1.0 / h); + } + } + Bitmap newBitmap = Bitmap.createScaledBitmap(bitmap, newW, newH, false); + FileOutputStream outputStream = null; + try { + outputStream = new FileOutputStream(newPath); + newBitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream); + } catch (FileNotFoundException e) { + LogUtils.logException(e); + } finally { + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + recycle(newBitmap); + recycle(bitmap); + return newPath; + } + + public static void recycle(Bitmap bitmap) { + // 先判断是否已经回收 + if (bitmap != null && !bitmap.isRecycled()) { + // 回收并且置为null + bitmap.recycle(); + } + System.gc(); + } + + /** + * 获取指定路径下的图片的指定大小的缩略图 getImageThumbnail + * + * @return Bitmap + * @throws + */ + public static Bitmap getImageThumbnail(String imagePath, int width, + int height) { + Bitmap bitmap = null; + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + // 获取这个图片的宽和高,注意此处的bitmap为null + bitmap = BitmapFactory.decodeFile(imagePath, options); + options.inJustDecodeBounds = false; // 设为 false + // 计算缩放比 + int h = options.outHeight; + int w = options.outWidth; + int beWidth = w / width; + int beHeight = h / height; + int be = 1; + if (beWidth < beHeight) { + be = beWidth; + } else { + be = beHeight; + } + if (be <= 0) { + be = 1; + } + options.inSampleSize = be; + // 重新读入图片,读取缩放后的bitmap,注意这次要把options.inJustDecodeBounds 设为 false + bitmap = BitmapFactory.decodeFile(imagePath, options); + // 利用ThumbnailUtils来创建缩略图,这里要指定要缩放哪个Bitmap对象 + bitmap = ThumbnailUtils.extractThumbnail(bitmap, width, height, + ThumbnailUtils.OPTIONS_RECYCLE_INPUT); + return bitmap; + } + + public static void saveBitmap(String filePath, + Bitmap bitmap) { + File file = new File(filePath); + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + FileOutputStream out = null; + try { + out = new FileOutputStream(file); + if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)) { + out.flush(); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + Utils.closeQuietly(out); + } + } + + public static File getFilePath(String filePath, String fileName) { + File file = null; + makeRootDirectory(filePath); + try { + file = new File(filePath + fileName); + if (!file.exists()) { + file.createNewFile(); + } + + } catch (Exception e) { + // TODO Auto-generated catch block + LogUtils.logException(e); + } + return file; + } + + public static void makeRootDirectory(String filePath) { + File file = null; + try { + file = new File(filePath); + if (!file.exists()) { + file.mkdirs(); + } + } catch (Exception e) { + + } + } + + /** + * 读取图片属性:旋转的角度 + * + * @param path 图片绝对路径 + * @return degree旋转的角度 + */ + + public static int readPictureDegree(String path) { + int degree = 0; + try { + ExifInterface exifInterface = new ExifInterface(path); + int orientation = exifInterface.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + } catch (IOException e) { + e.printStackTrace(); + } + return degree; + + } + + /** + * 旋转图片一定角度 + * rotaingImageView + * + * @return Bitmap + * @throws + */ + public static Bitmap rotaingImageView(int angle, Bitmap bitmap) { + // 旋转图片 动作 + Matrix matrix = new Matrix(); + matrix.postRotate(angle); + // 创建新的图片 + Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), matrix, true); + return resizedBitmap; + } + + /** + * 将图片变为圆角 + * + * @param bitmap 原Bitmap图片 + * @param pixels 图片圆角的弧度(单位:像素(px)) + * @return 带有圆角的图片(Bitmap 类型) + */ + public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) { + Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), + bitmap.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + final RectF rectF = new RectF(rect); + final float roundPx = pixels; + + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawRoundRect(rectF, roundPx, roundPx, paint); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + + return output; + } + + /** + * 将图片转化为圆形头像 + * + * @throws + * @Title: toRoundBitmap + */ + public static Bitmap toRoundBitmap(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + float roundPx; + float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom; + if (width <= height) { + roundPx = width / 2; + + left = 0; + top = 0; + right = width; + bottom = width; + + height = width; + + dst_left = 0; + dst_top = 0; + dst_right = width; + dst_bottom = width; + } else { + roundPx = height / 2; + + float clip = (width - height) / 2; + + left = clip; + right = width - clip; + top = 0; + bottom = height; + width = height; + + dst_left = 0; + dst_top = 0; + dst_right = height; + dst_bottom = height; + } + + Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final Paint paint = new Paint(); + final Rect src = new Rect((int) left, (int) top, (int) right, + (int) bottom); + final Rect dst = new Rect((int) dst_left, (int) dst_top, + (int) dst_right, (int) dst_bottom); + final RectF rectF = new RectF(dst); + + paint.setAntiAlias(true);// 设置画笔无锯齿 + + canvas.drawARGB(0, 0, 0, 0); // 填充整个Canvas + + // 以下有两种方法画圆,drawRounRect和drawCircle + canvas.drawRoundRect(rectF, roundPx, roundPx, paint);// 画圆角矩形,第一个参数为图形显示区域,第二个参数和第三个参数分别是水平圆角半径和垂直圆角半径。 + // canvas.drawCircle(roundPx, roundPx, roundPx, paint); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));// 设置两张图片相交时的模式,参考http://trylovecatch.iteye.com/blog/1189452 + canvas.drawBitmap(bitmap, src, dst, paint); // 以Mode.SRC_IN模式合并bitmap和已经draw了的Circle + + return output; + } + + public static String simpleCompressImage(String path, String newPath) { + Bitmap bitmap = BitmapFactory.decodeFile(path); + FileOutputStream outputStream = null; + try { + outputStream = new FileOutputStream(newPath); + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } finally { + Utils.closeQuietly(outputStream); + } + recycle(bitmap); + return newPath; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/ScreenUtil.java b/app/src/main/java/com/example/administrator/chatdemo/utils/ScreenUtil.java new file mode 100644 index 0000000..771e5c7 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/ScreenUtil.java @@ -0,0 +1,178 @@ +package com.example.administrator.chatdemo.utils; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.Window; +import android.view.WindowManager; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class ScreenUtil { + + private static int screenWidth = 0; + + private static int screenHeight = 0; + private static int screenTotalHeight = 0; + private static int statusBarHeight = 0; + + private static final int TITLE_HEIGHT = 0; + + /** + */ + public static int dip2px(Context context, float dpValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + /** + */ + public static int px2dip(Context context, float pxValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (pxValue / scale + 0.5f); + } + + public static int getScreenWidth(Context context) { +// if (screenWidth != 0) { +// return screenWidth; +// } + DisplayMetrics dm = new DisplayMetrics(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(dm); + screenWidth = dm.widthPixels; + return screenWidth; + } + + public static int getScreenHeight(Context context) { +// if (screenHeight != 0) { +// return screenHeight; +// } + int top = 0; + if (context instanceof Activity) { + top = ((Activity) context).getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop(); + if (top == 0) { + top = (int) (TITLE_HEIGHT * getScreenDensity(context)); + } + } + DisplayMetrics dm = new DisplayMetrics(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(dm); + screenHeight = dm.heightPixels - top; + return screenHeight; + } + + public static int getScreenTotalHeight(Context context) { + if (screenTotalHeight != 0) { + return screenTotalHeight; + } + DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics(); + screenTotalHeight = displayMetrics.heightPixels; + return screenTotalHeight; + } + public static float getScreenDensity(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metric = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metric); + return metric.density; + } + + public static float getScreenDensityDpi(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics metric = new DisplayMetrics(); + wm.getDefaultDisplay().getMetrics(metric); + return metric.densityDpi; + } + public static int getStatusBarHeight(Context context) { + if (statusBarHeight != 0) { + return statusBarHeight; + } + Class c = null; + Object obj = null; + Field field = null; + int x = 0; + try { + c = Class.forName("com.android.internal.R$dimen"); + obj = c.newInstance(); + field = c.getField("status_bar_height"); + x = Integer.parseInt(field.get(obj).toString()); + statusBarHeight = context.getResources().getDimensionPixelSize(x); + } catch (Exception e1) { + e1.printStackTrace(); + } + return statusBarHeight; + } + public static boolean isTablet(Context context) { + return (context.getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } + + + //获取屏幕原始尺寸高度,包括虚拟功能键高度 + public static int getDpi(Context context){ + int dpi = 0; + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = windowManager.getDefaultDisplay(); + DisplayMetrics displayMetrics = new DisplayMetrics(); + @SuppressWarnings("rawtypes") + Class c; + try { + c = Class.forName("android.view.Display"); + @SuppressWarnings("unchecked") + Method method = c.getMethod("getRealMetrics",DisplayMetrics.class); + method.invoke(display, displayMetrics); + dpi=displayMetrics.heightPixels; + }catch(Exception e){ + e.printStackTrace(); + } + return dpi; + } + + /** + * 获取 虚拟按键的高度 + * @param context + * @return + */ + public static int getBottomStatusHeight(Context context){ + int totalHeight = getDpi(context); + + int contentHeight = getScreenHeight(context); + + return totalHeight - contentHeight; + } + + /** + * 标题栏高度 + * @return + */ + public static int getTitleHeight(Activity activity){ + return activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop(); + } + + /** + * 获得状态栏的高度 + * + * @param context + * @return + */ + public static int getStatusHeight(Context context) + { + + int statusHeight = -1; + try + { + Class clazz = Class.forName("com.android.internal.R$dimen"); + Object object = clazz.newInstance(); + int height = Integer.parseInt(clazz.getField("status_bar_height") + .get(object).toString()); + statusHeight = context.getResources().getDimensionPixelSize(height); + } catch (Exception e) + { + e.printStackTrace(); + } + return statusHeight; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/administrator/chatdemo/utils/Utils.java b/app/src/main/java/com/example/administrator/chatdemo/utils/Utils.java new file mode 100644 index 0000000..0cbbf58 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/utils/Utils.java @@ -0,0 +1,54 @@ +package com.example.administrator.chatdemo.utils; + +import android.content.Context; + +import com.example.administrator.chatdemo.bean.ChatMessageBean; + +import org.ocpsoft.prettytime.PrettyTime; + +import java.io.Closeable; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Created by lzw on 15/4/27. + */ +public class Utils { + + public static String millisecsToDateString(long timestamp) { + long gap = System.currentTimeMillis() - timestamp; + if (gap < 1000 * 60 * 60 * 24) { + String s = (new PrettyTime()).format(new Date(timestamp)); + return s; + } else { + SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm"); + return format.format(new Date(timestamp)); + } + } + + public static void closeQuietly(Closeable closeable) { + try { + closeable.close(); + } catch (Exception e) { + } + } + + public static CharSequence getMessageeShorthand(Context context, ChatMessageBean message) { + if (message != null) { + int messageType = ((ChatMessageBean) message).getMessagetype(); + switch (messageType) { + case -1: + return EmotionHelper.replace(context, ((ChatMessageBean) message).getUserContent()); + case -2: + return "[图片]"; + case -3: + return "[语音]"; + + default: + return EmotionHelper.replace(context, ((ChatMessageBean) message).getUserContent()); + } + + } + return "[]"; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemAudioHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemAudioHolder.java new file mode 100644 index 0000000..2590b2f --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemAudioHolder.java @@ -0,0 +1,87 @@ +package com.example.administrator.chatdemo.viewholder; + +import android.content.Context; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.administrator.chatdemo.PlayButtonClickListener; +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.bean.ChatMessageBean; +import com.example.administrator.chatdemo.widget.PlayButton; + + +/** + * Created by wli on 15/9/17. + */ +public class ChatItemAudioHolder extends ChatItemHolder { + + public PlayButton playButton; + protected TextView durationView; + private ImageView ivAudioUnread; + private String path; + + private int mMinItemWith;// 设置对话框的最大宽度和最小宽度 + private int mMaxItemWith; + + public ChatItemAudioHolder(Context context, ViewGroup root, boolean isLeft) { + super(context, root, isLeft); + } + + @Override + public void initView() { + super.initView(); + // 获取系统宽度 + WindowManager wManager = (WindowManager) getContext() + .getSystemService(Context.WINDOW_SERVICE); + DisplayMetrics outMetrics = new DisplayMetrics(); + wManager.getDefaultDisplay().getMetrics(outMetrics); + mMaxItemWith = (int) (outMetrics.widthPixels * 0.7f); + mMinItemWith = (int) (outMetrics.widthPixels * 0.2f); + if (isLeft) { + conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_left_audio_layout, null)); + ivAudioUnread = (ImageView) itemView.findViewById(R.id.chat_item_audio_unread); + } else { + conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_right_audio_layout, null)); + } + playButton = (PlayButton) itemView.findViewById(R.id.chat_item_audio_play_btn); + durationView = (TextView) itemView.findViewById(R.id.chat_item_audio_duration_view); + } + + @Override + public void bindData(Object o) { + super.bindData(o); + if (o instanceof ChatMessageBean) { + final ChatMessageBean audioMessage = (ChatMessageBean) o; + durationView.setText(String.format("%.0f\"", audioMessage.getUserVoiceTime())); + ViewGroup.LayoutParams layoutParams = playButton.getLayoutParams(); + layoutParams.width = (int) (mMinItemWith + mMaxItemWith / 60f + * audioMessage.getUserVoiceTime()); + playButton.setLayoutParams(layoutParams); + String localFilePath = audioMessage.getUserVoicePath(); + final boolean leftSide = audioMessage.getType() == 1; + if (!TextUtils.isEmpty(localFilePath)) { + path = localFilePath; + } else { + path = audioMessage.getUserVoiceUrl(); +// LocalCacheUtils.downloadFileAsync(audioMessage.getUserVoiceUrl(), path); + } + playButton.setPath(path); + if (leftSide && ivAudioUnread != null) + if (audioMessage.getIsListened()) { + ivAudioUnread.setVisibility(View.INVISIBLE); + } + playButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + new PlayButtonClickListener(playButton, ivAudioUnread, audioMessage, leftSide, getContext(), path).onClick(v); + } + }); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemHolder.java new file mode 100644 index 0000000..0941063 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemHolder.java @@ -0,0 +1,105 @@ +package com.example.administrator.chatdemo.viewholder; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.bean.ChatConst; +import com.example.administrator.chatdemo.bean.ChatMessageBean; +import com.example.administrator.chatdemo.utils.Utils; + +/** + * Created by dwq on 2017/7/20/020. + * e-mail:lomapa@163.com + */ + +public class ChatItemHolder extends CommonViewHolder { + protected boolean isLeft; + + protected ImageView avatarView; + protected TextView timeView; + protected TextView nameView; + protected LinearLayout conventLayout; + protected FrameLayout statusLayout; + protected ProgressBar progressBar; + protected TextView statusView; + protected ImageView errorView; + protected ChatMessageBean message; + + public ChatItemHolder(Context context, ViewGroup root, boolean isLeft) { + super(context, root, isLeft ? R.layout.chat_item_left_layout : R.layout.chat_item_right_layout); + this.isLeft = isLeft; + initView(); + } + + public void initView() { + if (isLeft) { + avatarView = (ImageView) itemView.findViewById(R.id.chat_left_iv_avatar); + timeView = (TextView) itemView.findViewById(R.id.chat_left_tv_time); + nameView = (TextView) itemView.findViewById(R.id.chat_left_tv_name); + conventLayout = (LinearLayout) itemView.findViewById(R.id.chat_left_layout_content); + statusLayout = (FrameLayout) itemView.findViewById(R.id.chat_left_layout_status); + statusView = (TextView) itemView.findViewById(R.id.chat_left_tv_status); + progressBar = (ProgressBar) itemView.findViewById(R.id.chat_left_progressbar); + errorView = (ImageView) itemView.findViewById(R.id.chat_left_tv_error); + } else { + avatarView = (ImageView) itemView.findViewById(R.id.chat_right_iv_avatar); + timeView = (TextView) itemView.findViewById(R.id.chat_right_tv_time); + nameView = (TextView) itemView.findViewById(R.id.chat_right_tv_name); + conventLayout = (LinearLayout) itemView.findViewById(R.id.chat_right_layout_content); + statusLayout = (FrameLayout) itemView.findViewById(R.id.chat_right_layout_status); + progressBar = (ProgressBar) itemView.findViewById(R.id.chat_right_progressbar); + errorView = (ImageView) itemView.findViewById(R.id.chat_right_tv_error); + statusView = (TextView) itemView.findViewById(R.id.chat_right_tv_status); + } + } + + @Override + public void bindData(Object o) { + message = (ChatMessageBean) o; + timeView.setText(Utils.millisecsToDateString(message.getTime())); + + String userId = message.getUserId(); + nameView.setText(message.getUserName()); + if (isLeft) { + avatarView.setImageResource(R.mipmap.ic_head_01); + } else { + avatarView.setImageResource(R.mipmap.ic_head_02); + } + switch (message.getSendState()) { + case ChatConst.SENDING: + statusLayout.setVisibility(View.VISIBLE); + progressBar.setVisibility(View.VISIBLE); + statusView.setVisibility(View.GONE); + errorView.setVisibility(View.GONE); + break; + case ChatConst.COMPLETED: + statusLayout.setVisibility(View.VISIBLE); + progressBar.setVisibility(View.GONE); + statusView.setVisibility(View.VISIBLE); + statusView.setVisibility(View.GONE); + errorView.setVisibility(View.GONE); + break; + case ChatConst.SENDERROR: + statusLayout.setVisibility(View.VISIBLE); + progressBar.setVisibility(View.GONE); + statusView.setVisibility(View.GONE); + errorView.setVisibility(View.VISIBLE); + break; + } + } + + public void showTimeView(boolean isShow) { + timeView.setVisibility(isShow ? View.VISIBLE : View.GONE); + } + + public void showUserName(boolean isShow) { + nameView.setVisibility(isShow ? View.VISIBLE : View.GONE); + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemImageHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemImageHolder.java new file mode 100644 index 0000000..a6938ea --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemImageHolder.java @@ -0,0 +1,65 @@ +package com.example.administrator.chatdemo.viewholder; + +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.view.ViewGroup; + +import com.example.administrator.chatdemo.ImageBrowserActivity; +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.bean.ChatMessageBean; +import com.example.administrator.chatdemo.utils.Constants; +import com.example.administrator.chatdemo.utils.PhotoUtils; + +import io.github.leibnik.chatimageview.ChatImageView; + + +/** + * Created by wli on 15/9/17. + */ +public class ChatItemImageHolder extends ChatItemHolder { + + protected ChatImageView contentView; + + public ChatItemImageHolder(Context context, ViewGroup root, boolean isLeft) { + super(context, root, isLeft); + } + + @Override + public void initView() { + super.initView(); + if (isLeft) { +// contentView.setBackgroundResource(R.drawable.rc_ic_bubble_left); + conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_image_layout, null)); + } else { +// contentView.setBackgroundResource(R.drawable.rc_ic_bubble_right); + conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_right_image_layout, null)); + } + contentView = (ChatImageView) itemView.findViewById(R.id.chat_item_image_view); + + contentView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(getContext(), ImageBrowserActivity.class); + intent.putExtra(Constants.IMAGE_LOCAL_PATH, message.getImageLocal()); + intent.putExtra(Constants.IMAGE_URL, message.getImageUrl()); + getContext().startActivity(intent); + } + }); + } + + @Override + public void bindData(Object o) { + super.bindData(o); +// contentView.setImageResource(0); + ChatMessageBean message = (ChatMessageBean) o; + if (message != null) { + String localFilePath = message.getImageLocal(); +// if (!TextUtils.isEmpty(localFilePath)) { +// ImageLoader.getInstance().displayImage("file://" + localFilePath, contentView); +// } else { + PhotoUtils.displayImageCacheElseNetwork(contentView, localFilePath, message.getImageUrl()); +// } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemTextHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemTextHolder.java new file mode 100644 index 0000000..2d30ba9 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ChatItemTextHolder.java @@ -0,0 +1,45 @@ +package com.example.administrator.chatdemo.viewholder; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.bean.ChatMessageBean; +import com.example.administrator.chatdemo.utils.EmotionHelper; + + +/** + * Created by wli on 15/9/17. + */ +public class ChatItemTextHolder extends ChatItemHolder { + + protected TextView contentView; + + public ChatItemTextHolder(Context context, ViewGroup root, boolean isLeft) { + super(context, root, isLeft); + } + + @Override + public void initView() { + super.initView(); + if (isLeft) { + conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_left_text_layout, null)); + contentView = (TextView) itemView.findViewById(R.id.chat_left_text_tv_content); + } else { + conventLayout.addView(View.inflate(getContext(), R.layout.chat_item_right_text_layout, null)); + contentView = (TextView) itemView.findViewById(R.id.chat_right_text_tv_content); + } + } + + @Override + public void bindData(Object o) { + super.bindData(o); + ChatMessageBean message = (ChatMessageBean) o; + if (message != null) { +// ChatMessageBean textMessage = (ChatMessageBean) message; + contentView.setText(EmotionHelper.replace(getContext(), message.getUserContent())); + } + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/CommonViewHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/CommonViewHolder.java new file mode 100644 index 0000000..8ccb180 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/CommonViewHolder.java @@ -0,0 +1,45 @@ +package com.example.administrator.chatdemo.viewholder; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.ViewGroup; + + +/** + * Created by wli on 15/8/25. + * RecyclerView.ViewHolder 的公共类,做了一些封装。目的: + * ViewHolder 与 Adapter 解耦,和 ViewHolder 相关的逻辑都放到 ViewHolder 里边,避免 Adapter 有相关逻辑 + */ + +public abstract class CommonViewHolder extends RecyclerView.ViewHolder { + + public CommonViewHolder(Context context, ViewGroup root, int layoutRes) { + super(LayoutInflater.from(context).inflate(layoutRes, root, false)); + } + + public Context getContext() { + return itemView.getContext(); + } + + /** + * 用给定的 data 对 holder 的 view 进行赋值 + */ + public abstract void bindData(T t); + + public void setData(T t) { + bindData(t); + } + + /** + * 因为 CommonListAdapter 里边无法对于未知类型的 Class 进行实例化 + * 所以需要如果想用 CommonListAdapter,必须要在对应的 CommonViewHolder 实例化一个 HOLDER_CREATOR + * 注意:public static ViewHolderCreator HOLDER_CREATOR,名字与修饰符都不能更改,否则有可能引发失败 + * 具体样例可参见 DiscoverItemHolder + * 如果不使用 CommonListAdapter,则不需要实例化 ViewHolderCreator + * @param + */ + public interface ViewHolderCreator { + public VH createByViewGroupAndType(ViewGroup parent, int viewType); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/administrator/chatdemo/viewholder/ViewHolder.java b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ViewHolder.java new file mode 100644 index 0000000..b87a474 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/viewholder/ViewHolder.java @@ -0,0 +1,23 @@ +package com.example.administrator.chatdemo.viewholder; + +import android.util.SparseArray; +import android.view.View; + +/** + * Created by lzw on 14-9-21. + */ +public class ViewHolder { + public static T findViewById(View view, int id) { + SparseArray viewHolder = (SparseArray) view.getTag(); + if (viewHolder == null) { + viewHolder = new SparseArray<>(); + view.setTag(viewHolder); + } + View childView = viewHolder.get(id); + if (childView == null) { + childView = view.findViewById(id); + viewHolder.put(id, childView); + } + return (T) childView; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/AudioRecordButton.java b/app/src/main/java/com/example/administrator/chatdemo/widget/AudioRecordButton.java new file mode 100644 index 0000000..21fd9af --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/AudioRecordButton.java @@ -0,0 +1,339 @@ +package com.example.administrator.chatdemo.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.utils.AudioManager; +import com.example.administrator.chatdemo.utils.PathUtils; + +import java.io.File; +import java.math.BigDecimal; + +public class AudioRecordButton extends Button implements AudioManager.AudioStageListener { + private static final int STATE_NORMAL = 1; + private static final int STATE_RECORDING = 2; + private static final int STATE_WANT_TO_CANCEL = 3; + private static final int DISTANCE_Y_CANCEL = 50; + private static final int OVERTIME = 60; + private int mCurrentState = STATE_NORMAL; + // 已经开始录音 + private boolean isRecording = false; + private DialogManager mDialogManager; + private float mTime = 0; + // 是否触发了onlongclick,准备好了 + private boolean mReady; + private AudioManager mAudioManager; + private String saveDir = PathUtils.getSaveVoicePath(getContext()); + + private Handler mp3handler = new Handler() { + + @Override + public void handleMessage(Message msg) { + // TODO Auto-generated method stub + switch (msg.what) { + case AudioManager.MSG_ERROR_AUDIO_RECORD: + Toast.makeText(getContext(), "录音权限被屏蔽或者录音设备损坏!\n请在设置中检查是否开启权限!", + Toast.LENGTH_SHORT).show(); + mDialogManager.dimissDialog(); + mAudioManager.cancel(); + reset(); + break; + default: + break; + } + } + + }; + + /** + * 先实现两个参数的构造方法,布局会默认引用这个构造方法, 用一个 构造参数的构造方法来引用这个方法 * @param context + */ + + public AudioRecordButton(Context context) { + this(context, null); + // TODO Auto-generated constructor stub + } + + public AudioRecordButton(Context context, AttributeSet attrs) { + super(context, attrs); + + mDialogManager = new DialogManager(getContext()); +// +// try { +// FileSaveUtil.createSDDirectory(FileSaveUtil.voice_dir); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + mAudioManager = AudioManager.getInstance(PathUtils.getSaveVoicePath(getContext())); + mAudioManager.setOnAudioStageListener(this); + mAudioManager.setHandle(mp3handler); + setOnLongClickListener(new OnLongClickListener() { + + @Override + public boolean onLongClick(View v) { + // TODO Auto-generated method +// try { +// FileSaveUtil.createSDDirectory(saveDir); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + mAudioManager.setVocDir(saveDir); + mListener.onStart(); + mReady = true; + mAudioManager.prepareAudio(); + return false; + } + }); + // TODO Auto-generated constructor stub + } + + public void setSaveDir(String saveDir) { + this.saveDir = saveDir + saveDir; + } + + /** + * 录音完成后的回调,回调给activiy,可以获得mtime和文件的路径 + */ + public interface AudioFinishRecorderListener { + void onStart(); + + void onFinished(float seconds, String filePath); + } + + private AudioFinishRecorderListener mListener; + + public void setAudioFinishRecorderListener( + AudioFinishRecorderListener listener) { + mListener = listener; + } + + // 获取音量大小的runnable + private Runnable mGetVoiceLevelRunnable = new Runnable() { + + @Override + public void run() { + // TODO Auto-generated method stub + while (isRecording) { + try { + Thread.sleep(100); + mTime += 0.1f; + mhandler.sendEmptyMessage(MSG_VOICE_CHANGE); + if (mTime >= OVERTIME) { + mTime = 60; + mhandler.sendEmptyMessage(MSG_OVERTIME_SEND); + isRecording = false; + break; + } + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + }; + + // 准备三个常量 + private static final int MSG_AUDIO_PREPARED = 0X110; + private static final int MSG_VOICE_CHANGE = 0X111; + private static final int MSG_DIALOG_DIMISS = 0X112; + private static final int MSG_OVERTIME_SEND = 0X113; + + private Handler mhandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_AUDIO_PREPARED: + // 显示应该是在audio end prepare之后回调 + if (isTouch) { + mTime = 0; + mDialogManager.showRecordingDialog(); + isRecording = true; + new Thread(mGetVoiceLevelRunnable).start(); + } + // 需要开启一个线程来变换音量 + break; + case MSG_VOICE_CHANGE: + mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(3)); + break; + case MSG_DIALOG_DIMISS: + isRecording = false; + mDialogManager.dimissDialog(); + break; + case MSG_OVERTIME_SEND: + mDialogManager.tooLong(); + mhandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 持续1.3s + if (mListener != null) {// 并且callbackActivity,保存录音 + File file = new File(mAudioManager.getCurrentFilePath()); + if (PathUtils.isFileExists(file)) { + mListener.onFinished(mTime, + mAudioManager.getCurrentFilePath()); + } else { + mp3handler.sendEmptyMessage(AudioManager.MSG_ERROR_AUDIO_RECORD); + } + } + isRecording = false; + reset();// 恢复标志位 + break; + } + } + + ; + }; + + // 在这里面发送一个handler的消息 + @Override + public void wellPrepared() { + // TODO Auto-generated method stub + mhandler.sendEmptyMessage(MSG_AUDIO_PREPARED); + } + + /** + * 直接复写这个监听函数 + */ + private boolean isTouch = false; + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + // TODO Auto-generated method stub + int action = event.getAction(); + int x = (int) event.getX(); + int y = (int) event.getY(); + + switch (action) { + case MotionEvent.ACTION_DOWN: + isTouch = true; + changeState(STATE_RECORDING); + break; + case MotionEvent.ACTION_MOVE: + + if (isRecording) { + + // 根据x,y来判断用户是否想要取消 + if (wantToCancel(x, y)) { + changeState(STATE_WANT_TO_CANCEL); + } else { + changeState(STATE_RECORDING); + } + + } + + break; + case MotionEvent.ACTION_UP: + // 首先判断是否有触发onlongclick事件,没有的话直接返回reset + isTouch = false; + if (!mReady) { + reset(); + return super.onTouchEvent(event); + } + // 如果按的时间太短,还没准备好或者时间录制太短,就离开了,则显示这个dialog + if (!isRecording || mTime < 0.6f) { + mDialogManager.tooShort(); + mAudioManager.cancel(); + mhandler.sendEmptyMessageDelayed(MSG_DIALOG_DIMISS, 1300);// 持续1.3s + } else if (mCurrentState == STATE_RECORDING) {// 正常录制结束 + mDialogManager.dimissDialog(); + mAudioManager.release();// release释放一个mediarecorder + if (mListener != null) {// 并且callbackActivity,保存录音 + BigDecimal b = new BigDecimal(mTime); + float f1 = b.setScale(1, BigDecimal.ROUND_HALF_UP) + .floatValue(); + String currentFilePath = mAudioManager.getCurrentFilePath(); + Log.i("AudioRecordButton", "filePath:" + currentFilePath); + File file = new File(currentFilePath); + if (PathUtils.isFileExists(file)) { + mListener.onFinished(f1, currentFilePath); + } else { + mp3handler.sendEmptyMessage(AudioManager.MSG_ERROR_AUDIO_RECORD); + } + } + } else if (mCurrentState == STATE_WANT_TO_CANCEL) { + mAudioManager.cancel(); + mDialogManager.dimissDialog(); + } + isRecording = false; + reset();// 恢复标志位 + + break; + case MotionEvent.ACTION_CANCEL: + isTouch = false; + reset(); + break; + + } + + return super.onTouchEvent(event); + } + + /** + * 回复标志位以及状态 + */ + private void reset() { + // TODO Auto-generated method stub + isRecording = false; + changeState(STATE_NORMAL); + mReady = false; + mTime = 0; + } + + private boolean wantToCancel(int x, int y) { + // TODO Auto-generated method stub + + if (x < 0 || x > getWidth()) {// 判断是否在左边,右边,上边,下边 + return true; + } + if (y < -DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL) { + return true; + } + + return false; + } + + private void changeState(int state) { + // TODO Auto-generated method stub + if (mCurrentState != state) { + mCurrentState = state; + switch (mCurrentState) { + case STATE_NORMAL: + setBackgroundResource(R.drawable.button_recordnormal); + setText(R.string.normal); + + break; + case STATE_RECORDING: + setBackgroundResource(R.drawable.button_recording); + setText(R.string.recording); + if (isRecording) { + mDialogManager.recording(); + // 复写dialog.recording(); + } + break; + + case STATE_WANT_TO_CANCEL: + setBackgroundResource(R.drawable.button_recording); + setText(R.string.want_to_cancle); + // dialog want to cancel + mDialogManager.wantToCancel(); + break; + + } + } + + } + + @Override + public boolean onPreDraw() { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/ChatBottomView.java b/app/src/main/java/com/example/administrator/chatdemo/widget/ChatBottomView.java new file mode 100644 index 0000000..d5ed323 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/ChatBottomView.java @@ -0,0 +1,58 @@ +package com.example.administrator.chatdemo.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; + +import com.example.administrator.chatdemo.R; + + +public class ChatBottomView extends LinearLayout{ + private View baseView; + private LinearLayout imageGroup; + private LinearLayout cameraGroup; + private HeadIconSelectorView.OnHeadIconClickListener onHeadIconClickListener; + public static final int FROM_CAMERA = 1; + public static final int FROM_GALLERY = 2; + public ChatBottomView(Context context, AttributeSet attrs) { + super(context, attrs); + // TODO Auto-generated constructor stub + findView(); + init(); + } + + private void findView(){ + baseView = LayoutInflater.from(getContext()).inflate(R.layout.layout_tongbaobottom, this); + imageGroup = (LinearLayout) baseView.findViewById(R.id.image_bottom_group); + cameraGroup = (LinearLayout) baseView.findViewById(R.id.camera_group); + } + private void init(){ + cameraGroup.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (null != onHeadIconClickListener) { + onHeadIconClickListener.onClick(FROM_CAMERA); + } + } + }); + imageGroup.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + + if (null != onHeadIconClickListener) { + onHeadIconClickListener.onClick(FROM_GALLERY); + } + } + }); + } + + public void setOnHeadIconClickListener( + HeadIconSelectorView.OnHeadIconClickListener onHeadIconClickListener) { + // TODO Auto-generated method stub + this.onHeadIconClickListener = onHeadIconClickListener; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/CircleIndicator.java b/app/src/main/java/com/example/administrator/chatdemo/widget/CircleIndicator.java new file mode 100644 index 0000000..82bdd11 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/CircleIndicator.java @@ -0,0 +1,304 @@ +package com.example.administrator.chatdemo.widget; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.database.DataSetObserver; +import android.os.Build; +import android.support.annotation.AnimatorRes; +import android.support.annotation.DrawableRes; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.animation.Interpolator; +import android.widget.LinearLayout; + +import com.example.administrator.chatdemo.R; + +import static android.support.v4.view.ViewPager.OnPageChangeListener; + +public class CircleIndicator extends LinearLayout { + + private final static int DEFAULT_INDICATOR_WIDTH = 5; + private ViewPager mViewpager; + private int mIndicatorMargin = -1; + private int mIndicatorWidth = -1; + private int mIndicatorHeight = -1; + private int mAnimatorResId = R.anim.scale_with_alpha; + private int mAnimatorReverseResId = 0; + private int mIndicatorBackgroundResId = R.drawable.white_radius; + private int mIndicatorUnselectedBackgroundResId = R.drawable.white_radius; + private Animator mAnimatorOut; + private Animator mAnimatorIn; + private Animator mImmediateAnimatorOut; + private Animator mImmediateAnimatorIn; + + private int mLastPosition = -1; + + public CircleIndicator(Context context) { + super(context); + init(context, null); + } + + public CircleIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public CircleIndicator(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CircleIndicator(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + handleTypedArray(context, attrs); + checkIndicatorConfig(context); + } + + private void handleTypedArray(Context context, AttributeSet attrs) { + if (attrs == null) { + return; + } + + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleIndicator); + mIndicatorWidth = + typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_width, -1); + mIndicatorHeight = + typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_height, -1); + mIndicatorMargin = + typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_margin, -1); + + mAnimatorResId = typedArray.getResourceId(R.styleable.CircleIndicator_ci_animator, + R.anim.scale_with_alpha); + mAnimatorReverseResId = + typedArray.getResourceId(R.styleable.CircleIndicator_ci_animator_reverse, 0); + mIndicatorBackgroundResId = + typedArray.getResourceId(R.styleable.CircleIndicator_ci_drawable, + R.drawable.white_radius); + mIndicatorUnselectedBackgroundResId = + typedArray.getResourceId(R.styleable.CircleIndicator_ci_drawable_unselected, + mIndicatorBackgroundResId); + + int orientation = typedArray.getInt(R.styleable.CircleIndicator_orientation, -1); + setOrientation(orientation == VERTICAL ? VERTICAL : HORIZONTAL); + + int gravity = typedArray.getInt(R.styleable.CircleIndicator_gravity_ci, -1); + setGravity(gravity >= 0 ? gravity : Gravity.CENTER); + + typedArray.recycle(); + } + + /** + * Create and configure Indicator in Java code. + */ + public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin) { + configureIndicator(indicatorWidth, indicatorHeight, indicatorMargin, + R.anim.scale_with_alpha, 0, R.drawable.white_radius, R.drawable.white_radius); + } + + public void configureIndicator(int indicatorWidth, int indicatorHeight, int indicatorMargin, + @AnimatorRes int animatorId, @AnimatorRes int animatorReverseId, + @DrawableRes int indicatorBackgroundId, + @DrawableRes int indicatorUnselectedBackgroundId) { + + mIndicatorWidth = indicatorWidth; + mIndicatorHeight = indicatorHeight; + mIndicatorMargin = indicatorMargin; + + mAnimatorResId = animatorId; + mAnimatorReverseResId = animatorReverseId; + mIndicatorBackgroundResId = indicatorBackgroundId; + mIndicatorUnselectedBackgroundResId = indicatorUnselectedBackgroundId; + + checkIndicatorConfig(getContext()); + } + + private void checkIndicatorConfig(Context context) { + mIndicatorWidth = (mIndicatorWidth < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorWidth; + mIndicatorHeight = + (mIndicatorHeight < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorHeight; + mIndicatorMargin = + (mIndicatorMargin < 0) ? dip2px(DEFAULT_INDICATOR_WIDTH) : mIndicatorMargin; + + mAnimatorResId = (mAnimatorResId == 0) ? R.anim.scale_with_alpha : mAnimatorResId; + + mAnimatorOut = createAnimatorOut(context); + mImmediateAnimatorOut = createAnimatorOut(context); + mImmediateAnimatorOut.setDuration(0); + + mAnimatorIn = createAnimatorIn(context); + mImmediateAnimatorIn = createAnimatorIn(context); + mImmediateAnimatorIn.setDuration(0); + + mIndicatorBackgroundResId = (mIndicatorBackgroundResId == 0) ? R.drawable.white_radius + : mIndicatorBackgroundResId; + mIndicatorUnselectedBackgroundResId = + (mIndicatorUnselectedBackgroundResId == 0) ? mIndicatorBackgroundResId + : mIndicatorUnselectedBackgroundResId; + } + + private Animator createAnimatorOut(Context context) { + return AnimatorInflater.loadAnimator(context, mAnimatorResId); + } + + private Animator createAnimatorIn(Context context) { + Animator animatorIn; + if (mAnimatorReverseResId == 0) { + animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorResId); + animatorIn.setInterpolator(new ReverseInterpolator()); + } else { + animatorIn = AnimatorInflater.loadAnimator(context, mAnimatorReverseResId); + } + return animatorIn; + } + + public void setViewPager(ViewPager viewPager) { + mViewpager = viewPager; + if (mViewpager != null && mViewpager.getAdapter() != null) { + mLastPosition = -1; + createIndicators(); + mViewpager.removeOnPageChangeListener(mInternalPageChangeListener); + mViewpager.addOnPageChangeListener(mInternalPageChangeListener); + mInternalPageChangeListener.onPageSelected(mViewpager.getCurrentItem()); + } + } + + private final OnPageChangeListener mInternalPageChangeListener = new OnPageChangeListener() { + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + + if (mViewpager.getAdapter() == null || mViewpager.getAdapter().getCount() <= 0) { + return; + } + + if (mAnimatorIn.isRunning()) { + mAnimatorIn.end(); + mAnimatorIn.cancel(); + } + + if (mAnimatorOut.isRunning()) { + mAnimatorOut.end(); + mAnimatorOut.cancel(); + } + + View currentIndicator; + if (mLastPosition >= 0 && (currentIndicator = getChildAt(mLastPosition)) != null) { + currentIndicator.setBackgroundResource(mIndicatorUnselectedBackgroundResId); + mAnimatorIn.setTarget(currentIndicator); + mAnimatorIn.start(); + } + + View selectedIndicator = getChildAt(position); + if (selectedIndicator != null) { + selectedIndicator.setBackgroundResource(mIndicatorBackgroundResId); + mAnimatorOut.setTarget(selectedIndicator); + mAnimatorOut.start(); + } + mLastPosition = position; + } + + @Override + public void onPageScrollStateChanged(int state) { + } + }; + + public DataSetObserver getDataSetObserver() { + return mInternalDataSetObserver; + } + + private DataSetObserver mInternalDataSetObserver = new DataSetObserver() { + @Override + public void onChanged() { + super.onChanged(); + if (mViewpager == null) { + return; + } + + int newCount = mViewpager.getAdapter().getCount(); + int currentCount = getChildCount(); + + if (newCount == currentCount) { // No change + return; + } else if (mLastPosition < newCount) { + mLastPosition = mViewpager.getCurrentItem(); + } else { + mLastPosition = -1; + } + + createIndicators(); + } + }; + + /** + * @deprecated User ViewPager addOnPageChangeListener + */ + @Deprecated + public void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) { + if (mViewpager == null) { + throw new NullPointerException("can not find Viewpager , setViewPager first"); + } + mViewpager.removeOnPageChangeListener(onPageChangeListener); + mViewpager.addOnPageChangeListener(onPageChangeListener); + } + + private void createIndicators() { + removeAllViews(); + int count = mViewpager.getAdapter().getCount(); + if (count <= 0) { + return; + } + int currentItem = mViewpager.getCurrentItem(); + + for (int i = 0; i < count; i++) { + if (currentItem == i) { + addIndicator(mIndicatorBackgroundResId, mImmediateAnimatorOut); + } else { + addIndicator(mIndicatorUnselectedBackgroundResId, mImmediateAnimatorIn); + } + } + } + + private void addIndicator(@DrawableRes int backgroundDrawableId, Animator animator) { + if (animator.isRunning()) { + animator.end(); + animator.cancel(); + } + + View Indicator = new View(getContext()); + Indicator.setBackgroundResource(backgroundDrawableId); + addView(Indicator, mIndicatorWidth, mIndicatorHeight); + LayoutParams lp = (LayoutParams) Indicator.getLayoutParams(); + lp.leftMargin = mIndicatorMargin; + lp.rightMargin = mIndicatorMargin; + Indicator.setLayoutParams(lp); + + animator.setTarget(Indicator); + animator.start(); + } + + private class ReverseInterpolator implements Interpolator { + @Override + public float getInterpolation(float value) { + return Math.abs(1.0f - value); + } + } + + public int dip2px(float dpValue) { + final float scale = getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/DialogManager.java b/app/src/main/java/com/example/administrator/chatdemo/widget/DialogManager.java new file mode 100644 index 0000000..0ee367f --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/DialogManager.java @@ -0,0 +1,149 @@ +package com.example.administrator.chatdemo.widget; + +import android.annotation.SuppressLint; +import android.app.Dialog; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.TextView; + +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.utils.ScreenUtil; + + +@SuppressLint("InflateParams") +public class DialogManager { + + /** + * 以下为dialog的初始化控件,包括其中的布局文件 + */ + + private Dialog mDialog; + + private ImageView mIcon; + private ImageView mVoice; + + private TextView mLable; + + private Context mContext; + + public DialogManager(Context context) { + // TODO Auto-generated constructor stub + mContext = context; + } + + public void showRecordingDialog() { + // TODO Auto-generated method stub + + mDialog = new Dialog(mContext, R.style.Theme_audioDialog); + mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + + // 用layoutinflater来引用布局 + LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(R.layout.layout_voice_dialog_manager, null); + mDialog.setContentView(view); + + mIcon = (ImageView) mDialog.findViewById(R.id.dialog_icon); + mVoice = (ImageView) mDialog.findViewById(R.id.dialog_voice); + mLable = (TextView) mDialog.findViewById(R.id.recorder_dialogtext); + + Window dialogWindow = mDialog.getWindow(); + WindowManager.LayoutParams lp = dialogWindow.getAttributes(); + int width = ScreenUtil.getScreenWidth(mContext) / 2; + lp.width = width; // 宽度 + lp.height = width; // 高度 + dialogWindow.setAttributes(lp); + mDialog.setCancelable(false); + mDialog.show(); + + } + + /** + * 设置正在录音时的dialog界面 + */ + public void recording() { + if (mDialog != null && mDialog.isShowing()) { + mIcon.setVisibility(View.GONE); + mVoice.setVisibility(View.VISIBLE); + mLable.setVisibility(View.VISIBLE); + mLable.setText(R.string.shouzhishanghua); + } + } + + /** + * 取消界面 + */ + public void wantToCancel() { + // TODO Auto-generated method stub + if (mDialog != null && mDialog.isShowing()) { + mIcon.setVisibility(View.VISIBLE); + mVoice.setVisibility(View.GONE); + mLable.setVisibility(View.VISIBLE); + + mIcon.setImageResource(R.mipmap.cancel); + mLable.setText(R.string.want_to_cancle); + } + + } + + // 时间过短 + public void tooShort() { + // TODO Auto-generated method stub + if (mDialog != null && mDialog.isShowing()) { + mIcon.setVisibility(View.VISIBLE); + mVoice.setVisibility(View.GONE); + mLable.setVisibility(View.VISIBLE); + + mIcon.setImageResource(R.mipmap.voice_to_short); + mLable.setText(R.string.tooshort); + } + + } + // 时间过长 + public void tooLong() { + // TODO Auto-generated method stub + if (mDialog != null && mDialog.isShowing()) { + mIcon.setVisibility(View.VISIBLE); + mVoice.setVisibility(View.GONE); + mLable.setVisibility(View.VISIBLE); + + mIcon.setImageResource(R.mipmap.voice_to_short); + mLable.setText(R.string.toolong); + } + + } + // 隐藏dialog + public void dimissDialog() { + // TODO Auto-generated method stub + + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); + mDialog = null; + } + + } + + public void updateVoiceLevel(int level) { + // TODO Auto-generated method stub + + if (mDialog != null && mDialog.isShowing()) { + int resId; + if(level >= 1 && level < 2){ + resId = mContext.getResources().getIdentifier("tb_voice1", + "mipmap", mContext.getPackageName()); + }else if(level >= 2 && level < 3){ + resId = mContext.getResources().getIdentifier("tb_voice2", + "mipmap", mContext.getPackageName()); + }else{ + resId = mContext.getResources().getIdentifier("tb_voice3", + "mipmap", mContext.getPackageName()); + } + mVoice.setImageResource(resId); + } + + } + +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/EmotionEditText.java b/app/src/main/java/com/example/administrator/chatdemo/widget/EmotionEditText.java new file mode 100644 index 0000000..d3fdd95 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/EmotionEditText.java @@ -0,0 +1,33 @@ +package com.example.administrator.chatdemo.widget; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.widget.EditText; + +import com.example.administrator.chatdemo.utils.EmotionHelper; + + +public class EmotionEditText extends EditText { + + public EmotionEditText(Context context) { + super(context); + } + + public EmotionEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public EmotionEditText(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public void setText(CharSequence text, BufferType type) { + if (!TextUtils.isEmpty(text)) { + super.setText(EmotionHelper.replace(getContext(), text.toString()), type); + } else { + super.setText(text, type); + } + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/HeadIconSelectorView.java b/app/src/main/java/com/example/administrator/chatdemo/widget/HeadIconSelectorView.java new file mode 100644 index 0000000..3762bd5 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/HeadIconSelectorView.java @@ -0,0 +1,331 @@ +package com.example.administrator.chatdemo.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.ScaleAnimation; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import com.example.administrator.chatdemo.R; + + +public class HeadIconSelectorView extends RelativeLayout implements + GestureDetector.OnGestureListener { + + private View baseView; + + private RelativeLayout mainRl; + private LinearLayout bottomLl; + private LinearLayout cameraLl; + private LinearLayout galleryLl; + private LinearLayout cancelLl; + + private GestureDetector gestureDetector; // 手势检测器 + private boolean isAnimationing = false; + private OnHeadIconClickListener onHeadIconClickListener; + public static final int FROM_CAMERA = 2; + public static final int FROM_GALLERY = 3; + public static final int CANCEL = 4; + public static final int BLANK_CANCEL = 5; + + public HeadIconSelectorView(Context context) { + super(context); + init(); + } + + @SuppressLint("ClickableViewAccessibility") + private void init() { + findView(); + this.setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return gestureDetector.onTouchEvent(event); + } + }); + bottomLl.setVisibility(View.INVISIBLE); + mainRl.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (null != onHeadIconClickListener) { + onHeadIconClickListener.onClick(BLANK_CANCEL); + } + cancel(); + } + }); + cancelLl.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (null != onHeadIconClickListener) { + onHeadIconClickListener.onClick(CANCEL); + } + cancel(); + } + }); + cameraLl.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (null != onHeadIconClickListener) { + onHeadIconClickListener.onClick(FROM_CAMERA); + } + cancel(); + } + }); + galleryLl.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + + if (null != onHeadIconClickListener) { + onHeadIconClickListener.onClick(FROM_GALLERY); + } + cancel(); + } + }); + } + + @SuppressWarnings("deprecation") + private void findView() { + gestureDetector = new GestureDetector(this); + baseView = LayoutInflater.from(getContext()).inflate( + R.layout.layout_view_headicon, this); + mainRl = (RelativeLayout) baseView.findViewById(R.id.head_icon_main_rl); + bottomLl = (LinearLayout) baseView.findViewById(R.id.head_icon_main_ll); + cameraLl = (LinearLayout) baseView + .findViewById(R.id.head_icon_camera_ll); + galleryLl = (LinearLayout) baseView + .findViewById(R.id.head_icon_gallery_ll); + cancelLl = (LinearLayout) baseView + .findViewById(R.id.head_icon_cancel_ll); + } + + protected void bottomViewFlyIn() { + final ScaleAnimation sa1 = new ScaleAnimation(1, 1, 0, 1.2f, + Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f); + sa1.setDuration(250); + final ScaleAnimation sa2 = new ScaleAnimation(1, 1, 1.2f, 1, + Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f); + sa2.setDuration(150); + sa1.setAnimationListener(new AnimationListener() { + + @Override + public void onAnimationStart(Animation animation) { + isAnimationing = true; + bottomLl.setVisibility(VISIBLE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimationing = false; + bottomLl.startAnimation(sa2); + } + }); + bottomLl.startAnimation(sa1); + } + + protected void bottomViewFlyOut() { + final ScaleAnimation sa1 = new ScaleAnimation(1, 1, 1, 1.2f, + Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f); + sa1.setDuration(150); + final ScaleAnimation sa2 = new ScaleAnimation(1, 1, 1.2f, 0, + Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 1f); + sa2.setDuration(250); + sa1.setAnimationListener(new AnimationListener() { + + @Override + public void onAnimationStart(Animation animation) { + isAnimationing = true; + bottomLl.setVisibility(VISIBLE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + bottomLl.startAnimation(sa2); + } + }); + sa2.setAnimationListener(new AnimationListener() { + + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimationing = false; + bottomLl.setVisibility(INVISIBLE); + flyOut(); + } + }); + bottomLl.startAnimation(sa1); + } + + protected void cancel() { + if (!isAnimationing) { + if (bottomLl.getVisibility() == VISIBLE) { + bottomViewFlyOut(); + return; + } + } + } + + public void flyIn() { + AlphaAnimation aa = new AlphaAnimation(0, 1); + aa.setDuration(300); + aa.setAnimationListener(new AnimationListener() { + + @Override + public void onAnimationStart(Animation animation) { + isAnimationing = true; + setVisibility(VISIBLE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimationing = false; + bottomViewFlyIn(); + } + }); + startAnimation(aa); + } + + public void flyOut() { + AlphaAnimation aa = new AlphaAnimation(1, 0); + aa.setDuration(300); + aa.setAnimationListener(new AnimationListener() { + + @Override + public void onAnimationStart(Animation animation) { + isAnimationing = true; + setVisibility(VISIBLE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + isAnimationing = false; + destroy(); + } + }); + startAnimation(aa); + } + + protected void destroy() { + if (this.getParent() != null) { + ((ViewGroup) this.getParent()).removeView(this); + } + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (!isAnimationing) { + if (bottomLl.getVisibility() == VISIBLE) { + bottomViewFlyOut(); + return true; + } + } else { + return true; + } + } + return super.onKeyDown(keyCode, event); + } + + public OnHeadIconClickListener getOnHeadIconClickListener() { + return onHeadIconClickListener; + } + + public void setOnHeadIconClickListener( + OnHeadIconClickListener onHeadIconClickListener) { + this.onHeadIconClickListener = onHeadIconClickListener; + } + + public interface OnHeadIconClickListener { + public void onClick(int from); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + return gestureDetector.onTouchEvent(event); + } + + @Override + public boolean onDown(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + // TODO Auto-generated method stub + + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + // TODO Auto-generated method stub + + } + + private float minVelocityY = 100f;// 10个像素每秒 + private float minDistanceY = 100f;// 100个像素 + + // 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, + // 多个ACTION_MOVE, 1个ACTION_UP触发 + // e1:第1个ACTION_DOWN MotionEvent + // e2:最后一个ACTION_MOVE MotionEvent + // velocityX:X轴上的移动速度,像素/秒 + // velocityY:Y轴上的移动速度,像素/秒 + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + // 手势从上到下且移动速度较快 + if (e2.getY() - e1.getY() > minDistanceY && velocityY > minVelocityY) { + cancel(); + } + return false; + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/InputBarLayout.java b/app/src/main/java/com/example/administrator/chatdemo/widget/InputBarLayout.java new file mode 100644 index 0000000..abfde57 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/InputBarLayout.java @@ -0,0 +1,315 @@ +package com.example.administrator.chatdemo.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.text.Selection; +import android.text.Spannable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.adapter.ChatEmotionGridAdapter; +import com.example.administrator.chatdemo.adapter.ChatEmotionPagerAdapter; +import com.example.administrator.chatdemo.utils.EmotionHelper; +import com.example.administrator.chatdemo.utils.KeyBoardUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by dwq on 2017/7/19/019. + * e-mail:lomapa@163.com + */ + +public class InputBarLayout extends LinearLayout { + private EditText mEditTextContent; + private ImageView ivMore; + private ImageView ivEmoji; + private ImageView ivVoice; + private ViewPager emotionPager; + private AudioRecordButton voiceBtn; + private LinearLayout llyEmojiGroup; + private TextView tvSend; + private ChatBottomView cbvOther; + + private CircleIndicator ciBanner; + + private OnMessageSendListener onMessageSendListener; + + private OnBottomIconClickListener onBottomIconClickListener; + + public void setOnMessageSendListener(OnMessageSendListener onMessageSendListener) { + this.onMessageSendListener = onMessageSendListener; + } + + public void setOnBottomIconClickListener(OnBottomIconClickListener onBottomIconClickListener) { + this.onBottomIconClickListener = onBottomIconClickListener; + } + + public InputBarLayout(Context context) { + super(context); + initView(context); + } + + public InputBarLayout(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + public InputBarLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context); + } + + private void initView(Context context) { + View.inflate(context, R.layout.chat_input_bottom_bar_layout, this); + mEditTextContent = (EditText) findViewById(R.id.et_msg); + ivMore = (ImageView) findViewById(R.id.iv_more); + ivEmoji = (ImageView) findViewById(R.id.iv_emoji); + ivVoice = (ImageView) findViewById(R.id.iv_voice); + emotionPager = (ViewPager) findViewById(R.id.vPager); + voiceBtn = (AudioRecordButton) findViewById(R.id.voice_btn); + llyEmojiGroup = (LinearLayout) findViewById(R.id.lly_emoji_group); + tvSend = (TextView) findViewById(R.id.tv_send); + cbvOther = (ChatBottomView) findViewById(R.id.cbv_other); + ciBanner = (CircleIndicator) findViewById(R.id.ci_banner); + + initListener(context); + } + + private void initListener(final Context context) { + mEditTextContent.setOnKeyListener(onKeyListener); + + ivVoice.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View arg0) { + // TODO Auto-generated method stub + if (voiceBtn.getVisibility() == View.GONE) { + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji); + ivMore.setBackgroundResource(R.mipmap.ic_btn_more); + mEditTextContent.setVisibility(View.GONE); + llyEmojiGroup.setVisibility(View.GONE); + cbvOther.setVisibility(View.GONE); + voiceBtn.setVisibility(View.VISIBLE); + KeyBoardUtils.hideKeyBoard(context, + mEditTextContent); + ivVoice.setBackgroundResource(R.mipmap.ic_btn_keybroad); + } else { + mEditTextContent.setVisibility(View.VISIBLE); + voiceBtn.setVisibility(View.GONE); + ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice); + KeyBoardUtils.showKeyBoard(context, mEditTextContent); + } + } + + }); + + ivMore.setOnClickListener(new View.OnClickListener() { + + @SuppressLint("NewApi") + @Override + public void onClick(View v) { + // TODO Auto-generated method stub + llyEmojiGroup.setVisibility(View.GONE); + if (cbvOther.getVisibility() == View.GONE + ) { + mEditTextContent.setVisibility(View.VISIBLE); + ivMore.setFocusable(true); + voiceBtn.setVisibility(View.GONE); + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji); + ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice); + cbvOther.setVisibility(View.VISIBLE); + KeyBoardUtils.hideKeyBoard(context, + mEditTextContent); + } else { + cbvOther.setVisibility(View.GONE); + KeyBoardUtils.showKeyBoard(context, mEditTextContent); + } + } + }); + + tvSend.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + // TODO 发送消息 + String message = mEditTextContent.getText().toString().trim(); + if (onMessageSendListener != null && !TextUtils.isEmpty(message)) { + onMessageSendListener.sendMessage(message); + mEditTextContent.setText(""); + } + } + }); + + mEditTextContent.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + // 收起部分布局 + llyEmojiGroup.setVisibility(View.GONE); + cbvOther.setVisibility(View.GONE); + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji); + ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice); + } + + }); + + cbvOther.setOnHeadIconClickListener(new HeadIconSelectorView.OnHeadIconClickListener() { + @Override + public void onClick(int from) { + if (onBottomIconClickListener != null) + onBottomIconClickListener.onIconClickListener(from); + + } + }); + + + ivEmoji.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + // 显示或收起表情列表 + cbvOther.setVisibility(View.GONE); + if (llyEmojiGroup.getVisibility() == View.GONE) { + mEditTextContent.setVisibility(View.VISIBLE); + voiceBtn.setVisibility(View.GONE); + ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice); + llyEmojiGroup.setVisibility(View.VISIBLE); + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_keybroad); + KeyBoardUtils.hideKeyBoard(context, + mEditTextContent); + } else { + llyEmojiGroup.setVisibility(View.GONE); + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji); + KeyBoardUtils.showKeyBoard(context, mEditTextContent); + } + } + }); + + ivEmoji.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + // 显示或收起表情列表 + cbvOther.setVisibility(View.GONE); + if (llyEmojiGroup.getVisibility() == View.GONE) { + mEditTextContent.setVisibility(View.VISIBLE); + voiceBtn.setVisibility(View.GONE); + ivVoice.setBackgroundResource(R.mipmap.ic_btn_voice); + llyEmojiGroup.setVisibility(View.VISIBLE); + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_keybroad); + KeyBoardUtils.hideKeyBoard(context, + mEditTextContent); + } else { + llyEmojiGroup.setVisibility(View.GONE); + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji); + KeyBoardUtils.showKeyBoard(context, mEditTextContent); + } + } + }); + + initEmotionPager(); + + voiceBtn.setAudioFinishRecorderListener(new AudioRecordButton.AudioFinishRecorderListener() { + @Override + public void onStart() { + if (onMessageSendListener != null) + onMessageSendListener.onVoiceRecordStart(); + } + + @Override + public void onFinished(float seconds, String filePath) { + if (onMessageSendListener != null) + onMessageSendListener.sendVoice(seconds, filePath); + } + }); + } + + private View.OnKeyListener onKeyListener = new View.OnKeyListener() { + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ENTER + && event.getAction() == KeyEvent.ACTION_DOWN) { + // 发送消息 + String message = mEditTextContent.getText().toString().trim(); + if (onMessageSendListener != null && !TextUtils.isEmpty(message)) { + onMessageSendListener.sendMessage(message); + mEditTextContent.setText(""); + } + return true; + } + return false; + } + }; + + /** + * 初始化 emotionPager + */ + private void initEmotionPager() { + List views = new ArrayList(); + for (int i = 0; i < EmotionHelper.emojiGroups.size(); i++) { + views.add(getEmotionGridView(i)); + } + ChatEmotionPagerAdapter pagerAdapter = new ChatEmotionPagerAdapter(views); + emotionPager.setOffscreenPageLimit(4); + emotionPager.setAdapter(pagerAdapter); + ciBanner.setViewPager(emotionPager); + } + + private View getEmotionGridView(int pos) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + View emotionView = inflater.inflate(R.layout.chat_emotion_gridview, null, false); + GridView gridView = (GridView) emotionView.findViewById(R.id.gridview); + final ChatEmotionGridAdapter chatEmotionGridAdapter = new ChatEmotionGridAdapter(getContext()); + List pageEmotions = EmotionHelper.emojiGroups.get(pos); + chatEmotionGridAdapter.setDatas(pageEmotions); + gridView.setAdapter(chatEmotionGridAdapter); + gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + String emotionText = (String) parent.getAdapter().getItem(position); + int start = mEditTextContent.getSelectionStart(); + StringBuilder sb = new StringBuilder(mEditTextContent.getText()); + sb.replace(mEditTextContent.getSelectionStart(), mEditTextContent.getSelectionEnd(), emotionText); + mEditTextContent.setText(sb.toString()); + + CharSequence info = mEditTextContent.getText(); + if (info != null) { + Spannable spannable = (Spannable) info; + Selection.setSelection(spannable, start + emotionText.length()); + } + } + }); + return gridView; + } + + public void hideBottomLayout() { + llyEmojiGroup.setVisibility(GONE); + ivEmoji.setBackgroundResource(R.mipmap.ic_btn_emoji); + cbvOther.setVisibility(GONE); + } + + public interface OnMessageSendListener { + void sendMessage(String message); + + void sendVoice(float seconds, String filePath); + + void onVoiceRecordStart(); + } + + public interface OnBottomIconClickListener { + void onIconClickListener(int from); + } +} diff --git a/app/src/main/java/com/example/administrator/chatdemo/widget/PlayButton.java b/app/src/main/java/com/example/administrator/chatdemo/widget/PlayButton.java new file mode 100644 index 0000000..05794c0 --- /dev/null +++ b/app/src/main/java/com/example/administrator/chatdemo/widget/PlayButton.java @@ -0,0 +1,83 @@ +package com.example.administrator.chatdemo.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.AnimationDrawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import com.example.administrator.chatdemo.R; +import com.example.administrator.chatdemo.bean.ChatMessageBean; + + +/** + * Created by lzw on 14-9-22. + */ +public class PlayButton extends TextView implements View.OnClickListener { + private String path; + private boolean leftSide; + private AnimationDrawable anim; + + + private ChatMessageBean messageBean; + + public PlayButton(Context context, AttributeSet attrs) { + super(context, attrs); + leftSide = getLeftFromAttrs(context, attrs); + setLeftSide(leftSide); + setOnClickListener(this); + } + + public void setLeftSide(boolean leftSide) { + this.leftSide = leftSide; + stopRecordAnimation(); + } + + public boolean getLeftFromAttrs(Context context, AttributeSet attrs) { + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChatPlayBtn); + boolean left = true; + for (int i = 0; i < typedArray.getIndexCount(); i++) { + int attr = typedArray.getIndex(i); + if (attr == R.styleable.ChatPlayBtn_left) { + left = typedArray.getBoolean(attr, true); + } + } + return left; + } + + public void setPath(String path) { + this.path = path; + } + + public void setMessageBean(ChatMessageBean messageBean) { + this.messageBean = messageBean; + } + + @Override + public void onClick(View v) { + +// new PlayButtonClickListener(this, leftSide, getContext(), path).onClick(this); + } + + public void startRecordAnimation() { + Log.i("PlayButton", "startRecordAnimation" + leftSide); + setCompoundDrawablesWithIntrinsicBounds(leftSide ? R.drawable.chat_anim_voice_left : 0, + 0, !leftSide ? R.drawable.chat_anim_voice_right : 0, 0); + anim = (AnimationDrawable) getCompoundDrawables()[leftSide ? 0 : 2]; + anim.start(); + } + + // + public void stopRecordAnimation() { + Log.i("PlayButton", "stopRecordAnimation" + leftSide); + setCompoundDrawablesWithIntrinsicBounds(leftSide ? R.drawable.chat_voice_right3 : 0, + 0, !leftSide ? R.drawable.chat_voice_left3 : 0, 0); + if (anim != null) { + anim.stop(); + } + } + + +} diff --git a/app/src/main/res/anim/scale_with_alpha.xml b/app/src/main/res/anim/scale_with_alpha.xml new file mode 100644 index 0000000..8ef7f85 --- /dev/null +++ b/app/src/main/res/anim/scale_with_alpha.xml @@ -0,0 +1,22 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/chat_common_empty_photo.9.png b/app/src/main/res/drawable-xhdpi/chat_common_empty_photo.9.png new file mode 100644 index 0000000..25d57df Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_common_empty_photo.9.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_common_image_load_fail.png b/app/src/main/res/drawable-xhdpi/chat_common_image_load_fail.png new file mode 100644 index 0000000..750be72 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_common_image_load_fail.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_default_user_avatar.png b/app/src/main/res/drawable-xhdpi/chat_default_user_avatar.png new file mode 100644 index 0000000..b328653 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_default_user_avatar.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_fail_resend_normal.png b/app/src/main/res/drawable-xhdpi/chat_fail_resend_normal.png new file mode 100644 index 0000000..82eba61 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_fail_resend_normal.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_fail_resend_press.png b/app/src/main/res/drawable-xhdpi/chat_fail_resend_press.png new file mode 100644 index 0000000..21c1d2a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_fail_resend_press.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_bg.9.png b/app/src/main/res/drawable-xhdpi/chat_voice_bg.9.png new file mode 100644 index 0000000..72c6d01 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_bg.9.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_bg_pressed.9.png b/app/src/main/res/drawable-xhdpi/chat_voice_bg_pressed.9.png new file mode 100644 index 0000000..a443809 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_bg_pressed.9.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left.png b/app/src/main/res/drawable-xhdpi/chat_voice_left.png new file mode 100644 index 0000000..2d7fd8f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left1.png b/app/src/main/res/drawable-xhdpi/chat_voice_left1.png new file mode 100644 index 0000000..3a6d492 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left1.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left2.png b/app/src/main/res/drawable-xhdpi/chat_voice_left2.png new file mode 100644 index 0000000..d3306f8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left2.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_left3.png b/app/src/main/res/drawable-xhdpi/chat_voice_left3.png new file mode 100644 index 0000000..28b1fb7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_left3.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_pressed.png b/app/src/main/res/drawable-xhdpi/chat_voice_pressed.png new file mode 100644 index 0000000..4a29e24 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_pressed.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right.png b/app/src/main/res/drawable-xhdpi/chat_voice_right.png new file mode 100644 index 0000000..b173db3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right1.png b/app/src/main/res/drawable-xhdpi/chat_voice_right1.png new file mode 100644 index 0000000..3160a0b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right1.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right2.png b/app/src/main/res/drawable-xhdpi/chat_voice_right2.png new file mode 100644 index 0000000..286d579 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right2.png differ diff --git a/app/src/main/res/drawable-xhdpi/chat_voice_right3.png b/app/src/main/res/drawable-xhdpi/chat_voice_right3.png new file mode 100644 index 0000000..256cc20 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/chat_voice_right3.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_angry.png b/app/src/main/res/drawable-xhdpi/emoji_angry.png new file mode 100644 index 0000000..f95bfa8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_angry.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_anguished.png b/app/src/main/res/drawable-xhdpi/emoji_anguished.png new file mode 100644 index 0000000..c625947 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_anguished.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_astonished.png b/app/src/main/res/drawable-xhdpi/emoji_astonished.png new file mode 100644 index 0000000..858a834 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_astonished.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_blush.png b/app/src/main/res/drawable-xhdpi/emoji_blush.png new file mode 100644 index 0000000..3a95eb6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_blush.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_clap.png b/app/src/main/res/drawable-xhdpi/emoji_clap.png new file mode 100644 index 0000000..d01c982 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_clap.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_cold_sweat.png b/app/src/main/res/drawable-xhdpi/emoji_cold_sweat.png new file mode 100644 index 0000000..b9e39bc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_cold_sweat.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_confounded.png b/app/src/main/res/drawable-xhdpi/emoji_confounded.png new file mode 100644 index 0000000..762c376 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_confounded.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_confused.png b/app/src/main/res/drawable-xhdpi/emoji_confused.png new file mode 100644 index 0000000..8dc494d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_confused.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_cry.png b/app/src/main/res/drawable-xhdpi/emoji_cry.png new file mode 100644 index 0000000..6d0d9af Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_cry.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_disappointed.png b/app/src/main/res/drawable-xhdpi/emoji_disappointed.png new file mode 100644 index 0000000..8255200 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_disappointed.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_disappointed_relieved.png b/app/src/main/res/drawable-xhdpi/emoji_disappointed_relieved.png new file mode 100644 index 0000000..fa5f9e7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_disappointed_relieved.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_dizzy_face.png b/app/src/main/res/drawable-xhdpi/emoji_dizzy_face.png new file mode 100644 index 0000000..8001d6f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_dizzy_face.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_expressionless.png b/app/src/main/res/drawable-xhdpi/emoji_expressionless.png new file mode 100644 index 0000000..913ff4e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_expressionless.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_fearful.png b/app/src/main/res/drawable-xhdpi/emoji_fearful.png new file mode 100644 index 0000000..513fce4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_fearful.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_flushed.png b/app/src/main/res/drawable-xhdpi/emoji_flushed.png new file mode 100644 index 0000000..74b78c9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_flushed.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_frowning.png b/app/src/main/res/drawable-xhdpi/emoji_frowning.png new file mode 100644 index 0000000..487b770 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_frowning.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_grimacing.png b/app/src/main/res/drawable-xhdpi/emoji_grimacing.png new file mode 100644 index 0000000..1219ba7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_grimacing.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_grin.png b/app/src/main/res/drawable-xhdpi/emoji_grin.png new file mode 100644 index 0000000..591cfce Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_grin.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_grinning.png b/app/src/main/res/drawable-xhdpi/emoji_grinning.png new file mode 100644 index 0000000..7e812b7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_grinning.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_heart_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_heart_eyes.png new file mode 100644 index 0000000..0e57942 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_heart_eyes.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_hushed.png b/app/src/main/res/drawable-xhdpi/emoji_hushed.png new file mode 100644 index 0000000..bbd2cd4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_hushed.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_innocent.png b/app/src/main/res/drawable-xhdpi/emoji_innocent.png new file mode 100644 index 0000000..503b614 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_innocent.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_joy.png b/app/src/main/res/drawable-xhdpi/emoji_joy.png new file mode 100644 index 0000000..47df693 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_joy.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing.png b/app/src/main/res/drawable-xhdpi/emoji_kissing.png new file mode 100644 index 0000000..f3c8dcd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_closed_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_closed_eyes.png new file mode 100644 index 0000000..449de19 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_closed_eyes.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_face.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_face.png new file mode 100644 index 0000000..449de19 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_face.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_heart.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_heart.png new file mode 100644 index 0000000..af9a80b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_heart.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_kissing_smiling_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_kissing_smiling_eyes.png new file mode 100644 index 0000000..57f7b49 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_kissing_smiling_eyes.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_laughing.png b/app/src/main/res/drawable-xhdpi/emoji_laughing.png new file mode 100644 index 0000000..11c91eb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_laughing.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_mask.png b/app/src/main/res/drawable-xhdpi/emoji_mask.png new file mode 100644 index 0000000..05887e9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_mask.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_neutral_face.png b/app/src/main/res/drawable-xhdpi/emoji_neutral_face.png new file mode 100644 index 0000000..682a1ba Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_neutral_face.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_no_mouth.png b/app/src/main/res/drawable-xhdpi/emoji_no_mouth.png new file mode 100644 index 0000000..e678020 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_no_mouth.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_open_mouth.png b/app/src/main/res/drawable-xhdpi/emoji_open_mouth.png new file mode 100644 index 0000000..daf9142 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_open_mouth.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_pensive.png b/app/src/main/res/drawable-xhdpi/emoji_pensive.png new file mode 100644 index 0000000..4159f3c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_pensive.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_persevere.png b/app/src/main/res/drawable-xhdpi/emoji_persevere.png new file mode 100644 index 0000000..f99f6da Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_persevere.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_point_left.png b/app/src/main/res/drawable-xhdpi/emoji_point_left.png new file mode 100644 index 0000000..38a99b4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_point_left.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_point_right.png b/app/src/main/res/drawable-xhdpi/emoji_point_right.png new file mode 100644 index 0000000..6f9f029 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_point_right.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_rage.png b/app/src/main/res/drawable-xhdpi/emoji_rage.png new file mode 100644 index 0000000..c65ddff Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_rage.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_relaxed.png b/app/src/main/res/drawable-xhdpi/emoji_relaxed.png new file mode 100644 index 0000000..bbab82d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_relaxed.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_relieved.png b/app/src/main/res/drawable-xhdpi/emoji_relieved.png new file mode 100644 index 0000000..fe5629f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_relieved.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_satisfied.png b/app/src/main/res/drawable-xhdpi/emoji_satisfied.png new file mode 100644 index 0000000..11c91eb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_satisfied.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_scream.png b/app/src/main/res/drawable-xhdpi/emoji_scream.png new file mode 100644 index 0000000..9e93c88 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_scream.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_sleeping.png b/app/src/main/res/drawable-xhdpi/emoji_sleeping.png new file mode 100644 index 0000000..093b852 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sleeping.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_sleepy.png b/app/src/main/res/drawable-xhdpi/emoji_sleepy.png new file mode 100644 index 0000000..df4f55e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sleepy.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_smile.png b/app/src/main/res/drawable-xhdpi/emoji_smile.png new file mode 100644 index 0000000..81a8396 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_smile.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_smiley.png b/app/src/main/res/drawable-xhdpi/emoji_smiley.png new file mode 100644 index 0000000..77b581d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_smiley.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_smirk.png b/app/src/main/res/drawable-xhdpi/emoji_smirk.png new file mode 100644 index 0000000..bc6e508 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_smirk.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_sob.png b/app/src/main/res/drawable-xhdpi/emoji_sob.png new file mode 100644 index 0000000..1561df9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sob.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue.png b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue.png new file mode 100644 index 0000000..fa7b58e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_closed_eyes.png b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_closed_eyes.png new file mode 100644 index 0000000..333716e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_closed_eyes.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_winking_eye.png b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_winking_eye.png new file mode 100644 index 0000000..6ae9d49 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_stuck_out_tongue_winking_eye.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_sunglasses.png b/app/src/main/res/drawable-xhdpi/emoji_sunglasses.png new file mode 100644 index 0000000..f2e5247 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sunglasses.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_sweat.png b/app/src/main/res/drawable-xhdpi/emoji_sweat.png new file mode 100644 index 0000000..e894b76 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sweat.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_sweat_smile.png b/app/src/main/res/drawable-xhdpi/emoji_sweat_smile.png new file mode 100644 index 0000000..3903f71 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_sweat_smile.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_thumbsdown.png b/app/src/main/res/drawable-xhdpi/emoji_thumbsdown.png new file mode 100644 index 0000000..41c6b82 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_thumbsdown.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_thumbsup.png b/app/src/main/res/drawable-xhdpi/emoji_thumbsup.png new file mode 100644 index 0000000..81786c1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_thumbsup.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_tired_face.png b/app/src/main/res/drawable-xhdpi/emoji_tired_face.png new file mode 100644 index 0000000..77b7834 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_tired_face.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_triumph.png b/app/src/main/res/drawable-xhdpi/emoji_triumph.png new file mode 100644 index 0000000..92f93bd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_triumph.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_unamused.png b/app/src/main/res/drawable-xhdpi/emoji_unamused.png new file mode 100644 index 0000000..3722e6f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_unamused.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_weary.png b/app/src/main/res/drawable-xhdpi/emoji_weary.png new file mode 100644 index 0000000..0c54754 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_weary.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_wink.png b/app/src/main/res/drawable-xhdpi/emoji_wink.png new file mode 100644 index 0000000..756766d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_wink.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_worried.png b/app/src/main/res/drawable-xhdpi/emoji_worried.png new file mode 100644 index 0000000..bfa1856 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_worried.png differ diff --git a/app/src/main/res/drawable-xhdpi/emoji_yum.png b/app/src/main/res/drawable-xhdpi/emoji_yum.png new file mode 100644 index 0000000..fc39637 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/emoji_yum.png differ diff --git a/app/src/main/res/drawable-xhdpi/message_transfer_money_left.9.png b/app/src/main/res/drawable-xhdpi/message_transfer_money_left.9.png new file mode 100644 index 0000000..ad27cf9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/message_transfer_money_left.9.png differ diff --git a/app/src/main/res/drawable-xhdpi/message_transfer_money_right.9.png b/app/src/main/res/drawable-xhdpi/message_transfer_money_right.9.png new file mode 100644 index 0000000..49a4ecf Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/message_transfer_money_right.9.png differ diff --git a/app/src/main/res/drawable/button_recording.xml b/app/src/main/res/drawable/button_recording.xml new file mode 100644 index 0000000..0ae8d72 --- /dev/null +++ b/app/src/main/res/drawable/button_recording.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_recordnormal.xml b/app/src/main/res/drawable/button_recordnormal.xml new file mode 100644 index 0000000..336bcf0 --- /dev/null +++ b/app/src/main/res/drawable/button_recordnormal.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chat_anim_voice_left.xml b/app/src/main/res/drawable/chat_anim_voice_left.xml new file mode 100644 index 0000000..ebb048b --- /dev/null +++ b/app/src/main/res/drawable/chat_anim_voice_left.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chat_anim_voice_right.xml b/app/src/main/res/drawable/chat_anim_voice_right.xml new file mode 100644 index 0000000..bcd953f --- /dev/null +++ b/app/src/main/res/drawable/chat_anim_voice_right.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/chat_btn_fail_resend.xml b/app/src/main/res/drawable/chat_btn_fail_resend.xml new file mode 100644 index 0000000..62c4a54 --- /dev/null +++ b/app/src/main/res/drawable/chat_btn_fail_resend.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/click_bg.xml b/app/src/main/res/drawable/click_bg.xml new file mode 100644 index 0000000..d62509c --- /dev/null +++ b/app/src/main/res/drawable/click_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/frame_bg.xml b/app/src/main/res/drawable/frame_bg.xml new file mode 100644 index 0000000..c954432 --- /dev/null +++ b/app/src/main/res/drawable/frame_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/frame_blue_to_thin_bg.xml b/app/src/main/res/drawable/frame_blue_to_thin_bg.xml new file mode 100644 index 0000000..210092a --- /dev/null +++ b/app/src/main/res/drawable/frame_blue_to_thin_bg.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/item_divider_line.xml b/app/src/main/res/drawable/item_divider_line.xml new file mode 100644 index 0000000..8cf968c --- /dev/null +++ b/app/src/main/res/drawable/item_divider_line.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/msg_input_shape.xml b/app/src/main/res/drawable/msg_input_shape.xml new file mode 100644 index 0000000..81a19ec --- /dev/null +++ b/app/src/main/res/drawable/msg_input_shape.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/white_radius.xml b/app/src/main/res/drawable/white_radius.xml new file mode 100644 index 0000000..72390ab --- /dev/null +++ b/app/src/main/res/drawable/white_radius.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/xmxq_tc_bj.9.png b/app/src/main/res/drawable/xmxq_tc_bj.9.png new file mode 100644 index 0000000..53f545c Binary files /dev/null and b/app/src/main/res/drawable/xmxq_tc_bj.9.png differ diff --git a/app/src/main/res/layout/activity_service_chat.xml b/app/src/main/res/layout/activity_service_chat.xml new file mode 100644 index 0000000..268fe14 --- /dev/null +++ b/app/src/main/res/layout/activity_service_chat.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/chat_emotion_gridview.xml b/app/src/main/res/layout/chat_emotion_gridview.xml new file mode 100644 index 0000000..963734e --- /dev/null +++ b/app/src/main/res/layout/chat_emotion_gridview.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_emotion_item.xml b/app/src/main/res/layout/chat_emotion_item.xml new file mode 100644 index 0000000..9e5a592 --- /dev/null +++ b/app/src/main/res/layout/chat_emotion_item.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_image_brower_layout.xml b/app/src/main/res/layout/chat_image_brower_layout.xml new file mode 100644 index 0000000..c45b144 --- /dev/null +++ b/app/src/main/res/layout/chat_image_brower_layout.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_input_bottom_bar_layout.xml b/app/src/main/res/layout/chat_input_bottom_bar_layout.xml new file mode 100644 index 0000000..a2280cf --- /dev/null +++ b/app/src/main/res/layout/chat_input_bottom_bar_layout.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_image_layout.xml b/app/src/main/res/layout/chat_item_image_layout.xml new file mode 100644 index 0000000..19d77fc --- /dev/null +++ b/app/src/main/res/layout/chat_item_image_layout.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_left_audio_layout.xml b/app/src/main/res/layout/chat_item_left_audio_layout.xml new file mode 100644 index 0000000..d5cad55 --- /dev/null +++ b/app/src/main/res/layout/chat_item_left_audio_layout.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_left_layout.xml b/app/src/main/res/layout/chat_item_left_layout.xml new file mode 100644 index 0000000..847d2ef --- /dev/null +++ b/app/src/main/res/layout/chat_item_left_layout.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_left_text_layout.xml b/app/src/main/res/layout/chat_item_left_text_layout.xml new file mode 100644 index 0000000..d3b600b --- /dev/null +++ b/app/src/main/res/layout/chat_item_left_text_layout.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_right_audio_layout.xml b/app/src/main/res/layout/chat_item_right_audio_layout.xml new file mode 100644 index 0000000..afb0108 --- /dev/null +++ b/app/src/main/res/layout/chat_item_right_audio_layout.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_right_image_layout.xml b/app/src/main/res/layout/chat_item_right_image_layout.xml new file mode 100644 index 0000000..f255cc8 --- /dev/null +++ b/app/src/main/res/layout/chat_item_right_image_layout.xml @@ -0,0 +1,23 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_right_layout.xml b/app/src/main/res/layout/chat_item_right_layout.xml new file mode 100644 index 0000000..8f50cc0 --- /dev/null +++ b/app/src/main/res/layout/chat_item_right_layout.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/chat_item_right_text_layout.xml b/app/src/main/res/layout/chat_item_right_text_layout.xml new file mode 100644 index 0000000..6cc141d --- /dev/null +++ b/app/src/main/res/layout/chat_item_right_text_layout.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_mess_iv_listitem.xml b/app/src/main/res/layout/layout_mess_iv_listitem.xml new file mode 100644 index 0000000..65f9bfe --- /dev/null +++ b/app/src/main/res/layout/layout_mess_iv_listitem.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_tongbaobottom.xml b/app/src/main/res/layout/layout_tongbaobottom.xml new file mode 100644 index 0000000..1cc81d9 --- /dev/null +++ b/app/src/main/res/layout/layout_tongbaobottom.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_view_headicon.xml b/app/src/main/res/layout/layout_view_headicon.xml new file mode 100644 index 0000000..b9dd628 --- /dev/null +++ b/app/src/main/res/layout/layout_view_headicon.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_voice_dialog_manager.xml b/app/src/main/res/layout/layout_voice_dialog_manager.xml new file mode 100644 index 0000000..3331d1a --- /dev/null +++ b/app/src/main/res/layout/layout_voice_dialog_manager.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_emoji.png b/app/src/main/res/mipmap-hdpi/ic_btn_emoji.png new file mode 100644 index 0000000..5669edf Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_emoji.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_keybroad.png b/app/src/main/res/mipmap-hdpi/ic_btn_keybroad.png new file mode 100644 index 0000000..d164682 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_keybroad.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_more.png b/app/src/main/res/mipmap-hdpi/ic_btn_more.png new file mode 100644 index 0000000..dda5ff1 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_more.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_btn_voice.png b/app/src/main/res/mipmap-hdpi/ic_btn_voice.png new file mode 100644 index 0000000..f731870 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_btn_voice.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..9a078e3 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-hdpi/tb_bottom_images.png b/app/src/main/res/mipmap-hdpi/tb_bottom_images.png new file mode 100644 index 0000000..a0e1129 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/tb_bottom_images.png differ diff --git a/app/src/main/res/mipmap-hdpi/tb_bottom_take_photo.png b/app/src/main/res/mipmap-hdpi/tb_bottom_take_photo.png new file mode 100644 index 0000000..2acebac Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/tb_bottom_take_photo.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..efc028a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/cancel.png b/app/src/main/res/mipmap-xhdpi/cancel.png new file mode 100644 index 0000000..519218c Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/cancel.png differ diff --git a/app/src/main/res/mipmap-xhdpi/emoji_icon.png b/app/src/main/res/mipmap-xhdpi/emoji_icon.png new file mode 100644 index 0000000..a9b1a18 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/emoji_icon.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_emoji.png b/app/src/main/res/mipmap-xhdpi/ic_btn_emoji.png new file mode 100644 index 0000000..4c2cfb6 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_emoji.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_keybroad.png b/app/src/main/res/mipmap-xhdpi/ic_btn_keybroad.png new file mode 100644 index 0000000..7214656 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_keybroad.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_more.png b/app/src/main/res/mipmap-xhdpi/ic_btn_more.png new file mode 100644 index 0000000..3179b31 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_more.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_btn_voice.png b/app/src/main/res/mipmap-xhdpi/ic_btn_voice.png new file mode 100644 index 0000000..28b92a5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_btn_voice.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_head_01.jpg b/app/src/main/res/mipmap-xhdpi/ic_head_01.jpg new file mode 100644 index 0000000..9d50a96 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_head_01.jpg differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_head_02.jpg b/app/src/main/res/mipmap-xhdpi/ic_head_02.jpg new file mode 100644 index 0000000..55dad3a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_head_02.jpg differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..3af2608 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/msg_chat_voice_unread.png b/app/src/main/res/mipmap-xhdpi/msg_chat_voice_unread.png new file mode 100644 index 0000000..a6b2b50 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/msg_chat_voice_unread.png differ diff --git a/app/src/main/res/mipmap-xhdpi/receiver_voice_node_playing001.png b/app/src/main/res/mipmap-xhdpi/receiver_voice_node_playing001.png new file mode 100644 index 0000000..f38a0ce Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/receiver_voice_node_playing001.png differ diff --git a/app/src/main/res/mipmap-xhdpi/tb_bottom_images.png b/app/src/main/res/mipmap-xhdpi/tb_bottom_images.png new file mode 100644 index 0000000..91ef677 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_bottom_images.png differ diff --git a/app/src/main/res/mipmap-xhdpi/tb_bottom_offen.png b/app/src/main/res/mipmap-xhdpi/tb_bottom_offen.png new file mode 100644 index 0000000..2b1d7a6 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_bottom_offen.png differ diff --git a/app/src/main/res/mipmap-xhdpi/tb_bottom_take_photo.png b/app/src/main/res/mipmap-xhdpi/tb_bottom_take_photo.png new file mode 100644 index 0000000..f85f25b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_bottom_take_photo.png differ diff --git a/app/src/main/res/mipmap-xhdpi/tb_dialog_loading_bg.png b/app/src/main/res/mipmap-xhdpi/tb_dialog_loading_bg.png new file mode 100644 index 0000000..d200d97 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_dialog_loading_bg.png differ diff --git a/app/src/main/res/mipmap-xhdpi/tb_voice1.png b/app/src/main/res/mipmap-xhdpi/tb_voice1.png new file mode 100644 index 0000000..daee17f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_voice1.png differ diff --git a/app/src/main/res/mipmap-xhdpi/tb_voice2.png b/app/src/main/res/mipmap-xhdpi/tb_voice2.png new file mode 100644 index 0000000..81f930f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_voice2.png differ diff --git a/app/src/main/res/mipmap-xhdpi/tb_voice3.png b/app/src/main/res/mipmap-xhdpi/tb_voice3.png new file mode 100644 index 0000000..b801872 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/tb_voice3.png differ diff --git a/app/src/main/res/mipmap-xhdpi/voice_to_short.png b/app/src/main/res/mipmap-xhdpi/voice_to_short.png new file mode 100644 index 0000000..16bc857 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/voice_to_short.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_emoji.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_emoji.png new file mode 100644 index 0000000..03366e4 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_emoji.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_keybroad.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_keybroad.png new file mode 100644 index 0000000..cfea9a5 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_keybroad.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_more.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_more.png new file mode 100644 index 0000000..d90017e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_more.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_btn_voice.png b/app/src/main/res/mipmap-xxhdpi/ic_btn_voice.png new file mode 100644 index 0000000..754d201 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_btn_voice.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9bec2e6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/tb_bottom_images.png b/app/src/main/res/mipmap-xxhdpi/tb_bottom_images.png new file mode 100644 index 0000000..db1cea8 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/tb_bottom_images.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/tb_bottom_take_photo.png b/app/src/main/res/mipmap-xxhdpi/tb_bottom_take_photo.png new file mode 100644 index 0000000..8860bc3 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/tb_bottom_take_photo.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..34947cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..ad44270 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..808981f --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,18 @@ + + + #3F51B5 + #303F9F + #FF4081 + + #503097e6 + #3097e6 + #ffffff + #f0f2f6 + #7f000000 + #d9d9d9 + #051b28 + #e6e6e6 + #909090 + #e83060 + #88e83060 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..07bad09 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + ChatDemo + + + 按住 说话 + 松开 结束 + 松开手指,取消发送 + 手指上滑,取消发送 + 录音时间过短 + 录音时间过长 + + 相册 + 拍照 + 表情 + 发送 + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..3ec8f06 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/app/src/test/java/com/example/administrator/chatdemo/ExampleUnitTest.java b/app/src/test/java/com/example/administrator/chatdemo/ExampleUnitTest.java new file mode 100644 index 0000000..187f466 --- /dev/null +++ b/app/src/test/java/com/example/administrator/chatdemo/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package com.example.administrator.chatdemo; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see Testing documentation + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..221c810 --- /dev/null +++ b/build.gradle @@ -0,0 +1,27 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' + classpath 'com.novoda:bintray-release:0.3.4' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + maven { url 'https://jitpack.io' } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..aac7c9b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..63f9152 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue Jul 18 16:32:52 CST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app'