1
1
package com .zulip .android .activities ;
2
2
3
- import java .sql .SQLException ;
4
- import java .util .Arrays ;
5
- import java .util .HashMap ;
6
- import java .util .List ;
7
- import java .util .concurrent .Callable ;
8
- import java .util .ArrayList ;
9
- import java .util .concurrent .TimeUnit ;
10
-
3
+ import android .Manifest ;
11
4
import android .animation .Animator ;
12
5
import android .annotation .SuppressLint ;
13
6
import android .annotation .TargetApi ;
20
13
import android .content .Intent ;
21
14
import android .content .IntentFilter ;
22
15
import android .content .SharedPreferences ;
16
+ import android .content .pm .PackageManager ;
23
17
import android .content .res .Configuration ;
24
18
import android .database .Cursor ;
25
19
import android .database .MatrixCursor ;
26
20
import android .database .MergeCursor ;
27
21
import android .graphics .Bitmap ;
28
22
import android .graphics .PorterDuff ;
29
23
import android .graphics .drawable .Drawable ;
24
+ import android .net .Uri ;
30
25
import android .os .Build ;
31
26
import android .os .Bundle ;
32
27
import android .os .CountDownTimer ;
33
28
import android .os .Handler ;
34
29
import android .support .design .widget .AppBarLayout ;
35
30
import android .support .design .widget .FloatingActionButton ;
36
31
import android .support .v4 .app .ActionBarDrawerToggle ;
32
+ import android .support .v4 .app .ActivityCompat ;
37
33
import android .support .v4 .app .FragmentManager ;
38
34
import android .support .v4 .app .FragmentTransaction ;
39
35
import android .support .v4 .content .ContextCompat ;
42
38
import android .support .v4 .view .animation .FastOutSlowInInterpolator ;
43
39
import android .support .v4 .widget .DrawerLayout ;
44
40
import android .support .v4 .widget .SimpleCursorAdapter ;
45
- import android .support .v7 .app .AppCompatActivity ;
46
41
import android .support .v7 .app .AppCompatDelegate ;
47
42
import android .support .v7 .widget .Toolbar ;
48
43
import android .text .TextUtils ;
68
63
69
64
import com .j256 .ormlite .android .AndroidDatabaseResults ;
70
65
import com .zulip .android .BuildConfig ;
66
+ import com .zulip .android .R ;
67
+ import com .zulip .android .ZulipApp ;
71
68
import com .zulip .android .database .DatabaseHelper ;
72
- import com .zulip .android .models .Emoji ;
73
- import com .zulip .android .filters .NarrowFilterToday ;
74
- import com .zulip .android .models .Message ;
75
- import com .zulip .android .models .MessageType ;
76
69
import com .zulip .android .filters .NarrowFilter ;
77
70
import com .zulip .android .filters .NarrowFilterAllPMs ;
78
71
import com .zulip .android .filters .NarrowFilterPM ;
79
72
import com .zulip .android .filters .NarrowFilterSearch ;
80
73
import com .zulip .android .filters .NarrowFilterStream ;
74
+ import com .zulip .android .filters .NarrowFilterToday ;
81
75
import com .zulip .android .filters .NarrowListener ;
76
+ import com .zulip .android .gcm .GcmBroadcastReceiver ;
82
77
import com .zulip .android .gcm .Notifications ;
78
+ import com .zulip .android .models .Emoji ;
79
+ import com .zulip .android .models .Message ;
80
+ import com .zulip .android .models .MessageType ;
83
81
import com .zulip .android .models .Person ;
84
82
import com .zulip .android .models .Presence ;
85
83
import com .zulip .android .models .PresenceType ;
86
- import com .zulip .android .R ;
87
84
import com .zulip .android .models .Stream ;
85
+ import com .zulip .android .networking .AsyncGetEvents ;
88
86
import com .zulip .android .networking .AsyncSend ;
89
- import com .zulip .android .networking .ZulipInterceptor ;
90
- import com .zulip .android .networking .response . UserConfigurationResponse ;
91
- import com .zulip .android .service . ZulipServices ;
87
+ import com .zulip .android .networking .AsyncStatusUpdate ;
88
+ import com .zulip .android .networking .ZulipAsyncPushTask ;
89
+ import com .zulip .android .networking . response . UploadResponse ;
92
90
import com .zulip .android .util .AnimationHelper ;
91
+ import com .zulip .android .util .FilePathHelper ;
93
92
import com .zulip .android .util .MutedTopics ;
94
93
import com .zulip .android .util .SwipeRemoveLinearLayout ;
94
+ import com .zulip .android .util .UrlHelper ;
95
95
import com .zulip .android .util .ZLog ;
96
- import com .zulip .android .ZulipApp ;
97
- import com .zulip .android .gcm .GcmBroadcastReceiver ;
98
- import com .zulip .android .networking .AsyncGetEvents ;
99
- import com .zulip .android .networking .AsyncStatusUpdate ;
100
- import com .zulip .android .networking .ZulipAsyncPushTask ;
101
96
102
97
import org .json .JSONObject ;
103
98
104
- import okhttp3 .OkHttpClient ;
105
- import okhttp3 .Response ;
106
- import okhttp3 .ResponseBody ;
99
+ import java .io .File ;
100
+ import java .sql .SQLException ;
101
+ import java .util .ArrayList ;
102
+ import java .util .Arrays ;
103
+ import java .util .HashMap ;
104
+ import java .util .List ;
105
+ import java .util .concurrent .Callable ;
106
+
107
+ import okhttp3 .MediaType ;
108
+ import okhttp3 .MultipartBody ;
109
+ import okhttp3 .RequestBody ;
107
110
import retrofit2 .Call ;
108
111
import retrofit2 .Callback ;
109
- import retrofit2 .Retrofit ;
110
- import retrofit2 .converter .gson .GsonConverterFactory ;
112
+ import retrofit2 .Response ;
111
113
112
114
/**
113
115
* The main Activity responsible for holding the {@link MessageListFragment} which has the list to the
@@ -122,6 +124,7 @@ public class ZulipActivity extends BaseActivity implements
122
124
private static final int MAX_THRESOLD_EMOJI_HINT = 5 ;
123
125
//At these many letters the emoji/person hint starts to show up
124
126
private static final int MIN_THRESOLD_EMOJI_HINT = 1 ;
127
+ private static final int PERMISSION_REQUEST_READ_CONTACTS = 1 ;
125
128
private ZulipApp app ;
126
129
private List <Message > mutedTopics ;
127
130
@@ -178,6 +181,8 @@ public void onReceive(Context contenxt, Intent intent) {
178
181
}
179
182
};
180
183
private ExpandableStreamDrawerAdapter streamsDrawerAdapter ;
184
+ private boolean mReadExternalStorage ;
185
+ private Uri mImageUri ;
181
186
182
187
@ Override
183
188
public void removeChatBox (boolean animToRight ) {
@@ -535,6 +540,158 @@ public Cursor runQuery(CharSequence charSequence) {
535
540
}
536
541
});
537
542
messageEt .setAdapter (combinedAdapter );
543
+
544
+ // Get intent, action and MIME type
545
+ Intent intent = getIntent ();
546
+ String action = intent .getAction ();
547
+ String type = intent .getType ();
548
+
549
+ if (Intent .ACTION_SEND .equals (action ) && type != null ) {
550
+ if (type .startsWith ("image/" )) {
551
+ // Handle single image being sent
552
+ handleSentImage (intent );
553
+ }
554
+ }
555
+ }
556
+
557
+ @ Override
558
+ protected void onNewIntent (Intent intent ) {
559
+ super .onNewIntent (intent );
560
+
561
+ // Get action and MIME type of intent
562
+ String action = intent .getAction ();
563
+ String type = intent .getType ();
564
+
565
+ if (Intent .ACTION_SEND .equals (action ) && type != null ) {
566
+ if (type .startsWith ("image/" )) {
567
+ // Handle single image being sent
568
+ handleSentImage (intent );
569
+ }
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Function invoked when a user shares an image with the zulip app
575
+ * @param intent passed to the activity with action SEND
576
+ */
577
+ private void handleSentImage (Intent intent ) {
578
+ mImageUri = (Uri ) intent .getParcelableExtra (Intent .EXTRA_STREAM );
579
+ if (mImageUri != null ) {
580
+ // check if user has granted read external storage permission
581
+ // for Android 6.0 or higher
582
+ if (ContextCompat .checkSelfPermission (this ,
583
+ Manifest .permission .READ_EXTERNAL_STORAGE )
584
+ != PackageManager .PERMISSION_GRANTED ) {
585
+ // we need to request the permission.
586
+ ActivityCompat .requestPermissions (this ,
587
+ new String []{Manifest .permission .READ_EXTERNAL_STORAGE },
588
+ PERMISSION_REQUEST_READ_CONTACTS );
589
+ } else {
590
+ // permission already granted
591
+ // start with file upload
592
+ startFileUpload ();
593
+ }
594
+ } else {
595
+ Toast .makeText (this , R .string .cannot_find_image , Toast .LENGTH_SHORT ).show ();
596
+ }
597
+ }
598
+
599
+ @ Override
600
+ public void onRequestPermissionsResult (int requestCode ,
601
+ String permissions [], int [] grantResults ) {
602
+
603
+ switch (requestCode ) {
604
+ case PERMISSION_REQUEST_READ_CONTACTS : {
605
+ // If request is cancelled, the result arrays are empty.
606
+ if (grantResults .length > 0
607
+ && grantResults [0 ] == PackageManager .PERMISSION_GRANTED ) {
608
+ // permission granted
609
+ // start with file upload
610
+ startFileUpload ();
611
+ } else {
612
+ // permission denied
613
+ Toast .makeText (this , R .string .cannot_upload_image , Toast .LENGTH_SHORT ).show ();
614
+ }
615
+ }
616
+ break ;
617
+ }
618
+ }
619
+
620
+ /**
621
+ * Helper function to update UI to indicate image is being uploaded and call
622
+ * {@link ZulipActivity#uploadFile(String)} to upload the image.
623
+ */
624
+ private void startFileUpload () {
625
+ // Update UI to indicate image is being loaded
626
+ // hide fab and display chatbox
627
+ displayFAB (false );
628
+ displayChatBox (true );
629
+ String loadingMsg = getResources ().getString (R .string .uploading_message );
630
+ sendingMessage (true , loadingMsg );
631
+
632
+ // get actual file path
633
+ String imageFilePath = FilePathHelper .getPath (this , mImageUri );
634
+
635
+ // upload the file asynchronously to the server
636
+ uploadFile (imageFilePath );
637
+ }
638
+
639
+ /**
640
+ * Function to upload file asynchronously to the server using retrofit callback
641
+ * upload {@link com.zulip.android.service.ZulipServices#upload(MultipartBody.Part)}
642
+ * @param filePath on local storage
643
+ */
644
+ private void uploadFile (String filePath ) {
645
+ File file = new File (filePath );
646
+
647
+ // create RequestBody instance from file
648
+ RequestBody requestFile =
649
+ RequestBody .create (MediaType .parse ("multipart/form-data" ), file );
650
+
651
+ // MultipartBody.Part is used to send also the actual file name
652
+ MultipartBody .Part body =
653
+ MultipartBody .Part .createFormData ("picture" , file .getName (), requestFile );
654
+
655
+ final String loadingMsg = getResources ().getString (R .string .uploading_message );
656
+
657
+ // finally, execute the request
658
+ // create upload service client
659
+ Call <UploadResponse > call = ((ZulipApp ) getApplicationContext ()).getZulipServices ().upload (body );
660
+ call .enqueue (new Callback <UploadResponse >() {
661
+ @ Override
662
+ public void onResponse (Call <UploadResponse > call ,
663
+ Response <UploadResponse > response ) {
664
+ if (response .isSuccessful ()) {
665
+ String filePathOnServer = "" ;
666
+ UploadResponse uploadResponse = response .body ();
667
+ filePathOnServer = uploadResponse .getUri ();
668
+ if (!filePathOnServer .equals ("" )) {
669
+ // remove loading message from the screen
670
+ sendingMessage (false , loadingMsg );
671
+
672
+ // print message to compose box
673
+ messageEt .append (" " + UrlHelper .addHost (filePathOnServer ));
674
+ } else {
675
+ // remove loading message from the screen
676
+ sendingMessage (false , loadingMsg );
677
+ Toast .makeText (ZulipActivity .this , R .string .failed_to_upload , Toast .LENGTH_SHORT ).show ();
678
+ }
679
+ }
680
+ else {
681
+ // remove loading message from the screen
682
+ sendingMessage (false , loadingMsg );
683
+ Toast .makeText (ZulipActivity .this , R .string .failed_to_upload , Toast .LENGTH_SHORT ).show ();
684
+ }
685
+
686
+ }
687
+
688
+ @ Override
689
+ public void onFailure (Call <UploadResponse > call , Throwable t ) {
690
+ // remove loading message from the screen
691
+ sendingMessage (false , loadingMsg );
692
+ ZLog .logException (t );
693
+ }
694
+ });
538
695
}
539
696
540
697
/**
@@ -845,7 +1002,8 @@ private void sendMessage() {
845
1002
messageEt .requestFocus ();
846
1003
return ;
847
1004
}
848
- sendingMessage (true );
1005
+ final String sendingMsg = getResources ().getString (R .string .sending_message );
1006
+ sendingMessage (true , sendingMsg );
849
1007
MessageType messageType = isCurrentModeStream () ? MessageType .STREAM_MESSAGE : MessageType .PRIVATE_MESSAGE ;
850
1008
Message msg = new Message (app );
851
1009
msg .setSender (app .getYou ());
@@ -864,13 +1022,13 @@ private void sendMessage() {
864
1022
public void onTaskComplete (String result , JSONObject jsonObject ) {
865
1023
Toast .makeText (ZulipActivity .this , R .string .message_sent , Toast .LENGTH_SHORT ).show ();
866
1024
messageEt .setText ("" );
867
- sendingMessage (false );
1025
+ sendingMessage (false , sendingMsg );
868
1026
}
869
1027
870
1028
public void onTaskFailure (String result ) {
871
1029
Log .d ("onTaskFailure" , "Result: " + result );
872
1030
Toast .makeText (ZulipActivity .this , R .string .message_error , Toast .LENGTH_SHORT ).show ();
873
- sendingMessage (false );
1031
+ sendingMessage (false , sendingMsg );
874
1032
}
875
1033
});
876
1034
sender .execute ();
@@ -879,15 +1037,18 @@ public void onTaskFailure(String result) {
879
1037
/**
880
1038
* Disable chatBox and show a loading footer while sending the message.
881
1039
*/
882
- private void sendingMessage (boolean isSending ) {
1040
+ private void sendingMessage (boolean isSending , String message ) {
883
1041
streamActv .setEnabled (!isSending );
884
1042
textView .setEnabled (!isSending );
885
1043
messageEt .setEnabled (!isSending );
886
1044
topicActv .setEnabled (!isSending );
887
1045
sendBtn .setEnabled (!isSending );
888
1046
togglePrivateStreamBtn .setEnabled (!isSending );
889
- if (isSending )
1047
+ if (isSending ) {
1048
+ TextView msg = (TextView ) composeStatus .findViewById (R .id .sending_message );
1049
+ msg .setText (message );
890
1050
composeStatus .setVisibility (View .VISIBLE );
1051
+ }
891
1052
else
892
1053
composeStatus .setVisibility (View .GONE );
893
1054
}
@@ -981,7 +1142,7 @@ public Cursor runQuery(CharSequence charSequence) {
981
1142
}
982
1143
});
983
1144
984
- sendingMessage (false );
1145
+ sendingMessage (false , getResources (). getString ( R . string . sending_message ) );
985
1146
}
986
1147
987
1148
/**
0 commit comments