@ -16,6 +16,7 @@
package com.keylesspalace.tusky ;
package com.keylesspalace.tusky ;
import android.Manifest ;
import android.Manifest ;
import android.annotation.SuppressLint ;
import android.app.ProgressDialog ;
import android.app.ProgressDialog ;
import android.content.ContentResolver ;
import android.content.ContentResolver ;
import android.content.Context ;
import android.content.Context ;
@ -26,10 +27,7 @@ import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor ;
import android.content.res.AssetFileDescriptor ;
import android.content.res.Resources ;
import android.content.res.Resources ;
import android.graphics.Bitmap ;
import android.graphics.Bitmap ;
import android.graphics.BitmapFactory ;
import android.graphics.drawable.Drawable ;
import android.graphics.drawable.Drawable ;
import android.media.MediaMetadataRetriever ;
import android.media.ThumbnailUtils ;
import android.net.Uri ;
import android.net.Uri ;
import android.os.AsyncTask ;
import android.os.AsyncTask ;
import android.os.Build ;
import android.os.Build ;
@ -39,9 +37,9 @@ import android.os.Parcel;
import android.os.Parcelable ;
import android.os.Parcelable ;
import android.provider.MediaStore ;
import android.provider.MediaStore ;
import android.support.annotation.AttrRes ;
import android.support.annotation.AttrRes ;
import android.support.annotation.LayoutRes ;
import android.support.annotation.NonNull ;
import android.support.annotation.NonNull ;
import android.support.annotation.Nullable ;
import android.support.annotation.Nullable ;
import android.support.annotation.Px ;
import android.support.annotation.StringRes ;
import android.support.annotation.StringRes ;
import android.support.design.widget.Snackbar ;
import android.support.design.widget.Snackbar ;
import android.support.v13.view.inputmethod.InputConnectionCompat ;
import android.support.v13.view.inputmethod.InputConnectionCompat ;
@ -61,16 +59,11 @@ import android.text.TextUtils;
import android.text.TextWatcher ;
import android.text.TextWatcher ;
import android.text.style.URLSpan ;
import android.text.style.URLSpan ;
import android.util.Log ;
import android.util.Log ;
import android.view.LayoutInflater ;
import android.view.MenuItem ;
import android.view.MenuItem ;
import android.view.View ;
import android.view.View ;
import android.view.ViewGroup ;
import android.webkit.MimeTypeMap ;
import android.webkit.MimeTypeMap ;
import android.widget.ArrayAdapter ;
import android.widget.Button ;
import android.widget.Button ;
import android.widget.EditText ;
import android.widget.EditText ;
import android.widget.Filter ;
import android.widget.Filterable ;
import android.widget.ImageButton ;
import android.widget.ImageButton ;
import android.widget.ImageView ;
import android.widget.ImageView ;
import android.widget.LinearLayout ;
import android.widget.LinearLayout ;
@ -80,6 +73,7 @@ import android.widget.Toast;
import com.google.gson.Gson ;
import com.google.gson.Gson ;
import com.google.gson.reflect.TypeToken ;
import com.google.gson.reflect.TypeToken ;
import com.keylesspalace.tusky.adapter.MentionAutoCompleteAdapter ;
import com.keylesspalace.tusky.db.TootDao ;
import com.keylesspalace.tusky.db.TootDao ;
import com.keylesspalace.tusky.db.TootEntity ;
import com.keylesspalace.tusky.db.TootEntity ;
import com.keylesspalace.tusky.entity.Account ;
import com.keylesspalace.tusky.entity.Account ;
@ -98,9 +92,6 @@ import com.keylesspalace.tusky.util.StringUtils;
import com.keylesspalace.tusky.util.ThemeUtils ;
import com.keylesspalace.tusky.util.ThemeUtils ;
import com.keylesspalace.tusky.view.EditTextTyped ;
import com.keylesspalace.tusky.view.EditTextTyped ;
import com.keylesspalace.tusky.view.ProgressImageView ;
import com.keylesspalace.tusky.view.ProgressImageView ;
import com.keylesspalace.tusky.view.RoundedTransformation ;
import com.squareup.picasso.Picasso ;
import com.squareup.picasso.Target ;
import java.io.File ;
import java.io.File ;
import java.io.FileNotFoundException ;
import java.io.FileNotFoundException ;
@ -121,7 +112,8 @@ import retrofit2.Call;
import retrofit2.Callback ;
import retrofit2.Callback ;
import retrofit2.Response ;
import retrofit2.Response ;
public final class ComposeActivity extends BaseActivity implements ComposeOptionsFragment . Listener {
public final class ComposeActivity extends BaseActivity
implements ComposeOptionsFragment . Listener , MentionAutoCompleteAdapter . AccountSearchProvider {
private static final String TAG = "ComposeActivity" ; // logging tag
private static final String TAG = "ComposeActivity" ; // logging tag
private static final int STATUS_CHARACTER_LIMIT = 500 ;
private static final int STATUS_CHARACTER_LIMIT = 500 ;
private static final int STATUS_MEDIA_SIZE_LIMIT = 8388608 ; // 8MiB
private static final int STATUS_MEDIA_SIZE_LIMIT = 8388608 ; // 8MiB
@ -129,7 +121,8 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
private static final int MEDIA_TAKE_PHOTO_RESULT = 2 ;
private static final int MEDIA_TAKE_PHOTO_RESULT = 2 ;
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1 ;
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1 ;
private static final int COMPOSE_SUCCESS = - 1 ;
private static final int COMPOSE_SUCCESS = - 1 ;
private static final int THUMBNAIL_SIZE = 128 ; // pixels
@Px
private static final int THUMBNAIL_SIZE = 128 ;
private static final String SAVED_TOOT_UID_EXTRA = "saved_toot_uid" ;
private static final String SAVED_TOOT_UID_EXTRA = "saved_toot_uid" ;
private static final String SAVED_TOOT_TEXT_EXTRA = "saved_toot_text" ;
private static final String SAVED_TOOT_TEXT_EXTRA = "saved_toot_text" ;
@ -140,8 +133,11 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
private static final String MENTIONED_USERNAMES_EXTRA = "netnioned_usernames" ;
private static final String MENTIONED_USERNAMES_EXTRA = "netnioned_usernames" ;
private static final String REPLYING_STATUS_AUTHOR_USERNAME_EXTRA = "replying_author_nickname_extra" ;
private static final String REPLYING_STATUS_AUTHOR_USERNAME_EXTRA = "replying_author_nickname_extra" ;
private static final String REPLYING_STATUS_CONTENT_EXTRA = "replying_status_content" ;
private static final String REPLYING_STATUS_CONTENT_EXTRA = "replying_status_content" ;
private static final String REMEMBERED_VISIBILITY_PREF = "rememberedVisibilityNum" ;
private static TootDao tootDao = TuskyApplication . getDB ( ) . tootDao ( ) ;
private static TootDao tootDao = TuskyApplication . getDB ( ) . tootDao ( ) ;
private TextView replyTextView ;
private TextView replyContentTextView ;
private TextView replyContentTextView ;
private EditTextTyped textEditor ;
private EditTextTyped textEditor ;
private LinearLayout mediaPreviewBar ;
private LinearLayout mediaPreviewBar ;
@ -160,27 +156,21 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
private ArrayList < QueuedMedia > mediaQueued ;
private ArrayList < QueuedMedia > mediaQueued ;
private CountUpDownLatch waitForMediaLatch ;
private CountUpDownLatch waitForMediaLatch ;
private boolean showMarkSensitive ;
private boolean showMarkSensitive ;
private String statusVisibility ; // The current values of the options that will be applied
private Status . Visibility statusVisibility ; // The current values of the options that will be applied
private boolean statusMarkSensitive ; // to the status being composed.
private boolean statusMarkSensitive ; // to the status being composed.
private boolean statusHideText ; //
private boolean statusHideText ;
private boolean statusAlreadyInFlight ; // to prevent duplicate sends by mashing the send button
private boolean statusAlreadyInFlight ; // to prevent duplicate sends by mashing the send button
private InputContentInfoCompat currentInputContentInfo ;
private InputContentInfoCompat currentInputContentInfo ;
private int currentFlags ;
private int currentFlags ;
private Uri photoUploadUri ;
private Uri photoUploadUri ;
private int savedTootUid = 0 ;
private int savedTootUid = 0 ;
/ * *
* The Target object must be stored as a member field or method and cannot be an anonymous class otherwise this won ' t work as expected . The reason is that Picasso accepts this parameter as a weak memory reference . Because anonymous classes are eligible for garbage collection when there are no more references , the network request to fetch the image may finish after this anonymous class has already been reclaimed . See this Stack Overflow discussion for more details .
* /
@SuppressWarnings ( "FieldCanBeLocal" )
private Target target ;
@Override
@Override
public void onCreate ( Bundle savedInstanceState ) {
public void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState ) ;
super . onCreate ( savedInstanceState ) ;
setContentView ( R . layout . activity_compose ) ;
setContentView ( R . layout . activity_compose ) ;
TextView replyTextView = findViewById ( R . id . reply_tv ) ;
replyTextView = findViewById ( R . id . reply_tv ) ;
replyContentTextView = findViewById ( R . id . reply_content_tv ) ;
replyContentTextView = findViewById ( R . id . reply_content_tv ) ;
textEditor = findViewById ( R . id . compose_edit_field ) ;
textEditor = findViewById ( R . id . compose_edit_field ) ;
mediaPreviewBar = findViewById ( R . id . compose_media_preview_bar ) ;
mediaPreviewBar = findViewById ( R . id . compose_media_preview_bar ) ;
@ -253,13 +243,16 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
* state . * /
* state . * /
SharedPreferences preferences = getPrivatePreferences ( ) ;
SharedPreferences preferences = getPrivatePreferences ( ) ;
String startingVisibility ;
Status . Visibility startingVisibility ;
boolean startingHideText ;
boolean startingHideText ;
String startingContentWarning = null ;
String startingContentWarning = null ;
ArrayList < SavedQueuedMedia > savedMediaQueued = null ;
ArrayList < SavedQueuedMedia > savedMediaQueued = null ;
if ( savedInstanceState ! = null ) {
if ( savedInstanceState ! = null ) {
showMarkSensitive = savedInstanceState . getBoolean ( "showMarkSensitive" ) ;
showMarkSensitive = savedInstanceState . getBoolean ( "showMarkSensitive" ) ;
startingVisibility = savedInstanceState . getString ( "statusVisibility" ) ;
startingVisibility = Status . Visibility . byNum (
savedInstanceState . getInt ( "statusVisibility" ,
Status . Visibility . PUBLIC . getNum ( ) )
) ;
statusMarkSensitive = savedInstanceState . getBoolean ( "statusMarkSensitive" ) ;
statusMarkSensitive = savedInstanceState . getBoolean ( "statusMarkSensitive" ) ;
startingHideText = savedInstanceState . getBoolean ( "statusHideText" ) ;
startingHideText = savedInstanceState . getBoolean ( "statusHideText" ) ;
// Keep these until everything needed to put them in the queue is finished initializing.
// Keep these until everything needed to put them in the queue is finished initializing.
@ -274,7 +267,10 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
photoUploadUri = savedInstanceState . getParcelable ( "photoUploadUri" ) ;
photoUploadUri = savedInstanceState . getParcelable ( "photoUploadUri" ) ;
} else {
} else {
showMarkSensitive = false ;
showMarkSensitive = false ;
startingVisibility = preferences . getString ( "rememberedVisibility" , "public" ) ;
startingVisibility = Status . Visibility . byNum (
preferences . getInt ( REMEMBERED_VISIBILITY_PREF ,
Status . Visibility . UNKNOWN . getNum ( ) )
) ;
statusMarkSensitive = false ;
statusMarkSensitive = false ;
startingHideText = false ;
startingHideText = false ;
photoUploadUri = null ;
photoUploadUri = null ;
@ -287,37 +283,20 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
String [ ] mentionedUsernames = null ;
String [ ] mentionedUsernames = null ;
ArrayList < String > loadedDraftMediaUris = null ;
ArrayList < String > loadedDraftMediaUris = null ;
inReplyToId = null ;
inReplyToId = null ;
Status . Visibility replyVisibility = Status . Visibility . UNKNOWN ;
if ( intent ! = null ) {
if ( intent ! = null ) {
inReplyToId = intent . getStringExtra ( IN_REPLY_TO_ID_EXTRA ) ;
inReplyToId = intent . getStringExtra ( IN_REPLY_TO_ID_EXTRA ) ;
String replyVisibility = intent . getStringExtra ( REPLY_VISIBILITY_EXTRA ) ;
replyVisibility = Status . Visibility . byNum (
intent . getIntExtra ( REPLY_VISIBILITY_EXTRA , Status . Visibility . UNKNOWN . getNum ( ) )
if ( replyVisibility ! = null & & startingVisibility ! = null ) {
) ;
// Lowest possible visibility setting in response
if ( startingVisibility . equals ( "direct" ) | | replyVisibility . equals ( "direct" ) ) {
startingVisibility = "direct" ;
} else if ( startingVisibility . equals ( "private" ) | | replyVisibility . equals ( "private" ) ) {
startingVisibility = "private" ;
} else if ( startingVisibility . equals ( "unlisted" ) | | replyVisibility . equals ( "unlisted" ) ) {
startingVisibility = "unlisted" ;
} else {
startingVisibility = replyVisibility ;
}
}
mentionedUsernames = intent . getStringArrayExtra ( MENTIONED_USERNAMES_EXTRA ) ;
mentionedUsernames = intent . getStringArrayExtra ( MENTIONED_USERNAMES_EXTRA ) ;
if ( inReplyToId ! = null ) {
String contentWarning = intent . getStringExtra ( CONTENT_WARNING_EXTRA ) ;
startingHideText = ! intent . getStringExtra ( CONTENT_WARNING_EXTRA ) . equals ( "" ) ;
if ( contentWarning ! = null ) {
startingHideText = ! contentWarning . isEmpty ( ) ;
if ( startingHideText ) {
if ( startingHideText ) {
startingContentWarning = intent . getStringExtra ( CONTENT_WARNING_EXTRA ) ;
startingContentWarning = contentWarning ;
}
} else {
String contentWarning = intent . getStringExtra ( CONTENT_WARNING_EXTRA ) ;
if ( contentWarning ! = null ) {
startingHideText = ! contentWarning . isEmpty ( ) ;
if ( startingHideText ) {
startingContentWarning = contentWarning ;
}
}
}
}
}
@ -361,14 +340,12 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
}
}
/ * If the currently logged in account is locked , its posts should default to private . This
Status . Visibility pickedVisibility = pickVisibility ( startingVisibility , replyVisibility ,
* should override even the reply settings , so this must be done after those are set up . * /
preferences . getBoolean ( "loggedInAccountLocked" , false ) ) ;
if ( preferences . getBoolean ( "loggedInAccountLocked" , false ) ) {
startingVisibility = "private" ;
}
// After the starting state is finalised, the interface can be set to reflect this state.
// After the starting state is finalised, the interface can be set to reflect this state.
setStatusVisibility ( startingVisibility ) ;
setStatusVisibility ( pickedVisibility ) ;
postProgress . setVisibility ( View . INVISIBLE ) ;
postProgress . setVisibility ( View . INVISIBLE ) ;
updateHideMediaToggleColor ( ) ;
updateHideMediaToggleColor ( ) ;
updateVisibleCharactersLeft ( ) ;
updateVisibleCharactersLeft ( ) ;
@ -393,7 +370,8 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
} ) ;
} ) ;
textEditor . setAdapter ( new MentionAutoCompleteAdapter ( this , R . layout . item_autocomplete ) ) ;
textEditor . setAdapter (
new MentionAutoCompleteAdapter ( this , R . layout . item_autocomplete , this ) ) ;
textEditor . setTokenizer ( new MentionTokenizer ( ) ) ;
textEditor . setTokenizer ( new MentionTokenizer ( ) ) ;
// Add any mentions to the text field when a reply is first composed.
// Add any mentions to the text field when a reply is first composed.
@ -442,7 +420,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
} else if ( savedMediaQueued ! = null ) {
} else if ( savedMediaQueued ! = null ) {
for ( SavedQueuedMedia item : savedMediaQueued ) {
for ( SavedQueuedMedia item : savedMediaQueued ) {
Bitmap preview = getImageThumbnail ( getContentResolver ( ) , item . uri ) ;
Bitmap preview = MediaUtils . getImageThumbnail ( getContentResolver ( ) , item . uri , THUMBNAIL_SIZE ) ;
addMediaToQueue ( item . type , preview , item . uri , item . mediaSize , item . readyStage ) ;
addMediaToQueue ( item . type , preview , item . uri , item . mediaSize , item . readyStage ) ;
}
}
} else if ( intent ! = null & & savedInstanceState = = null ) {
} else if ( intent ! = null & & savedInstanceState = = null ) {
@ -453,25 +431,27 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
if ( type ! = null ) {
if ( type ! = null ) {
if ( type . startsWith ( "image/" ) ) {
if ( type . startsWith ( "image/" ) ) {
List < Uri > uriList = new ArrayList < > ( ) ;
List < Uri > uriList = new ArrayList < > ( ) ;
switch ( intent . getAction ( ) ) {
if ( intent . getAction ( ) ! = null ) {
case Intent . ACTION_SEND : {
switch ( intent . getAction ( ) ) {
Uri uri = intent . getParcelableExtra ( Intent . EXTRA_STREAM ) ;
case Intent . ACTION_SEND : {
if ( uri ! = null ) {
Uri uri = intent . getParcelableExtra ( Intent . EXTRA_STREAM ) ;
uriList . add ( uri ) ;
if ( uri ! = null ) {
uriList . add ( uri ) ;
}
break ;
}
}
break ;
case Intent . ACTION_SEND_MULTIPLE : {
}
ArrayList < Uri > list = intent . getParcelableArrayListExtra (
case Intent . ACTION_SEND_MULTIPLE : {
Intent . EXTRA_STREAM ) ;
ArrayList < Uri > list = intent . getParcelableArrayListExtra (
if ( list ! = null ) {
Intent . EXTRA_STREAM ) ;
for ( Uri uri : list ) {
if ( list ! = null ) {
if ( uri ! = null ) {
for ( Uri uri : list ) {
uriList . add ( uri ) ;
if ( uri ! = null ) {
}
uriList . add ( uri ) ;
}
}
}
}
break ;
}
}
break ;
}
}
}
}
for ( Uri uri : uriList ) {
for ( Uri uri : uriList ) {
@ -506,7 +486,6 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
outState . putParcelableArrayList ( "savedMediaQueued" , savedMediaQueued ) ;
outState . putParcelableArrayList ( "savedMediaQueued" , savedMediaQueued ) ;
outState . putBoolean ( "showMarkSensitive" , showMarkSensitive ) ;
outState . putBoolean ( "showMarkSensitive" , showMarkSensitive ) ;
outState . putString ( "statusVisibility" , statusVisibility ) ;
outState . putBoolean ( "statusMarkSensitive" , statusMarkSensitive ) ;
outState . putBoolean ( "statusMarkSensitive" , statusMarkSensitive ) ;
outState . putBoolean ( "statusHideText" , statusHideText ) ;
outState . putBoolean ( "statusHideText" , statusHideText ) ;
if ( currentInputContentInfo ! = null ) {
if ( currentInputContentInfo ! = null ) {
@ -701,6 +680,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
return c ;
return c ;
}
}
@SuppressLint ( "StaticFieldLeak" )
private boolean saveTheToot ( String s , @Nullable String contentWarning ) {
private boolean saveTheToot ( String s , @Nullable String contentWarning ) {
if ( TextUtils . isEmpty ( s ) ) {
if ( TextUtils . isEmpty ( s ) ) {
return false ;
return false ;
@ -715,14 +695,11 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
} . getType ( ) ) ;
} . getType ( ) ) ;
}
}
final TootEntity toot = new TootEntity ( ) ;
String mediaUrlsSerialized = null ;
toot . setText ( s ) ;
toot . setContentWarning ( contentWarning ) ;
if ( ! ListUtils . isEmpty ( mediaQueued ) ) {
if ( ! ListUtils . isEmpty ( mediaQueued ) ) {
List < String > savedList = saveMedia ( existingUris ) ;
List < String > savedList = saveMedia ( existingUris ) ;
if ( ! ListUtils . isEmpty ( savedList ) ) {
if ( ! ListUtils . isEmpty ( savedList ) ) {
String json = new Gson ( ) . toJson ( savedList ) ;
mediaUrlsSerialized = new Gson ( ) . toJson ( savedList ) ;
toot . setUrls ( json ) ;
if ( ! ListUtils . isEmpty ( existingUris ) ) {
if ( ! ListUtils . isEmpty ( existingUris ) ) {
deleteMedia ( setDifference ( existingUris , savedList ) ) ;
deleteMedia ( setDifference ( existingUris , savedList ) ) ;
}
}
@ -734,16 +711,15 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
* can be deleted . * /
* can be deleted . * /
deleteMedia ( existingUris ) ;
deleteMedia ( existingUris ) ;
}
}
final TootEntity toot = new TootEntity ( savedTootUid , s , mediaUrlsSerialized , contentWarning ,
inReplyToId ,
getIntent ( ) . getStringExtra ( REPLYING_STATUS_CONTENT_EXTRA ) ,
getIntent ( ) . getStringExtra ( REPLYING_STATUS_AUTHOR_USERNAME_EXTRA ) , statusVisibility ) ;
new AsyncTask < Void , Void , Void > ( ) {
new AsyncTask < Void , Void , Void > ( ) {
@Override
@Override
protected Void doInBackground ( Void . . . params ) {
protected Void doInBackground ( Void . . . params ) {
if ( savedTootUid ! = 0 ) {
tootDao . insertOrReplace ( toot ) ;
toot . setUid ( savedTootUid ) ;
tootDao . updateToot ( toot ) ;
} else {
tootDao . insert ( toot ) ;
}
return null ;
return null ;
}
}
} . execute ( ) ;
} . execute ( ) ;
@ -759,10 +735,10 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
}
}
private void setStatusVisibility ( String visibility ) {
private void setStatusVisibility ( Status . Visibility visibility ) {
statusVisibility = visibility ;
statusVisibility = visibility ;
switch ( visibility ) {
switch ( visibility ) {
case "public" : {
case PUBLIC : {
floatingBtn . setText ( R . string . action_send_public ) ;
floatingBtn . setText ( R . string . action_send_public ) ;
floatingBtn . setCompoundDrawables ( null , null , null , null ) ;
floatingBtn . setCompoundDrawables ( null , null , null , null ) ;
Drawable globe = AppCompatResources . getDrawable ( this , R . drawable . ic_public_24dp ) ;
Drawable globe = AppCompatResources . getDrawable ( this , R . drawable . ic_public_24dp ) ;
@ -771,7 +747,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
break ;
break ;
}
}
case "private" : {
case PRIVATE : {
addLockToSendButton ( ) ;
addLockToSendButton ( ) ;
Drawable lock = AppCompatResources . getDrawable ( this ,
Drawable lock = AppCompatResources . getDrawable ( this ,
R . drawable . ic_lock_outline_24dp ) ;
R . drawable . ic_lock_outline_24dp ) ;
@ -780,7 +756,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
break ;
break ;
}
}
case "direct" : {
case DIRECT : {
addLockToSendButton ( ) ;
addLockToSendButton ( ) ;
Drawable envelope = AppCompatResources . getDrawable ( this , R . drawable . ic_email_24dp ) ;
Drawable envelope = AppCompatResources . getDrawable ( this , R . drawable . ic_email_24dp ) ;
if ( envelope ! = null ) {
if ( envelope ! = null ) {
@ -788,7 +764,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
break ;
break ;
}
}
case "unlisted" :
case UNLISTED :
default : {
default : {
floatingBtn . setText ( R . string . action_send ) ;
floatingBtn . setText ( R . string . action_send ) ;
floatingBtn . setCompoundDrawables ( null , null , null , null ) ;
floatingBtn . setCompoundDrawables ( null , null , null , null ) ;
@ -808,7 +784,8 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
fragment . show ( getSupportFragmentManager ( ) , null ) ;
fragment . show ( getSupportFragmentManager ( ) , null ) ;
}
}
public void onVisibilityChanged ( String visibility ) {
@Override
public void onVisibilityChanged ( Status . Visibility visibility ) {
setStatusVisibility ( visibility ) ;
setStatusVisibility ( visibility ) ;
}
}
@ -848,18 +825,17 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
@Override
@Override
protected void onStop ( ) {
protected void onStop ( ) {
super . onStop ( ) ;
super . onStop ( ) ;
if ( inReplyToId ! = null ) {
// Don't save the visibility setting for replies because they adopt the visibility of
/ * Don ' t save the visibility setting for replies because they adopt the visibility of
// the status they reply to and that behaviour needs to be kept separate.
* the status they reply to and that behaviour needs to be kept separate . * /
if ( inReplyToId = = null ) {
return ;
getPrivatePreferences ( ) . edit ( )
. putInt ( REMEMBERED_VISIBILITY_PREF , statusVisibility . getNum ( ) )
. apply ( ) ;
}
}
getPrivatePreferences ( ) . edit ( )
. putString ( "rememberedVisibility" , statusVisibility )
. apply ( ) ;
}
}
private void setEditTextMimeTypes ( ) {
private void setEditTextMimeTypes ( ) {
final String [ ] mimeTypes = new String [ ] { "image/*" } ;
final String [ ] mimeTypes = new String [ ] { "image/*" } ;
textEditor . setMimeTypes ( mimeTypes , new InputConnectionCompat . OnCommitContentListener ( ) {
textEditor . setMimeTypes ( mimeTypes , new InputConnectionCompat . OnCommitContentListener ( ) {
@Override
@Override
public boolean onCommitContent ( InputContentInfoCompat inputContentInfo ,
public boolean onCommitContent ( InputContentInfoCompat inputContentInfo ,
@ -932,7 +908,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
return true ;
return true ;
}
}
private void sendStatus ( String content , String visibility , boolean sensitive ,
private void sendStatus ( String content , Status . Visibility visibility , boolean sensitive ,
String spoilerText ) {
String spoilerText ) {
ArrayList < String > mediaIds = new ArrayList < > ( ) ;
ArrayList < String > mediaIds = new ArrayList < > ( ) ;
@ -946,25 +922,23 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
if ( response . isSuccessful ( ) ) {
if ( response . isSuccessful ( ) ) {
onSendSuccess ( ) ;
onSendSuccess ( ) ;
} else {
} else {
onSendFailure ( ) ;
onSendFailure ( response ) ;
}
}
}
}
@Override
@Override
public void onFailure ( @NonNull Call < Status > call , @NonNull Throwable t ) {
public void onFailure ( @NonNull Call < Status > call , @NonNull Throwable t ) {
onSendFailure ( ) ;
onSendFailure ( null ) ;
}
}
} ;
} ;
mastodonApi . createStatus ( content , inReplyToId , spoilerText , visibility , sensitive , mediaIds )
mastodonApi . createStatus ( content , inReplyToId , spoilerText , visibility . serverString ( ) ,
. enqueue ( callback ) ;
sensitive , mediaIds ) . enqueue ( callback ) ;
}
}
private void onSendSuccess ( ) {
private void onSendSuccess ( ) {
// If the status was loaded from a draft, delete the draft and associated media files.
// If the status was loaded from a draft, delete the draft and associated media files.
if ( savedTootUid ! = 0 ) {
if ( savedTootUid ! = 0 ) {
TootEntity status = new TootEntity ( ) ;
tootDao . delete ( savedTootUid ) ;
status . setUid ( savedTootUid ) ;
tootDao . delete ( status ) ;
for ( QueuedMedia item : mediaQueued ) {
for ( QueuedMedia item : mediaQueued ) {
try {
try {
if ( getContentResolver ( ) . delete ( item . uri , null , null ) = = 0 ) {
if ( getContentResolver ( ) . delete ( item . uri , null , null ) = = 0 ) {
@ -983,16 +957,32 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
finish ( ) ;
finish ( ) ;
}
}
private void onSendFailure ( ) {
private void onSendFailure ( @Nullable Response < Status > response ) {
textEditor . setError ( getString ( R . string . error_generic ) ) ;
setStateToNotReadying ( ) ;
setStateToNotReadying ( ) ;
if ( response ! = null & & inReplyToId ! = null & & response . code ( ) = = 404 ) {
new AlertDialog . Builder ( this )
. setMessage ( R . string . dialog_reply_not_found )
. setPositiveButton ( android . R . string . ok , new DialogInterface . OnClickListener ( ) {
@Override
public void onClick ( DialogInterface dialog , int which ) {
inReplyToId = null ;
replyContentTextView . setVisibility ( View . GONE ) ;
replyTextView . setVisibility ( View . GONE ) ;
}
} )
. setNegativeButton ( android . R . string . cancel , null )
. show ( ) ;
} else {
textEditor . setError ( getString ( R . string . error_generic ) ) ;
}
}
}
private void readyStatus ( final String visibility , final boolean sensitive ) {
private void readyStatus ( final Status . Visibility visibility , final boolean sensitive ) {
finishingUploadDialog = ProgressDialog . show (
finishingUploadDialog = ProgressDialog . show (
this , getString ( R . string . dialog_title_finishing_media_upload ) ,
this , getString ( R . string . dialog_title_finishing_media_upload ) ,
getString ( R . string . dialog_message_uploading_media ) , true , true ) ;
getString ( R . string . dialog_message_uploading_media ) , true , true ) ;
final AsyncTask < Void , Void , Boolean > waitForMediaTask =
@SuppressLint ( "StaticFieldLeak" ) final AsyncTask < Void , Void , Boolean > waitForMediaTask =
new AsyncTask < Void , Void , Boolean > ( ) {
new AsyncTask < Void , Void , Boolean > ( ) {
@Override
@Override
protected Boolean doInBackground ( Void . . . params ) {
protected Boolean doInBackground ( Void . . . params ) {
@ -1035,7 +1025,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
waitForMediaTask . execute ( ) ;
waitForMediaTask . execute ( ) ;
}
}
private void onReadySuccess ( String visibility , boolean sensitive ) {
private void onReadySuccess ( Status . Visibility visibility , boolean sensitive ) {
/ * Validate the status meets the character limit . This has to be delayed until after all
/ * Validate the status meets the character limit . This has to be delayed until after all
* uploads finish because their links are added when the upload succeeds and that affects
* uploads finish because their links are added when the upload succeeds and that affects
* whether the limit is met or not . * /
* whether the limit is met or not . * /
@ -1056,7 +1046,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
}
}
private void onReadyFailure ( final String visibility , final boolean sensitive ) {
private void onReadyFailure ( final Status . Visibility visibility , final boolean sensitive ) {
doErrorDialog ( R . string . error_media_upload_sending , R . string . action_retry ,
doErrorDialog ( R . string . error_media_upload_sending , R . string . action_retry ,
new View . OnClickListener ( ) {
new View . OnClickListener ( ) {
@Override
@Override
@ -1439,44 +1429,6 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
}
}
@Nullable
private static Bitmap getImageThumbnail ( ContentResolver contentResolver , Uri uri ) {
InputStream stream ;
try {
stream = contentResolver . openInputStream ( uri ) ;
} catch ( FileNotFoundException e ) {
return null ;
}
Bitmap source = BitmapFactory . decodeStream ( stream ) ;
if ( source = = null ) {
IOUtils . closeQuietly ( stream ) ;
return null ;
}
Bitmap bitmap = ThumbnailUtils . extractThumbnail ( source , THUMBNAIL_SIZE , THUMBNAIL_SIZE ) ;
source . recycle ( ) ;
try {
if ( stream ! = null ) {
stream . close ( ) ;
}
} catch ( IOException e ) {
bitmap . recycle ( ) ;
return null ;
}
return bitmap ;
}
@Nullable
private static Bitmap getVideoThumbnail ( Context context , Uri uri ) {
MediaMetadataRetriever retriever = new MediaMetadataRetriever ( ) ;
retriever . setDataSource ( context , uri ) ;
Bitmap source = retriever . getFrameAtTime ( ) ;
if ( source = = null ) {
return null ;
}
Bitmap bitmap = ThumbnailUtils . extractThumbnail ( source , THUMBNAIL_SIZE , THUMBNAIL_SIZE ) ;
source . recycle ( ) ;
return bitmap ;
}
private void pickMedia ( Uri uri , long mediaSize ) {
private void pickMedia ( Uri uri , long mediaSize ) {
ContentResolver contentResolver = getContentResolver ( ) ;
ContentResolver contentResolver = getContentResolver ( ) ;
@ -1498,7 +1450,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
displayTransientError ( R . string . error_media_upload_image_or_video ) ;
displayTransientError ( R . string . error_media_upload_image_or_video ) ;
return ;
return ;
}
}
Bitmap bitmap = getVideoThumbnail ( this , uri ) ;
Bitmap bitmap = MediaUtils . getVideoThumbnail ( this , uri , THUMBNAIL_SIZE ) ;
if ( bitmap ! = null ) {
if ( bitmap ! = null ) {
addMediaToQueue ( QueuedMedia . Type . VIDEO , bitmap , uri , mediaSize , null ) ;
addMediaToQueue ( QueuedMedia . Type . VIDEO , bitmap , uri , mediaSize , null ) ;
} else {
} else {
@ -1507,7 +1459,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
break ;
break ;
}
}
case "image" : {
case "image" : {
Bitmap bitmap = getImageThumbnail ( contentResolver , uri ) ;
Bitmap bitmap = MediaUtils . getImageThumbnail ( contentResolver , uri , THUMBNAIL_SIZE ) ;
if ( bitmap ! = null ) {
if ( bitmap ! = null ) {
addMediaToQueue ( QueuedMedia . Type . IMAGE , bitmap , uri , mediaSize , null ) ;
addMediaToQueue ( QueuedMedia . Type . IMAGE , bitmap , uri , mediaSize , null ) ;
} else {
} else {
@ -1562,10 +1514,8 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
return super . onOptionsItemSelected ( item ) ;
return super . onOptionsItemSelected ( item ) ;
}
}
/ * *
@Override
* Does a synchronous search request for accounts fulfilling the given partial mention text .
public List < Account > searchAccounts ( String mention ) {
* /
private ArrayList < Account > autocompleteMention ( String mention ) {
ArrayList < Account > resultList = new ArrayList < > ( ) ;
ArrayList < Account > resultList = new ArrayList < > ( ) ;
try {
try {
List < Account > accountList = mastodonApi . searchAccounts ( mention , false , 40 )
List < Account > accountList = mastodonApi . searchAccounts ( mention , false , 40 )
@ -1580,7 +1530,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
return resultList ;
return resultList ;
}
}
private static class QueuedMedia {
private static final class QueuedMedia {
Type type ;
Type type ;
ProgressImageView preview ;
ProgressImageView preview ;
Uri uri ;
Uri uri ;
@ -1657,98 +1607,42 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
}
}
}
}
private class MentionAutoCompleteAdapter extends ArrayAdapter < Account > implements Filterable {
/ * *
private ArrayList < Account > resultList ;
* Function to decide which visibility should be used for posting a status
@LayoutRes
*
private int layoutId ;
* @return { @code PRIVATE } if account is locked , { @code PUBLIC } if both start and reply
* visibilities are unknown or minimal known visibility of two of them .
MentionAutoCompleteAdapter ( Context context , @LayoutRes int resource ) {
* /
super ( context , resource ) ;
private static Status . Visibility pickVisibility ( final Status . Visibility startVisibility ,
layoutId = resource ;
final Status . Visibility replyVisibility ,
resultList = new ArrayList < > ( ) ;
boolean isAccountLocked ) {
// If the currently logged in account is locked, its posts should default to private.
// This should override even the reply settings.
if ( isAccountLocked ) {
return Status . Visibility . PRIVATE ;
}
}
@Override
if ( startVisibility = = Status . Visibility . UNKNOWN & &
public int getCount ( ) {
replyVisibility = = Status . Visibility . UNKNOWN ) {
return resultList . size ( ) ;
return Status . Visibility . PUBLIC ;
}
}
@Override
if ( replyVisibility = = Status . Visibility . UNKNOWN ) {
public Account getItem ( int index ) {
return startVisibility ;
return resultList . get ( index ) ;
}
}
@Override
if ( startVisibility = = Status . Visibility . UNKNOWN ) {
@NonNull
return replyVisibility ;
public Filter getFilter ( ) {
return new Filter ( ) {
@Override
public CharSequence convertResultToString ( Object resultValue ) {
return ( ( Account ) resultValue ) . username ;
}
// This method is invoked in a worker thread.
@Override
protected FilterResults performFiltering ( CharSequence constraint ) {
FilterResults filterResults = new FilterResults ( ) ;
if ( constraint ! = null ) {
ArrayList < Account > accounts = autocompleteMention ( constraint . toString ( ) ) ;
filterResults . values = accounts ;
filterResults . count = accounts . size ( ) ;
}
return filterResults ;
}
@SuppressWarnings ( "unchecked" )
@Override
protected void publishResults ( CharSequence constraint , FilterResults results ) {
if ( results ! = null & & results . count > 0 ) {
resultList . clear ( ) ;
ArrayList < Account > newResults = ( ArrayList < Account > ) results . values ;
resultList . addAll ( newResults ) ;
notifyDataSetChanged ( ) ;
} else {
notifyDataSetInvalidated ( ) ;
}
}
} ;
}
}
@Override
if ( startVisibility . getNum ( ) > replyVisibility . getNum ( ) ) {
@NonNull
return startVisibility ;
public View getView ( int position , @Nullable View convertView , @NonNull ViewGroup parent ) {
} else {
View view = convertView ;
return replyVisibility ;
Context context = getContext ( ) ;
if ( convertView = = null ) {
LayoutInflater layoutInflater =
( LayoutInflater ) context . getSystemService ( Context . LAYOUT_INFLATER_SERVICE ) ;
view = layoutInflater . inflate ( layoutId , null ) ;
}
Account account = getItem ( position ) ;
if ( account ! = null ) {
TextView username = view . findViewById ( R . id . username ) ;
TextView displayName = view . findViewById ( R . id . display_name ) ;
ImageView avatar = view . findViewById ( R . id . avatar ) ;
String format = getContext ( ) . getString ( R . string . status_username_format ) ;
String formattedUsername = String . format ( format , account . username ) ;
username . setText ( formattedUsername ) ;
displayName . setText ( account . getDisplayName ( ) ) ;
if ( ! account . avatar . isEmpty ( ) ) {
Picasso . with ( context )
. load ( account . avatar )
. placeholder ( R . drawable . avatar_default )
. transform ( new RoundedTransformation ( 7 , 0 ) )
. into ( avatar ) ;
}
}
return view ;
}
}
}
}
@SuppressWarnings ( "WeakerAccess" )
public static final class IntentBuilder {
public static final class IntentBuilder {
@Nullable
@Nullable
private Integer savedTootUid ;
private Integer savedTootUid ;
@ -1761,11 +1655,11 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
@Nullable
@Nullable
private String inReplyToId ;
private String inReplyToId ;
@Nullable
@Nullable
private String replyVisibility ;
private Status . Visibility replyVisibility ;
@Nullable
@Nullable
private String contentWarning ;
private String contentWarning ;
@Nullable
@Nullable
private Account replyingStatusAuthor ;
private String replyingStatusAuthor ;
@Nullable
@Nullable
private String replyingStatusContent ;
private String replyingStatusContent ;
@ -1794,7 +1688,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
return this ;
return this ;
}
}
public IntentBuilder replyVisibility ( String replyVisibility ) {
public IntentBuilder replyVisibility ( Status . Visibility replyVisibility ) {
this . replyVisibility = replyVisibility ;
this . replyVisibility = replyVisibility ;
return this ;
return this ;
}
}
@ -1804,8 +1698,8 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
return this ;
return this ;
}
}
public IntentBuilder repyingStatusAuthor ( Account author ) {
public IntentBuilder repyingStatusAuthor ( String username ) {
this . replyingStatusAuthor = author ;
this . replyingStatusAuthor = username ;
return this ;
return this ;
}
}
@ -1834,7 +1728,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
intent . putExtra ( IN_REPLY_TO_ID_EXTRA , inReplyToId ) ;
intent . putExtra ( IN_REPLY_TO_ID_EXTRA , inReplyToId ) ;
}
}
if ( replyVisibility ! = null ) {
if ( replyVisibility ! = null ) {
intent . putExtra ( REPLY_VISIBILITY_EXTRA , replyVisibility ) ;
intent . putExtra ( REPLY_VISIBILITY_EXTRA , replyVisibility . getNum ( ) ) ;
}
}
if ( contentWarning ! = null ) {
if ( contentWarning ! = null ) {
intent . putExtra ( CONTENT_WARNING_EXTRA , contentWarning ) ;
intent . putExtra ( CONTENT_WARNING_EXTRA , contentWarning ) ;
@ -1843,8 +1737,7 @@ public final class ComposeActivity extends BaseActivity implements ComposeOption
intent . putExtra ( REPLYING_STATUS_CONTENT_EXTRA , replyingStatusContent ) ;
intent . putExtra ( REPLYING_STATUS_CONTENT_EXTRA , replyingStatusContent ) ;
}
}
if ( replyingStatusAuthor ! = null ) {
if ( replyingStatusAuthor ! = null ) {
intent . putExtra ( REPLYING_STATUS_AUTHOR_USERNAME_EXTRA ,
intent . putExtra ( REPLYING_STATUS_AUTHOR_USERNAME_EXTRA , replyingStatusAuthor ) ;
replyingStatusAuthor . localUsername ) ;
}
}
return intent ;
return intent ;
}
}