@ -115,11 +115,6 @@ import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import java.io.File
import java.io.IOException
import java.text.DecimalFormat
@ -127,6 +122,11 @@ import java.util.Locale
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
class ComposeActivity :
BaseActivity ( ) ,
@ -163,14 +163,23 @@ class ComposeActivity :
private var maxUploadMediaNumber = InstanceInfoRepository . DEFAULT _MAX _MEDIA _ATTACHMENTS
private val takePicture = registerForActivityResult ( ActivityResultContracts . TakePicture ( ) ) { success ->
if ( success ) {
pickMedia ( photoUploadUri !! )
private val takePicture =
registerForActivityResult ( ActivityResultContracts . TakePicture ( ) ) { success ->
if ( success ) {
pickMedia ( photoUploadUri !! )
}
}
}
private val pickMediaFile = registerForActivityResult ( PickMediaFiles ( ) ) { uris ->
if ( viewModel . media . value . size + uris . size > maxUploadMediaNumber ) {
Toast . makeText ( this , resources . getQuantityString ( R . plurals . error _upload _max _media _reached , maxUploadMediaNumber , maxUploadMediaNumber ) , Toast . LENGTH _SHORT ) . show ( )
Toast . makeText (
this ,
resources . getQuantityString (
R . plurals . error _upload _max _media _reached ,
maxUploadMediaNumber ,
maxUploadMediaNumber
) ,
Toast . LENGTH _SHORT
) . show ( )
} else {
uris . forEach { uri ->
pickMedia ( uri )
@ -191,7 +200,8 @@ class ComposeActivity :
uriNew ,
size ,
itemOld . description ,
null , // Intentionally reset focus when cropping
// Intentionally reset focus when cropping
null ,
itemOld
)
}
@ -222,7 +232,11 @@ class ComposeActivity :
val mediaAdapter = MediaPreviewAdapter (
this ,
onAddCaption = { item ->
CaptionDialog . newInstance ( item . localId , item . description , item . uri ) . show ( supportFragmentManager , " caption_dialog " )
CaptionDialog . newInstance (
item . localId ,
item . description ,
item . uri
) . show ( supportFragmentManager , " caption_dialog " )
} ,
onAddFocus = { item ->
makeFocusDialog ( item . focus , item . uri ) { newFocus ->
@ -240,7 +254,11 @@ class ComposeActivity :
/ * If the composer is started up as a reply to another post , override the " starting " state
* based on what the intent from the reply request passes . * /
val composeOptions : ComposeOptions ? = IntentCompat . getParcelableExtra ( intent , COMPOSE _OPTIONS _EXTRA , ComposeOptions :: class . java )
val composeOptions : ComposeOptions ? = IntentCompat . getParcelableExtra (
intent ,
COMPOSE _OPTIONS _EXTRA ,
ComposeOptions :: class . java
)
viewModel . setup ( composeOptions )
setupButtons ( )
@ -303,12 +321,20 @@ class ComposeActivity :
if ( type . startsWith ( " image/ " ) || type . startsWith ( " video/ " ) || type . startsWith ( " audio/ " ) ) {
when ( intent . action ) {
Intent . ACTION _SEND -> {
IntentCompat . getParcelableExtra ( intent , Intent . EXTRA _STREAM , Uri :: class . java ) ?. let { uri ->
IntentCompat . getParcelableExtra (
intent ,
Intent . EXTRA _STREAM ,
Uri :: class . java
) ?. let { uri ->
pickMedia ( uri )
}
}
Intent . ACTION _SEND _MULTIPLE -> {
IntentCompat . getParcelableArrayListExtra ( intent , Intent . EXTRA _STREAM , Uri :: class . java ) ?. forEach { uri ->
IntentCompat . getParcelableArrayListExtra (
intent ,
Intent . EXTRA _STREAM ,
Uri :: class . java
) ?. forEach { uri ->
pickMedia ( uri )
}
}
@ -328,7 +354,13 @@ class ComposeActivity :
val end = binding . composeEditField . selectionEnd . coerceAtLeast ( 0 )
val left = min ( start , end )
val right = max ( start , end )
binding . composeEditField . text . replace ( left , right , shareBody , 0 , shareBody . length )
binding . composeEditField . text . replace (
left ,
right ,
shareBody ,
0 ,
shareBody . length
)
// move edittext cursor to first when shareBody parsed
binding . composeEditField . text . insert ( 0 , " \n " )
binding . composeEditField . setSelection ( 0 )
@ -341,23 +373,48 @@ class ComposeActivity :
if ( replyingStatusAuthor != null ) {
binding . composeReplyView . show ( )
binding . composeReplyView . text = getString ( R . string . replying _to , replyingStatusAuthor )
val arrowDownIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _arrow _drop _down ) . apply { sizeDp = 12 }
val arrowDownIcon = IconicsDrawable (
this ,
GoogleMaterial . Icon . gmd _arrow _drop _down
) . apply {
sizeDp = 12
}
setDrawableTint ( this , arrowDownIcon , android . R . attr . textColorTertiary )
binding . composeReplyView . setCompoundDrawablesRelativeWithIntrinsicBounds ( null , null , arrowDownIcon , null )
binding . composeReplyView . setCompoundDrawablesRelativeWithIntrinsicBounds (
null ,
null ,
arrowDownIcon ,
null
)
binding . composeReplyView . setOnClickListener {
TransitionManager . beginDelayedTransition ( binding . composeReplyContentView . parent as ViewGroup )
TransitionManager . beginDelayedTransition (
binding . composeReplyContentView . parent as ViewGroup
)
if ( binding . composeReplyContentView . isVisible ) {
binding . composeReplyContentView . hide ( )
binding . composeReplyView . setCompoundDrawablesRelativeWithIntrinsicBounds ( null , null , arrowDownIcon , null )
binding . composeReplyView . setCompoundDrawablesRelativeWithIntrinsicBounds (
null ,
null ,
arrowDownIcon ,
null
)
} else {
binding . composeReplyContentView . show ( )
val arrowUpIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _arrow _drop _up ) . apply { sizeDp = 12 }
val arrowUpIcon = IconicsDrawable (
this ,
GoogleMaterial . Icon . gmd _arrow _drop _up
) . apply { sizeDp = 12 }
setDrawableTint ( this , arrowUpIcon , android . R . attr . textColorTertiary )
binding . composeReplyView . setCompoundDrawablesRelativeWithIntrinsicBounds ( null , null , arrowUpIcon , null )
binding . composeReplyView . setCompoundDrawablesRelativeWithIntrinsicBounds (
null ,
null ,
arrowUpIcon ,
null
)
}
}
}
@ -374,7 +431,12 @@ class ComposeActivity :
private fun setupComposeField ( preferences : SharedPreferences , startingText : String ? ) {
binding . composeEditField . setOnReceiveContentListener ( this )
binding . composeEditField . setOnKeyListener { _ , keyCode , event -> this . onKeyDown ( keyCode , event ) }
binding . composeEditField . setOnKeyListener { _ , keyCode , event ->
this . onKeyDown (
keyCode ,
event
)
}
binding . composeEditField . setAdapter (
ComposeAutoCompleteAdapter (
@ -419,7 +481,9 @@ class ComposeActivity :
}
lifecycleScope . launch {
viewModel . showContentWarning . combine ( viewModel . markMediaAsSensitive ) { showContentWarning , markSensitive ->
viewModel . showContentWarning . combine (
viewModel . markMediaAsSensitive
) { showContentWarning , markSensitive ->
updateSensitiveMediaToggle ( markSensitive , showContentWarning )
showContentWarning ( showContentWarning )
} . collect ( )
@ -434,7 +498,10 @@ class ComposeActivity :
mediaAdapter . submitList ( media )
binding . composeMediaPreviewBar . visible ( media . isNotEmpty ( ) )
updateSensitiveMediaToggle ( viewModel . markMediaAsSensitive . value , viewModel . showContentWarning . value )
updateSensitiveMediaToggle (
viewModel . markMediaAsSensitive . value ,
viewModel . showContentWarning . value
)
}
}
@ -510,16 +577,42 @@ class ComposeActivity :
val textColor = MaterialColors . getColor ( binding . root , android . R . attr . textColorTertiary )
val cameraIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _camera _alt ) . apply { colorInt = textColor ; sizeDp = 18 }
binding . actionPhotoTake . setCompoundDrawablesRelativeWithIntrinsicBounds ( cameraIcon , null , null , null )
val cameraIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _camera _alt ) . apply {
colorInt = textColor
sizeDp = 18
}
binding . actionPhotoTake . setCompoundDrawablesRelativeWithIntrinsicBounds (
cameraIcon ,
null ,
null ,
null
)
val imageIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _image ) . apply { colorInt = textColor ; sizeDp = 18 }
binding . actionPhotoPick . setCompoundDrawablesRelativeWithIntrinsicBounds ( imageIcon , null , null , null )
val imageIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _image ) . apply {
colorInt = textColor
sizeDp = 18
}
binding . actionPhotoPick . setCompoundDrawablesRelativeWithIntrinsicBounds (
imageIcon ,
null ,
null ,
null
)
val pollIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _poll ) . apply { colorInt = textColor ; sizeDp = 18 }
binding . addPollTextActionTextView . setCompoundDrawablesRelativeWithIntrinsicBounds ( pollIcon , null , null , null )
val pollIcon = IconicsDrawable ( this , GoogleMaterial . Icon . gmd _poll ) . apply {
colorInt = textColor
sizeDp = 18
}
binding . addPollTextActionTextView . setCompoundDrawablesRelativeWithIntrinsicBounds (
pollIcon ,
null ,
null ,
null
)
binding . actionPhotoTake . visible ( Intent ( MediaStore . ACTION _IMAGE _CAPTURE ) . resolveActivity ( packageManager ) != null )
binding . actionPhotoTake . visible (
Intent ( MediaStore . ACTION _IMAGE _CAPTURE ) . resolveActivity ( packageManager ) != null
)
binding . actionPhotoTake . setOnClickListener { initiateCameraApp ( ) }
binding . actionPhotoPick . setOnClickListener { onMediaPick ( ) }
@ -549,7 +642,12 @@ class ComposeActivity :
private fun setupLanguageSpinner ( initialLanguages : List < String > ) {
binding . composePostLanguageButton . onItemSelectedListener = object : AdapterView . OnItemSelectedListener {
override fun onItemSelected ( parent : AdapterView < * > , view : View ? , position : Int , id : Long ) {
override fun onItemSelected (
parent : AdapterView < * > ,
view : View ? ,
position : Int ,
id : Long
) {
viewModel . postLanguage = ( parent . adapter . getItem ( position ) as Locale ) . modernLanguageCode
}
@ -594,8 +692,12 @@ class ComposeActivity :
private fun replaceTextAtCaret ( text : CharSequence ) {
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
val start = binding . composeEditField . selectionStart . coerceAtMost ( binding . composeEditField . selectionEnd )
val end = binding . composeEditField . selectionStart . coerceAtLeast ( binding . composeEditField . selectionEnd )
val start = binding . composeEditField . selectionStart . coerceAtMost (
binding . composeEditField . selectionEnd
)
val end = binding . composeEditField . selectionStart . coerceAtLeast (
binding . composeEditField . selectionEnd
)
val textToInsert = if ( start > 0 && ! binding . composeEditField . text [ start - 1 ] . isWhitespace ( ) ) {
" $text "
} else {
@ -609,8 +711,12 @@ class ComposeActivity :
fun prependSelectedWordsWith ( text : CharSequence ) {
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
val start = binding . composeEditField . selectionStart . coerceAtMost ( binding . composeEditField . selectionEnd )
val end = binding . composeEditField . selectionStart . coerceAtLeast ( binding . composeEditField . selectionEnd )
val start = binding . composeEditField . selectionStart . coerceAtMost (
binding . composeEditField . selectionEnd
)
val end = binding . composeEditField . selectionStart . coerceAtLeast (
binding . composeEditField . selectionEnd
)
val editorText = binding . composeEditField . text
if ( start == end ) {
@ -678,7 +784,10 @@ class ComposeActivity :
this . viewModel . toggleMarkSensitive ( )
}
private fun updateSensitiveMediaToggle ( markMediaSensitive : Boolean , contentWarningShown : Boolean ) {
private fun updateSensitiveMediaToggle (
markMediaSensitive : Boolean ,
contentWarningShown : Boolean
) {
if ( viewModel . media . value . isEmpty ( ) ) {
binding . composeHideMediaButton . hide ( )
binding . descriptionMissingWarningButton . hide ( )
@ -695,7 +804,10 @@ class ComposeActivity :
getColor ( R . color . tusky _blue )
} else {
binding . composeHideMediaButton . setImageResource ( R . drawable . ic _eye _24dp )
MaterialColors . getColor ( binding . composeHideMediaButton , android . R . attr . textColorTertiary )
MaterialColors . getColor (
binding . composeHideMediaButton ,
android . R . attr . textColorTertiary
)
}
}
binding . composeHideMediaButton . drawable . colorFilter = PorterDuffColorFilter ( color , PorterDuff . Mode . SRC _IN )
@ -717,7 +829,10 @@ class ComposeActivity :
enableButton ( binding . composeScheduleButton , clickable = false , colorActive = false )
} else {
@ColorInt val color = if ( binding . composeScheduleView . time == null ) {
MaterialColors . getColor ( binding . composeScheduleButton , android . R . attr . textColorTertiary )
MaterialColors . getColor (
binding . composeScheduleButton ,
android . R . attr . textColorTertiary
)
} else {
getColor ( R . color . tusky _blue )
}
@ -748,7 +863,11 @@ class ComposeActivity :
binding . composeToggleVisibilityButton . setImageResource ( iconRes )
if ( viewModel . editing ) {
// Can't update visibility on published status
enableButton ( binding . composeToggleVisibilityButton , clickable = false , colorActive = false )
enableButton (
binding . composeToggleVisibilityButton ,
clickable = false ,
colorActive = false
)
}
}
@ -785,7 +904,11 @@ class ComposeActivity :
private fun showEmojis ( ) {
binding . emojiView . adapter ?. let {
if ( it . itemCount == 0 ) {
val errorMessage = getString ( R . string . error _no _custom _emojis , accountManager . activeAccount !! . domain )
val errorMessage =
getString (
R . string . error _no _custom _emojis ,
accountManager . activeAccount !! . domain
)
displayTransientMessage ( errorMessage )
} else {
if ( emojiBehavior . state == BottomSheetBehavior . STATE _HIDDEN || emojiBehavior . state == BottomSheetBehavior . STATE _COLLAPSED ) {
@ -852,9 +975,14 @@ class ComposeActivity :
private fun setupPollView ( ) {
val margin = resources . getDimensionPixelSize ( R . dimen . compose _media _preview _margin )
val marginBottom = resources . getDimensionPixelSize ( R . dimen . compose _media _preview _margin _bottom )
val marginBottom = resources . getDimensionPixelSize (
R . dimen . compose _media _preview _margin _bottom
)
val layoutParams = LinearLayout . LayoutParams ( ViewGroup . LayoutParams . WRAP _CONTENT , ViewGroup . LayoutParams . WRAP _CONTENT )
val layoutParams = LinearLayout . LayoutParams (
ViewGroup . LayoutParams . WRAP _CONTENT ,
ViewGroup . LayoutParams . WRAP _CONTENT
)
layoutParams . setMargins ( margin , margin , margin , marginBottom )
binding . pollPreview . layoutParams = layoutParams
@ -905,7 +1033,10 @@ class ComposeActivity :
val textColor = if ( remainingLength < 0 ) {
getColor ( R . color . tusky _red )
} else {
MaterialColors . getColor ( binding . composeCharactersLeftView , android . R . attr . textColorTertiary )
MaterialColors . getColor (
binding . composeCharactersLeftView ,
android . R . attr . textColorTertiary
)
}
binding . composeCharactersLeftView . setTextColor ( textColor )
}
@ -917,7 +1048,9 @@ class ComposeActivity :
}
private fun verifyScheduledTime ( ) : Boolean {
return binding . composeScheduleView . verifyScheduledTime ( binding . composeScheduleView . getDateTime ( viewModel . scheduledAt . value ) )
return binding . composeScheduleView . verifyScheduledTime (
binding . composeScheduleView . getDateTime ( viewModel . scheduledAt . value )
)
}
private fun onSendClicked ( ) {
@ -967,7 +1100,11 @@ class ComposeActivity :
}
}
override fun onRequestPermissionsResult ( requestCode : Int , permissions : Array < String > , grantResults : IntArray ) {
override fun onRequestPermissionsResult (
requestCode : Int ,
permissions : Array < String > ,
grantResults : IntArray
) {
super . onRequestPermissionsResult ( requestCode , permissions , grantResults )
if ( requestCode == PERMISSIONS _REQUEST _READ _EXTERNAL _STORAGE ) {
@ -1042,14 +1179,20 @@ class ComposeActivity :
val tempFile = createNewImageFile ( this , if ( isPng ) " .png " else " .jpg " )
// "Authority" must be the same as the android:authorities string in AndroidManifest.xml
val uriNew = FileProvider . getUriForFile ( this , BuildConfig . APPLICATION _ID + " .fileprovider " , tempFile )
val uriNew = FileProvider . getUriForFile (
this ,
BuildConfig . APPLICATION _ID + " .fileprovider " ,
tempFile
)
viewModel . cropImageItemOld = item
cropImage . launch (
options ( uri = item . uri ) {
setOutputUri ( uriNew )
setOutputCompressFormat ( if ( isPng ) Bitmap . CompressFormat . PNG else Bitmap . CompressFormat . JPEG )
setOutputCompressFormat (
if ( isPng ) Bitmap . CompressFormat . PNG else Bitmap . CompressFormat . JPEG
)
}
)
}
@ -1087,7 +1230,9 @@ class ComposeActivity :
val formattedSize = decimalFormat . format ( allowedSizeInMb )
getString ( R . string . error _multimedia _size _limit , formattedSize )
}
is VideoOrImageException -> getString ( R . string . error _media _upload _image _or _video )
is VideoOrImageException -> getString (
R . string . error _media _upload _image _or _video
)
else -> getString ( R . string . error _media _upload _opening )
}
displayTransientMessage ( errorString )
@ -1096,16 +1241,23 @@ class ComposeActivity :
}
private fun showContentWarning ( show : Boolean ) {
TransitionManager . beginDelayedTransition ( binding . composeContentWarningBar . parent as ViewGroup )
TransitionManager . beginDelayedTransition (
binding . composeContentWarningBar . parent as ViewGroup
)
@ColorInt val color = if ( show ) {
binding . composeContentWarningBar . show ( )
binding . composeContentWarningField . setSelection ( binding . composeContentWarningField . text . length )
binding . composeContentWarningField . setSelection (
binding . composeContentWarningField . text . length
)
binding . composeContentWarningField . requestFocus ( )
getColor ( R . color . tusky _blue )
} else {
binding . composeContentWarningBar . hide ( )
binding . composeEditField . requestFocus ( )
MaterialColors . getColor ( binding . composeContentWarningButton , android . R . attr . textColorTertiary )
MaterialColors . getColor (
binding . composeContentWarningButton ,
android . R . attr . textColorTertiary
)
}
binding . composeContentWarningButton . drawable . colorFilter = PorterDuffColorFilter ( color , PorterDuff . Mode . SRC _IN )
}
@ -1159,7 +1311,10 @@ class ComposeActivity :
/ * *
* User is editing a new post , and can either save the changes as a draft or discard them .
* /
private fun getSaveAsDraftOrDiscardDialog ( contentText : String , contentWarning : String ) : AlertDialog . Builder {
private fun getSaveAsDraftOrDiscardDialog (
contentText : String ,
contentWarning : String
) : AlertDialog . Builder {
val warning = if ( viewModel . media . value . isNotEmpty ( ) ) {
R . string . compose _save _draft _loses _media
} else {
@ -1182,7 +1337,10 @@ class ComposeActivity :
* User is editing an existing draft , and can either update the draft with the new changes or
* discard them .
* /
private fun getUpdateDraftOrDiscardDialog ( contentText : String , contentWarning : String ) : AlertDialog . Builder {
private fun getUpdateDraftOrDiscardDialog (
contentText : String ,
contentWarning : String
) : AlertDialog . Builder {
val warning = if ( viewModel . media . value . isNotEmpty ( ) ) {
R . string . compose _save _draft _loses _media
} else {
@ -1286,10 +1444,15 @@ class ComposeActivity :
val state : State
) {
enum class Type {
IMAGE , VIDEO , AUDIO ;
IMAGE ,
VIDEO ,
AUDIO
}
enum class State {
UPLOADING , UNPROCESSED , PROCESSED , PUBLISHED
UPLOADING ,
UNPROCESSED ,
PROCESSED ,
PUBLISHED
}
}
@ -1370,10 +1533,7 @@ class ComposeActivity :
* @return an Intent to start the ComposeActivity
* /
@JvmStatic
fun startIntent (
context : Context ,
options : ComposeOptions
) : Intent {
fun startIntent ( context : Context , options : ComposeOptions ) : Intent {
return Intent ( context , ComposeActivity :: class . java ) . apply {
putExtra ( COMPOSE _OPTIONS _EXTRA , options )
}