├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | 1.7
38 |
39 |
40 |
41 |
42 |
43 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
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 |
16 |
17 |
18 |
19 |
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 |
16 |
17 |
18 |
19 |
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 |
--------------------------------------------------------------------------------