├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── silencebeat │ │ └── com │ │ └── chatview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── fonts │ │ │ ├── book.otf │ │ │ ├── medium.otf │ │ │ └── roman.otf │ ├── java │ │ └── silencebeat │ │ │ └── com │ │ │ └── chatview │ │ │ ├── Entities │ │ │ └── Models │ │ │ │ └── Comment.java │ │ │ ├── MainActivity.kt │ │ │ ├── Modules │ │ │ ├── Interfaces.kt │ │ │ └── MainWireframe.java │ │ │ ├── Supports │ │ │ └── Utils │ │ │ │ ├── AudioRecorder.java │ │ │ │ ├── ChatAdapter.java │ │ │ │ ├── CustomTextView.java │ │ │ │ ├── Debug.java │ │ │ │ ├── ImageChoser.java │ │ │ │ ├── StaticVariable.java │ │ │ │ └── TouchImageView.java │ │ │ └── Views │ │ │ ├── Activities │ │ │ ├── ChatActivity.java │ │ │ └── MediaActivity │ │ │ │ ├── CameraActivity.java │ │ │ │ └── VideoPlayerActivity.java │ │ │ ├── Fragments │ │ │ ├── CameraResultFragment.java │ │ │ └── DetailFotoDialogFragment.java │ │ │ └── ViewHolders │ │ │ ├── Incoming │ │ │ ├── IncomingChatAudioViewHolder.java │ │ │ ├── IncomingChatImageViewHolder.java │ │ │ ├── IncomingChatTextViewHolder.java │ │ │ └── IncomingChatVideoViewHolder.java │ │ │ └── Outcoming │ │ │ ├── OutcomingChatAudioViewHolder.java │ │ │ ├── OutcomingChatImageViewHolder.java │ │ │ ├── OutcomingChatTextViewHolder.java │ │ │ └── OutcomingChatVideoViewHolder.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_arrow_forward_24dp.xml │ │ ├── ic_check_24dp.xml │ │ ├── ic_close_24dp.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_mic_24dp.xml │ │ ├── ic_pause.xml │ │ ├── ic_photo_camera.xml │ │ ├── ic_picture.xml │ │ ├── ic_play.xml │ │ ├── ic_send_24dp.xml │ │ └── view_gradient_background.xml │ │ ├── layout │ │ ├── activity_camera.xml │ │ ├── activity_chat.xml │ │ ├── activity_main.xml │ │ ├── activity_video_player.xml │ │ ├── fragment_camera_result.xml │ │ ├── fragment_detail_foto_dialog.xml │ │ ├── item_chat_audio_incoming.xml │ │ ├── item_chat_audio_outcoming.xml │ │ ├── item_chat_image_incoming.xml │ │ ├── item_chat_image_outcoming.xml │ │ ├── item_chat_text_incoming.xml │ │ ├── item_chat_text_outcoming.xml │ │ ├── item_chat_video_incoming.xml │ │ └── item_chat_video_outcoming.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-v21 │ │ └── styles.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── silencebeat │ └── com │ └── chatview │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 1.7 38 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 26 9 | defaultConfig { 10 | applicationId "silencebeat.com.chatview" 11 | minSdkVersion 16 12 | targetSdkVersion 26 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | dataBinding { 25 | enabled = true 26 | } 27 | } 28 | 29 | repositories { 30 | 31 | maven { url "https://dl.bintray.com/drummer-aidan/maven" } 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: 'libs', include: ['*.jar']) 36 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 37 | implementation 'com.android.support:appcompat-v7:26.1.0' 38 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 39 | implementation 'com.android.support:cardview-v7:26.1.0' 40 | implementation 'com.android.support:support-v4:26.1.0' 41 | implementation 'com.android.support:design:26.1.0' 42 | implementation 'com.android.support:recyclerview-v7:26.1.0' 43 | implementation 'com.github.bumptech.glide:glide:3.7.0' 44 | implementation 'com.devlomi.record-view:record-view:1.2.1beta' 45 | implementation 'com.github.florent37:camerafragment:1.0.8' 46 | implementation 'com.google.code.gson:gson:2.8.0' 47 | implementation 'com.afollestad:easyvideoplayer:0.3.0' 48 | testImplementation 'junit:junit:4.12' 49 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 51 | } 52 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/silencebeat/com/chatview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("silencebeat.com.chatview", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/assets/fonts/book.otf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/assets/fonts/medium.otf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/roman.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/assets/fonts/roman.otf -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Entities/Models/Comment.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Entities.Models; 2 | 3 | 4 | import com.google.gson.annotations.Expose; 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | /** 8 | * Created by Candra Triyadi on 15/03/2018. 9 | */ 10 | 11 | public class Comment { 12 | 13 | @SerializedName("noteId") 14 | @Expose 15 | private String noteId; 16 | @SerializedName("requestId") 17 | @Expose 18 | private String requestId; 19 | @SerializedName("leaseId") 20 | @Expose 21 | private String leaseId; 22 | @SerializedName("propertyId") 23 | @Expose 24 | private String propertyId; 25 | @SerializedName("type_note") 26 | @Expose 27 | private String typeNote; 28 | @SerializedName("adminId") 29 | @Expose 30 | private String adminId; 31 | @SerializedName("userId") 32 | @Expose 33 | private String userId; 34 | @SerializedName("noteText") 35 | @Expose 36 | private String noteText; 37 | @SerializedName("noteDate") 38 | @Expose 39 | private String noteDate; 40 | @SerializedName("lastUpdated") 41 | @Expose 42 | private String lastUpdated; 43 | @SerializedName("ipAddress") 44 | @Expose 45 | private String ipAddress; 46 | @SerializedName("fileUrl") 47 | @Expose 48 | private String fileUrl; 49 | @SerializedName("thumbnail_video") 50 | @Expose 51 | private String thumbnailVideo; 52 | @SerializedName("adminName") 53 | @Expose 54 | private String adminName; 55 | 56 | public Comment(){ 57 | 58 | } 59 | 60 | public Comment(String adminId, String noteText, String noteDate, String fileUrl, String thumbnailVideo){ 61 | this.adminId = adminId; 62 | this.noteText = noteText; 63 | this.noteDate = noteDate; 64 | this.fileUrl = fileUrl; 65 | this.thumbnailVideo = thumbnailVideo; 66 | } 67 | 68 | public String getNoteId() { 69 | return noteId; 70 | } 71 | 72 | public void setNoteId(String noteId) { 73 | this.noteId = noteId; 74 | } 75 | 76 | public String getRequestId() { 77 | return requestId; 78 | } 79 | 80 | public void setRequestId(String requestId) { 81 | this.requestId = requestId; 82 | } 83 | 84 | public String getLeaseId() { 85 | return leaseId; 86 | } 87 | 88 | public void setLeaseId(String leaseId) { 89 | this.leaseId = leaseId; 90 | } 91 | 92 | public String getPropertyId() { 93 | return propertyId; 94 | } 95 | 96 | public void setPropertyId(String propertyId) { 97 | this.propertyId = propertyId; 98 | } 99 | 100 | public String getTypeNote() { 101 | return typeNote; 102 | } 103 | 104 | public void setTypeNote(String typeNote) { 105 | this.typeNote = typeNote; 106 | } 107 | 108 | public String getAdminId() { 109 | return adminId; 110 | } 111 | 112 | public void setAdminId(String adminId) { 113 | this.adminId = adminId; 114 | } 115 | 116 | public String getUserId() { 117 | return userId; 118 | } 119 | 120 | public void setUserId(String userId) { 121 | this.userId = userId; 122 | } 123 | 124 | public String getNoteText() { 125 | return noteText; 126 | } 127 | 128 | public void setNoteText(String noteText) { 129 | this.noteText = noteText; 130 | } 131 | 132 | public String getNoteDate() { 133 | return noteDate; 134 | } 135 | 136 | public void setNoteDate(String noteDate) { 137 | this.noteDate = noteDate; 138 | } 139 | 140 | public String getLastUpdated() { 141 | return lastUpdated; 142 | } 143 | 144 | public void setLastUpdated(String lastUpdated) { 145 | this.lastUpdated = lastUpdated; 146 | } 147 | 148 | public String getIpAddress() { 149 | return ipAddress; 150 | } 151 | 152 | public void setIpAddress(String ipAddress) { 153 | this.ipAddress = ipAddress; 154 | } 155 | 156 | public String getFileUrl() { 157 | return fileUrl; 158 | } 159 | 160 | public void setFileUrl(String fileUrl) { 161 | this.fileUrl = fileUrl; 162 | } 163 | 164 | public String getThumbnailVideo() { 165 | return thumbnailVideo; 166 | } 167 | 168 | public void setThumbnailVideo(String thumbnailVideo) { 169 | this.thumbnailVideo = thumbnailVideo; 170 | } 171 | 172 | public String getAdminName() { 173 | return adminName; 174 | } 175 | 176 | public void setAdminName(String adminName) { 177 | this.adminName = adminName; 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | 6 | class MainActivity : AppCompatActivity() { 7 | 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Modules/Interfaces.kt: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Modules 2 | 3 | import silencebeat.com.chatview.Entities.Models.Comment 4 | 5 | /** 6 | * Created by Candra Triyadi on 27/02/2018. 7 | */ 8 | 9 | 10 | interface OnItemCommentClickListener{ 11 | fun onItemCommentClicked(comment: Comment) 12 | } 13 | 14 | interface AudioRecordingListener{ 15 | fun onFinishRecording(filePath: String) 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Modules/MainWireframe.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Modules; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | import silencebeat.com.chatview.Views.Activities.ChatActivity; 7 | import silencebeat.com.chatview.Views.Activities.MediaActivity.VideoPlayerActivity; 8 | 9 | /** 10 | * Created by Candra Triyadi on 27/02/2018. 11 | */ 12 | 13 | public class MainWireframe { 14 | 15 | private MainWireframe(){ 16 | } 17 | 18 | private static class SingletonHelper{ 19 | private static final MainWireframe INSTANCE = new MainWireframe(); 20 | } 21 | 22 | public static MainWireframe getInstance() { 23 | return SingletonHelper.INSTANCE; 24 | } 25 | 26 | public void toChatView(Context context, String param){ 27 | Intent intent = new Intent(context, ChatActivity.class); 28 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 29 | intent.putExtra("param", param); 30 | context.startActivity(intent); 31 | } 32 | 33 | 34 | public void toPlayVideoView(Context context, String videoURL){ 35 | Intent intent = new Intent(context, VideoPlayerActivity.class); 36 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 37 | intent.putExtra("videoURL", videoURL); 38 | context.startActivity(intent); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Supports/Utils/AudioRecorder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Supports.Utils; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.pm.PackageManager; 7 | import android.media.MediaRecorder; 8 | import android.os.Environment; 9 | import android.support.v4.app.ActivityCompat; 10 | import android.support.v4.content.ContextCompat; 11 | import android.util.Log; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.text.SimpleDateFormat; 16 | import java.util.Date; 17 | import java.util.Locale; 18 | 19 | import silencebeat.com.chatview.Modules.AudioRecordingListener; 20 | 21 | /** 22 | * Created by Candra Triyadi on 05/03/2018. 23 | */ 24 | 25 | public class AudioRecorder { 26 | 27 | public static final int PERMISSIONS_RECORD_AUDIO = 9876; 28 | Activity activity; 29 | Context context; 30 | MediaRecorder mRecorder; 31 | private File mOutputFile; 32 | AudioRecordingListener listener; 33 | boolean isStartRecording = false; 34 | 35 | public AudioRecorder(Activity activity, Context context, AudioRecordingListener listener){ 36 | this.activity = activity; 37 | this.listener = listener; 38 | this.context = context; 39 | } 40 | 41 | public boolean requestAudioPermission(){ 42 | if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED 43 | && ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){ 44 | RecordAudio(); 45 | return true; 46 | }else{ 47 | ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.RECORD_AUDIO 48 | , Manifest.permission.WRITE_EXTERNAL_STORAGE},PERMISSIONS_RECORD_AUDIO); 49 | 50 | return false; 51 | } 52 | } 53 | 54 | public void RecordAudio(){ 55 | if (mRecorder == null){ 56 | mRecorder = new MediaRecorder(); 57 | mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 58 | mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 59 | mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 60 | } 61 | 62 | 63 | mOutputFile = getOutputFile(); 64 | mOutputFile.getParentFile().mkdirs(); 65 | mRecorder.setOutputFile(mOutputFile.getAbsolutePath()); 66 | 67 | if (!isStartRecording){ 68 | try { 69 | mRecorder.prepare(); 70 | mRecorder.start(); 71 | isStartRecording = true; 72 | Log.d("Voice Recorder","started recording to "+mOutputFile.getAbsolutePath()); 73 | } catch (IOException e) { 74 | mRecorder.release(); 75 | Log.e("Voice Recorder", "prepare() failed "+e.getMessage()); 76 | } 77 | }else{ 78 | isStartRecording = false; 79 | try{ 80 | mRecorder.stop(); 81 | mRecorder.reset(); 82 | mRecorder.release(); 83 | mRecorder = null; 84 | }catch (Exception e){ 85 | Debug.log("voice recorder", "stop failed "+e.getMessage()); 86 | } 87 | 88 | } 89 | 90 | } 91 | 92 | public void stopRecording(boolean saveFile) { 93 | if (mRecorder != null){ 94 | mRecorder.stop(); 95 | mRecorder.reset(); 96 | mRecorder.release(); 97 | mRecorder = null; 98 | isStartRecording = false; 99 | if (!saveFile && mOutputFile != null) { 100 | mOutputFile.delete(); 101 | }else{ 102 | listener.onFinishRecording(mOutputFile.getPath()); 103 | } 104 | } 105 | } 106 | 107 | private File getOutputFile() { 108 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); 109 | return new File(Environment.getExternalStorageDirectory().getAbsolutePath() 110 | + "/"+activity.getPackageName() 111 | + "/Voice_Recorder/RECORDING_" 112 | + dateFormat.format(new Date()) 113 | + ".amr"); 114 | } 115 | 116 | public void release(){ 117 | stopRecording(false); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Supports/Utils/ChatAdapter.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Supports.Utils; 2 | 3 | import android.content.Context; 4 | import android.media.MediaPlayer; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | 11 | import java.util.List; 12 | 13 | import silencebeat.com.chatview.Entities.Models.Comment; 14 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 15 | import silencebeat.com.chatview.R; 16 | import silencebeat.com.chatview.Views.ViewHolders.Incoming.IncomingChatAudioViewHolder; 17 | import silencebeat.com.chatview.Views.ViewHolders.Incoming.IncomingChatImageViewHolder; 18 | import silencebeat.com.chatview.Views.ViewHolders.Incoming.IncomingChatTextViewHolder; 19 | import silencebeat.com.chatview.Views.ViewHolders.Incoming.IncomingChatVideoViewHolder; 20 | import silencebeat.com.chatview.Views.ViewHolders.Outcoming.OutcomingChatAudioViewHolder; 21 | import silencebeat.com.chatview.Views.ViewHolders.Outcoming.OutcomingChatImageViewHolder; 22 | import silencebeat.com.chatview.Views.ViewHolders.Outcoming.OutcomingChatTextViewHolder; 23 | import silencebeat.com.chatview.Views.ViewHolders.Outcoming.OutcomingChatVideoViewHolder; 24 | 25 | /** 26 | * Created by Candra Triyadi on 05/03/2018. 27 | */ 28 | 29 | public class ChatAdapter extends RecyclerView.Adapter { 30 | 31 | private final static int INCOMING_TEXT = 10; 32 | private final static int INCOMING_IMAGE = 11; 33 | private final static int INCOMING_AUDIO = 12; 34 | private final static int INCOMING_VIDEO = 13; 35 | 36 | private final static int OUTCOMING_TEXT = 20; 37 | private final static int OUTCOMING_IMAGE = 21; 38 | private final static int OUTCOMING_AUDIO = 22; 39 | private final static int OUTCOMING_VIDEO = 23; 40 | 41 | private List list; 42 | private OnItemCommentClickListener listener; 43 | private Context context; 44 | private String userID; 45 | MediaPlayer mediaPlayer; 46 | 47 | public ChatAdapter(Context context, List list, OnItemCommentClickListener listener){ 48 | this.list = list; 49 | this.listener = listener; 50 | this.context = context; 51 | } 52 | 53 | public void setUserId(String userId){ 54 | this.userID = userId; 55 | } 56 | 57 | public void setMediaPlayer(MediaPlayer mediaPlayer) { 58 | this.mediaPlayer = mediaPlayer; 59 | } 60 | 61 | public void setList(List list) { 62 | this.list = list; 63 | notifyDataSetChanged(); 64 | } 65 | 66 | public void addItem(Comment chat){ 67 | list.add(chat); 68 | notifyItemInserted(list.size() - 1); 69 | } 70 | 71 | @Override 72 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 73 | int layout; 74 | RecyclerView.ViewHolder viewHolder = null; 75 | View view; 76 | switch (viewType){ 77 | case INCOMING_AUDIO: 78 | layout = R.layout.item_chat_audio_incoming; 79 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 80 | viewHolder = new IncomingChatAudioViewHolder(view); 81 | break; 82 | case INCOMING_TEXT: 83 | layout = R.layout.item_chat_text_incoming; 84 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 85 | viewHolder = new IncomingChatTextViewHolder(view); 86 | break; 87 | case INCOMING_IMAGE: 88 | layout = R.layout.item_chat_image_incoming; 89 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 90 | viewHolder = new IncomingChatImageViewHolder(view); 91 | break; 92 | case INCOMING_VIDEO: 93 | layout = R.layout.item_chat_video_incoming; 94 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 95 | viewHolder = new IncomingChatVideoViewHolder(view); 96 | break; 97 | case OUTCOMING_AUDIO: 98 | layout = R.layout.item_chat_audio_outcoming; 99 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 100 | viewHolder = new OutcomingChatAudioViewHolder(view); 101 | break; 102 | case OUTCOMING_TEXT: 103 | layout = R.layout.item_chat_text_outcoming; 104 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 105 | viewHolder = new OutcomingChatTextViewHolder(view); 106 | break; 107 | case OUTCOMING_IMAGE: 108 | layout = R.layout.item_chat_image_outcoming; 109 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 110 | viewHolder = new OutcomingChatImageViewHolder(view); 111 | break; 112 | case OUTCOMING_VIDEO: 113 | layout = R.layout.item_chat_video_outcoming; 114 | view = LayoutInflater.from(parent.getContext()).inflate(layout, parent, false); 115 | viewHolder = new OutcomingChatVideoViewHolder(view); 116 | break; 117 | } 118 | 119 | return viewHolder; 120 | } 121 | 122 | @Override 123 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 124 | int viewType = holder.getItemViewType(); 125 | Comment chat = list.get(position); 126 | switch (viewType){ 127 | case INCOMING_AUDIO: 128 | ((IncomingChatAudioViewHolder) holder).onBind(context, chat, listener); 129 | break; 130 | case INCOMING_TEXT: 131 | ((IncomingChatTextViewHolder) holder).onBind(context, chat, listener); 132 | break; 133 | case INCOMING_IMAGE: 134 | ((IncomingChatImageViewHolder) holder).onBind(context, chat, listener); 135 | break; 136 | case INCOMING_VIDEO: 137 | ((IncomingChatVideoViewHolder) holder).onBind(context, chat, listener); 138 | break; 139 | case OUTCOMING_AUDIO: 140 | ((OutcomingChatAudioViewHolder) holder).onBind(context, chat, listener); 141 | break; 142 | case OUTCOMING_TEXT: 143 | ((OutcomingChatTextViewHolder) holder).onBind(context, chat, listener); 144 | break; 145 | case OUTCOMING_IMAGE: 146 | ((OutcomingChatImageViewHolder) holder).onBind(context, chat, listener); 147 | break; 148 | case OUTCOMING_VIDEO: 149 | ((OutcomingChatVideoViewHolder) holder).onBind(context, chat, listener); 150 | break; 151 | } 152 | } 153 | 154 | @Override 155 | public int getItemViewType(int position) { 156 | Comment chat = list.get(position); 157 | if (chat.getAdminId().equalsIgnoreCase(userID)){ 158 | if (chat.getFileUrl() == null){ 159 | return OUTCOMING_TEXT; 160 | }else if (chat.getFileUrl().contains(".mp4")){ 161 | return OUTCOMING_VIDEO; 162 | }else if (chat.getFileUrl().contains(".jpg")){ 163 | return OUTCOMING_IMAGE; 164 | }else{ 165 | return OUTCOMING_AUDIO; 166 | } 167 | 168 | }else{ 169 | if (chat.getFileUrl() == null){ 170 | return INCOMING_TEXT; 171 | }else if (chat.getFileUrl().contains(".mp4")){ 172 | return INCOMING_VIDEO; 173 | }else if (chat.getFileUrl().contains(".jpg")){ 174 | return INCOMING_IMAGE; 175 | }else{ 176 | return INCOMING_AUDIO; 177 | } 178 | } 179 | } 180 | 181 | @Override 182 | public int getItemCount() { 183 | return list.size(); 184 | } 185 | 186 | public List getList() { 187 | return list; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Supports/Utils/CustomTextView.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Supports.Utils; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Typeface; 6 | import android.support.v7.widget.AppCompatTextView; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | 10 | import silencebeat.com.chatview.R; 11 | 12 | /** 13 | * Created by Candra Triyadi on 27/02/2018. 14 | */ 15 | 16 | public class CustomTextView extends AppCompatTextView { 17 | 18 | private static final String TAG = "CustomTextView"; 19 | 20 | public CustomTextView(Context context) { 21 | super(context); 22 | } 23 | 24 | public CustomTextView(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | setCustomFont(context, attrs); 27 | } 28 | 29 | public CustomTextView(Context context, AttributeSet attrs, int defStyle) { 30 | super(context, attrs, defStyle); 31 | setCustomFont(context, attrs); 32 | } 33 | 34 | private void setCustomFont(Context ctx, AttributeSet attrs) { 35 | TypedArray a = ctx.obtainStyledAttributes(attrs, R.styleable.CustomTextView); 36 | String customFont = a.getString(R.styleable.CustomTextView_customFont); 37 | setCustomFont(ctx, customFont); 38 | a.recycle(); 39 | } 40 | 41 | public boolean setCustomFont(Context ctx, String asset) { 42 | Typeface typeface = null; 43 | try { 44 | typeface = Typeface.createFromAsset(ctx.getAssets(),"fonts/"+asset); 45 | } catch (Exception e) { 46 | Log.e(TAG, "Unable to load typeface: "+e.getMessage()); 47 | return false; 48 | } 49 | 50 | setTypeface(typeface); 51 | return true; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Supports/Utils/Debug.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Supports.Utils; 2 | 3 | import android.util.Log; 4 | 5 | import silencebeat.com.chatview.BuildConfig; 6 | 7 | /** 8 | * Created by Candra Triyadi on 07/11/2017. 9 | */ 10 | 11 | public class Debug { 12 | 13 | public static void log(String TAG, String message){ 14 | if (BuildConfig.DEBUG){ 15 | Log.e(TAG, message); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Supports/Utils/ImageChoser.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Supports.Utils; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.os.Environment; 9 | import android.provider.MediaStore; 10 | import android.support.v4.app.ActivityCompat; 11 | import android.support.v4.content.ContextCompat; 12 | 13 | 14 | import java.io.File; 15 | 16 | /** 17 | * Created by Candra Triyadi on 10/21/16. 18 | */ 19 | 20 | public class ImageChoser { 21 | Context context; 22 | Activity activity; 23 | 24 | public final static int PERMISSION_CODE_CAMERA = 123; 25 | public final static int RESULT_CODE_CAMERA = 111; 26 | public final static int PERMIISION_CODE_WRITE2 = 789; 27 | public final static int RESULT_CODE_GALLERY = 222; 28 | public final static int PERMISSION_CODE_VIDEO = 963; 29 | public final static int RESULT_CODE_VIDEO = 852; 30 | 31 | public ImageChoser(Context context, Activity activity) { 32 | this.context = context; 33 | this.activity = activity; 34 | } 35 | 36 | public void permissionCamera(){ 37 | if(ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){ 38 | ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.CAMERA},PERMISSION_CODE_CAMERA); 39 | }else { 40 | this.Camera(); 41 | } 42 | } 43 | 44 | public void permissionWriteExternalGalery(){ 45 | if(ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ 46 | ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},PERMIISION_CODE_WRITE2); 47 | }else { 48 | Gallery(); 49 | } 50 | } 51 | 52 | public void Camera(){ 53 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 54 | activity.startActivityForResult(intent, RESULT_CODE_CAMERA); 55 | } 56 | 57 | public void Gallery(){ 58 | File path = new File(Environment.getExternalStorageDirectory() + "/"); 59 | if (!path.exists()) { 60 | path.mkdirs(); 61 | } 62 | 63 | Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 64 | galleryIntent.setType("image/*"); 65 | activity.startActivityForResult(galleryIntent, RESULT_CODE_GALLERY); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Supports/Utils/StaticVariable.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Supports.Utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Bitmap; 6 | import android.graphics.Matrix; 7 | import android.os.Environment; 8 | import android.text.format.DateUtils; 9 | 10 | 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | import java.text.ParseException; 16 | import java.text.SimpleDateFormat; 17 | import java.util.Calendar; 18 | import java.util.Date; 19 | import java.util.Locale; 20 | 21 | import silencebeat.com.chatview.R; 22 | 23 | /** 24 | * Created by Candra Triyadi on 07/03/2018. 25 | */ 26 | 27 | public class StaticVariable { 28 | 29 | 30 | public static Bitmap RotateBitmap(Bitmap source, float angle) 31 | { 32 | Matrix matrix = new Matrix(); 33 | matrix.postRotate(angle); 34 | return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true); 35 | } 36 | 37 | public static String savebitmap(Activity activity, Bitmap bmp) throws IOException { 38 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); 39 | ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 40 | bmp.compress(Bitmap.CompressFormat.JPEG, 60, bytes); 41 | File f = new File(Environment.getExternalStorageDirectory().getAbsolutePath() 42 | + File.separator + activity.getPackageName()+ File.separator +"temp/"+dateFormat.format(new Date())+".jpg"); 43 | f.getParentFile().mkdirs(); 44 | f.createNewFile(); 45 | FileOutputStream fo = new FileOutputStream(f); 46 | fo.write(bytes.toByteArray()); 47 | fo.close(); 48 | return f.getAbsolutePath(); 49 | } 50 | 51 | public static String parseDate(Context context, Calendar calendar){ 52 | String time; 53 | int year = calendar.get(Calendar.YEAR); 54 | int month = calendar.get(Calendar.MONTH); 55 | int day = calendar.get(Calendar.DAY_OF_MONTH); 56 | int hour = calendar.get(Calendar.HOUR_OF_DAY); 57 | int minute = calendar.get(Calendar.MINUTE); 58 | if (DateUtils.isToday(calendar.getTimeInMillis())){ 59 | time = hour+":"+minute; 60 | }else{ 61 | time = context.getResources().getStringArray(R.array.arr_month_short)[month] + 62 | " "+day+", "+year; 63 | } 64 | return time; 65 | } 66 | 67 | public static String toDate(Context context, String dateString){ 68 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US); 69 | Calendar calendar = Calendar.getInstance(); 70 | Date date = null; 71 | try { 72 | date = sdf.parse(dateString); 73 | } catch (ParseException e) { 74 | e.printStackTrace(); 75 | } 76 | calendar.setTime(date); 77 | String time; 78 | int year = calendar.get(Calendar.YEAR); 79 | int month = calendar.get(Calendar.MONTH); 80 | int day = calendar.get(Calendar.DAY_OF_MONTH); 81 | time = context.getResources().getStringArray(R.array.arr_month)[month] + 82 | " "+day+", "+year; 83 | return time; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Supports/Utils/TouchImageView.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Supports.Utils; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.Configuration; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Canvas; 8 | import android.graphics.Matrix; 9 | import android.graphics.PointF; 10 | import android.graphics.RectF; 11 | import android.graphics.drawable.Drawable; 12 | import android.net.Uri; 13 | import android.os.Build.VERSION; 14 | import android.os.Build.VERSION_CODES; 15 | import android.os.Bundle; 16 | import android.os.Parcelable; 17 | import android.support.v7.widget.AppCompatImageView; 18 | import android.util.AttributeSet; 19 | import android.util.Log; 20 | import android.view.GestureDetector; 21 | import android.view.MotionEvent; 22 | import android.view.ScaleGestureDetector; 23 | import android.view.View; 24 | import android.view.animation.AccelerateDecelerateInterpolator; 25 | import android.widget.OverScroller; 26 | import android.widget.Scroller; 27 | 28 | 29 | public class TouchImageView extends AppCompatImageView { 30 | 31 | private static final String DEBUG = "DEBUG"; 32 | 33 | // 34 | // SuperMin and SuperMax multipliers. Determine how much the image can be 35 | // zoomed below or above the zoom boundaries, before animating back to the 36 | // min/max zoom boundary. 37 | // 38 | private static final float SUPER_MIN_MULTIPLIER = .75f; 39 | private static final float SUPER_MAX_MULTIPLIER = 1.25f; 40 | 41 | // 42 | // Scale of image ranges from minScale to maxScale, where minScale == 1 43 | // when the image is stretched to fit view. 44 | // 45 | private float normalizedScale; 46 | 47 | // 48 | // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal. 49 | // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix 50 | // saved prior to the screen rotating. 51 | // 52 | private Matrix matrix, prevMatrix; 53 | 54 | private static enum State { NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM }; 55 | private State state; 56 | 57 | private float minScale; 58 | private float maxScale; 59 | private float superMinScale; 60 | private float superMaxScale; 61 | private float[] m; 62 | 63 | private Context context; 64 | private Fling fling; 65 | 66 | private ScaleType mScaleType; 67 | 68 | private boolean imageRenderedAtLeastOnce; 69 | private boolean onDrawReady; 70 | 71 | private ZoomVariables delayedZoomVariables; 72 | 73 | // 74 | // Size of view and previous view size (ie before rotation) 75 | // 76 | private int viewWidth, viewHeight, prevViewWidth, prevViewHeight; 77 | 78 | // 79 | // Size of image when it is stretched to fit view. Before and After rotation. 80 | // 81 | private float matchViewWidth, matchViewHeight, prevMatchViewWidth, prevMatchViewHeight; 82 | 83 | private ScaleGestureDetector mScaleDetector; 84 | private GestureDetector mGestureDetector; 85 | private GestureDetector.OnDoubleTapListener doubleTapListener = null; 86 | private OnTouchListener userTouchListener = null; 87 | private OnTouchImageViewListener touchImageViewListener = null; 88 | 89 | public TouchImageView(Context context) { 90 | super(context); 91 | sharedConstructing(context); 92 | } 93 | 94 | public TouchImageView(Context context, AttributeSet attrs) { 95 | super(context, attrs); 96 | sharedConstructing(context); 97 | } 98 | 99 | public TouchImageView(Context context, AttributeSet attrs, int defStyle) { 100 | super(context, attrs, defStyle); 101 | sharedConstructing(context); 102 | } 103 | 104 | private void sharedConstructing(Context context) { 105 | super.setClickable(true); 106 | this.context = context; 107 | mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); 108 | mGestureDetector = new GestureDetector(context, new GestureListener()); 109 | matrix = new Matrix(); 110 | prevMatrix = new Matrix(); 111 | m = new float[9]; 112 | normalizedScale = 1; 113 | if (mScaleType == null) { 114 | mScaleType = ScaleType.FIT_CENTER; 115 | } 116 | minScale = 1; 117 | maxScale = 3; 118 | superMinScale = SUPER_MIN_MULTIPLIER * minScale; 119 | superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; 120 | setImageMatrix(matrix); 121 | setScaleType(ScaleType.MATRIX); 122 | setState(State.NONE); 123 | onDrawReady = false; 124 | super.setOnTouchListener(new PrivateOnTouchListener()); 125 | } 126 | 127 | @Override 128 | public void setOnTouchListener(OnTouchListener l) { 129 | userTouchListener = l; 130 | } 131 | 132 | public void setOnTouchImageViewListener(OnTouchImageViewListener l) { 133 | touchImageViewListener = l; 134 | } 135 | 136 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener l) { 137 | doubleTapListener = l; 138 | } 139 | 140 | @Override 141 | public void setImageResource(int resId) { 142 | super.setImageResource(resId); 143 | savePreviousImageValues(); 144 | fitImageToView(); 145 | } 146 | 147 | @Override 148 | public void setImageBitmap(Bitmap bm) { 149 | super.setImageBitmap(bm); 150 | savePreviousImageValues(); 151 | fitImageToView(); 152 | } 153 | 154 | @Override 155 | public void setImageDrawable(Drawable drawable) { 156 | super.setImageDrawable(drawable); 157 | savePreviousImageValues(); 158 | fitImageToView(); 159 | } 160 | 161 | @Override 162 | public void setImageURI(Uri uri) { 163 | super.setImageURI(uri); 164 | savePreviousImageValues(); 165 | fitImageToView(); 166 | } 167 | 168 | @Override 169 | public void setScaleType(ScaleType type) { 170 | if (type == ScaleType.FIT_START || type == ScaleType.FIT_END) { 171 | throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); 172 | } 173 | if (type == ScaleType.MATRIX) { 174 | super.setScaleType(ScaleType.MATRIX); 175 | 176 | } else { 177 | mScaleType = type; 178 | if (onDrawReady) { 179 | // 180 | // If the image is already rendered, scaleType has been called programmatically 181 | // and the TouchImageView should be updated with the new scaleType. 182 | // 183 | setZoom(this); 184 | } 185 | } 186 | } 187 | 188 | @Override 189 | public ScaleType getScaleType() { 190 | return mScaleType; 191 | } 192 | 193 | /** 194 | * Returns false if image is in initial, unzoomed state. False, otherwise. 195 | * @return true if image is zoomed 196 | */ 197 | public boolean isZoomed() { 198 | return normalizedScale != 1; 199 | } 200 | 201 | /** 202 | * Return a Rect representing the zoomed image. 203 | * @return rect representing zoomed image 204 | */ 205 | public RectF getZoomedRect() { 206 | if (mScaleType == ScaleType.FIT_XY) { 207 | throw new UnsupportedOperationException("getZoomedRect() not supported with FIT_XY"); 208 | } 209 | PointF topLeft = transformCoordTouchToBitmap(0, 0, true); 210 | PointF bottomRight = transformCoordTouchToBitmap(viewWidth, viewHeight, true); 211 | 212 | float w = getDrawable().getIntrinsicWidth(); 213 | float h = getDrawable().getIntrinsicHeight(); 214 | return new RectF(topLeft.x / w, topLeft.y / h, bottomRight.x / w, bottomRight.y / h); 215 | } 216 | 217 | /** 218 | * Save the current matrix and view dimensions 219 | * in the prevMatrix and prevView variables. 220 | */ 221 | private void savePreviousImageValues() { 222 | if (matrix != null && viewHeight != 0 && viewWidth != 0) { 223 | matrix.getValues(m); 224 | prevMatrix.setValues(m); 225 | prevMatchViewHeight = matchViewHeight; 226 | prevMatchViewWidth = matchViewWidth; 227 | prevViewHeight = viewHeight; 228 | prevViewWidth = viewWidth; 229 | } 230 | } 231 | 232 | @Override 233 | public Parcelable onSaveInstanceState() { 234 | Bundle bundle = new Bundle(); 235 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 236 | bundle.putFloat("saveScale", normalizedScale); 237 | bundle.putFloat("matchViewHeight", matchViewHeight); 238 | bundle.putFloat("matchViewWidth", matchViewWidth); 239 | bundle.putInt("viewWidth", viewWidth); 240 | bundle.putInt("viewHeight", viewHeight); 241 | matrix.getValues(m); 242 | bundle.putFloatArray("matrix", m); 243 | bundle.putBoolean("imageRendered", imageRenderedAtLeastOnce); 244 | return bundle; 245 | } 246 | 247 | @Override 248 | public void onRestoreInstanceState(Parcelable state) { 249 | if (state instanceof Bundle) { 250 | Bundle bundle = (Bundle) state; 251 | normalizedScale = bundle.getFloat("saveScale"); 252 | m = bundle.getFloatArray("matrix"); 253 | prevMatrix.setValues(m); 254 | prevMatchViewHeight = bundle.getFloat("matchViewHeight"); 255 | prevMatchViewWidth = bundle.getFloat("matchViewWidth"); 256 | prevViewHeight = bundle.getInt("viewHeight"); 257 | prevViewWidth = bundle.getInt("viewWidth"); 258 | imageRenderedAtLeastOnce = bundle.getBoolean("imageRendered"); 259 | super.onRestoreInstanceState(bundle.getParcelable("instanceState")); 260 | return; 261 | } 262 | 263 | super.onRestoreInstanceState(state); 264 | } 265 | 266 | @Override 267 | protected void onDraw(Canvas canvas) { 268 | onDrawReady = true; 269 | imageRenderedAtLeastOnce = true; 270 | if (delayedZoomVariables != null) { 271 | setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX, delayedZoomVariables.focusY, delayedZoomVariables.scaleType); 272 | delayedZoomVariables = null; 273 | } 274 | super.onDraw(canvas); 275 | } 276 | 277 | @Override 278 | public void onConfigurationChanged(Configuration newConfig) { 279 | super.onConfigurationChanged(newConfig); 280 | savePreviousImageValues(); 281 | } 282 | 283 | /** 284 | * Get the max zoom multiplier. 285 | * @return max zoom multiplier. 286 | */ 287 | public float getMaxZoom() { 288 | return maxScale; 289 | } 290 | 291 | /** 292 | * Set the max zoom multiplier. Default value: 3. 293 | * @param max max zoom multiplier. 294 | */ 295 | public void setMaxZoom(float max) { 296 | maxScale = max; 297 | superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; 298 | } 299 | 300 | /** 301 | * Get the min zoom multiplier. 302 | * @return min zoom multiplier. 303 | */ 304 | public float getMinZoom() { 305 | return minScale; 306 | } 307 | 308 | /** 309 | * Get the current zoom. This is the zoom relative to the initial 310 | * scale, not the original resource. 311 | * @return current zoom multiplier. 312 | */ 313 | public float getCurrentZoom() { 314 | return normalizedScale; 315 | } 316 | 317 | /** 318 | * Set the min zoom multiplier. Default value: 1. 319 | * @param min min zoom multiplier. 320 | */ 321 | public void setMinZoom(float min) { 322 | minScale = min; 323 | superMinScale = SUPER_MIN_MULTIPLIER * minScale; 324 | } 325 | 326 | /** 327 | * Reset zoom and translation to initial state. 328 | */ 329 | public void resetZoom() { 330 | normalizedScale = 1; 331 | fitImageToView(); 332 | } 333 | 334 | /** 335 | * Set zoom to the specified scale. Image will be centered by default. 336 | * @param scale 337 | */ 338 | public void setZoom(float scale) { 339 | setZoom(scale, 0.5f, 0.5f); 340 | } 341 | 342 | /** 343 | * Set zoom to the specified scale. Image will be centered around the point 344 | * (focusX, focusY). These floats range from 0 to 1 and denote the focus point 345 | * as a fraction from the left and top of the view. For example, the top left 346 | * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). 347 | * @param scale 348 | * @param focusX 349 | * @param focusY 350 | */ 351 | public void setZoom(float scale, float focusX, float focusY) { 352 | setZoom(scale, focusX, focusY, mScaleType); 353 | } 354 | 355 | /** 356 | * Set zoom to the specified scale. Image will be centered around the point 357 | * (focusX, focusY). These floats range from 0 to 1 and denote the focus point 358 | * as a fraction from the left and top of the view. For example, the top left 359 | * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). 360 | * @param scale 361 | * @param focusX 362 | * @param focusY 363 | * @param scaleType 364 | */ 365 | public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) { 366 | // 367 | // setZoom can be called before the image is on the screen, but at this point, 368 | // image and view sizes have not yet been calculated in onMeasure. Thus, we should 369 | // delay calling setZoom until the view has been measured. 370 | // 371 | if (!onDrawReady) { 372 | delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType); 373 | return; 374 | } 375 | 376 | if (scaleType != mScaleType) { 377 | setScaleType(scaleType); 378 | } 379 | resetZoom(); 380 | scaleImage(scale, viewWidth / 2, viewHeight / 2, true); 381 | matrix.getValues(m); 382 | m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f)); 383 | m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f)); 384 | matrix.setValues(m); 385 | fixTrans(); 386 | setImageMatrix(matrix); 387 | } 388 | 389 | /** 390 | * Set zoom parameters equal to another TouchImageView. Including scale, position, 391 | * and ScaleType. 392 | * @param //TouchImageView 393 | */ 394 | public void setZoom(TouchImageView img) { 395 | PointF center = img.getScrollPosition(); 396 | setZoom(img.getCurrentZoom(), center.x, center.y, img.getScaleType()); 397 | } 398 | 399 | /** 400 | * Return the point at the center of the zoomed image. The PointF coordinates range 401 | * in value between 0 and 1 and the focus point is denoted as a fraction from the left 402 | * and top of the view. For example, the top left corner of the image would be (0, 0). 403 | * And the bottom right corner would be (1, 1). 404 | * @return PointF representing the scroll position of the zoomed image. 405 | */ 406 | public PointF getScrollPosition() { 407 | Drawable drawable = getDrawable(); 408 | if (drawable == null) { 409 | return null; 410 | } 411 | int drawableWidth = drawable.getIntrinsicWidth(); 412 | int drawableHeight = drawable.getIntrinsicHeight(); 413 | 414 | PointF point = transformCoordTouchToBitmap(viewWidth / 2, viewHeight / 2, true); 415 | point.x /= drawableWidth; 416 | point.y /= drawableHeight; 417 | return point; 418 | } 419 | 420 | /** 421 | * Set the focus point of the zoomed image. The focus points are denoted as a fraction from the 422 | * left and top of the view. The focus points can range in value between 0 and 1. 423 | * @param focusX 424 | * @param focusY 425 | */ 426 | public void setScrollPosition(float focusX, float focusY) { 427 | setZoom(normalizedScale, focusX, focusY); 428 | } 429 | 430 | /** 431 | * Performs boundary checking and fixes the image matrix if it 432 | * is out of bounds. 433 | */ 434 | private void fixTrans() { 435 | matrix.getValues(m); 436 | float transX = m[Matrix.MTRANS_X]; 437 | float transY = m[Matrix.MTRANS_Y]; 438 | 439 | float fixTransX = getFixTrans(transX, viewWidth, getImageWidth()); 440 | float fixTransY = getFixTrans(transY, viewHeight, getImageHeight()); 441 | 442 | if (fixTransX != 0 || fixTransY != 0) { 443 | matrix.postTranslate(fixTransX, fixTransY); 444 | } 445 | } 446 | 447 | /** 448 | * When transitioning from zooming from focus to zoom from center (or vice versa) 449 | * the image can become unaligned within the view. This is apparent when zooming 450 | * quickly. When the content size is less than the view size, the content will often 451 | * be centered incorrectly within the view. fixScaleTrans first calls fixTrans() and 452 | * then makes sure the image is centered correctly within the view. 453 | */ 454 | private void fixScaleTrans() { 455 | fixTrans(); 456 | matrix.getValues(m); 457 | if (getImageWidth() < viewWidth) { 458 | m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2; 459 | } 460 | 461 | if (getImageHeight() < viewHeight) { 462 | m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2; 463 | } 464 | matrix.setValues(m); 465 | } 466 | 467 | private float getFixTrans(float trans, float viewSize, float contentSize) { 468 | float minTrans, maxTrans; 469 | 470 | if (contentSize <= viewSize) { 471 | minTrans = 0; 472 | maxTrans = viewSize - contentSize; 473 | 474 | } else { 475 | minTrans = viewSize - contentSize; 476 | maxTrans = 0; 477 | } 478 | 479 | if (trans < minTrans) 480 | return -trans + minTrans; 481 | if (trans > maxTrans) 482 | return -trans + maxTrans; 483 | return 0; 484 | } 485 | 486 | private float getFixDragTrans(float delta, float viewSize, float contentSize) { 487 | if (contentSize <= viewSize) { 488 | return 0; 489 | } 490 | return delta; 491 | } 492 | 493 | private float getImageWidth() { 494 | return matchViewWidth * normalizedScale; 495 | } 496 | 497 | private float getImageHeight() { 498 | return matchViewHeight * normalizedScale; 499 | } 500 | 501 | @Override 502 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 503 | Drawable drawable = getDrawable(); 504 | if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { 505 | setMeasuredDimension(0, 0); 506 | return; 507 | } 508 | 509 | int drawableWidth = drawable.getIntrinsicWidth(); 510 | int drawableHeight = drawable.getIntrinsicHeight(); 511 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 512 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 513 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 514 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 515 | viewWidth = setViewSize(widthMode, widthSize, drawableWidth); 516 | viewHeight = setViewSize(heightMode, heightSize, drawableHeight); 517 | 518 | // 519 | // Set view dimensions 520 | // 521 | setMeasuredDimension(viewWidth, viewHeight); 522 | 523 | // 524 | // Fit content within view 525 | // 526 | fitImageToView(); 527 | } 528 | 529 | /** 530 | * If the normalizedScale is equal to 1, then the image is made to fit the screen. Otherwise, 531 | * it is made to fit the screen according to the dimensions of the previous image matrix. This 532 | * allows the image to maintain its zoom after rotation. 533 | */ 534 | private void fitImageToView() { 535 | Drawable drawable = getDrawable(); 536 | if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { 537 | return; 538 | } 539 | if (matrix == null || prevMatrix == null) { 540 | return; 541 | } 542 | 543 | int drawableWidth = drawable.getIntrinsicWidth(); 544 | int drawableHeight = drawable.getIntrinsicHeight(); 545 | 546 | // 547 | // Scale image for view 548 | // 549 | float scaleX = (float) viewWidth / drawableWidth; 550 | float scaleY = (float) viewHeight / drawableHeight; 551 | 552 | switch (mScaleType) { 553 | case CENTER: 554 | scaleX = scaleY = 1; 555 | break; 556 | 557 | case CENTER_CROP: 558 | scaleX = scaleY = Math.max(scaleX, scaleY); 559 | break; 560 | 561 | case CENTER_INSIDE: 562 | scaleX = scaleY = Math.min(1, Math.min(scaleX, scaleY)); 563 | 564 | case FIT_CENTER: 565 | scaleX = scaleY = Math.min(scaleX, scaleY); 566 | break; 567 | 568 | case FIT_XY: 569 | break; 570 | 571 | default: 572 | // 573 | // FIT_START and FIT_END not supported 574 | // 575 | throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); 576 | 577 | } 578 | 579 | // 580 | // Center the image 581 | // 582 | float redundantXSpace = viewWidth - (scaleX * drawableWidth); 583 | float redundantYSpace = viewHeight - (scaleY * drawableHeight); 584 | matchViewWidth = viewWidth - redundantXSpace; 585 | matchViewHeight = viewHeight - redundantYSpace; 586 | if (!isZoomed() && !imageRenderedAtLeastOnce) { 587 | // 588 | // Stretch and center image to fit view 589 | // 590 | matrix.setScale(scaleX, scaleY); 591 | matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2); 592 | normalizedScale = 1; 593 | 594 | } else { 595 | // 596 | // These values should never be 0 or we will set viewWidth and viewHeight 597 | // to NaN in translateMatrixAfterRotate. To avoid this, call savePreviousImageValues 598 | // to set them equal to the current values. 599 | // 600 | if (prevMatchViewWidth == 0 || prevMatchViewHeight == 0) { 601 | savePreviousImageValues(); 602 | } 603 | 604 | prevMatrix.getValues(m); 605 | 606 | // 607 | // Rescale Matrix after rotation 608 | // 609 | m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth * normalizedScale; 610 | m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight * normalizedScale; 611 | 612 | // 613 | // TransX and TransY from previous matrix 614 | // 615 | float transX = m[Matrix.MTRANS_X]; 616 | float transY = m[Matrix.MTRANS_Y]; 617 | 618 | // 619 | // Width 620 | // 621 | float prevActualWidth = prevMatchViewWidth * normalizedScale; 622 | float actualWidth = getImageWidth(); 623 | translateMatrixAfterRotate(Matrix.MTRANS_X, transX, prevActualWidth, actualWidth, prevViewWidth, viewWidth, drawableWidth); 624 | 625 | // 626 | // Height 627 | // 628 | float prevActualHeight = prevMatchViewHeight * normalizedScale; 629 | float actualHeight = getImageHeight(); 630 | translateMatrixAfterRotate(Matrix.MTRANS_Y, transY, prevActualHeight, actualHeight, prevViewHeight, viewHeight, drawableHeight); 631 | 632 | // 633 | // Set the matrix to the adjusted scale and translate values. 634 | // 635 | matrix.setValues(m); 636 | } 637 | fixTrans(); 638 | setImageMatrix(matrix); 639 | } 640 | 641 | /** 642 | * Set view dimensions based on layout params 643 | * 644 | * @param mode 645 | * @param size 646 | * @param drawableWidth 647 | * @return 648 | */ 649 | private int setViewSize(int mode, int size, int drawableWidth) { 650 | int viewSize; 651 | switch (mode) { 652 | case MeasureSpec.EXACTLY: 653 | viewSize = size; 654 | break; 655 | 656 | case MeasureSpec.AT_MOST: 657 | viewSize = Math.min(drawableWidth, size); 658 | break; 659 | 660 | case MeasureSpec.UNSPECIFIED: 661 | viewSize = drawableWidth; 662 | break; 663 | 664 | default: 665 | viewSize = size; 666 | break; 667 | } 668 | return viewSize; 669 | } 670 | 671 | /** 672 | * After rotating, the matrix needs to be translated. This function finds the area of image 673 | * which was previously centered and adjusts translations so that is again the center, post-rotation. 674 | * 675 | * @param axis Matrix.MTRANS_X or Matrix.MTRANS_Y 676 | * @param trans the value of trans in that axis before the rotation 677 | * @param prevImageSize the width/height of the image before the rotation 678 | * @param imageSize width/height of the image after rotation 679 | * @param prevViewSize width/height of view before rotation 680 | * @param viewSize width/height of view after rotation 681 | * @param drawableSize width/height of drawable 682 | */ 683 | private void translateMatrixAfterRotate(int axis, float trans, float prevImageSize, float imageSize, int prevViewSize, int viewSize, int drawableSize) { 684 | if (imageSize < viewSize) { 685 | // 686 | // The width/height of image is less than the view's width/height. Center it. 687 | // 688 | m[axis] = (viewSize - (drawableSize * m[Matrix.MSCALE_X])) * 0.5f; 689 | 690 | } else if (trans > 0) { 691 | // 692 | // The image is larger than the view, but was not before rotation. Center it. 693 | // 694 | m[axis] = -((imageSize - viewSize) * 0.5f); 695 | 696 | } else { 697 | // 698 | // Find the area of the image which was previously centered in the view. Determine its distance 699 | // from the left/top side of the view as a fraction of the entire image's width/height. Use that percentage 700 | // to calculate the trans in the new view width/height. 701 | // 702 | float percentage = (Math.abs(trans) + (0.5f * prevViewSize)) / prevImageSize; 703 | m[axis] = -((percentage * imageSize) - (viewSize * 0.5f)); 704 | } 705 | } 706 | 707 | private void setState(State state) { 708 | this.state = state; 709 | } 710 | 711 | public boolean canScrollHorizontallyFroyo(int direction) { 712 | return canScrollHorizontally(direction); 713 | } 714 | 715 | @Override 716 | public boolean canScrollHorizontally(int direction) { 717 | matrix.getValues(m); 718 | float x = m[Matrix.MTRANS_X]; 719 | 720 | if (getImageWidth() < viewWidth) { 721 | return false; 722 | 723 | } else if (x >= -1 && direction < 0) { 724 | return false; 725 | 726 | } else if (Math.abs(x) + viewWidth + 1 >= getImageWidth() && direction > 0) { 727 | return false; 728 | } 729 | 730 | return true; 731 | } 732 | 733 | /** 734 | * Gesture Listener detects a single click or long click and passes that on 735 | * to the view's listener. 736 | * @author Ortiz 737 | * 738 | */ 739 | private class GestureListener extends GestureDetector.SimpleOnGestureListener { 740 | 741 | @Override 742 | public boolean onSingleTapConfirmed(MotionEvent e) 743 | { 744 | if(doubleTapListener != null) { 745 | return doubleTapListener.onSingleTapConfirmed(e); 746 | } 747 | return performClick(); 748 | } 749 | 750 | @Override 751 | public void onLongPress(MotionEvent e) 752 | { 753 | performLongClick(); 754 | } 755 | 756 | @Override 757 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) 758 | { 759 | if (fling != null) { 760 | // 761 | // If a previous fling is still active, it should be cancelled so that two flings 762 | // are not run simultaenously. 763 | // 764 | fling.cancelFling(); 765 | } 766 | fling = new Fling((int) velocityX, (int) velocityY); 767 | compatPostOnAnimation(fling); 768 | return super.onFling(e1, e2, velocityX, velocityY); 769 | } 770 | 771 | @Override 772 | public boolean onDoubleTap(MotionEvent e) { 773 | boolean consumed = false; 774 | if(doubleTapListener != null) { 775 | consumed = doubleTapListener.onDoubleTap(e); 776 | } 777 | if (state == State.NONE) { 778 | float targetZoom = (normalizedScale == minScale) ? maxScale : minScale; 779 | DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false); 780 | compatPostOnAnimation(doubleTap); 781 | consumed = true; 782 | } 783 | return consumed; 784 | } 785 | 786 | @Override 787 | public boolean onDoubleTapEvent(MotionEvent e) { 788 | if(doubleTapListener != null) { 789 | return doubleTapListener.onDoubleTapEvent(e); 790 | } 791 | return false; 792 | } 793 | } 794 | 795 | public interface OnTouchImageViewListener { 796 | public void onMove(); 797 | } 798 | 799 | /** 800 | * Responsible for all touch events. Handles the heavy lifting of drag and also sends 801 | * touch events to Scale Detector and Gesture Detector. 802 | * @author Ortiz 803 | * 804 | */ 805 | private class PrivateOnTouchListener implements OnTouchListener { 806 | 807 | // 808 | // Remember last point position for dragging 809 | // 810 | private PointF last = new PointF(); 811 | 812 | @Override 813 | public boolean onTouch(View v, MotionEvent event) { 814 | mScaleDetector.onTouchEvent(event); 815 | mGestureDetector.onTouchEvent(event); 816 | PointF curr = new PointF(event.getX(), event.getY()); 817 | 818 | if (state == State.NONE || state == State.DRAG || state == State.FLING) { 819 | switch (event.getAction()) { 820 | case MotionEvent.ACTION_DOWN: 821 | last.set(curr); 822 | if (fling != null) 823 | fling.cancelFling(); 824 | setState(State.DRAG); 825 | break; 826 | 827 | case MotionEvent.ACTION_MOVE: 828 | if (state == State.DRAG) { 829 | float deltaX = curr.x - last.x; 830 | float deltaY = curr.y - last.y; 831 | float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth()); 832 | float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight()); 833 | matrix.postTranslate(fixTransX, fixTransY); 834 | fixTrans(); 835 | last.set(curr.x, curr.y); 836 | } 837 | break; 838 | 839 | case MotionEvent.ACTION_UP: 840 | case MotionEvent.ACTION_POINTER_UP: 841 | setState(State.NONE); 842 | break; 843 | } 844 | } 845 | 846 | setImageMatrix(matrix); 847 | 848 | // 849 | // User-defined OnTouchListener 850 | // 851 | if(userTouchListener != null) { 852 | userTouchListener.onTouch(v, event); 853 | } 854 | 855 | // 856 | // OnTouchImageViewListener is set: TouchImageView dragged by user. 857 | // 858 | if (touchImageViewListener != null) { 859 | touchImageViewListener.onMove(); 860 | } 861 | 862 | // 863 | // indicate event was handled 864 | // 865 | return true; 866 | } 867 | } 868 | 869 | /** 870 | * ScaleListener detects user two finger scaling and scales image. 871 | * @author Ortiz 872 | * 873 | */ 874 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { 875 | @Override 876 | public boolean onScaleBegin(ScaleGestureDetector detector) { 877 | setState(State.ZOOM); 878 | return true; 879 | } 880 | 881 | @Override 882 | public boolean onScale(ScaleGestureDetector detector) { 883 | scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true); 884 | 885 | // 886 | // OnTouchImageViewListener is set: TouchImageView pinch zoomed by user. 887 | // 888 | if (touchImageViewListener != null) { 889 | touchImageViewListener.onMove(); 890 | } 891 | return true; 892 | } 893 | 894 | @Override 895 | public void onScaleEnd(ScaleGestureDetector detector) { 896 | super.onScaleEnd(detector); 897 | setState(State.NONE); 898 | boolean animateToZoomBoundary = false; 899 | float targetZoom = normalizedScale; 900 | if (normalizedScale > maxScale) { 901 | targetZoom = maxScale; 902 | animateToZoomBoundary = true; 903 | 904 | } else if (normalizedScale < minScale) { 905 | targetZoom = minScale; 906 | animateToZoomBoundary = true; 907 | } 908 | 909 | if (animateToZoomBoundary) { 910 | DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true); 911 | compatPostOnAnimation(doubleTap); 912 | } 913 | } 914 | } 915 | 916 | private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) { 917 | 918 | float lowerScale, upperScale; 919 | if (stretchImageToSuper) { 920 | lowerScale = superMinScale; 921 | upperScale = superMaxScale; 922 | 923 | } else { 924 | lowerScale = minScale; 925 | upperScale = maxScale; 926 | } 927 | 928 | float origScale = normalizedScale; 929 | normalizedScale *= deltaScale; 930 | if (normalizedScale > upperScale) { 931 | normalizedScale = upperScale; 932 | deltaScale = upperScale / origScale; 933 | } else if (normalizedScale < lowerScale) { 934 | normalizedScale = lowerScale; 935 | deltaScale = lowerScale / origScale; 936 | } 937 | 938 | matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY); 939 | fixScaleTrans(); 940 | } 941 | 942 | /** 943 | * DoubleTapZoom calls a series of runnables which apply 944 | * an animated zoom in/out graphic to the image. 945 | * @author Ortiz 946 | * 947 | */ 948 | private class DoubleTapZoom implements Runnable { 949 | 950 | private long startTime; 951 | private static final float ZOOM_TIME = 500; 952 | private float startZoom, targetZoom; 953 | private float bitmapX, bitmapY; 954 | private boolean stretchImageToSuper; 955 | private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); 956 | private PointF startTouch; 957 | private PointF endTouch; 958 | 959 | DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) { 960 | setState(State.ANIMATE_ZOOM); 961 | startTime = System.currentTimeMillis(); 962 | this.startZoom = normalizedScale; 963 | this.targetZoom = targetZoom; 964 | this.stretchImageToSuper = stretchImageToSuper; 965 | PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false); 966 | this.bitmapX = bitmapPoint.x; 967 | this.bitmapY = bitmapPoint.y; 968 | 969 | // 970 | // Used for translating image during scaling 971 | // 972 | startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY); 973 | endTouch = new PointF(viewWidth / 2, viewHeight / 2); 974 | } 975 | 976 | @Override 977 | public void run() { 978 | float t = interpolate(); 979 | double deltaScale = calculateDeltaScale(t); 980 | scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper); 981 | translateImageToCenterTouchPosition(t); 982 | fixScaleTrans(); 983 | setImageMatrix(matrix); 984 | 985 | // 986 | // OnTouchImageViewListener is set: double tap runnable updates listener 987 | // with every frame. 988 | // 989 | if (touchImageViewListener != null) { 990 | touchImageViewListener.onMove(); 991 | } 992 | 993 | if (t < 1f) { 994 | // 995 | // We haven't finished zooming 996 | // 997 | compatPostOnAnimation(this); 998 | 999 | } else { 1000 | // 1001 | // Finished zooming 1002 | // 1003 | setState(State.NONE); 1004 | } 1005 | } 1006 | 1007 | /** 1008 | * Interpolate between where the image should start and end in order to translate 1009 | * the image so that the point that is touched is what ends up centered at the end 1010 | * of the zoom. 1011 | * @param t 1012 | */ 1013 | private void translateImageToCenterTouchPosition(float t) { 1014 | float targetX = startTouch.x + t * (endTouch.x - startTouch.x); 1015 | float targetY = startTouch.y + t * (endTouch.y - startTouch.y); 1016 | PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY); 1017 | matrix.postTranslate(targetX - curr.x, targetY - curr.y); 1018 | } 1019 | 1020 | /** 1021 | * Use interpolator to get t 1022 | * @return 1023 | */ 1024 | private float interpolate() { 1025 | long currTime = System.currentTimeMillis(); 1026 | float elapsed = (currTime - startTime) / ZOOM_TIME; 1027 | elapsed = Math.min(1f, elapsed); 1028 | return interpolator.getInterpolation(elapsed); 1029 | } 1030 | 1031 | /** 1032 | * Interpolate the current targeted zoom and get the delta 1033 | * from the current zoom. 1034 | * @param t 1035 | * @return 1036 | */ 1037 | private double calculateDeltaScale(float t) { 1038 | double zoom = startZoom + t * (targetZoom - startZoom); 1039 | return zoom / normalizedScale; 1040 | } 1041 | } 1042 | 1043 | /** 1044 | * This function will transform the coordinates in the touch event to the coordinate 1045 | * system of the drawable that the imageview contain 1046 | * @param x x-coordinate of touch event 1047 | * @param y y-coordinate of touch event 1048 | * @param clipToBitmap Touch event may occur within view, but outside image content. True, to clip return value 1049 | * to the bounds of the bitmap size. 1050 | * @return Coordinates of the point touched, in the coordinate system of the original drawable. 1051 | */ 1052 | private PointF transformCoordTouchToBitmap(float x, float y, boolean clipToBitmap) { 1053 | matrix.getValues(m); 1054 | float origW = getDrawable().getIntrinsicWidth(); 1055 | float origH = getDrawable().getIntrinsicHeight(); 1056 | float transX = m[Matrix.MTRANS_X]; 1057 | float transY = m[Matrix.MTRANS_Y]; 1058 | float finalX = ((x - transX) * origW) / getImageWidth(); 1059 | float finalY = ((y - transY) * origH) / getImageHeight(); 1060 | 1061 | if (clipToBitmap) { 1062 | finalX = Math.min(Math.max(finalX, 0), origW); 1063 | finalY = Math.min(Math.max(finalY, 0), origH); 1064 | } 1065 | 1066 | return new PointF(finalX , finalY); 1067 | } 1068 | 1069 | /** 1070 | * Inverse of transformCoordTouchToBitmap. This function will transform the coordinates in the 1071 | * drawable's coordinate system to the view's coordinate system. 1072 | * @param bx x-coordinate in original bitmap coordinate system 1073 | * @param by y-coordinate in original bitmap coordinate system 1074 | * @return Coordinates of the point in the view's coordinate system. 1075 | */ 1076 | private PointF transformCoordBitmapToTouch(float bx, float by) { 1077 | matrix.getValues(m); 1078 | float origW = getDrawable().getIntrinsicWidth(); 1079 | float origH = getDrawable().getIntrinsicHeight(); 1080 | float px = bx / origW; 1081 | float py = by / origH; 1082 | float finalX = m[Matrix.MTRANS_X] + getImageWidth() * px; 1083 | float finalY = m[Matrix.MTRANS_Y] + getImageHeight() * py; 1084 | return new PointF(finalX , finalY); 1085 | } 1086 | 1087 | /** 1088 | * Fling launches sequential runnables which apply 1089 | * the fling graphic to the image. The values for the translation 1090 | * are interpolated by the Scroller. 1091 | * @author Ortiz 1092 | * 1093 | */ 1094 | private class Fling implements Runnable { 1095 | 1096 | CompatScroller scroller; 1097 | int currX, currY; 1098 | 1099 | Fling(int velocityX, int velocityY) { 1100 | setState(State.FLING); 1101 | scroller = new CompatScroller(context); 1102 | matrix.getValues(m); 1103 | 1104 | int startX = (int) m[Matrix.MTRANS_X]; 1105 | int startY = (int) m[Matrix.MTRANS_Y]; 1106 | int minX, maxX, minY, maxY; 1107 | 1108 | if (getImageWidth() > viewWidth) { 1109 | minX = viewWidth - (int) getImageWidth(); 1110 | maxX = 0; 1111 | 1112 | } else { 1113 | minX = maxX = startX; 1114 | } 1115 | 1116 | if (getImageHeight() > viewHeight) { 1117 | minY = viewHeight - (int) getImageHeight(); 1118 | maxY = 0; 1119 | 1120 | } else { 1121 | minY = maxY = startY; 1122 | } 1123 | 1124 | scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX, 1125 | maxX, minY, maxY); 1126 | currX = startX; 1127 | currY = startY; 1128 | } 1129 | 1130 | public void cancelFling() { 1131 | if (scroller != null) { 1132 | setState(State.NONE); 1133 | scroller.forceFinished(true); 1134 | } 1135 | } 1136 | 1137 | @Override 1138 | public void run() { 1139 | 1140 | // 1141 | // OnTouchImageViewListener is set: TouchImageView listener has been flung by user. 1142 | // Listener runnable updated with each frame of fling animation. 1143 | // 1144 | if (touchImageViewListener != null) { 1145 | touchImageViewListener.onMove(); 1146 | } 1147 | 1148 | if (scroller.isFinished()) { 1149 | scroller = null; 1150 | return; 1151 | } 1152 | 1153 | if (scroller.computeScrollOffset()) { 1154 | int newX = scroller.getCurrX(); 1155 | int newY = scroller.getCurrY(); 1156 | int transX = newX - currX; 1157 | int transY = newY - currY; 1158 | currX = newX; 1159 | currY = newY; 1160 | matrix.postTranslate(transX, transY); 1161 | fixTrans(); 1162 | setImageMatrix(matrix); 1163 | compatPostOnAnimation(this); 1164 | } 1165 | } 1166 | } 1167 | 1168 | @TargetApi(VERSION_CODES.GINGERBREAD) 1169 | private class CompatScroller { 1170 | Scroller scroller; 1171 | OverScroller overScroller; 1172 | boolean isPreGingerbread; 1173 | 1174 | public CompatScroller(Context context) { 1175 | if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) { 1176 | isPreGingerbread = true; 1177 | scroller = new Scroller(context); 1178 | 1179 | } else { 1180 | isPreGingerbread = false; 1181 | overScroller = new OverScroller(context); 1182 | } 1183 | } 1184 | 1185 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { 1186 | if (isPreGingerbread) { 1187 | scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 1188 | } else { 1189 | overScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 1190 | } 1191 | } 1192 | 1193 | public void forceFinished(boolean finished) { 1194 | if (isPreGingerbread) { 1195 | scroller.forceFinished(finished); 1196 | } else { 1197 | overScroller.forceFinished(finished); 1198 | } 1199 | } 1200 | 1201 | public boolean isFinished() { 1202 | if (isPreGingerbread) { 1203 | return scroller.isFinished(); 1204 | } else { 1205 | return overScroller.isFinished(); 1206 | } 1207 | } 1208 | 1209 | public boolean computeScrollOffset() { 1210 | if (isPreGingerbread) { 1211 | return scroller.computeScrollOffset(); 1212 | } else { 1213 | overScroller.computeScrollOffset(); 1214 | return overScroller.computeScrollOffset(); 1215 | } 1216 | } 1217 | 1218 | public int getCurrX() { 1219 | if (isPreGingerbread) { 1220 | return scroller.getCurrX(); 1221 | } else { 1222 | return overScroller.getCurrX(); 1223 | } 1224 | } 1225 | 1226 | public int getCurrY() { 1227 | if (isPreGingerbread) { 1228 | return scroller.getCurrY(); 1229 | } else { 1230 | return overScroller.getCurrY(); 1231 | } 1232 | } 1233 | } 1234 | 1235 | @TargetApi(VERSION_CODES.JELLY_BEAN) 1236 | private void compatPostOnAnimation(Runnable runnable) { 1237 | if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 1238 | postOnAnimation(runnable); 1239 | 1240 | } else { 1241 | postDelayed(runnable, 1000/60); 1242 | } 1243 | } 1244 | 1245 | private class ZoomVariables { 1246 | public float scale; 1247 | public float focusX; 1248 | public float focusY; 1249 | public ScaleType scaleType; 1250 | 1251 | public ZoomVariables(float scale, float focusX, float focusY, ScaleType scaleType) { 1252 | this.scale = scale; 1253 | this.focusX = focusX; 1254 | this.focusY = focusY; 1255 | this.scaleType = scaleType; 1256 | } 1257 | } 1258 | 1259 | private void printMatrixInfo() { 1260 | float[] n = new float[9]; 1261 | matrix.getValues(n); 1262 | Log.d(DEBUG, "Scale: " + n[Matrix.MSCALE_X] + " TransX: " + n[Matrix.MTRANS_X] + " TransY: " + n[Matrix.MTRANS_Y]); 1263 | } 1264 | } 1265 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/Activities/ChatActivity.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.Activities; 2 | 3 | import android.app.FragmentTransaction; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.databinding.DataBindingUtil; 7 | import android.graphics.Bitmap; 8 | import android.media.MediaPlayer; 9 | import android.net.Uri; 10 | import android.os.Bundle; 11 | import android.os.Handler; 12 | import android.provider.MediaStore; 13 | import android.support.annotation.NonNull; 14 | import android.support.annotation.Nullable; 15 | import android.support.v7.app.AppCompatActivity; 16 | import android.support.v7.widget.LinearLayoutManager; 17 | import android.text.Editable; 18 | import android.text.TextWatcher; 19 | import android.view.View; 20 | 21 | import com.devlomi.record_view.OnRecordListener; 22 | 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.io.IOException; 26 | import java.text.SimpleDateFormat; 27 | import java.util.ArrayList; 28 | import java.util.Date; 29 | import java.util.Locale; 30 | 31 | import silencebeat.com.chatview.Entities.Models.Comment; 32 | import silencebeat.com.chatview.Modules.AudioRecordingListener; 33 | import silencebeat.com.chatview.Modules.MainWireframe; 34 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 35 | import silencebeat.com.chatview.R; 36 | import silencebeat.com.chatview.Supports.Utils.AudioRecorder; 37 | import silencebeat.com.chatview.Supports.Utils.ChatAdapter; 38 | import silencebeat.com.chatview.Supports.Utils.Debug; 39 | import silencebeat.com.chatview.Supports.Utils.ImageChoser; 40 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 41 | import silencebeat.com.chatview.Views.Activities.MediaActivity.CameraActivity; 42 | import silencebeat.com.chatview.Views.Fragments.DetailFotoDialogFragment; 43 | import silencebeat.com.chatview.databinding.ActivityChatBinding; 44 | 45 | /** 46 | * Created by Candra Triyadi on 05/03/2018. 47 | */ 48 | 49 | public class ChatActivity extends AppCompatActivity implements OnItemCommentClickListener 50 | , AudioRecordingListener{ 51 | 52 | ActivityChatBinding binding; 53 | ChatAdapter chatAdapter; 54 | ImageChoser imageChoser; 55 | AudioRecorder audioRecorder; 56 | MediaPlayer mediaPlayer; 57 | String userId = "-1"; 58 | String friendId = "1"; 59 | 60 | @Override 61 | protected void onCreate(@Nullable Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | binding = DataBindingUtil.setContentView(this, R.layout.activity_chat); 64 | imageChoser = new ImageChoser(this, this); 65 | audioRecorder = new AudioRecorder(this, this, this); 66 | setView(); 67 | } 68 | 69 | public void setMediaPlayer(MediaPlayer mediaPlayer) { 70 | this.mediaPlayer = mediaPlayer; 71 | } 72 | 73 | 74 | private void setView(){ 75 | chatAdapter = new ChatAdapter(this, new ArrayList(), this); 76 | chatAdapter.setUserId(userId); 77 | binding.list.setLayoutManager(new LinearLayoutManager(this)); 78 | binding.list.setAdapter(chatAdapter); 79 | 80 | binding.editMessage.addTextChangedListener(new TextWatcher() { 81 | @Override 82 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 83 | 84 | } 85 | 86 | @Override 87 | public void onTextChanged(CharSequence s, int start, int before, int count) { 88 | 89 | } 90 | 91 | @Override 92 | public void afterTextChanged(Editable s) { 93 | if(s.toString().trim().isEmpty()){ 94 | binding.btnSend.setVisibility(View.GONE); 95 | binding.btnRecord.setVisibility(View.VISIBLE); 96 | }else{ 97 | binding.btnSend.setVisibility(View.VISIBLE); 98 | binding.btnRecord.setVisibility(View.GONE); 99 | } 100 | } 101 | }); 102 | 103 | binding.btnSend.setOnClickListener(new View.OnClickListener() { 104 | @Override 105 | public void onClick(View v) { 106 | sendComment(binding.editMessage.getText().toString(), null); 107 | } 108 | }); 109 | 110 | binding.recordView.setOnRecordListener(new OnRecordListener() { 111 | @Override 112 | public void onStart() { 113 | audioRecorder.requestAudioPermission(); 114 | binding.editMessage.setVisibility(View.GONE); 115 | binding.btnGallery.setVisibility(View.GONE); 116 | binding.btnCamera.setVisibility(View.GONE); 117 | } 118 | 119 | @Override 120 | public void onCancel() { 121 | audioRecorder.stopRecording(false); 122 | binding.editMessage.setVisibility(View.VISIBLE); 123 | binding.btnGallery.setVisibility(View.VISIBLE); 124 | binding.btnCamera.setVisibility(View.VISIBLE); 125 | } 126 | 127 | @Override 128 | public void onFinish(long recordTime) { 129 | audioRecorder.stopRecording(true); 130 | binding.editMessage.setVisibility(View.VISIBLE); 131 | binding.btnGallery.setVisibility(View.VISIBLE); 132 | binding.btnCamera.setVisibility(View.VISIBLE); 133 | } 134 | 135 | @Override 136 | public void onLessThanSecond() { 137 | 138 | } 139 | }); 140 | 141 | binding.recordView.setCancelBounds(130); 142 | binding.recordView.setSoundEnabled(true); 143 | binding.recordView.setLessThanSecondAllowed(false); 144 | binding.btnRecord.setRecordView(binding.recordView); 145 | 146 | binding.btnCamera.setOnClickListener(new View.OnClickListener() { 147 | @Override 148 | public void onClick(View v) { 149 | Intent intent = new Intent(ChatActivity.this, CameraActivity.class); 150 | startActivityForResult(intent, 10); 151 | } 152 | }); 153 | 154 | binding.btnGallery.setOnClickListener(new View.OnClickListener() { 155 | @Override 156 | public void onClick(View v) { 157 | imageChoser.permissionWriteExternalGalery(); 158 | } 159 | }); 160 | 161 | binding.btnBack.setOnClickListener(new View.OnClickListener() { 162 | @Override 163 | public void onClick(View v) { 164 | onBackPressed(); 165 | } 166 | }); 167 | } 168 | 169 | @Override 170 | public void onItemCommentClicked(@NotNull Comment comment) { 171 | 172 | if (comment.getFileUrl().contains(".jpg")){ 173 | FragmentTransaction ft = getFragmentManager().beginTransaction(); 174 | DetailFotoDialogFragment detailFotoDialogFragment = DetailFotoDialogFragment.getInstance(comment.getFileUrl()); 175 | detailFotoDialogFragment.show(ft, ""); 176 | }else if (comment.getFileUrl().contains(".mp4")){ 177 | MainWireframe.getInstance().toPlayVideoView(this, comment.getFileUrl()); 178 | }else{ 179 | mediaPlayer.stop(); 180 | } 181 | } 182 | 183 | @Override 184 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 185 | switch (requestCode){ 186 | case ImageChoser.PERMISSION_CODE_CAMERA: 187 | 188 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ 189 | imageChoser.Camera(); 190 | } 191 | break; 192 | case ImageChoser.PERMIISION_CODE_WRITE2: 193 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ 194 | imageChoser.Gallery(); 195 | } 196 | break; 197 | case AudioRecorder.PERMISSIONS_RECORD_AUDIO: 198 | if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ 199 | } 200 | break; 201 | } 202 | } 203 | 204 | @Override 205 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 206 | super.onActivityResult(requestCode, resultCode, data); 207 | if (resultCode != RESULT_OK){ 208 | return; 209 | } 210 | 211 | if (requestCode == ImageChoser.RESULT_CODE_GALLERY){ 212 | Uri uri = data.getData(); 213 | try { 214 | Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri); 215 | sendComment("", StaticVariable.savebitmap(this, bitmap)); 216 | 217 | } catch (IOException e) { 218 | e.printStackTrace(); 219 | } 220 | }else if (requestCode == 10){ 221 | String path = data.getStringExtra("filepath"); 222 | if (path != null){ 223 | if (path.contains(".mp4")){ 224 | // Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND); 225 | sendComment("", path); 226 | }else { 227 | sendComment("", path); 228 | } 229 | } 230 | } 231 | } 232 | 233 | @Override 234 | public void onFinishRecording(@NotNull String filePath) { 235 | sendComment("", filePath); 236 | } 237 | 238 | @Override 239 | protected void onPause() { 240 | audioRecorder.release(); 241 | if (mediaPlayer != null) 242 | mediaPlayer.stop(); 243 | super.onPause(); 244 | } 245 | 246 | 247 | private void sendComment(String noteText, String fileUrl){ 248 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US); 249 | String time = sdf.format(new Date()); 250 | 251 | if (fileUrl != null && !fileUrl.isEmpty() 252 | && !fileUrl.equalsIgnoreCase("null")){ 253 | chatAdapter.addItem(new Comment(userId, noteText, time, fileUrl, "")); 254 | Debug.log("FileURL", fileUrl); 255 | }else{ 256 | chatAdapter.addItem(new Comment(userId, noteText, time, null 257 | , null)); 258 | } 259 | binding.editMessage.setText(""); 260 | binding.list.smoothScrollToPosition(chatAdapter.getItemCount() - 1); 261 | 262 | new Handler().postDelayed(new Runnable() { 263 | @Override 264 | public void run() { 265 | dummyReply(); 266 | } 267 | }, 1000); 268 | } 269 | 270 | private void dummyReply(){ 271 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US); 272 | String time = sdf.format(new Date()); 273 | chatAdapter.addItem(new Comment(friendId, "hallo", time, null 274 | , null)); 275 | binding.list.smoothScrollToPosition(chatAdapter.getItemCount() - 1); 276 | } 277 | 278 | 279 | } 280 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/Activities/MediaActivity/CameraActivity.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.Activities.MediaActivity; 2 | 3 | import android.Manifest; 4 | import android.app.FragmentTransaction; 5 | import android.content.Intent; 6 | import android.content.pm.PackageManager; 7 | import android.databinding.DataBindingUtil; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.os.Environment; 11 | import android.support.annotation.NonNull; 12 | import android.support.annotation.Nullable; 13 | import android.support.annotation.RequiresPermission; 14 | import android.support.v4.app.ActivityCompat; 15 | import android.support.v7.app.AppCompatActivity; 16 | import android.view.View; 17 | import com.github.florent37.camerafragment.CameraFragment; 18 | import com.github.florent37.camerafragment.CameraFragmentApi; 19 | import com.github.florent37.camerafragment.configuration.Configuration; 20 | import com.github.florent37.camerafragment.listeners.CameraFragmentResultListener; 21 | import com.github.florent37.camerafragment.listeners.CameraFragmentStateListener; 22 | 23 | import java.io.File; 24 | import java.text.SimpleDateFormat; 25 | import java.util.ArrayList; 26 | import java.util.Date; 27 | import java.util.List; 28 | import java.util.Locale; 29 | 30 | import silencebeat.com.chatview.R; 31 | import silencebeat.com.chatview.Views.Fragments.CameraResultFragment; 32 | import silencebeat.com.chatview.databinding.ActivityCameraBinding; 33 | 34 | /** 35 | * Created by Candra Triyadi on 07/03/2018. 36 | */ 37 | 38 | public class CameraActivity extends AppCompatActivity implements CameraFragmentResultListener 39 | , CameraResultFragment.OnCameraResultListener { 40 | 41 | private static final int REQUEST_CAMERA_PERMISSIONS = 931; 42 | public static final String FRAGMENT_TAG = "camera"; 43 | 44 | ActivityCameraBinding binding; 45 | 46 | @Override 47 | protected void onCreate(@Nullable Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | binding = DataBindingUtil.setContentView(this, R.layout.activity_camera); 50 | requestPermission(); 51 | } 52 | 53 | private void requestPermission() { 54 | if (Build.VERSION.SDK_INT > 15) { 55 | final String[] permissions = { 56 | Manifest.permission.CAMERA, 57 | Manifest.permission.RECORD_AUDIO, 58 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 59 | Manifest.permission.READ_EXTERNAL_STORAGE}; 60 | 61 | final List permissionsToRequest = new ArrayList<>(); 62 | for (String permission : permissions) { 63 | if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { 64 | permissionsToRequest.add(permission); 65 | } 66 | } 67 | if (!permissionsToRequest.isEmpty()) { 68 | ActivityCompat.requestPermissions(this, permissionsToRequest.toArray(new String[permissionsToRequest.size()]), REQUEST_CAMERA_PERMISSIONS); 69 | } else addCamera(); 70 | } else { 71 | addCamera(); 72 | } 73 | } 74 | 75 | @RequiresPermission(Manifest.permission.CAMERA) 76 | public void addCamera() { 77 | 78 | final Configuration.Builder builder = new Configuration.Builder(); 79 | builder 80 | .setCamera(Configuration.CAMERA_FACE_REAR) 81 | .setFlashMode(Configuration.FLASH_MODE_OFF) 82 | .setMediaAction(Configuration.MEDIA_ACTION_PHOTO); 83 | 84 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 85 | // TODO: Consider calling 86 | // ActivityCompat#requestPermissions 87 | // here to request the missing permissions, and then overriding 88 | // public void onRequestPermissionsResult(int requestCode, String[] permissions, 89 | // int[] grantResults) 90 | // to handle the case where the user grants the permission. See the documentation 91 | // for ActivityCompat#requestPermissions for more details. 92 | return; 93 | } 94 | final CameraFragment cameraFragment = CameraFragment.newInstance(builder.build()); 95 | getSupportFragmentManager().beginTransaction() 96 | .replace(R.id.content, cameraFragment, FRAGMENT_TAG) 97 | .commit(); 98 | 99 | if (cameraFragment != null) { 100 | cameraFragment.setResultListener(this); 101 | cameraFragment.setStateListener(new CameraFragmentStateListener() { 102 | 103 | @Override 104 | public void onCurrentCameraBack() { 105 | binding.btnCameraSwitch.displayBackCamera(); 106 | } 107 | 108 | @Override 109 | public void onCurrentCameraFront() { 110 | binding.btnCameraSwitch.displayFrontCamera(); 111 | } 112 | 113 | @Override 114 | public void onFlashAuto() { 115 | binding.btnFlashSwitch.displayFlashAuto(); 116 | } 117 | 118 | @Override 119 | public void onFlashOn() { 120 | binding.btnFlashSwitch.displayFlashOn(); 121 | } 122 | 123 | @Override 124 | public void onFlashOff() { 125 | binding.btnFlashSwitch.displayFlashOff(); 126 | } 127 | 128 | @Override 129 | public void onCameraSetupForPhoto() { 130 | binding.btnFlashSwitch.setVisibility(View.VISIBLE); 131 | binding.btnRecord.displayPhotoState(); 132 | binding.btnSwitchAction.displayActionWillSwitchVideo(); 133 | } 134 | 135 | @Override 136 | public void onCameraSetupForVideo() { 137 | binding.btnSwitchAction.displayActionWillSwitchPhoto(); 138 | binding.btnRecord.displayVideoRecordStateReady(); 139 | binding.btnFlashSwitch.setVisibility(View.GONE); 140 | } 141 | 142 | @Override 143 | public void shouldRotateControls(int degrees) { 144 | binding.btnRecord.setRotation(degrees); 145 | binding.btnSwitchAction.setRotation(degrees); 146 | binding.btnRecord.setRotation(degrees); 147 | binding.btnCameraSwitch.setRotation(degrees); 148 | binding.btnFlashSwitch.setRotation(degrees); 149 | binding.btnSetting.setRotation(degrees); 150 | } 151 | 152 | @Override 153 | public void onRecordStateVideoReadyForRecord() { 154 | binding.btnRecord.displayVideoRecordStateReady(); 155 | } 156 | 157 | @Override 158 | public void onRecordStateVideoInProgress() { 159 | binding.btnRecord.displayVideoRecordStateInProgress(); 160 | } 161 | 162 | @Override 163 | public void onRecordStatePhoto() { 164 | binding.btnRecord.displayPhotoState(); 165 | } 166 | 167 | @Override 168 | public void onStopVideoRecord() { 169 | binding.btnRecord.displayVideoRecordStateReady(); 170 | } 171 | 172 | @Override 173 | public void onStartVideoRecord(File outputFile) { 174 | } 175 | }); 176 | } 177 | 178 | binding.btnSetting.setOnClickListener(new View.OnClickListener() { 179 | @Override 180 | public void onClick(View v) { 181 | final CameraFragmentApi cameraFragment = getCameraFragment(); 182 | if (cameraFragment != null) 183 | cameraFragment.openSettingDialog(); 184 | } 185 | }); 186 | 187 | binding.btnCameraSwitch.setOnClickListener(new View.OnClickListener() { 188 | @Override 189 | public void onClick(View v) { 190 | final CameraFragmentApi cameraFragment = getCameraFragment(); 191 | if (cameraFragment != null) 192 | cameraFragment.switchCameraTypeFrontBack(); 193 | } 194 | }); 195 | 196 | binding.btnFlashSwitch.setOnClickListener(new View.OnClickListener() { 197 | @Override 198 | public void onClick(View v) { 199 | final CameraFragmentApi cameraFragment = getCameraFragment(); 200 | if (cameraFragment != null) 201 | cameraFragment.toggleFlashMode(); 202 | } 203 | }); 204 | 205 | binding.btnSwitchAction.setOnClickListener(new View.OnClickListener() { 206 | @Override 207 | public void onClick(View v) { 208 | final CameraFragmentApi cameraFragment = getCameraFragment(); 209 | if (cameraFragment != null) 210 | cameraFragment.switchActionPhotoVideo(); 211 | } 212 | }); 213 | 214 | binding.btnRecord.setOnClickListener(new View.OnClickListener() { 215 | @Override 216 | public void onClick(View v) { 217 | final CameraFragmentApi cameraFragment = getCameraFragment(); 218 | if (cameraFragment != null){ 219 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); 220 | cameraFragment.takePhotoOrCaptureVideo(CameraActivity.this, Environment 221 | .getExternalStorageDirectory().getAbsolutePath() 222 | + File.separator + getPackageName()+ File.separator +"temp/", 223 | dateFormat.format(new Date())); 224 | } 225 | 226 | } 227 | }); 228 | } 229 | 230 | @Override 231 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 232 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 233 | if (grantResults.length != 0) { 234 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 235 | // TODO: Consider calling 236 | // ActivityCompat#requestPermissions 237 | // here to request the missing permissions, and then overriding 238 | // public void onRequestPermissionsResult(int requestCode, String[] permissions, 239 | // int[] grantResults) 240 | // to handle the case where the user grants the permission. See the documentation 241 | // for ActivityCompat#requestPermissions for more details. 242 | return; 243 | } 244 | finish(); 245 | } 246 | } 247 | 248 | private CameraFragmentApi getCameraFragment() { 249 | return (CameraFragmentApi) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); 250 | } 251 | 252 | @Override 253 | public void onVideoRecorded(String filePath) { 254 | FragmentTransaction ft = getFragmentManager().beginTransaction(); 255 | CameraResultFragment fragment = CameraResultFragment.newInstance(filePath, CameraResultFragment.TYPE_VIDEO); 256 | fragment.show(ft, ""); 257 | } 258 | 259 | @Override 260 | public void onPhotoTaken(byte[] bytes, String filePath) { 261 | FragmentTransaction ft = getFragmentManager().beginTransaction(); 262 | CameraResultFragment fragment = CameraResultFragment.newInstance(filePath, CameraResultFragment.TYPE_PHOTO); 263 | fragment.show(ft, ""); 264 | } 265 | 266 | @Override 267 | public void onResultTaken(String pathUrl) { 268 | Intent resultIntent = new Intent(); 269 | resultIntent.putExtra("filepath", pathUrl); 270 | setResult(RESULT_OK, resultIntent); 271 | finish(); 272 | } 273 | 274 | @Override 275 | public void onCancel() { 276 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { 277 | addCamera(); 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/Activities/MediaActivity/VideoPlayerActivity.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.Activities.MediaActivity; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.widget.Toast; 9 | 10 | import com.afollestad.easyvideoplayer.EasyVideoCallback; 11 | import com.afollestad.easyvideoplayer.EasyVideoPlayer; 12 | 13 | import silencebeat.com.chatview.R; 14 | import silencebeat.com.chatview.databinding.ActivityVideoPlayerBinding; 15 | 16 | /** 17 | * Created by Candra Triyadi on 16/03/2018. 18 | */ 19 | 20 | public class VideoPlayerActivity extends AppCompatActivity implements EasyVideoCallback { 21 | 22 | ActivityVideoPlayerBinding binding; 23 | 24 | @Override 25 | protected void onCreate(@Nullable Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | binding = DataBindingUtil.setContentView(this, R.layout.activity_video_player); 28 | String videoURL = getIntent().getStringExtra("videoURL"); 29 | binding.player.setCallback(this); 30 | binding.player.setSource(Uri.parse(videoURL)); 31 | binding.player.start(); 32 | } 33 | 34 | @Override 35 | protected void onPause() { 36 | super.onPause(); 37 | binding.player.pause(); 38 | } 39 | 40 | @Override 41 | public void onStarted(EasyVideoPlayer player) { 42 | 43 | } 44 | 45 | @Override 46 | public void onPaused(EasyVideoPlayer player) { 47 | 48 | } 49 | 50 | @Override 51 | public void onPreparing(EasyVideoPlayer player) { 52 | 53 | } 54 | 55 | @Override 56 | public void onPrepared(EasyVideoPlayer player) { 57 | 58 | } 59 | 60 | @Override 61 | public void onBuffering(int percent) { 62 | 63 | } 64 | 65 | @Override 66 | public void onError(EasyVideoPlayer player, Exception e) { 67 | 68 | Toast.makeText(this,"Can't play this video", Toast.LENGTH_SHORT).show(); 69 | finish(); 70 | } 71 | 72 | @Override 73 | public void onCompletion(EasyVideoPlayer player) { 74 | 75 | } 76 | 77 | @Override 78 | public void onRetry(EasyVideoPlayer player, Uri source) { 79 | 80 | } 81 | 82 | @Override 83 | public void onSubmit(EasyVideoPlayer player, Uri source) { 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/Fragments/CameraResultFragment.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.Fragments; 2 | 3 | import android.app.DialogFragment; 4 | import android.content.Context; 5 | import android.databinding.DataBindingUtil; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | import android.support.annotation.Nullable; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import silencebeat.com.chatview.R; 16 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 17 | import silencebeat.com.chatview.databinding.FragmentCameraResultBinding; 18 | 19 | 20 | /** 21 | * Created by Candra Triyadi on 07/03/2018. 22 | */ 23 | 24 | public class CameraResultFragment extends DialogFragment { 25 | 26 | public interface OnCameraResultListener{ 27 | void onResultTaken(String pathUrl); 28 | void onCancel(); 29 | } 30 | 31 | OnCameraResultListener listener; 32 | FragmentCameraResultBinding binding; 33 | String type, pathUrl; 34 | public static final String TYPE_PHOTO = "TYPE_PHOTO"; 35 | public static final String TYPE_VIDEO = "TYPE_VIDEO"; 36 | 37 | @Override 38 | public void onAttach(Context context) { 39 | super.onAttach(context); 40 | listener = (OnCameraResultListener) context; 41 | } 42 | 43 | public static CameraResultFragment newInstance(String pathUrl, String type){ 44 | CameraResultFragment fragment = new CameraResultFragment(); 45 | Bundle bundle = new Bundle(); 46 | bundle.putString("pathUrl", pathUrl); 47 | bundle.putString("type", type); 48 | fragment.setArguments(bundle); 49 | return fragment; 50 | } 51 | 52 | @Override 53 | public void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen); 56 | type = getArguments().getString("type"); 57 | pathUrl = getArguments().getString("pathUrl"); 58 | } 59 | 60 | @Nullable 61 | @Override 62 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { 63 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_camera_result, container, false); 64 | return binding.getRoot(); 65 | } 66 | 67 | @Override 68 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 69 | super.onViewCreated(view, savedInstanceState); 70 | 71 | if (type.equalsIgnoreCase(TYPE_PHOTO)){ 72 | binding.imgPhoto.setVisibility(View.VISIBLE); 73 | Bitmap myBitmap = BitmapFactory.decodeFile(pathUrl); 74 | binding.imgPhoto.setImageBitmap(StaticVariable.RotateBitmap(myBitmap, 90)); 75 | }else{ 76 | binding.videoPreview.setVisibility(View.VISIBLE); 77 | Uri uri = Uri.parse(pathUrl); 78 | binding.videoPreview.setVideoURI(uri); 79 | binding.videoPreview.start(); 80 | } 81 | 82 | binding.btnCancel.setOnClickListener(new View.OnClickListener() { 83 | @Override 84 | public void onClick(View v) { 85 | dismiss(); 86 | listener.onCancel(); 87 | } 88 | }); 89 | 90 | binding.btnOk.setOnClickListener(new View.OnClickListener() { 91 | @Override 92 | public void onClick(View v) { 93 | listener.onResultTaken(pathUrl); 94 | } 95 | }); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/Fragments/DetailFotoDialogFragment.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.Fragments; 2 | 3 | import android.app.DialogFragment; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.util.DisplayMetrics; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 14 | 15 | import silencebeat.com.chatview.R; 16 | import silencebeat.com.chatview.databinding.FragmentDetailFotoDialogBinding; 17 | 18 | /** 19 | * Created by Candra Triyadi on 06/03/2018. 20 | */ 21 | 22 | public class DetailFotoDialogFragment extends DialogFragment { 23 | 24 | FragmentDetailFotoDialogBinding binding; 25 | 26 | public static DetailFotoDialogFragment getInstance(String path){ 27 | DetailFotoDialogFragment fragment = new DetailFotoDialogFragment(); 28 | Bundle bundle = new Bundle(); 29 | bundle.putString("path", path); 30 | fragment.setArguments(bundle); 31 | return fragment; 32 | } 33 | 34 | @Override 35 | public void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Black_NoTitleBar_Fullscreen); 38 | } 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { 43 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_detail_foto_dialog, container, false); 44 | return binding.getRoot(); 45 | } 46 | 47 | @Override 48 | public void onActivityCreated(Bundle savedInstanceState) { 49 | super.onActivityCreated(savedInstanceState); 50 | DisplayMetrics displayMetrics = new DisplayMetrics(); 51 | getActivity().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); 52 | int height = displayMetrics.heightPixels; 53 | int width = displayMetrics.widthPixels; 54 | String path = getArguments().getString("path"); 55 | Glide.with(getActivity()) 56 | .load(path) 57 | .asBitmap() 58 | .override(width,height) 59 | .dontAnimate() 60 | .diskCacheStrategy(DiskCacheStrategy.ALL) 61 | .into(binding.img); 62 | } 63 | 64 | @Override 65 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 66 | super.onViewCreated(view, savedInstanceState); 67 | 68 | // Bitmap bitmap = BitmapFactory.decodeFile(path); 69 | // try { 70 | // ExifInterface exif = new ExifInterface(path); 71 | // int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 72 | // int rotationInDegrees = exifToDegrees(rotation); 73 | // Matrix matrix = new Matrix(); 74 | // if (rotation != 0f) {matrix.preRotate(rotationInDegrees);} 75 | // bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); 76 | // } catch (IOException e) { 77 | // e.printStackTrace(); 78 | // } 79 | // 80 | // binding.img.setImageBitmap(bitmap); 81 | // 82 | // 83 | 84 | binding.btnClose.setOnClickListener(new View.OnClickListener() { 85 | @Override 86 | public void onClick(View v) { 87 | dismiss(); 88 | } 89 | }); 90 | } 91 | 92 | // private static int exifToDegrees(int exifOrientation) { 93 | // if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { return 90; } 94 | // else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) { return 180; } 95 | // else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) { return 270; } 96 | // return 0; 97 | // } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Incoming/IncomingChatAudioViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Incoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.media.MediaPlayer; 6 | import android.os.Handler; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | import android.widget.SeekBar; 10 | import java.io.IOException; 11 | import java.text.ParseException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Calendar; 14 | import java.util.Date; 15 | import java.util.Locale; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import silencebeat.com.chatview.Entities.Models.Comment; 19 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 20 | import silencebeat.com.chatview.R; 21 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 22 | import silencebeat.com.chatview.Views.Activities.ChatActivity; 23 | import silencebeat.com.chatview.databinding.ItemChatAudioIncomingBinding; 24 | 25 | /** 26 | * Created by Candra Triyadi on 15/03/2018. 27 | */ 28 | 29 | public class IncomingChatAudioViewHolder extends RecyclerView.ViewHolder { 30 | 31 | ItemChatAudioIncomingBinding binding; 32 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 33 | Calendar calendar = Calendar.getInstance(); 34 | private int lenght = 0; 35 | 36 | public IncomingChatAudioViewHolder(View itemView) { 37 | super(itemView); 38 | binding = DataBindingUtil.bind(itemView); 39 | } 40 | 41 | public void onBind(final Context context, final Comment comment 42 | , final OnItemCommentClickListener listener){ 43 | 44 | try { 45 | Date date = sdf.parse(comment.getNoteDate()); 46 | calendar.setTime(date); 47 | String time = StaticVariable.parseDate(context, calendar); 48 | binding.txtTime.setText(time); 49 | } catch (ParseException e) { 50 | e.printStackTrace(); 51 | } 52 | 53 | final MediaPlayer mediaPlayer = new MediaPlayer(); 54 | final Handler mSeekbarUpdateHandler = new Handler(); 55 | 56 | try { 57 | mediaPlayer.setDataSource(comment.getFileUrl()); 58 | mediaPlayer.prepareAsync(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | binding.seekbar.setMax(100); 63 | 64 | final Runnable mUpdateSeekbar = new Runnable() { 65 | 66 | @Override 67 | public void run() { 68 | 69 | String time = String.format(Locale.US, "%02d:%02d", 70 | TimeUnit.MILLISECONDS.toMinutes(mediaPlayer.getCurrentPosition()), 71 | TimeUnit.MILLISECONDS.toSeconds(mediaPlayer.getCurrentPosition()) - 72 | TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(mediaPlayer.getCurrentPosition())) 73 | ); 74 | 75 | float a = (float) mediaPlayer.getCurrentPosition()/ (float)mediaPlayer.getDuration(); 76 | binding.txtTimePlay.setText(time); 77 | binding.seekbar.setProgress((int)(a*100f)); 78 | mSeekbarUpdateHandler.postDelayed(this, 15); 79 | } 80 | }; 81 | 82 | mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 83 | @Override 84 | public void onCompletion(MediaPlayer mp) { 85 | lenght = 0; 86 | binding.btnPlay.setImageResource(R.drawable.ic_play); 87 | mSeekbarUpdateHandler.removeCallbacksAndMessages(null); 88 | } 89 | }); 90 | 91 | binding.btnPlay.setOnClickListener(new View.OnClickListener() { 92 | @Override 93 | public void onClick(View v) { 94 | mSeekbarUpdateHandler.removeCallbacksAndMessages(null); 95 | setMediaPlayer(context, mediaPlayer); 96 | 97 | if (mediaPlayer.isPlaying()){ 98 | lenght = mediaPlayer.getCurrentPosition(); 99 | binding.btnPlay.setImageResource(R.drawable.ic_play); 100 | mediaPlayer.pause(); 101 | 102 | }else{ 103 | binding.btnPlay.setImageResource(R.drawable.ic_pause); 104 | mediaPlayer.seekTo(lenght); 105 | mediaPlayer.start(); 106 | mediaPlayer.setVolume(100,100); 107 | mSeekbarUpdateHandler.postDelayed(mUpdateSeekbar, 100); 108 | } 109 | } 110 | }); 111 | 112 | binding.seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 113 | @Override 114 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 115 | if (fromUser){ 116 | mediaPlayer.seekTo(progress * 1000); 117 | mSeekbarUpdateHandler.removeCallbacksAndMessages(null); 118 | mSeekbarUpdateHandler.postDelayed(mUpdateSeekbar, progress * 1000); 119 | } 120 | } 121 | 122 | @Override 123 | public void onStartTrackingTouch(SeekBar seekBar) { 124 | 125 | } 126 | 127 | @Override 128 | public void onStopTrackingTouch(SeekBar seekBar) { 129 | 130 | } 131 | }); 132 | 133 | } 134 | 135 | private void setMediaPlayer(Context context, MediaPlayer mediaPlayer){ 136 | ((ChatActivity)context).setMediaPlayer(mediaPlayer); 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Incoming/IncomingChatImageViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Incoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | import com.bumptech.glide.Glide; 9 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 10 | 11 | import java.text.ParseException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Calendar; 14 | import java.util.Date; 15 | import java.util.Locale; 16 | 17 | import silencebeat.com.chatview.Entities.Models.Comment; 18 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 19 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 20 | import silencebeat.com.chatview.databinding.ItemChatImageIncomingBinding; 21 | 22 | /** 23 | * Created by Candra Triyadi on 15/03/2018. 24 | */ 25 | 26 | public class IncomingChatImageViewHolder extends RecyclerView.ViewHolder { 27 | 28 | ItemChatImageIncomingBinding binding; 29 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 30 | Calendar calendar = Calendar.getInstance(); 31 | 32 | public IncomingChatImageViewHolder(View itemView) { 33 | super(itemView); 34 | binding = DataBindingUtil.bind(itemView); 35 | } 36 | 37 | public void onBind(Context context, final Comment comment, final OnItemCommentClickListener listener){ 38 | 39 | try { 40 | Date date = sdf.parse(comment.getNoteDate()); 41 | calendar.setTime(date); 42 | String time = StaticVariable.parseDate(context, calendar); 43 | binding.txtTime.setText(time); 44 | } catch (ParseException e) { 45 | e.printStackTrace(); 46 | } 47 | 48 | Glide.with(context) 49 | .load(comment.getFileUrl()) 50 | .asBitmap() 51 | .override(100,100 ) 52 | .dontAnimate() 53 | .diskCacheStrategy(DiskCacheStrategy.ALL) 54 | .into(binding.imgPhoto); 55 | 56 | binding.imgPhoto.setOnClickListener(new View.OnClickListener() { 57 | @Override 58 | public void onClick(View v) { 59 | listener.onItemCommentClicked(comment); 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Incoming/IncomingChatTextViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Incoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | 9 | import java.text.ParseException; 10 | import java.text.SimpleDateFormat; 11 | import java.util.Calendar; 12 | import java.util.Date; 13 | import java.util.Locale; 14 | 15 | import silencebeat.com.chatview.Entities.Models.Comment; 16 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 17 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 18 | import silencebeat.com.chatview.databinding.ItemChatTextIncomingBinding; 19 | 20 | /** 21 | * Created by Candra Triyadi on 15/03/2018. 22 | */ 23 | 24 | public class IncomingChatTextViewHolder extends RecyclerView.ViewHolder { 25 | 26 | private ItemChatTextIncomingBinding binding; 27 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 28 | Calendar calendar = Calendar.getInstance(); 29 | 30 | public IncomingChatTextViewHolder(View itemView) { 31 | super(itemView); 32 | binding = DataBindingUtil.bind(itemView); 33 | } 34 | 35 | public void onBind(Context context, Comment comment, OnItemCommentClickListener listener){ 36 | binding.txtMessage.setText(comment.getNoteText()); 37 | 38 | try { 39 | Date date = sdf.parse(comment.getNoteDate()); 40 | calendar.setTime(date); 41 | String time = StaticVariable.parseDate(context, calendar); 42 | binding.txtTime.setText(time); 43 | } catch (ParseException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Incoming/IncomingChatVideoViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Incoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | import com.bumptech.glide.Glide; 9 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 10 | 11 | import java.text.ParseException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Calendar; 14 | import java.util.Date; 15 | import java.util.Locale; 16 | 17 | import silencebeat.com.chatview.Entities.Models.Comment; 18 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 19 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 20 | import silencebeat.com.chatview.databinding.ItemChatVideoIncomingBinding; 21 | 22 | /** 23 | * Created by Candra Triyadi on 15/03/2018. 24 | */ 25 | 26 | public class IncomingChatVideoViewHolder extends RecyclerView.ViewHolder { 27 | 28 | ItemChatVideoIncomingBinding binding; 29 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 30 | Calendar calendar = Calendar.getInstance(); 31 | 32 | public IncomingChatVideoViewHolder(View itemView) { 33 | super(itemView); 34 | binding = DataBindingUtil.bind(itemView); 35 | } 36 | 37 | public void onBind(Context context, final Comment comment, final OnItemCommentClickListener listener){ 38 | 39 | try { 40 | Date date = sdf.parse(comment.getNoteDate()); 41 | calendar.setTime(date); 42 | String time = StaticVariable.parseDate(context, calendar); 43 | binding.txtTime.setText(time); 44 | } catch (ParseException e) { 45 | e.printStackTrace(); 46 | } 47 | 48 | Glide.with(context) 49 | .load(comment.getThumbnailVideo()) 50 | .asBitmap() 51 | .override(100,100 ) 52 | .dontAnimate() 53 | .diskCacheStrategy(DiskCacheStrategy.ALL) 54 | .into(binding.imgPhoto); 55 | 56 | 57 | binding.btnPlay.setOnClickListener(new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | listener.onItemCommentClicked(comment); 61 | } 62 | }); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Outcoming/OutcomingChatAudioViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Outcoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.media.MediaPlayer; 6 | import android.os.Handler; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | import android.widget.SeekBar; 10 | 11 | 12 | import java.io.IOException; 13 | import java.text.ParseException; 14 | import java.text.SimpleDateFormat; 15 | import java.util.Calendar; 16 | import java.util.Date; 17 | import java.util.Locale; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | import silencebeat.com.chatview.Entities.Models.Comment; 21 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 22 | import silencebeat.com.chatview.R; 23 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 24 | import silencebeat.com.chatview.Views.Activities.ChatActivity; 25 | import silencebeat.com.chatview.databinding.ItemChatAudioOutcomingBinding; 26 | 27 | /** 28 | * Created by Candra Triyadi on 15/03/2018. 29 | */ 30 | 31 | public class OutcomingChatAudioViewHolder extends RecyclerView.ViewHolder { 32 | 33 | ItemChatAudioOutcomingBinding binding; 34 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 35 | Calendar calendar = Calendar.getInstance(); 36 | private int lenght = 0; 37 | 38 | public OutcomingChatAudioViewHolder(View itemView) { 39 | super(itemView); 40 | binding = DataBindingUtil.bind(itemView); 41 | } 42 | 43 | public void onBind(final Context context, final Comment comment, 44 | final OnItemCommentClickListener listener){ 45 | 46 | try { 47 | Date date = sdf.parse(comment.getNoteDate()); 48 | calendar.setTime(date); 49 | String time = StaticVariable.parseDate(context, calendar); 50 | binding.txtTime.setText(time); 51 | } catch (ParseException e) { 52 | e.printStackTrace(); 53 | } 54 | 55 | 56 | final MediaPlayer mediaPlayer = new MediaPlayer(); 57 | final Handler mSeekbarUpdateHandler = new Handler(); 58 | 59 | try { 60 | mediaPlayer.setDataSource(comment.getFileUrl()); 61 | mediaPlayer.prepareAsync(); 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | 66 | // int duration = mediaPlayer.getDuration(); 67 | binding.seekbar.setMax(100); 68 | 69 | 70 | final Runnable mUpdateSeekbar = new Runnable() { 71 | 72 | @Override 73 | public void run() { 74 | 75 | String time = String.format(Locale.US, "%02d:%02d", 76 | TimeUnit.MILLISECONDS.toMinutes(mediaPlayer.getCurrentPosition()), 77 | TimeUnit.MILLISECONDS.toSeconds(mediaPlayer.getCurrentPosition()) - 78 | TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(mediaPlayer.getCurrentPosition())) 79 | ); 80 | 81 | float a = (float) mediaPlayer.getCurrentPosition()/ (float)mediaPlayer.getDuration(); 82 | binding.txtTimePlay.setText(time); 83 | binding.seekbar.setProgress((int)(a*100f)); 84 | mSeekbarUpdateHandler.postDelayed(this, 15); 85 | } 86 | }; 87 | 88 | mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 89 | @Override 90 | public void onCompletion(MediaPlayer mp) { 91 | lenght = 0; 92 | binding.btnPlay.setImageResource(R.drawable.ic_play); 93 | mSeekbarUpdateHandler.removeCallbacksAndMessages(null); 94 | } 95 | }); 96 | 97 | binding.btnPlay.setOnClickListener(new View.OnClickListener() { 98 | @Override 99 | public void onClick(View v) { 100 | mSeekbarUpdateHandler.removeCallbacksAndMessages(null); 101 | setMediaPlayer(context, mediaPlayer); 102 | if (mediaPlayer.isPlaying()){ 103 | lenght = mediaPlayer.getCurrentPosition(); 104 | binding.btnPlay.setImageResource(R.drawable.ic_play); 105 | mediaPlayer.pause(); 106 | 107 | }else{ 108 | binding.btnPlay.setImageResource(R.drawable.ic_pause); 109 | mediaPlayer.seekTo(lenght); 110 | mediaPlayer.start(); 111 | mediaPlayer.setVolume(100,100); 112 | mSeekbarUpdateHandler.postDelayed(mUpdateSeekbar, 15); 113 | } 114 | } 115 | }); 116 | 117 | binding.seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 118 | @Override 119 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 120 | if (fromUser){ 121 | mediaPlayer.seekTo(progress * 1000); 122 | mSeekbarUpdateHandler.removeCallbacksAndMessages(null); 123 | mSeekbarUpdateHandler.postDelayed(mUpdateSeekbar, progress * 1000); 124 | } 125 | } 126 | 127 | @Override 128 | public void onStartTrackingTouch(SeekBar seekBar) { 129 | 130 | } 131 | 132 | @Override 133 | public void onStopTrackingTouch(SeekBar seekBar) { 134 | 135 | } 136 | }); 137 | } 138 | 139 | private void setMediaPlayer(Context context, MediaPlayer mediaPlayer){ 140 | ((ChatActivity)context).setMediaPlayer(mediaPlayer); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Outcoming/OutcomingChatImageViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Outcoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | import com.bumptech.glide.Glide; 9 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 10 | 11 | import java.text.ParseException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Calendar; 14 | import java.util.Date; 15 | import java.util.Locale; 16 | 17 | import silencebeat.com.chatview.Entities.Models.Comment; 18 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 19 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 20 | import silencebeat.com.chatview.databinding.ItemChatImageOutcomingBinding; 21 | 22 | /** 23 | * Created by Candra Triyadi on 15/03/2018. 24 | */ 25 | 26 | public class OutcomingChatImageViewHolder extends RecyclerView.ViewHolder{ 27 | 28 | ItemChatImageOutcomingBinding binding; 29 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 30 | Calendar calendar = Calendar.getInstance(); 31 | 32 | public OutcomingChatImageViewHolder(View itemView) { 33 | super(itemView); 34 | binding = DataBindingUtil.bind(itemView); 35 | } 36 | 37 | public void onBind(Context context, final Comment comment, final OnItemCommentClickListener listener){ 38 | try { 39 | Date date = sdf.parse(comment.getNoteDate()); 40 | calendar.setTime(date); 41 | String time = StaticVariable.parseDate(context, calendar); 42 | binding.txtTime.setText(time); 43 | } catch (ParseException e) { 44 | e.printStackTrace(); 45 | } 46 | 47 | Glide.with(context) 48 | .load(comment.getFileUrl()) 49 | .asBitmap() 50 | .override(100,100 ) 51 | .dontAnimate() 52 | .diskCacheStrategy(DiskCacheStrategy.ALL) 53 | .into(binding.imgPhoto); 54 | 55 | 56 | binding.imgPhoto.setOnClickListener(new View.OnClickListener() { 57 | @Override 58 | public void onClick(View v) { 59 | listener.onItemCommentClicked(comment); 60 | } 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Outcoming/OutcomingChatTextViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Outcoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | import java.text.ParseException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Calendar; 11 | import java.util.Date; 12 | import java.util.Locale; 13 | 14 | import silencebeat.com.chatview.Entities.Models.Comment; 15 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 16 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 17 | import silencebeat.com.chatview.databinding.ItemChatTextOutcomingBinding; 18 | 19 | /** 20 | * Created by Candra Triyadi on 15/03/2018. 21 | */ 22 | 23 | public class OutcomingChatTextViewHolder extends RecyclerView.ViewHolder { 24 | 25 | ItemChatTextOutcomingBinding binding; 26 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 27 | Calendar calendar = Calendar.getInstance(); 28 | 29 | public OutcomingChatTextViewHolder(View itemView) { 30 | super(itemView); 31 | binding = DataBindingUtil.bind(itemView); 32 | } 33 | 34 | public void onBind(Context context, Comment comment, OnItemCommentClickListener listener){ 35 | binding.txtMessage.setText(comment.getNoteText()); 36 | 37 | try { 38 | Date date = sdf.parse(comment.getNoteDate()); 39 | calendar.setTime(date); 40 | String time = StaticVariable.parseDate(context, calendar); 41 | binding.txtTime.setText(time); 42 | } catch (ParseException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/silencebeat/com/chatview/Views/ViewHolders/Outcoming/OutcomingChatVideoViewHolder.java: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview.Views.ViewHolders.Outcoming; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | 8 | 9 | import com.bumptech.glide.Glide; 10 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 11 | 12 | import java.text.ParseException; 13 | import java.text.SimpleDateFormat; 14 | import java.util.Calendar; 15 | import java.util.Date; 16 | import java.util.Locale; 17 | 18 | import silencebeat.com.chatview.Entities.Models.Comment; 19 | import silencebeat.com.chatview.Modules.OnItemCommentClickListener; 20 | import silencebeat.com.chatview.Supports.Utils.StaticVariable; 21 | import silencebeat.com.chatview.databinding.ItemChatVideoOutcomingBinding; 22 | 23 | /** 24 | * Created by Candra Triyadi on 15/03/2018. 25 | */ 26 | 27 | public class OutcomingChatVideoViewHolder extends RecyclerView.ViewHolder { 28 | 29 | ItemChatVideoOutcomingBinding binding; 30 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); 31 | Calendar calendar = Calendar.getInstance(); 32 | 33 | public OutcomingChatVideoViewHolder(View itemView) { 34 | super(itemView); 35 | binding = DataBindingUtil.bind(itemView); 36 | } 37 | 38 | public void onBind(Context context, final Comment comment, final OnItemCommentClickListener listener){ 39 | 40 | try { 41 | Date date = sdf.parse(comment.getNoteDate()); 42 | calendar.setTime(date); 43 | String time = StaticVariable.parseDate(context, calendar); 44 | binding.txtTime.setText(time); 45 | } catch (ParseException e) { 46 | e.printStackTrace(); 47 | } 48 | 49 | Glide.with(context) 50 | .load(comment.getThumbnailVideo()) 51 | .asBitmap() 52 | .override(100,100 ) 53 | .dontAnimate() 54 | .diskCacheStrategy(DiskCacheStrategy.ALL) 55 | .into(binding.imgPhoto); 56 | 57 | binding.btnPlay.setOnClickListener(new View.OnClickListener() { 58 | @Override 59 | public void onClick(View v) { 60 | listener.onItemCommentClicked(comment); 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_forward_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mic_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_photo_camera.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_picture.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_send_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/view_gradient_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 18 | 19 | 24 | 25 | 33 | 34 | 39 | 40 | 41 | 47 | 48 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_chat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 17 | 20 | 21 | 31 | 32 | 46 | 47 | 48 | 49 | 50 | 51 | 59 | 60 | 68 | 69 | 82 | 83 | 96 | 97 | 108 | 109 | 122 | 123 | 124 | 125 | 139 | 140 | 149 | 150 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_video_player.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_camera_result.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | 20 | 21 | 27 | 28 | 35 | 36 | 41 | 42 | 43 | 44 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_detail_foto_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | 15 | 16 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_audio_incoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 19 | 20 | 25 | 26 | 27 | 28 | 36 | 37 | 43 | 44 | 48 | 49 | 59 | 60 | 66 | 67 | 76 | 77 | 86 | 87 | 88 | 89 | 90 | 99 | 100 | 101 | 102 | 103 | 104 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_audio_outcoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 20 | 21 | 27 | 28 | 32 | 33 | 43 | 44 | 50 | 51 | 60 | 61 | 70 | 71 | 72 | 73 | 74 | 83 | 84 | 85 | 86 | 87 | 88 | 100 | 101 | 102 | 103 | 104 | 112 | 113 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_image_incoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 19 | 20 | 25 | 26 | 27 | 28 | 36 | 37 | 42 | 43 | 48 | 49 | 50 | 51 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_image_outcoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 20 | 21 | 27 | 28 | 33 | 34 | 35 | 36 | 48 | 49 | 50 | 51 | 52 | 60 | 61 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_text_incoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 19 | 20 | 25 | 26 | 27 | 28 | 36 | 37 | 43 | 44 | 57 | 58 | 59 | 60 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_text_outcoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 20 | 21 | 27 | 28 | 41 | 42 | 43 | 44 | 56 | 57 | 58 | 59 | 60 | 68 | 69 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_video_incoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 19 | 20 | 25 | 26 | 27 | 28 | 36 | 37 | 42 | 43 | 46 | 47 | 52 | 53 | 57 | 58 | 67 | 68 | 69 | 70 | 71 | 72 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chat_video_outcoming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 20 | 21 | 27 | 28 | 31 | 32 | 37 | 38 | 42 | 43 | 52 | 53 | 54 | 55 | 56 | 57 | 69 | 70 | 71 | 72 | 73 | 81 | 82 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 22 | 23 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4fd2c2 4 | #46b9aa 5 | #46b9aa 6 | 7 | #8e8e92 8 | #1d1d26 9 | #f8f8f8 10 | #000 11 | #FFF 12 | #dd5656 13 | #c5c5c5 14 | #4fd2c2 15 | #e2e2e3 16 | #f3a321 17 | #80000000 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ChatView 3 | 4 | 5 | January 6 | February 7 | March 8 | April 9 | May 10 | June 11 | July 12 | August 13 | September 14 | October 15 | November 16 | December 17 | 18 | 19 | 20 | Jan 21 | Feb 22 | March 23 | April 24 | May 25 | June 26 | July 27 | August 28 | Sept 29 | Oct 30 | Nov 31 | Dec 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 22 | 23 | 26 | 27 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/test/java/silencebeat/com/chatview/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package silencebeat.com.chatview 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.30' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.0.1' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silencebeat/ChatView/f01f3a49020d9421103a6e2bed896ce7b14a26fd/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 19 09:23:24 ICT 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------