From 2cc53d67725b935db084f2c88ec40cb63218f610 Mon Sep 17 00:00:00 2001 From: Konrad Pozniak Date: Mon, 28 Jun 2021 22:04:34 +0200 Subject: [PATCH] fix codestyle --- .../com/keylesspalace/tusky/MainActivity.kt | 328 ++++++++++-------- .../components/compose/ComposeViewModel.kt | 256 ++++++++------ .../tusky/components/drafts/DraftHelper.kt | 129 ++++--- .../tusky/components/drafts/DraftsAdapter.kt | 18 +- .../scheduled/ScheduledTootActivity.kt | 25 +- .../scheduled/ScheduledTootAdapter.kt | 21 +- .../scheduled/ScheduledTootPagingSource.kt | 4 +- 7 files changed, 423 insertions(+), 358 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt index bb0ac6589..39239139a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.kt @@ -48,7 +48,12 @@ import com.bumptech.glide.request.transition.Transition import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener import com.google.android.material.tabs.TabLayoutMediator -import com.keylesspalace.tusky.appstore.* +import com.keylesspalace.tusky.appstore.AnnouncementReadEvent +import com.keylesspalace.tusky.appstore.CacheUpdater +import com.keylesspalace.tusky.appstore.Event +import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.appstore.MainTabsChangedEvent +import com.keylesspalace.tusky.appstore.ProfileEditedEvent import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeActivity.Companion.canHandleMimeType @@ -67,7 +72,14 @@ import com.keylesspalace.tusky.interfaces.ActionButtonActivity import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.pager.MainPagerAdapter import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.ThemeUtils +import com.keylesspalace.tusky.util.deleteStaleCachedMedia +import com.keylesspalace.tusky.util.emojify +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.removeShortcut +import com.keylesspalace.tusky.util.updateShortcut +import com.keylesspalace.tusky.util.viewBinding +import com.keylesspalace.tusky.util.visible import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.utils.colorInt @@ -76,9 +88,24 @@ import com.mikepenz.materialdrawer.holder.BadgeStyle import com.mikepenz.materialdrawer.holder.ColorHolder import com.mikepenz.materialdrawer.holder.StringHolder import com.mikepenz.materialdrawer.iconics.iconicsIcon -import com.mikepenz.materialdrawer.model.* -import com.mikepenz.materialdrawer.model.interfaces.* -import com.mikepenz.materialdrawer.util.* +import com.mikepenz.materialdrawer.model.AbstractDrawerItem +import com.mikepenz.materialdrawer.model.DividerDrawerItem +import com.mikepenz.materialdrawer.model.PrimaryDrawerItem +import com.mikepenz.materialdrawer.model.ProfileDrawerItem +import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem +import com.mikepenz.materialdrawer.model.SecondaryDrawerItem +import com.mikepenz.materialdrawer.model.interfaces.IProfile +import com.mikepenz.materialdrawer.model.interfaces.descriptionRes +import com.mikepenz.materialdrawer.model.interfaces.descriptionText +import com.mikepenz.materialdrawer.model.interfaces.iconRes +import com.mikepenz.materialdrawer.model.interfaces.iconUrl +import com.mikepenz.materialdrawer.model.interfaces.nameRes +import com.mikepenz.materialdrawer.model.interfaces.nameText +import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader +import com.mikepenz.materialdrawer.util.DrawerImageLoader +import com.mikepenz.materialdrawer.util.addItems +import com.mikepenz.materialdrawer.util.addItemsAtPosition +import com.mikepenz.materialdrawer.util.updateBadge import com.mikepenz.materialdrawer.widget.AccountHeaderView import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector @@ -156,19 +183,22 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje forwardShare(intent) } else { // No account was provided, show the chooser - showAccountChooserDialog(getString(R.string.action_share_as), true, object : AccountSelectionListener { - override fun onAccountSelected(account: AccountEntity) { - val requestedId = account.id - if (requestedId == activeAccount.id) { - // The correct account is already active - forwardShare(intent) - } else { - // A different account was requested, restart the activity - intent.putExtra(NotificationHelper.ACCOUNT_ID, requestedId) - changeAccount(requestedId, intent) + showAccountChooserDialog( + getString(R.string.action_share_as), true, + object : AccountSelectionListener { + override fun onAccountSelected(account: AccountEntity) { + val requestedId = account.id + if (requestedId == activeAccount.id) { + // The correct account is already active + forwardShare(intent) + } else { + // A different account was requested, restart the activity + intent.putExtra(NotificationHelper.ACCOUNT_ID, requestedId) + changeAccount(requestedId, intent) + } } } - }) + ) } } else if (accountRequested && savedInstanceState == null) { // user clicked a notification, show notification tab @@ -323,12 +353,15 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje headerBackgroundScaleType = ImageView.ScaleType.CENTER_CROP currentHiddenInList = true onAccountHeaderListener = { _: View?, profile: IProfile, current: Boolean -> handleProfileClick(profile, current) } - addProfile(ProfileSettingDrawerItem().apply { - identifier = DRAWER_ITEM_ADD_ACCOUNT - nameRes = R.string.add_account_name - descriptionRes = R.string.add_account_description - iconicsIcon = GoogleMaterial.Icon.gmd_add - }, 0) + addProfile( + ProfileSettingDrawerItem().apply { + identifier = DRAWER_ITEM_ADD_ACCOUNT + nameRes = R.string.add_account_name + descriptionRes = R.string.add_account_description + iconicsIcon = GoogleMaterial.Icon.gmd_add + }, + 0 + ) attachToSliderView(binding.mainDrawer) dividerBelowHeader = false closeDrawerOnProfileListClick = true @@ -468,14 +501,16 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje ) if (addSearchButton) { - binding.mainDrawer.addItemsAtPosition(4, + binding.mainDrawer.addItemsAtPosition( + 4, primaryDrawerItem { nameRes = R.string.action_search iconicsIcon = GoogleMaterial.Icon.gmd_search onClick = { startActivityWithSlideInAnimation(SearchActivity.getIntent(context)) } - }) + } + ) } setSavedInstance(savedInstanceState) @@ -572,24 +607,23 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje binding.mainToolbar.setOnClickListener { (adapter.getFragment(activeTabLayout.selectedTabPosition) as? ReselectableFragment)?.onReselect() } - } private fun handleProfileClick(profile: IProfile, current: Boolean): Boolean { val activeAccount = accountManager.activeAccount - //open profile when active image was clicked + // open profile when active image was clicked if (current && activeAccount != null) { val intent = AccountActivity.getIntent(this, activeAccount.accountId) startActivityWithSlideInAnimation(intent) return false } - //open LoginActivity to add new account + // open LoginActivity to add new account if (profile.identifier == DRAWER_ITEM_ADD_ACCOUNT) { startActivityWithSlideInAnimation(LoginActivity.getIntent(this, true)) return false } - //change Account + // change Account changeAccount(profile.identifier, null) return false } @@ -638,130 +672,130 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje finishWithoutSlideOutAnimation() } } - .setNegativeButton(android.R.string.cancel, null) - .show() - } -} - -private fun fetchUserInfo() { - mastodonApi.accountVerifyCredentials() - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(this, Lifecycle.Event.ON_DESTROY) - .subscribe( - { userInfo -> - onFetchUserInfoSuccess(userInfo) - }, - { throwable -> - Log.e(TAG, "Failed to fetch user info. " + throwable.message) - } - ) -} - -private fun onFetchUserInfoSuccess(me: Account) { - glide.asBitmap() - .load(me.header) - .into(header.accountHeaderBackground) - - loadDrawerAvatar(me.avatar, false) - - accountManager.updateActiveAccount(me) - NotificationHelper.createNotificationChannelsForAccount(accountManager.activeAccount!!, this) - - accountLocked = me.locked - - updateProfiles() - updateShortcut(this, accountManager.activeAccount!!) -} - -private fun loadDrawerAvatar(avatarUrl: String, showPlaceholder: Boolean) { - val navIconSize = resources.getDimensionPixelSize(R.dimen.avatar_toolbar_nav_icon_size) - - glide.asDrawable() - .load(avatarUrl) - .transform( - RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp)) - ) - .apply { - if (showPlaceholder) { - placeholder(R.drawable.avatar_default) - } - } - .into(object : CustomTarget(navIconSize, navIconSize) { - - override fun onLoadStarted(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } - } - - override fun onResourceReady(resource: Drawable, transition: Transition?) { - binding.mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize) - } - - override fun onLoadCleared(placeholder: Drawable?) { - if (placeholder != null) { - binding.mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) - } - } - }) -} - -private fun fetchAnnouncements() { - mastodonApi.listAnnouncements(false) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(this, Lifecycle.Event.ON_DESTROY) - .subscribe( - { announcements -> - unreadAnnouncementsCount = announcements.count { !it.read } - updateAnnouncementsBadge() - }, - { - Log.w(TAG, "Failed to fetch announcements.", it) - } - ) -} - -private fun updateAnnouncementsBadge() { - binding.mainDrawer.updateBadge(DRAWER_ITEM_ANNOUNCEMENTS, StringHolder(if (unreadAnnouncementsCount <= 0) null else unreadAnnouncementsCount.toString())) -} - -private fun updateProfiles() { - val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) - val profiles: MutableList = accountManager.getAllAccountsOrderedByActive().map { acc -> - val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header, animateEmojis)) - - ProfileDrawerItem().apply { - isSelected = acc.isActive - nameText = emojifiedName - iconUrl = acc.profilePictureUrl - isNameShown = true - identifier = acc.id - descriptionText = acc.fullName - } - }.toMutableList() - - // reuse the already existing "add account" item - for (profile in header.profiles.orEmpty()) { - if (profile.identifier == DRAWER_ITEM_ADD_ACCOUNT) { - profiles.add(profile) - break + .setNegativeButton(android.R.string.cancel, null) + .show() } } - header.clear() - header.profiles = profiles - header.setActiveProfile(accountManager.activeAccount!!.id) -} -override fun getActionButton() = binding.composeButton + private fun fetchUserInfo() { + mastodonApi.accountVerifyCredentials() + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this, Lifecycle.Event.ON_DESTROY) + .subscribe( + { userInfo -> + onFetchUserInfoSuccess(userInfo) + }, + { throwable -> + Log.e(TAG, "Failed to fetch user info. " + throwable.message) + } + ) + } -override fun androidInjector() = androidInjector + private fun onFetchUserInfoSuccess(me: Account) { + glide.asBitmap() + .load(me.header) + .into(header.accountHeaderBackground) -companion object { - private const val TAG = "MainActivity" // logging tag - private const val DRAWER_ITEM_ADD_ACCOUNT: Long = -13 - private const val DRAWER_ITEM_ANNOUNCEMENTS: Long = 14 - const val STATUS_URL = "statusUrl" -} + loadDrawerAvatar(me.avatar, false) + + accountManager.updateActiveAccount(me) + NotificationHelper.createNotificationChannelsForAccount(accountManager.activeAccount!!, this) + + accountLocked = me.locked + + updateProfiles() + updateShortcut(this, accountManager.activeAccount!!) + } + + private fun loadDrawerAvatar(avatarUrl: String, showPlaceholder: Boolean) { + val navIconSize = resources.getDimensionPixelSize(R.dimen.avatar_toolbar_nav_icon_size) + + glide.asDrawable() + .load(avatarUrl) + .transform( + RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_36dp)) + ) + .apply { + if (showPlaceholder) { + placeholder(R.drawable.avatar_default) + } + } + .into(object : CustomTarget(navIconSize, navIconSize) { + + override fun onLoadStarted(placeholder: Drawable?) { + if (placeholder != null) { + binding.mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) + } + } + + override fun onResourceReady(resource: Drawable, transition: Transition?) { + binding.mainToolbar.navigationIcon = FixedSizeDrawable(resource, navIconSize, navIconSize) + } + + override fun onLoadCleared(placeholder: Drawable?) { + if (placeholder != null) { + binding.mainToolbar.navigationIcon = FixedSizeDrawable(placeholder, navIconSize, navIconSize) + } + } + }) + } + + private fun fetchAnnouncements() { + mastodonApi.listAnnouncements(false) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this, Lifecycle.Event.ON_DESTROY) + .subscribe( + { announcements -> + unreadAnnouncementsCount = announcements.count { !it.read } + updateAnnouncementsBadge() + }, + { + Log.w(TAG, "Failed to fetch announcements.", it) + } + ) + } + + private fun updateAnnouncementsBadge() { + binding.mainDrawer.updateBadge(DRAWER_ITEM_ANNOUNCEMENTS, StringHolder(if (unreadAnnouncementsCount <= 0) null else unreadAnnouncementsCount.toString())) + } + + private fun updateProfiles() { + val animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + val profiles: MutableList = accountManager.getAllAccountsOrderedByActive().map { acc -> + val emojifiedName = EmojiCompat.get().process(acc.displayName.emojify(acc.emojis, header, animateEmojis)) + + ProfileDrawerItem().apply { + isSelected = acc.isActive + nameText = emojifiedName + iconUrl = acc.profilePictureUrl + isNameShown = true + identifier = acc.id + descriptionText = acc.fullName + } + }.toMutableList() + + // reuse the already existing "add account" item + for (profile in header.profiles.orEmpty()) { + if (profile.identifier == DRAWER_ITEM_ADD_ACCOUNT) { + profiles.add(profile) + break + } + } + header.clear() + header.profiles = profiles + header.setActiveProfile(accountManager.activeAccount!!.id) + } + + override fun getActionButton() = binding.composeButton + + override fun androidInjector() = androidInjector + + companion object { + private const val TAG = "MainActivity" // logging tag + private const val DRAWER_ITEM_ADD_ACCOUNT: Long = -13 + private const val DRAWER_ITEM_ANNOUNCEMENTS: Long = 14 + const val STATUS_URL = "statusUrl" + } } private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index 7fffa68d2..f7969ac08 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -28,26 +28,36 @@ import com.keylesspalace.tusky.components.search.SearchType import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.db.InstanceEntity -import com.keylesspalace.tusky.entity.* +import com.keylesspalace.tusky.entity.Attachment +import com.keylesspalace.tusky.entity.Emoji +import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.service.ServiceClient import com.keylesspalace.tusky.service.TootToSend -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.Either +import com.keylesspalace.tusky.util.RxAwareViewModel +import com.keylesspalace.tusky.util.VersionUtils +import com.keylesspalace.tusky.util.combineLiveData +import com.keylesspalace.tusky.util.filter +import com.keylesspalace.tusky.util.map +import com.keylesspalace.tusky.util.randomAlphanumericString +import com.keylesspalace.tusky.util.toLiveData +import com.keylesspalace.tusky.util.withoutFirstWhich import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.Disposable import kotlinx.coroutines.launch -import java.util.* +import java.util.Locale import javax.inject.Inject class ComposeViewModel @Inject constructor( - private val api: MastodonApi, - private val accountManager: AccountManager, - private val mediaUploader: MediaUploader, - private val serviceClient: ServiceClient, - private val draftHelper: DraftHelper, - private val db: AppDatabase + private val api: MastodonApi, + private val accountManager: AccountManager, + private val mediaUploader: MediaUploader, + private val serviceClient: ServiceClient, + private val draftHelper: DraftHelper, + private val db: AppDatabase ) : RxAwareViewModel() { private var replyingStatusAuthor: String? = null @@ -66,15 +76,15 @@ class ComposeViewModel @Inject constructor( val instanceParams: LiveData = instance.map { instance -> ComposeInstanceParams( - maxChars = instance?.maximumTootCharacters ?: DEFAULT_CHARACTER_LIMIT, - pollMaxOptions = instance?.maxPollOptions ?: DEFAULT_MAX_OPTION_COUNT, - pollMaxLength = instance?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH, - supportsScheduled = instance?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false + maxChars = instance?.maximumTootCharacters ?: DEFAULT_CHARACTER_LIMIT, + pollMaxOptions = instance?.maxPollOptions ?: DEFAULT_MAX_OPTION_COUNT, + pollMaxLength = instance?.maxPollOptionLength ?: DEFAULT_MAX_OPTION_LENGTH, + supportsScheduled = instance?.version?.let { VersionUtils(it).supportsScheduledToots() } ?: false ) } val emoji: MutableLiveData?> = MutableLiveData() val markMediaAsSensitive = - mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false) + mutableLiveData(accountManager.activeAccount?.defaultMediaSensitivity ?: false) val statusVisibility = mutableLiveData(Status.Visibility.UNKNOWN) val showContentWarning = mutableLiveData(false) @@ -91,30 +101,36 @@ class ComposeViewModel @Inject constructor( init { - Single.zip(api.getCustomEmojis(), api.getInstance(), { emojis, instance -> - InstanceEntity( + Single.zip( + api.getCustomEmojis(), api.getInstance(), + { emojis, instance -> + InstanceEntity( instance = accountManager.activeAccount?.domain!!, emojiList = emojis, maximumTootCharacters = instance.maxTootChars, maxPollOptions = instance.pollLimits?.maxOptions, maxPollOptionLength = instance.pollLimits?.maxOptionChars, version = instance.version - ) - }) - .doOnSuccess { - db.instanceDao().insertOrReplace(it) - } - .onErrorResumeNext { - db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!) - } - .subscribe({ instanceEntity -> + ) + } + ) + .doOnSuccess { + db.instanceDao().insertOrReplace(it) + } + .onErrorResumeNext { + db.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!) + } + .subscribe( + { instanceEntity -> emoji.postValue(instanceEntity.emojiList) instance.postValue(instanceEntity) - }, { throwable -> + }, + { throwable -> // this can happen on network error when no cached data is available Log.w(TAG, "error loading instance data", throwable) - }) - .autoDispose() + } + ) + .autoDispose() } fun pickMedia(uri: Uri, description: String? = null): LiveData> { @@ -122,44 +138,49 @@ class ComposeViewModel @Inject constructor( // the Activity goes away temporarily (like on screen rotation). val liveData = MutableLiveData>() mediaUploader.prepareMedia(uri) - .map { (type, uri, size) -> - val mediaItems = media.value!! - if (type != QueuedMedia.Type.IMAGE - && mediaItems.isNotEmpty() - && mediaItems[0].type == QueuedMedia.Type.IMAGE) { - throw VideoOrImageException() - } else { - addMediaToQueue(type, uri, size, description) - } + .map { (type, uri, size) -> + val mediaItems = media.value!! + if (type != QueuedMedia.Type.IMAGE && + mediaItems.isNotEmpty() && + mediaItems[0].type == QueuedMedia.Type.IMAGE + ) { + throw VideoOrImageException() + } else { + addMediaToQueue(type, uri, size, description) } - .subscribe({ queuedMedia -> + } + .subscribe( + { queuedMedia -> liveData.postValue(Either.Right(queuedMedia)) - }, { error -> + }, + { error -> liveData.postValue(Either.Left(error)) - }) - .autoDispose() + } + ) + .autoDispose() return liveData } private fun addMediaToQueue( - type: QueuedMedia.Type, - uri: Uri, - mediaSize: Long, - description: String? = null + type: QueuedMedia.Type, + uri: Uri, + mediaSize: Long, + description: String? = null ): QueuedMedia { val mediaItem = QueuedMedia( - localId = System.currentTimeMillis(), - uri = uri, - type = type, - mediaSize = mediaSize, - description = description + localId = System.currentTimeMillis(), + uri = uri, + type = type, + mediaSize = mediaSize, + description = description ) media.value = media.value!! + mediaItem mediaToDisposable[mediaItem.localId] = mediaUploader - .uploadMedia(mediaItem) - .subscribe({ event -> + .uploadMedia(mediaItem) + .subscribe( + { event -> val item = media.value?.find { it.localId == mediaItem.localId } - ?: return@subscribe + ?: return@subscribe val newMediaItem = when (event) { is UploadEvent.ProgressEvent -> item.copy(uploadPercent = event.percentage) @@ -169,16 +190,20 @@ class ComposeViewModel @Inject constructor( synchronized(media) { val mediaValue = media.value!! val index = mediaValue.indexOfFirst { it.localId == newMediaItem.localId } - media.postValue(if (index == -1) { - mediaValue + newMediaItem - } else { - mediaValue.toMutableList().also { it[index] = newMediaItem } - }) + media.postValue( + if (index == -1) { + mediaValue + newMediaItem + } else { + mediaValue.toMutableList().also { it[index] = newMediaItem } + } + ) } - }, { error -> + }, + { error -> media.postValue(media.value?.filter { it.localId != mediaItem.localId } ?: emptyList()) uploadError.postValue(error) - }) + } + ) return mediaItem } @@ -198,12 +223,14 @@ class ComposeViewModel @Inject constructor( fun didChange(content: String?, contentWarning: String?): Boolean { - val textChanged = !(content.isNullOrEmpty() - || startingText?.startsWith(content.toString()) ?: false) + val textChanged = !( + content.isNullOrEmpty() || + startingText?.startsWith(content.toString()) ?: false + ) - val contentWarningChanged = showContentWarning.value!! - && !contentWarning.isNullOrEmpty() - && !startingContentWarning.startsWith(contentWarning.toString()) + val contentWarningChanged = showContentWarning.value!! && + !contentWarning.isNullOrEmpty() && + !startingContentWarning.startsWith(contentWarning.toString()) val mediaChanged = !media.value.isNullOrEmpty() val pollChanged = poll.value != null @@ -254,8 +281,8 @@ class ComposeViewModel @Inject constructor( * @return LiveData which will signal once the screen can be closed or null if there are errors */ fun sendStatus( - content: String, - spoilerText: String + content: String, + spoilerText: String ): LiveData { val deletionObservable = if (isEditingScheduledToot) { @@ -265,39 +292,39 @@ class ComposeViewModel @Inject constructor( }.toLiveData() val sendObservable = media - .filter { items -> items.all { it.uploadPercent == -1 } } - .map { - val mediaIds = ArrayList() - val mediaUris = ArrayList() - val mediaDescriptions = ArrayList() - for (item in media.value!!) { - mediaIds.add(item.id!!) - mediaUris.add(item.uri) - mediaDescriptions.add(item.description ?: "") - } - - val tootToSend = TootToSend( - text = content, - warningText = spoilerText, - visibility = statusVisibility.value!!.serverString(), - sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!), - mediaIds = mediaIds, - mediaUris = mediaUris.map { it.toString() }, - mediaDescriptions = mediaDescriptions, - scheduledAt = scheduledAt.value, - inReplyToId = inReplyToId, - poll = poll.value, - replyingStatusContent = null, - replyingStatusAuthorUsername = null, - accountId = accountManager.activeAccount!!.id, - draftId = draftId, - idempotencyKey = randomAlphanumericString(16), - retries = 0 - ) - - serviceClient.sendToot(tootToSend) + .filter { items -> items.all { it.uploadPercent == -1 } } + .map { + val mediaIds = ArrayList() + val mediaUris = ArrayList() + val mediaDescriptions = ArrayList() + for (item in media.value!!) { + mediaIds.add(item.id!!) + mediaUris.add(item.uri) + mediaDescriptions.add(item.description ?: "") } + val tootToSend = TootToSend( + text = content, + warningText = spoilerText, + visibility = statusVisibility.value!!.serverString(), + sensitive = mediaUris.isNotEmpty() && (markMediaAsSensitive.value!! || showContentWarning.value!!), + mediaIds = mediaIds, + mediaUris = mediaUris.map { it.toString() }, + mediaDescriptions = mediaDescriptions, + scheduledAt = scheduledAt.value, + inReplyToId = inReplyToId, + poll = poll.value, + replyingStatusContent = null, + replyingStatusAuthorUsername = null, + accountId = accountManager.activeAccount!!.id, + draftId = draftId, + idempotencyKey = randomAlphanumericString(16), + retries = 0 + ) + + serviceClient.sendToot(tootToSend) + } + return combineLiveData(deletionObservable, sendObservable) { _, _ -> } } @@ -316,12 +343,15 @@ class ComposeViewModel @Inject constructor( media.removeObserver(this) } else if (updatedItem.id != null) { api.updateMedia(updatedItem.id, description) - .subscribe({ + .subscribe( + { completedCaptioningLiveData.postValue(true) - }, { + }, + { completedCaptioningLiveData.postValue(false) - }) - .autoDispose() + } + ) + .autoDispose() media.removeObserver(this) } } @@ -334,8 +364,8 @@ class ComposeViewModel @Inject constructor( '@' -> { return try { api.searchAccounts(query = token.substring(1), limit = 10) - .blockingGet() - .map { ComposeAutoCompleteAdapter.AccountResult(it) } + .blockingGet() + .map { ComposeAutoCompleteAdapter.AccountResult(it) } } catch (e: Throwable) { Log.e(TAG, String.format("Autocomplete search for %s failed.", token), e) emptyList() @@ -344,9 +374,9 @@ class ComposeViewModel @Inject constructor( '#' -> { return try { api.searchObservable(query = token, type = SearchType.Hashtag.apiParameter, limit = 10) - .blockingGet() - .hashtags - .map { ComposeAutoCompleteAdapter.HashtagResult(it) } + .blockingGet() + .hashtags + .map { ComposeAutoCompleteAdapter.HashtagResult(it) } } catch (e: Throwable) { Log.e(TAG, String.format("Autocomplete search for %s failed.", token), e) emptyList() @@ -389,7 +419,8 @@ class ComposeViewModel @Inject constructor( val replyVisibility = composeOptions?.replyVisibility ?: Status.Visibility.UNKNOWN startingVisibility = Status.Visibility.byNum( - preferredVisibility.num.coerceAtLeast(replyVisibility.num)) + preferredVisibility.num.coerceAtLeast(replyVisibility.num) + ) inReplyToId = composeOptions?.inReplyToId @@ -468,7 +499,6 @@ class ComposeViewModel @Inject constructor( private companion object { const val TAG = "ComposeViewModel" } - } fun mutableLiveData(default: T) = MutableLiveData().apply { value = default } @@ -478,10 +508,10 @@ private const val DEFAULT_MAX_OPTION_COUNT = 4 private const val DEFAULT_MAX_OPTION_LENGTH = 25 data class ComposeInstanceParams( - val maxChars: Int, - val pollMaxOptions: Int, - val pollMaxLength: Int, - val supportsScheduled: Boolean + val maxChars: Int, + val pollMaxOptions: Int, + val pollMaxLength: Int, + val supportsScheduled: Boolean ) /** diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt index 82a7dae50..7511dc3c3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftHelper.kt @@ -37,83 +37,83 @@ import java.util.Locale import javax.inject.Inject class DraftHelper @Inject constructor( - val context: Context, - db: AppDatabase + val context: Context, + db: AppDatabase ) { private val draftDao = db.draftDao() suspend fun saveDraft( - draftId: Int, - accountId: Long, - inReplyToId: String?, - content: String?, - contentWarning: String?, - sensitive: Boolean, - visibility: Status.Visibility, - mediaUris: List, - mediaDescriptions: List, - poll: NewPoll?, - failedToSend: Boolean + draftId: Int, + accountId: Long, + inReplyToId: String?, + content: String?, + contentWarning: String?, + sensitive: Boolean, + visibility: Status.Visibility, + mediaUris: List, + mediaDescriptions: List, + poll: NewPoll?, + failedToSend: Boolean ) = withContext(Dispatchers.IO) { - val externalFilesDir = context.getExternalFilesDir("Tusky") + val externalFilesDir = context.getExternalFilesDir("Tusky") - if (externalFilesDir == null || !(externalFilesDir.exists())) { - Log.e("DraftHelper", "Error obtaining directory to save media.") - throw Exception() + if (externalFilesDir == null || !(externalFilesDir.exists())) { + Log.e("DraftHelper", "Error obtaining directory to save media.") + throw Exception() + } + + val draftDirectory = File(externalFilesDir, "Drafts") + + if (!draftDirectory.exists()) { + draftDirectory.mkdir() + } + + val uris = mediaUris.map { uriString -> + uriString.toUri() + }.map { uri -> + if (uri.isNotInFolder(draftDirectory)) { + uri.copyToFolder(draftDirectory) + } else { + uri } + } - val draftDirectory = File(externalFilesDir, "Drafts") - - if (!draftDirectory.exists()) { - draftDirectory.mkdir() + val types = uris.map { uri -> + val mimeType = context.contentResolver.getType(uri) + when (mimeType?.substring(0, mimeType.indexOf('/'))) { + "video" -> DraftAttachment.Type.VIDEO + "image" -> DraftAttachment.Type.IMAGE + "audio" -> DraftAttachment.Type.AUDIO + else -> throw IllegalStateException("unknown media type") } + } - val uris = mediaUris.map { uriString -> - uriString.toUri() - }.map { uri -> - if (uri.isNotInFolder(draftDirectory)) { - uri.copyToFolder(draftDirectory) - } else { - uri - } - } - - val types = uris.map { uri -> - val mimeType = context.contentResolver.getType(uri) - when (mimeType?.substring(0, mimeType.indexOf('/'))) { - "video" -> DraftAttachment.Type.VIDEO - "image" -> DraftAttachment.Type.IMAGE - "audio" -> DraftAttachment.Type.AUDIO - else -> throw IllegalStateException("unknown media type") - } - } - - val attachments: MutableList = mutableListOf() - for (i in mediaUris.indices) { - attachments.add( - DraftAttachment( - uriString = uris[i].toString(), - description = mediaDescriptions[i], - type = types[i] - ) + val attachments: MutableList = mutableListOf() + for (i in mediaUris.indices) { + attachments.add( + DraftAttachment( + uriString = uris[i].toString(), + description = mediaDescriptions[i], + type = types[i] ) - } - - val draft = DraftEntity( - id = draftId, - accountId = accountId, - inReplyToId = inReplyToId, - content = content, - contentWarning = contentWarning, - sensitive = sensitive, - visibility = visibility, - attachments = attachments, - poll = poll, - failedToSend = failedToSend ) + } - draftDao.insertOrReplace(draft) + val draft = DraftEntity( + id = draftId, + accountId = accountId, + inReplyToId = inReplyToId, + content = content, + contentWarning = contentWarning, + sensitive = sensitive, + visibility = visibility, + attachments = attachments, + poll = poll, + failedToSend = failedToSend + ) + + draftDao.insertOrReplace(draft) } suspend fun deleteDraftAndAttachments(draftId: Int) { @@ -162,5 +162,4 @@ class DraftHelper @Inject constructor( IOUtils.copyToFile(contentResolver, this, file) return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", file) } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt index 42c93b0b0..18621fd3f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsAdapter.kt @@ -34,17 +34,17 @@ interface DraftActionListener { } class DraftsAdapter( - private val listener: DraftActionListener + private val listener: DraftActionListener ) : PagingDataAdapter>( - object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { - return oldItem == newItem - } + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { + return oldItem.id == newItem.id } + + override fun areContentsTheSame(oldItem: DraftEntity, newItem: DraftEntity): Boolean { + return oldItem == newItem + } + } ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt index 0df191f81..06a4dee00 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootActivity.kt @@ -93,7 +93,7 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec } if (loadState.refresh is LoadState.NotLoading) { binding.progressBar.hide() - if(adapter.itemCount == 0) { + if (adapter.itemCount == 0) { binding.errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.no_scheduled_status) binding.errorMessageView.show() } else { @@ -117,16 +117,19 @@ class ScheduledTootActivity : BaseActivity(), ScheduledTootActionListener, Injec } override fun edit(item: ScheduledStatus) { - val intent = ComposeActivity.startIntent(this, ComposeActivity.ComposeOptions( - scheduledTootId = item.id, - tootText = item.params.text, - contentWarning = item.params.spoilerText, - mediaAttachments = item.mediaAttachments, - inReplyToId = item.params.inReplyToId, - visibility = item.params.visibility, - scheduledAt = item.scheduledAt, - sensitive = item.params.sensitive - )) + val intent = ComposeActivity.startIntent( + this, + ComposeActivity.ComposeOptions( + scheduledTootId = item.id, + tootText = item.params.text, + contentWarning = item.params.spoilerText, + mediaAttachments = item.mediaAttachments, + inReplyToId = item.params.inReplyToId, + visibility = item.params.visibility, + scheduledAt = item.scheduledAt, + sensitive = item.params.sensitive + ) + ) startActivity(intent) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootAdapter.kt index e21019ee7..75b83e5d2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootAdapter.kt @@ -30,18 +30,17 @@ interface ScheduledTootActionListener { } class ScheduledTootAdapter( - val listener: ScheduledTootActionListener + val listener: ScheduledTootActionListener ) : PagingDataAdapter>( - object: DiffUtil.ItemCallback(){ - override fun areItemsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean { - return oldItem == newItem - } - + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean { + return oldItem.id == newItem.id } + + override fun areContentsTheSame(oldItem: ScheduledStatus, newItem: ScheduledStatus): Boolean { + return oldItem == newItem + } + } ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { @@ -50,7 +49,7 @@ class ScheduledTootAdapter( } override fun onBindViewHolder(holder: BindingHolder, position: Int) { - getItem(position)?.let{ item -> + getItem(position)?.let { item -> holder.binding.edit.isEnabled = true holder.binding.delete.isEnabled = true holder.binding.text.text = item.params.text diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootPagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootPagingSource.kt index 0578c5b12..c4994cef6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootPagingSource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootPagingSource.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.rx3.await class ScheduledTootPagingSourceFactory( private val mastodonApi: MastodonApi -): () -> ScheduledTootPagingSource { +) : () -> ScheduledTootPagingSource { private val scheduledTootsCache = mutableListOf() @@ -45,7 +45,7 @@ class ScheduledTootPagingSourceFactory( class ScheduledTootPagingSource( private val mastodonApi: MastodonApi, private val scheduledTootsCache: MutableList -): PagingSource() { +) : PagingSource() { override fun getRefreshKey(state: PagingState): String? { return null