diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0e4f81ab2..428f50eab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,17 +11,23 @@ All English text that will be visible to users should be put in ```app/src/main/res/values/strings.xml```. Any text that is missing in a translation will fall back to the version in this file. Be aware that anything added to this file will need to be translated, so be very concise with wording and try to add as few things as possible. Look for existing strings to use first. If there is untranslatable text that you don't want to keep as a string constant in a Java class, you can use the string resource file ```app/src/main/res/values/donottranslate.xml```. ### Translation -Translations are done through https://weblate.tusky.app/projects/tusky/tusky/ . -To add a new language, clic on the 'Start a new translation' button on at the bottom of the page. +Translations are done through our [Weblate](https://weblate.tusky.app/projects/tusky/tusky/). +To add a new language, click on the 'Start a new translation' button on at the bottom of the page. ### Kotlin -This project is in the process of migrating to Kotlin, we prefer new code to be written in Kotlin. We try to follow the [Kotlin Style Guide](https://android.github.io/kotlin-guides/style.html) and make use of the [Kotlin Android Extensions](https://kotlinlang.org/docs/tutorials/android-plugin.html). +This project is in the process of migrating to Kotlin, all new code must be written in Kotlin. +We try to follow the [Kotlin Style Guide](https://developer.android.com/kotlin/style-guide) and make format the code according to the default [ktlint codestyle](https://github.com/pinterest/ktlint). +You can check the codestyle by running `./gradlew ktlintCheck`. ### Java -Existing code in Java should follow the [Android Style Guide](https://source.android.com/source/code-style), which is what Android uses for their own source code. ```@Nullable``` and ```@NotNull``` annotations are really helpful for Kotlin interoperability. +Existing code in Java should follow the [Android Style Guide](https://source.android.com/source/code-style), which is what Android uses for their own source code. ```@Nullable``` and ```@NotNull``` annotations are really helpful for Kotlin interoperability. Please don't submit new features written in Kotlin. + +### Viewbinding +We use [Viewbinding](https://developer.android.com/topic/libraries/view-binding) to reference views. No contribution using another mechanism will be accepted. +There are useful extensions in `src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt` that make working with viewbinding easier. ### Visuals -There are three themes in the app, so any visual changes should be checked with each of them to ensure they look appropriate no matter which theme is selected. Usually, you can use existing color attributes like ```?attr/colorPrimary``` and ```?attr/textColorSecondary```. For icons and drawables, use a white drawable and tint it at runtime using ```ThemeUtils``` and specify an attribute that references different colours depending on the theme. +There are three themes in the app, so any visual changes should be checked with each of them to ensure they look appropriate no matter which theme is selected. Usually, you can use existing color attributes like ```?attr/colorPrimary``` and ```?attr/textColorSecondary```. ### Saving Any time you get a good chunk of work done it's good to make a commit. You can either uses Android Studio's built-in UI for doing this or running the commands: diff --git a/app/src/androidTest/java/com/keylesspalace/tusky/MigrationsTest.kt b/app/src/androidTest/java/com/keylesspalace/tusky/MigrationsTest.kt index 9c65aebf3..69641cc41 100644 --- a/app/src/androidTest/java/com/keylesspalace/tusky/MigrationsTest.kt +++ b/app/src/androidTest/java/com/keylesspalace/tusky/MigrationsTest.kt @@ -2,8 +2,8 @@ package com.keylesspalace.tusky import androidx.room.testing.MigrationTestHelper import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import com.keylesspalace.tusky.db.AppDatabase import org.junit.Assert.assertEquals import org.junit.Rule @@ -18,9 +18,9 @@ class MigrationsTest { @JvmField @Rule var helper: MigrationTestHelper = MigrationTestHelper( - InstrumentationRegistry.getInstrumentation(), - AppDatabase::class.java.canonicalName, - FrameworkSQLiteOpenHelperFactory() + InstrumentationRegistry.getInstrumentation(), + AppDatabase::class.java.canonicalName, + FrameworkSQLiteOpenHelperFactory() ) @Test @@ -33,12 +33,15 @@ class MigrationsTest { val active = true val accountId = "accountId" val username = "username" - val values = arrayOf(id, domain, token, active, accountId, username, "Display Name", - "https://picture.url", true, true, true, true, true, true, true, - true, "1000", "[]", "[{\"shortcode\": \"emoji\", \"url\": \"yes\"}]", 0, false, - false, true) + val values = arrayOf( + id, domain, token, active, accountId, username, "Display Name", + "https://picture.url", true, true, true, true, true, true, true, + true, "1000", "[]", "[{\"shortcode\": \"emoji\", \"url\": \"yes\"}]", 0, false, + false, true + ) - db.execSQL("INSERT OR REPLACE INTO `AccountEntity`(`id`,`domain`,`accessToken`,`isActive`," + + db.execSQL( + "INSERT OR REPLACE INTO `AccountEntity`(`id`,`domain`,`accessToken`,`isActive`," + "`accountId`,`username`,`displayName`,`profilePictureUrl`,`notificationsEnabled`," + "`notificationsMentioned`,`notificationsFollowed`,`notificationsReblogged`," + "`notificationsFavorited`,`notificationSound`,`notificationVibration`," + @@ -46,7 +49,8 @@ class MigrationsTest { "`defaultPostPrivacy`,`defaultMediaSensitivity`,`alwaysShowSensitiveMedia`," + "`mediaPreviewEnabled`) " + "VALUES (nullif(?, 0),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", - values) + values + ) db.close() @@ -61,4 +65,4 @@ class MigrationsTest { assertEquals(accountId, cursor.getString(4)) assertEquals(username, cursor.getString(5)) } -} \ No newline at end of file +} diff --git a/app/src/androidTest/java/com/keylesspalace/tusky/TimelineDAOTest.kt b/app/src/androidTest/java/com/keylesspalace/tusky/TimelineDAOTest.kt index 92288bba2..c4959b3ab 100644 --- a/app/src/androidTest/java/com/keylesspalace/tusky/TimelineDAOTest.kt +++ b/app/src/androidTest/java/com/keylesspalace/tusky/TimelineDAOTest.kt @@ -3,9 +3,13 @@ package com.keylesspalace.tusky import androidx.room.Room import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import com.keylesspalace.tusky.db.* -import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.components.timeline.TimelineRepository +import com.keylesspalace.tusky.db.AppDatabase +import com.keylesspalace.tusky.db.TimelineAccountEntity +import com.keylesspalace.tusky.db.TimelineDao +import com.keylesspalace.tusky.db.TimelineStatusEntity +import com.keylesspalace.tusky.db.TimelineStatusWithAccount +import com.keylesspalace.tusky.entity.Status import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNull @@ -41,9 +45,11 @@ class TimelineDAOTest { timelineDao.insertInTransaction(status, author, reblogger) } - val resultsFromDb = timelineDao.getStatusesForAccount(setOne.first.timelineUserId, - maxId = "21", sinceId = ignoredOne.first.serverId, limit = 10) - .blockingGet() + val resultsFromDb = timelineDao.getStatusesForAccount( + setOne.first.timelineUserId, + maxId = "21", sinceId = ignoredOne.first.serverId, limit = 10 + ) + .blockingGet() assertEquals(2, resultsFromDb.size) for ((set, fromDb) in listOf(setTwo, setOne).zip(resultsFromDb)) { @@ -64,14 +70,13 @@ class TimelineDAOTest { timelineDao.insertStatusIfNotThere(placeholder) val fromDb = timelineDao.getStatusesForAccount(status.timelineUserId, null, null, 10) - .blockingGet() + .blockingGet() val result = fromDb.first() assertEquals(1, fromDb.size) assertEquals(author, result.account) assertEquals(status, result.status) assertNull(result.reblogAccount) - } @Test @@ -79,22 +84,22 @@ class TimelineDAOTest { val now = System.currentTimeMillis() val oldDate = now - TimelineRepository.CLEANUP_INTERVAL - 20_000 val oldThisAccount = makeStatus( - statusId = 5, - createdAt = oldDate + statusId = 5, + createdAt = oldDate ) val oldAnotherAccount = makeStatus( - statusId = 10, - createdAt = oldDate, - accountId = 2 + statusId = 10, + createdAt = oldDate, + accountId = 2 ) val recentThisAccount = makeStatus( - statusId = 30, - createdAt = System.currentTimeMillis() + statusId = 30, + createdAt = System.currentTimeMillis() ) val recentAnotherAccount = makeStatus( - statusId = 60, - createdAt = System.currentTimeMillis(), - accountId = 2 + statusId = 60, + createdAt = System.currentTimeMillis(), + accountId = 2 ) for ((status, author, reblogAuthor) in listOf(oldThisAccount, oldAnotherAccount, recentThisAccount, recentAnotherAccount)) { @@ -104,15 +109,15 @@ class TimelineDAOTest { timelineDao.cleanup(now - TimelineRepository.CLEANUP_INTERVAL) assertEquals( - listOf(recentThisAccount), - timelineDao.getStatusesForAccount(1, null, null, 100).blockingGet() - .map { it.toTriple() } + listOf(recentThisAccount), + timelineDao.getStatusesForAccount(1, null, null, 100).blockingGet() + .map { it.toTriple() } ) assertEquals( - listOf(recentAnotherAccount), - timelineDao.getStatusesForAccount(2, null, null, 100).blockingGet() - .map { it.toTriple() } + listOf(recentAnotherAccount), + timelineDao.getStatusesForAccount(2, null, null, 100).blockingGet() + .map { it.toTriple() } ) } @@ -120,9 +125,9 @@ class TimelineDAOTest { fun overwriteDeletedStatus() { val oldStatuses = listOf( - makeStatus(statusId = 3), - makeStatus(statusId = 2), - makeStatus(statusId = 1) + makeStatus(statusId = 3), + makeStatus(statusId = 2), + makeStatus(statusId = 1) ) timelineDao.deleteRange(1, oldStatuses.last().first.serverId, oldStatuses.first().first.serverId) @@ -133,8 +138,8 @@ class TimelineDAOTest { // status 2 gets deleted, newly loaded status contain only 1 + 3 val newStatuses = listOf( - makeStatus(statusId = 3), - makeStatus(statusId = 1) + makeStatus(statusId = 3), + makeStatus(statusId = 1) ) timelineDao.deleteRange(1, newStatuses.last().first.serverId, newStatuses.first().first.serverId) @@ -143,107 +148,106 @@ class TimelineDAOTest { timelineDao.insertInTransaction(status, author, reblogAuthor) } - //make sure status 2 is no longer in db + // make sure status 2 is no longer in db assertEquals( - newStatuses, - timelineDao.getStatusesForAccount(1, null, null, 100).blockingGet() - .map { it.toTriple() } + newStatuses, + timelineDao.getStatusesForAccount(1, null, null, 100).blockingGet() + .map { it.toTriple() } ) } private fun makeStatus( - accountId: Long = 1, - statusId: Long = 10, - reblog: Boolean = false, - createdAt: Long = statusId, - authorServerId: String = "20" + accountId: Long = 1, + statusId: Long = 10, + reblog: Boolean = false, + createdAt: Long = statusId, + authorServerId: String = "20" ): Triple { val author = TimelineAccountEntity( - authorServerId, - accountId, - "localUsername", - "username", - "displayName", - "blah", - "avatar", - "[\"tusky\": \"http://tusky.cool/emoji.jpg\"]", - false + authorServerId, + accountId, + "localUsername", + "username", + "displayName", + "blah", + "avatar", + "[\"tusky\": \"http://tusky.cool/emoji.jpg\"]", + false ) val reblogAuthor = if (reblog) { TimelineAccountEntity( - "R$authorServerId", - accountId, - "RlocalUsername", - "Rusername", - "RdisplayName", - "Rblah", - "Ravatar", - "[]", - false + "R$authorServerId", + accountId, + "RlocalUsername", + "Rusername", + "RdisplayName", + "Rblah", + "Ravatar", + "[]", + false ) } else null - val even = accountId % 2 == 0L val status = TimelineStatusEntity( - serverId = statusId.toString(), - url = "url$statusId", - timelineUserId = accountId, - authorServerId = authorServerId, - inReplyToId = "inReplyToId$statusId", - inReplyToAccountId = "inReplyToAccountId$statusId", - content = "Content!$statusId", - createdAt = createdAt, - emojis = "emojis$statusId", - reblogsCount = 1 * statusId.toInt(), - favouritesCount = 2 * statusId.toInt(), - reblogged = even, - favourited = !even, - bookmarked = false, - sensitive = even, - spoilerText = "spoier$statusId", - visibility = Status.Visibility.PRIVATE, - attachments = "attachments$accountId", - mentions = "mentions$accountId", - application = "application$accountId", - reblogServerId = if (reblog) (statusId * 100).toString() else null, - reblogAccountId = reblogAuthor?.serverId, - poll = null, - muted = false + serverId = statusId.toString(), + url = "url$statusId", + timelineUserId = accountId, + authorServerId = authorServerId, + inReplyToId = "inReplyToId$statusId", + inReplyToAccountId = "inReplyToAccountId$statusId", + content = "Content!$statusId", + createdAt = createdAt, + emojis = "emojis$statusId", + reblogsCount = 1 * statusId.toInt(), + favouritesCount = 2 * statusId.toInt(), + reblogged = even, + favourited = !even, + bookmarked = false, + sensitive = even, + spoilerText = "spoier$statusId", + visibility = Status.Visibility.PRIVATE, + attachments = "attachments$accountId", + mentions = "mentions$accountId", + application = "application$accountId", + reblogServerId = if (reblog) (statusId * 100).toString() else null, + reblogAccountId = reblogAuthor?.serverId, + poll = null, + muted = false ) return Triple(status, author, reblogAuthor) } private fun createPlaceholder(serverId: String, timelineUserId: Long): TimelineStatusEntity { return TimelineStatusEntity( - serverId = serverId, - url = null, - timelineUserId = timelineUserId, - authorServerId = null, - inReplyToId = null, - inReplyToAccountId = null, - content = null, - createdAt = 0L, - emojis = null, - reblogsCount = 0, - favouritesCount = 0, - reblogged = false, - favourited = false, - bookmarked = false, - sensitive = false, - spoilerText = null, - visibility = null, - attachments = null, - mentions = null, - application = null, - reblogServerId = null, - reblogAccountId = null, - poll = null, - muted = false + serverId = serverId, + url = null, + timelineUserId = timelineUserId, + authorServerId = null, + inReplyToId = null, + inReplyToAccountId = null, + content = null, + createdAt = 0L, + emojis = null, + reblogsCount = 0, + favouritesCount = 0, + reblogged = false, + favourited = false, + bookmarked = false, + sensitive = false, + spoilerText = null, + visibility = null, + attachments = null, + mentions = null, + application = null, + reblogServerId = null, + reblogAccountId = null, + poll = null, + muted = false ) } private fun TimelineStatusWithAccount.toTriple() = Triple(status, account, reblogAccount) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt index 8246555b8..fc8b3db2b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AboutActivity.kt @@ -2,13 +2,13 @@ package com.keylesspalace.tusky import android.content.Intent import android.os.Bundle -import androidx.annotation.StringRes import android.text.SpannableString import android.text.SpannableStringBuilder import android.text.method.LinkMovementMethod import android.text.style.URLSpan import android.text.util.Linkify import android.widget.TextView +import androidx.annotation.StringRes import com.keylesspalace.tusky.databinding.ActivityAboutBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.util.NoUnderlineURLSpan @@ -32,7 +32,7 @@ class AboutActivity : BottomSheetActivity(), Injectable { binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME) - if(BuildConfig.CUSTOM_INSTANCE.isBlank()) { + if (BuildConfig.CUSTOM_INSTANCE.isBlank()) { binding.aboutPoweredByTusky.hide() } diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt index a408bc43f..adcdcac6e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.kt @@ -62,7 +62,16 @@ import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.pager.AccountPagerAdapter import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.DefaultTextWatcher +import com.keylesspalace.tusky.util.LinkHelper +import com.keylesspalace.tusky.util.Success +import com.keylesspalace.tusky.util.ThemeUtils +import com.keylesspalace.tusky.util.emojify +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.loadAvatar +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.viewBinding +import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.view.showMuteAccountDialog import com.keylesspalace.tusky.viewmodel.AccountViewModel import dagger.android.DispatchingAndroidInjector @@ -82,7 +91,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private val binding: ActivityAccountBinding by viewBinding(ActivityAccountBinding::inflate) - private lateinit var accountFieldAdapter : AccountFieldAdapter + private lateinit var accountFieldAdapter: AccountFieldAdapter private var followState: FollowState = FollowState.NOT_FOLLOWING private var blocking: Boolean = false @@ -233,7 +242,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI override fun onTabUnselected(tab: TabLayout.Tab?) {} override fun onTabSelected(tab: TabLayout.Tab?) {} - }) } @@ -266,8 +274,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI fillColor = ColorStateList.valueOf(toolbarColor) elevation = appBarElevation shapeAppearanceModel = ShapeAppearanceModel.builder() - .setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius)) - .build() + .setAllCornerSizes(resources.getDimension(R.dimen.account_avatar_background_radius)) + .build() } binding.accountAvatarImageView.background = avatarBackground @@ -314,7 +322,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0 } }) - } private fun makeNotificationBarTransparent() { @@ -331,8 +338,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI is Success -> onAccountChanged(it.data) is Error -> { Snackbar.make(binding.accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG) - .setAction(R.string.action_retry) { viewModel.refresh() } - .show() + .setAction(R.string.action_retry) { viewModel.refresh() } + .show() } } } @@ -344,15 +351,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI if (it is Error) { Snackbar.make(binding.accountCoordinatorLayout, R.string.error_generic, Snackbar.LENGTH_LONG) - .setAction(R.string.action_retry) { viewModel.refresh() } - .show() + .setAction(R.string.action_retry) { viewModel.refresh() } + .show() } - } - viewModel.accountFieldData.observe(this, { - accountFieldAdapter.fields = it - accountFieldAdapter.notifyDataSetChanged() - }) + viewModel.accountFieldData.observe( + this, + { + accountFieldAdapter.fields = it + accountFieldAdapter.notifyDataSetChanged() + } + ) viewModel.noteSaved.observe(this) { binding.saveNoteInfo.visible(it, View.INVISIBLE) } @@ -366,9 +375,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI viewModel.refresh() adapter.refreshContent() } - viewModel.isRefreshing.observe(this, { isRefreshing -> - binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true - }) + viewModel.isRefreshing.observe( + this, + { isRefreshing -> + binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true + } + ) binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue) } @@ -382,7 +394,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI val emojifiedNote = account.note.emojify(account.emojis, binding.accountNoteTextView, animateEmojis) LinkHelper.setClickableText(binding.accountNoteTextView, emojifiedNote, null, this) - // accountFieldAdapter.fields = account.fields ?: emptyList() + // accountFieldAdapter.fields = account.fields ?: emptyList() accountFieldAdapter.emojis = account.emojis ?: emptyList() accountFieldAdapter.notifyDataSetChanged() @@ -409,18 +421,17 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI loadedAccount?.let { account -> loadAvatar( - account.avatar, - binding.accountAvatarImageView, - resources.getDimensionPixelSize(R.dimen.avatar_radius_94dp), - animateAvatar + account.avatar, + binding.accountAvatarImageView, + resources.getDimensionPixelSize(R.dimen.avatar_radius_94dp), + animateAvatar ) Glide.with(this) - .asBitmap() - .load(account.header) - .centerCrop() - .into(binding.accountHeaderImageView) - + .asBitmap() + .load(account.header) + .centerCrop() + .into(binding.accountHeaderImageView) binding.accountAvatarImageView.setOnClickListener { avatarView -> val intent = ViewMediaActivity.newSingleImageIntent(avatarView.context, account.avatar) @@ -478,7 +489,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI binding.accountMovedText.setCompoundDrawablesRelativeWithIntrinsicBounds(movedIcon, null, null, null) } - } /** @@ -554,15 +564,16 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI // because subscribing is Pleroma extension, enable it __only__ when we have non-null subscribing field // it's also now supported in Mastodon 3.3.0rc but called notifying and use different API call - if(!viewModel.isSelf && followState == FollowState.FOLLOWING - && (relation.subscribing != null || relation.notifying != null)) { + if (!viewModel.isSelf && followState == FollowState.FOLLOWING && + (relation.subscribing != null || relation.notifying != null) + ) { binding.accountSubscribeButton.show() binding.accountSubscribeButton.setOnClickListener { viewModel.changeSubscribingState() } - if(relation.notifying != null) + if (relation.notifying != null) subscribing = relation.notifying - else if(relation.subscribing != null) + else if (relation.subscribing != null) subscribing = relation.subscribing } @@ -577,7 +588,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI updateButtons() } - private val noteWatcher = object: DefaultTextWatcher() { + private val noteWatcher = object : DefaultTextWatcher() { override fun afterTextChanged(s: Editable) { viewModel.noteChanged(s.toString()) } @@ -615,11 +626,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI } private fun updateSubscribeButton() { - if(followState != FollowState.FOLLOWING) { + if (followState != FollowState.FOLLOWING) { binding.accountSubscribeButton.hide() } - if(subscribing) { + if (subscribing) { binding.accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp) binding.accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account) } else { @@ -648,7 +659,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI binding.accountMuteButton.hide() updateMuteButton() } - } else { binding.accountFloatingActionButton.hide() binding.accountFollowButton.hide() @@ -698,11 +708,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI } else { getString(R.string.action_show_reblogs) } - } else { menu.removeItem(R.id.action_show_reblogs) } - } else { // It shouldn't be possible to block, mute or report yourself. menu.removeItem(R.id.action_block) @@ -717,39 +725,39 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private fun showFollowRequestPendingDialog() { AlertDialog.Builder(this) - .setMessage(R.string.dialog_message_cancel_follow_request) - .setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setMessage(R.string.dialog_message_cancel_follow_request) + .setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() } + .setNegativeButton(android.R.string.cancel, null) + .show() } private fun showUnfollowWarningDialog() { AlertDialog.Builder(this) - .setMessage(R.string.dialog_unfollow_warning) - .setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setMessage(R.string.dialog_unfollow_warning) + .setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeFollowState() } + .setNegativeButton(android.R.string.cancel, null) + .show() } private fun toggleBlockDomain(instance: String) { - if(blockingDomain) { + if (blockingDomain) { viewModel.unblockDomain(instance) } else { AlertDialog.Builder(this) - .setMessage(getString(R.string.mute_domain_warning, instance)) - .setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.blockDomain(instance) } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setMessage(getString(R.string.mute_domain_warning, instance)) + .setPositiveButton(getString(R.string.mute_domain_warning_dialog_ok)) { _, _ -> viewModel.blockDomain(instance) } + .setNegativeButton(android.R.string.cancel, null) + .show() } } private fun toggleBlock() { if (viewModel.relationshipData.value?.data?.blocking != true) { AlertDialog.Builder(this) - .setMessage(getString(R.string.dialog_block_warning, loadedAccount?.username)) - .setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeBlockState() } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setMessage(getString(R.string.dialog_block_warning, loadedAccount?.username)) + .setPositiveButton(android.R.string.ok) { _, _ -> viewModel.changeBlockState() } + .setNegativeButton(android.R.string.cancel, null) + .show() } else { viewModel.changeBlockState() } @@ -759,8 +767,8 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI if (viewModel.relationshipData.value?.data?.muting != true) { loadedAccount?.let { showMuteAccountDialog( - this, - it.username + this, + it.username ) { notifications, duration -> viewModel.muteAccount(notifications, duration) } @@ -772,8 +780,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI private fun mention() { loadedAccount?.let { - val intent = ComposeActivity.startIntent(this, - ComposeActivity.ComposeOptions(mentionedUsernames = setOf(it.username))) + val intent = ComposeActivity.startIntent( + this, + ComposeActivity.ComposeOptions(mentionedUsernames = setOf(it.username)) + ) startActivity(intent) } } @@ -849,5 +859,4 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI return intent } } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt index 7f00150f4..ca23f7912 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountListActivity.kt @@ -64,9 +64,9 @@ class AccountListActivity : BaseActivity(), HasAndroidInjector { } supportFragmentManager - .beginTransaction() - .replace(R.id.fragment_container, AccountListFragment.newInstance(type, id, accountLocked)) - .commit() + .beginTransaction() + .replace(R.id.fragment_container, AccountListFragment.newInstance(type, id, accountLocked)) + .commit() } override fun androidInjector() = dispatchingAndroidInjector diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt index 59859b313..02fa07382 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt @@ -36,7 +36,13 @@ import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.BindingHolder +import com.keylesspalace.tusky.util.Either +import com.keylesspalace.tusky.util.emojify +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.loadAvatar +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel import com.keylesspalace.tusky.viewmodel.State import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers @@ -93,19 +99,19 @@ class AccountsInListFragment : DialogFragment(), Injectable { binding.accountsSearchRecycler.adapter = searchAdapter viewModel.state - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this)) - .subscribe { state -> - adapter.submitList(state.accounts.asRightOrNull() ?: listOf()) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this)) + .subscribe { state -> + adapter.submitList(state.accounts.asRightOrNull() ?: listOf()) - when (state.accounts) { - is Either.Right -> binding.messageView.hide() - is Either.Left -> handleError(state.accounts.value) - } - - setupSearchView(state) + when (state.accounts) { + is Either.Right -> binding.messageView.hide() + is Either.Left -> handleError(state.accounts.value) } + setupSearchView(state) + } + binding.searchView.isSubmitButtonEnabled = true binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { @@ -146,11 +152,15 @@ class AccountsInListFragment : DialogFragment(), Injectable { viewModel.load(listId) } if (error is IOException) { - binding.messageView.setup(R.drawable.elephant_offline, - R.string.error_network, retryAction) + binding.messageView.setup( + R.drawable.elephant_offline, + R.string.error_network, retryAction + ) } else { - binding.messageView.setup(R.drawable.elephant_error, - R.string.error_generic, retryAction) + binding.messageView.setup( + R.drawable.elephant_error, + R.string.error_generic, retryAction + ) } } @@ -184,7 +194,7 @@ class AccountsInListFragment : DialogFragment(), Injectable { onRemoveFromList(getItem(holder.bindingAdapterPosition).id) } binding.rejectButton.contentDescription = - binding.root.context.getString(R.string.action_remove_from_list) + binding.root.context.getString(R.string.action_remove_from_list) return holder } @@ -203,8 +213,8 @@ class AccountsInListFragment : DialogFragment(), Injectable { } override fun areContentsTheSame(oldItem: AccountInfo, newItem: AccountInfo): Boolean { - return oldItem.second == newItem.second - && oldItem.first.deepEquals(newItem.first) + return oldItem.second == newItem.second && + oldItem.first.deepEquals(newItem.first) } } @@ -260,4 +270,4 @@ class AccountsInListFragment : DialogFragment(), Injectable { return AccountsInListFragment().apply { arguments = args } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt b/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt index 6fd42b8d4..99903e32d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/BottomSheetActivity.kt @@ -60,7 +60,6 @@ abstract class BottomSheetActivity : BaseActivity() { override fun onSlide(bottomSheet: View, slideOffset: Float) {} }) - } open fun viewUrl(url: String, lookupFallbackBehavior: PostLookupFallbackBehavior = PostLookupFallbackBehavior.OPEN_IN_BROWSER) { @@ -70,11 +69,12 @@ abstract class BottomSheetActivity : BaseActivity() { } mastodonApi.searchObservable( - query = url, - resolve = true + query = url, + resolve = true ).observeOn(AndroidSchedulers.mainThread()) - .autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe({ (accounts, statuses) -> + .autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe( + { (accounts, statuses) -> if (getCancelSearchRequested(url)) { return@subscribe } @@ -90,12 +90,14 @@ abstract class BottomSheetActivity : BaseActivity() { } performUrlFallbackAction(url, lookupFallbackBehavior) - }, { + }, + { if (!getCancelSearchRequested(url)) { onEndSearch(url) performUrlFallbackAction(url, lookupFallbackBehavior) } - }) + } + ) onBeginSearch(url) } @@ -186,20 +188,21 @@ fun looksLikeMastodonUrl(urlString: String): Boolean { } if (uri.query != null || - uri.fragment != null || - uri.path == null) { + uri.fragment != null || + uri.path == null + ) { return false } val path = uri.path return path.matches("^/@[^/]+$".toRegex()) || - path.matches("^/@[^/]+/\\d+$".toRegex()) || - path.matches("^/users/\\w+$".toRegex()) || - path.matches("^/notice/[a-zA-Z0-9]+$".toRegex()) || - path.matches("^/objects/[-a-f0-9]+$".toRegex()) || - path.matches("^/notes/[a-z0-9]+$".toRegex()) || - path.matches("^/display/[-a-f0-9]+$".toRegex()) || - path.matches("^/profile/\\w+$".toRegex()) + path.matches("^/@[^/]+/\\d+$".toRegex()) || + path.matches("^/users/\\w+$".toRegex()) || + path.matches("^/notice/[a-zA-Z0-9]+$".toRegex()) || + path.matches("^/objects/[-a-f0-9]+$".toRegex()) || + path.matches("^/notes/[a-z0-9]+$".toRegex()) || + path.matches("^/display/[-a-f0-9]+$".toRegex()) || + path.matches("^/profile/\\w+$".toRegex()) } enum class PostLookupFallbackBehavior { diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt index e1d9768ef..1115cd41e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt @@ -36,18 +36,24 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.canhub.cropper.CropImage import com.google.android.material.snackbar.Snackbar import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter import com.keylesspalace.tusky.databinding.ActivityEditProfileBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.Error +import com.keylesspalace.tusky.util.Loading +import com.keylesspalace.tusky.util.Resource +import com.keylesspalace.tusky.util.Success +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.viewmodel.EditProfileViewModel 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 com.canhub.cropper.CropImage import javax.inject.Inject class EditProfileActivity : BaseActivity(), Injectable { @@ -110,11 +116,11 @@ class EditProfileActivity : BaseActivity(), Injectable { binding.addFieldButton.setOnClickListener { accountFieldEditAdapter.addField() - if(accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) { + if (accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) { it.isVisible = false } - binding.scrollView.post{ + binding.scrollView.post { binding.scrollView.smoothScrollTo(0, it.bottom) } } @@ -134,23 +140,22 @@ class EditProfileActivity : BaseActivity(), Injectable { accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList()) binding.addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS - if(viewModel.avatarData.value == null) { + if (viewModel.avatarData.value == null) { Glide.with(this) - .load(me.avatar) - .placeholder(R.drawable.avatar_default) - .transform( - FitCenter(), - RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)) - ) - .into(binding.avatarPreview) + .load(me.avatar) + .placeholder(R.drawable.avatar_default) + .transform( + FitCenter(), + RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)) + ) + .into(binding.avatarPreview) } - if(viewModel.headerData.value == null) { + if (viewModel.headerData.value == null) { Glide.with(this) - .load(me.header) - .into(binding.headerPreview) + .load(me.header) + .into(binding.headerPreview) } - } } is Error -> { @@ -159,7 +164,6 @@ class EditProfileActivity : BaseActivity(), Injectable { viewModel.obtainProfile() } snackbar.show() - } } } @@ -179,20 +183,22 @@ class EditProfileActivity : BaseActivity(), Injectable { observeImage(viewModel.avatarData, binding.avatarPreview, binding.avatarProgressBar, true) observeImage(viewModel.headerData, binding.headerPreview, binding.headerProgressBar, false) - viewModel.saveData.observe(this, { - when(it) { - is Success -> { - finish() - } - is Loading -> { - binding.saveProgressBar.visibility = View.VISIBLE - } - is Error -> { - onSaveFailure(it.errorMessage) + viewModel.saveData.observe( + this, + { + when (it) { + is Success -> { + finish() + } + is Loading -> { + binding.saveProgressBar.visibility = View.VISIBLE + } + is Error -> { + onSaveFailure(it.errorMessage) + } } } - }) - + ) } override fun onSaveInstanceState(outState: Bundle) { @@ -202,50 +208,56 @@ class EditProfileActivity : BaseActivity(), Injectable { override fun onStop() { super.onStop() - if(!isFinishing) { - viewModel.updateProfile(binding.displayNameEditText.text.toString(), - binding.noteEditText.text.toString(), - binding.lockedCheckBox.isChecked, - accountFieldEditAdapter.getFieldData()) + if (!isFinishing) { + viewModel.updateProfile( + binding.displayNameEditText.text.toString(), + binding.noteEditText.text.toString(), + binding.lockedCheckBox.isChecked, + accountFieldEditAdapter.getFieldData() + ) } } - private fun observeImage(liveData: LiveData>, - imageView: ImageView, - progressBar: View, - roundedCorners: Boolean) { - liveData.observe(this, { + private fun observeImage( + liveData: LiveData>, + imageView: ImageView, + progressBar: View, + roundedCorners: Boolean + ) { + liveData.observe( + this, + { - when (it) { - is Success -> { - val glide = Glide.with(imageView) + when (it) { + is Success -> { + val glide = Glide.with(imageView) .load(it.data) - if (roundedCorners) { - glide.transform( - FitCenter(), - RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)) - ) - } + if (roundedCorners) { + glide.transform( + FitCenter(), + RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)) + ) + } - glide.into(imageView) + glide.into(imageView) - imageView.show() - progressBar.hide() - } - is Loading -> { - progressBar.show() - } - is Error -> { - progressBar.hide() - if(!it.consumed) { - onResizeFailure() - it.consumed = true + imageView.show() + progressBar.hide() + } + is Loading -> { + progressBar.show() + } + is Error -> { + progressBar.hide() + if (!it.consumed) { + onResizeFailure() + it.consumed = true + } } - } } - }) + ) } private fun onMediaPick(pickType: PickType) { @@ -261,8 +273,11 @@ class EditProfileActivity : BaseActivity(), Injectable { } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, - grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { when (requestCode) { PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE -> { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { @@ -307,14 +322,16 @@ class EditProfileActivity : BaseActivity(), Injectable { private fun save() { if (currentlyPicking != PickType.NOTHING) { - return + return } - viewModel.save(binding.displayNameEditText.text.toString(), - binding.noteEditText.text.toString(), - binding.lockedCheckBox.isChecked, - accountFieldEditAdapter.getFieldData(), - this) + viewModel.save( + binding.displayNameEditText.text.toString(), + binding.noteEditText.text.toString(), + binding.lockedCheckBox.isChecked, + accountFieldEditAdapter.getFieldData(), + this + ) } private fun onSaveFailure(msg: String?) { @@ -352,10 +369,10 @@ class EditProfileActivity : BaseActivity(), Injectable { AVATAR_PICK_RESULT -> { if (resultCode == Activity.RESULT_OK && data != null) { CropImage.activity(data.data) - .setInitialCropWindowPaddingRatio(0f) - .setOutputCompressFormat(Bitmap.CompressFormat.PNG) - .setAspectRatio(AVATAR_SIZE, AVATAR_SIZE) - .start(this) + .setInitialCropWindowPaddingRatio(0f) + .setOutputCompressFormat(Bitmap.CompressFormat.PNG) + .setAspectRatio(AVATAR_SIZE, AVATAR_SIZE) + .start(this) } else { endMediaPicking() } @@ -363,10 +380,10 @@ class EditProfileActivity : BaseActivity(), Injectable { HEADER_PICK_RESULT -> { if (resultCode == Activity.RESULT_OK && data != null) { CropImage.activity(data.data) - .setInitialCropWindowPaddingRatio(0f) - .setOutputCompressFormat(Bitmap.CompressFormat.PNG) - .setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT) - .start(this) + .setInitialCropWindowPaddingRatio(0f) + .setOutputCompressFormat(Bitmap.CompressFormat.PNG) + .setAspectRatio(HEADER_WIDTH, HEADER_HEIGHT) + .start(this) } else { endMediaPicking() } @@ -383,7 +400,7 @@ class EditProfileActivity : BaseActivity(), Injectable { } private fun beginResize(uri: Uri?) { - if(uri == null) { + if (uri == null) { currentlyPicking = PickType.NOTHING return } @@ -409,5 +426,4 @@ class EditProfileActivity : BaseActivity(), Injectable { Snackbar.make(binding.avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show() endMediaPicking() } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt index 1fe165b74..d6de5d8ec 100644 --- a/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/FiltersActivity.kt @@ -22,10 +22,9 @@ import retrofit2.Call import retrofit2.Callback import retrofit2.Response import java.io.IOException -import java.lang.Exception import javax.inject.Inject -class FiltersActivity: BaseActivity() { +class FiltersActivity : BaseActivity() { @Inject lateinit var api: MastodonApi @@ -34,7 +33,7 @@ class FiltersActivity: BaseActivity() { private val binding by viewBinding(ActivityFiltersBinding::inflate) - private lateinit var context : String + private lateinit var context: String private lateinit var filters: MutableList override fun onCreate(savedInstanceState: Bundle?) { @@ -58,7 +57,7 @@ class FiltersActivity: BaseActivity() { private fun updateFilter(filter: Filter, itemIndex: Int) { api.updateFilter(filter.id, filter.phrase, filter.context, filter.irreversible, filter.wholeWord, filter.expiresAt) - .enqueue(object: Callback{ + .enqueue(object : Callback { override fun onFailure(call: Call, t: Throwable) { Toast.makeText(this@FiltersActivity, "Error updating filter '${filter.phrase}'", Toast.LENGTH_SHORT).show() } @@ -80,7 +79,7 @@ class FiltersActivity: BaseActivity() { val filter = filters[itemIndex] if (filter.context.size == 1) { // This is the only context for this filter; delete it - api.deleteFilter(filters[itemIndex].id).enqueue(object: Callback { + api.deleteFilter(filters[itemIndex].id).enqueue(object : Callback { override fun onFailure(call: Call, t: Throwable) { Toast.makeText(this@FiltersActivity, "Error updating filter '${filters[itemIndex].phrase}'", Toast.LENGTH_SHORT).show() } @@ -94,17 +93,19 @@ class FiltersActivity: BaseActivity() { } else { // Keep the filter, but remove it from this context val oldFilter = filters[itemIndex] - val newFilter = Filter(oldFilter.id, oldFilter.phrase, oldFilter.context.filter { c -> c != context }, - oldFilter.expiresAt, oldFilter.irreversible, oldFilter.wholeWord) + val newFilter = Filter( + oldFilter.id, oldFilter.phrase, oldFilter.context.filter { c -> c != context }, + oldFilter.expiresAt, oldFilter.irreversible, oldFilter.wholeWord + ) updateFilter(newFilter, itemIndex) } } private fun createFilter(phrase: String, wholeWord: Boolean) { - api.createFilter(phrase, listOf(context), false, wholeWord, "").enqueue(object: Callback { + api.createFilter(phrase, listOf(context), false, wholeWord, "").enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { val filterResponse = response.body() - if(response.isSuccessful && filterResponse != null) { + if (response.isSuccessful && filterResponse != null) { filters.add(filterResponse) refreshFilterDisplay() eventHub.dispatch(PreferenceChangedEvent(context)) @@ -123,13 +124,13 @@ class FiltersActivity: BaseActivity() { val binding = DialogFilterBinding.inflate(layoutInflater) binding.phraseWholeWord.isChecked = true AlertDialog.Builder(this@FiltersActivity) - .setTitle(R.string.filter_addition_dialog_title) - .setView(binding.root) - .setPositiveButton(android.R.string.ok){ _, _ -> - createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked) - } - .setNeutralButton(android.R.string.cancel, null) - .show() + .setTitle(R.string.filter_addition_dialog_title) + .setView(binding.root) + .setPositiveButton(android.R.string.ok) { _, _ -> + createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked) + } + .setNeutralButton(android.R.string.cancel, null) + .show() } private fun setupEditDialogForItem(itemIndex: Int) { @@ -139,19 +140,21 @@ class FiltersActivity: BaseActivity() { binding.phraseWholeWord.isChecked = filter.wholeWord AlertDialog.Builder(this@FiltersActivity) - .setTitle(R.string.filter_edit_dialog_title) - .setView(binding.root) - .setPositiveButton(R.string.filter_dialog_update_button) { _, _ -> - val oldFilter = filters[itemIndex] - val newFilter = Filter(oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context, - oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked) - updateFilter(newFilter, itemIndex) - } - .setNegativeButton(R.string.filter_dialog_remove_button) { _, _ -> - deleteFilter(itemIndex) - } - .setNeutralButton(android.R.string.cancel, null) - .show() + .setTitle(R.string.filter_edit_dialog_title) + .setView(binding.root) + .setPositiveButton(R.string.filter_dialog_update_button) { _, _ -> + val oldFilter = filters[itemIndex] + val newFilter = Filter( + oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context, + oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked + ) + updateFilter(newFilter, itemIndex) + } + .setNegativeButton(R.string.filter_dialog_remove_button) { _, _ -> + deleteFilter(itemIndex) + } + .setNeutralButton(android.R.string.cancel, null) + .show() } private fun refreshFilterDisplay() { @@ -173,11 +176,15 @@ class FiltersActivity: BaseActivity() { binding.filterProgressBar.hide() binding.filterMessageView.show() if (t is IOException) { - binding.filterMessageView.setup(R.drawable.elephant_offline, - R.string.error_network) { loadFilters() } + binding.filterMessageView.setup( + R.drawable.elephant_offline, + R.string.error_network + ) { loadFilters() } } else { - binding.filterMessageView.setup(R.drawable.elephant_error, - R.string.error_generic) { loadFilters() } + binding.filterMessageView.setup( + R.drawable.elephant_error, + R.string.error_generic + ) { loadFilters() } } return@launch } @@ -195,4 +202,4 @@ class FiltersActivity: BaseActivity() { const val FILTERS_CONTEXT = "filters_context" const val FILTERS_TITLE = "filters_title" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt index 406a4aafb..ed3aed3a5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LicenseActivity.kt @@ -16,9 +16,9 @@ package com.keylesspalace.tusky import android.os.Bundle -import androidx.annotation.RawRes import android.util.Log import android.widget.TextView +import androidx.annotation.RawRes import com.keylesspalace.tusky.databinding.ActivityLicenseBinding import com.keylesspalace.tusky.util.IOUtils import java.io.BufferedReader @@ -41,7 +41,6 @@ class LicenseActivity : BaseActivity() { setTitle(R.string.title_licenses) loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView) - } private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) { diff --git a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt index cb6acd866..e816a0a55 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt @@ -23,25 +23,41 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.* +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.PopupMenu +import android.widget.TextView import androidx.activity.viewModels import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog -import androidx.recyclerview.widget.* +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView import at.connyduck.sparkbutton.helpers.Utils import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from import autodispose2.autoDispose import com.google.android.material.snackbar.Snackbar +import com.keylesspalace.tusky.components.timeline.TimelineViewModel import com.keylesspalace.tusky.databinding.ActivityListsBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.MastoList -import com.keylesspalace.tusky.components.timeline.TimelineViewModel -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.ThemeUtils +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.onTextChanged +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.viewBinding +import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.viewmodel.ListsViewModel -import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event.* -import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.* +import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event +import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.ERROR_NETWORK +import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.ERROR_OTHER +import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.INITIAL +import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.LOADED +import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.LOADING import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.utils.colorInt @@ -84,12 +100,13 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { binding.listsRecycler.adapter = adapter binding.listsRecycler.layoutManager = LinearLayoutManager(this) binding.listsRecycler.addItemDecoration( - DividerItemDecoration(this, DividerItemDecoration.VERTICAL)) + DividerItemDecoration(this, DividerItemDecoration.VERTICAL) + ) viewModel.state - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this)) - .subscribe(this::update) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this)) + .subscribe(this::update) viewModel.retryLoading() binding.addListButton.setOnClickListener { @@ -97,15 +114,15 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { } viewModel.events.observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this)) - .subscribe { event -> - @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") - when (event) { - CREATE_ERROR -> showMessage(R.string.error_create_list) - RENAME_ERROR -> showMessage(R.string.error_rename_list) - DELETE_ERROR -> showMessage(R.string.error_delete_list) - } + .autoDispose(from(this)) + .subscribe { event -> + @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") + when (event) { + Event.CREATE_ERROR -> showMessage(R.string.error_create_list) + Event.RENAME_ERROR -> showMessage(R.string.error_rename_list) + Event.DELETE_ERROR -> showMessage(R.string.error_delete_list) } + } } private fun showlistNameDialog(list: MastoList?) { @@ -115,17 +132,18 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { layout.addView(editText) val margin = Utils.dpToPx(this, 8) (editText.layoutParams as ViewGroup.MarginLayoutParams) - .setMargins(margin, margin, margin, 0) + .setMargins(margin, margin, margin, 0) val dialog = AlertDialog.Builder(this) - .setView(layout) - .setPositiveButton( - if (list == null) R.string.action_create_list - else R.string.action_rename_list) { _, _ -> - onPickedDialogName(editText.text, list?.id) - } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setView(layout) + .setPositiveButton( + if (list == null) R.string.action_create_list + else R.string.action_rename_list + ) { _, _ -> + onPickedDialogName(editText.text, list?.id) + } + .setNegativeButton(android.R.string.cancel, null) + .show() val positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE) editText.onTextChanged { s, _, _, _ -> @@ -137,15 +155,14 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { private fun showListDeleteDialog(list: MastoList) { AlertDialog.Builder(this) - .setMessage(getString(R.string.dialog_delete_list_warning, list.title)) - .setPositiveButton(R.string.action_delete){ _, _ -> - viewModel.deleteList(list.id) - } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setMessage(getString(R.string.dialog_delete_list_warning, list.title)) + .setPositiveButton(R.string.action_delete) { _, _ -> + viewModel.deleteList(list.id) + } + .setNegativeButton(android.R.string.cancel, null) + .show() } - private fun update(state: ListsViewModel.State) { adapter.submitList(state.lists) binding.progressBar.visible(state.loadingState == LOADING) @@ -166,8 +183,10 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { LOADED -> if (state.lists.isEmpty()) { binding.messageView.show() - binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, - null) + binding.messageView.setup( + R.drawable.elephant_friend_empty, R.string.message_empty, + null + ) } else { binding.messageView.hide() } @@ -176,13 +195,14 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { private fun showMessage(@StringRes messageId: Int) { Snackbar.make( - binding.listsRecycler, messageId, Snackbar.LENGTH_SHORT + binding.listsRecycler, messageId, Snackbar.LENGTH_SHORT ).show() } private fun onListSelected(listId: String) { startActivityWithSlideInAnimation( - ModalTimelineActivity.newIntent(this, TimelineViewModel.Kind.LIST, listId)) + ModalTimelineActivity.newIntent(this, TimelineViewModel.Kind.LIST, listId) + ) } private fun openListSettings(list: MastoList) { @@ -219,27 +239,28 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { } } - private inner class ListsAdapter - : ListAdapter(ListsDiffer) { + private inner class ListsAdapter : + ListAdapter(ListsDiffer) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder { return LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false) - .let(this::ListViewHolder) - .apply { - val context = nameTextView.context - val iconColor = ThemeUtils.getColor(context, android.R.attr.textColorTertiary) - val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list).apply { sizeDp = 20; colorInt = iconColor } + .let(this::ListViewHolder) + .apply { + val context = nameTextView.context + val iconColor = ThemeUtils.getColor(context, android.R.attr.textColorTertiary) + val icon = IconicsDrawable(context, GoogleMaterial.Icon.gmd_list).apply { sizeDp = 20; colorInt = iconColor } - nameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) - } + nameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) + } } override fun onBindViewHolder(holder: ListViewHolder, position: Int) { holder.nameTextView.text = getItem(position).title } - private inner class ListViewHolder(view: View) : RecyclerView.ViewHolder(view), - View.OnClickListener { + private inner class ListViewHolder(view: View) : + RecyclerView.ViewHolder(view), + View.OnClickListener { val nameTextView: TextView = view.findViewById(R.id.list_name_textview) val moreButton: ImageButton = view.findViewById(R.id.editListButton) @@ -271,4 +292,4 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector { companion object { fun newIntent(context: Context) = Intent(context, ListsActivity::class.java) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt index 2ba797983..08140b63d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.kt @@ -34,7 +34,11 @@ import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.AccessToken import com.keylesspalace.tusky.entity.AppCredentials import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.ThemeUtils +import com.keylesspalace.tusky.util.getNonNullString +import com.keylesspalace.tusky.util.rickRoll +import com.keylesspalace.tusky.util.shouldRickRoll +import com.keylesspalace.tusky.util.viewBinding import okhttp3.HttpUrl import retrofit2.Call import retrofit2.Callback @@ -62,28 +66,29 @@ class LoginActivity : BaseActivity(), Injectable { setContentView(binding.root) - if(savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) { + if (savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) { binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE) binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length) } - if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) { + if (BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) { Glide.with(binding.loginLogo) - .load(BuildConfig.CUSTOM_LOGO_URL) - .placeholder(null) - .into(binding.loginLogo) + .load(BuildConfig.CUSTOM_LOGO_URL) + .placeholder(null) + .into(binding.loginLogo) } preferences = getSharedPreferences( - getString(R.string.preferences_file_key), Context.MODE_PRIVATE) + getString(R.string.preferences_file_key), Context.MODE_PRIVATE + ) binding.loginButton.setOnClickListener { onButtonClick() } binding.whatsAnInstanceTextView.setOnClickListener { val dialog = AlertDialog.Builder(this) - .setMessage(R.string.dialog_whats_an_instance) - .setPositiveButton(R.string.action_close, null) - .show() + .setMessage(R.string.dialog_whats_an_instance) + .setPositiveButton(R.string.action_close, null) + .show() val textView = dialog.findViewById(android.R.id.message) textView?.movementMethod = LinkMovementMethod.getInstance() } @@ -95,7 +100,6 @@ class LoginActivity : BaseActivity(), Injectable { } else { binding.toolbar.visibility = View.GONE } - } override fun requiresLogin(): Boolean { @@ -104,7 +108,7 @@ class LoginActivity : BaseActivity(), Injectable { override fun finish() { super.finish() - if(isAdditionalLogin()) { + if (isAdditionalLogin()) { overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right) } } @@ -134,8 +138,10 @@ class LoginActivity : BaseActivity(), Injectable { } val callback = object : Callback { - override fun onResponse(call: Call, - response: Response) { + override fun onResponse( + call: Call, + response: Response + ) { if (!response.isSuccessful) { binding.loginButton.isEnabled = true binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration) @@ -148,10 +154,10 @@ class LoginActivity : BaseActivity(), Injectable { val clientSecret = credentials.clientSecret preferences.edit() - .putString("domain", domain) - .putString("clientId", clientId) - .putString("clientSecret", clientSecret) - .apply() + .putString("domain", domain) + .putString("clientId", clientId) + .putString("clientSecret", clientSecret) + .apply() redirectUserToAuthorizeAndLogin(domain, clientId) } @@ -165,11 +171,12 @@ class LoginActivity : BaseActivity(), Injectable { } mastodonApi - .authenticateApp(domain, getString(R.string.app_name), oauthRedirectUri, - OAUTH_SCOPES, getString(R.string.tusky_website)) - .enqueue(callback) + .authenticateApp( + domain, getString(R.string.app_name), oauthRedirectUri, + OAUTH_SCOPES, getString(R.string.tusky_website) + ) + .enqueue(callback) setLoading(true) - } private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) { @@ -177,10 +184,10 @@ class LoginActivity : BaseActivity(), Injectable { * login there, and the server will redirect back to the app with its response. */ val endpoint = MastodonApi.ENDPOINT_AUTHORIZE val parameters = mapOf( - "client_id" to clientId, - "redirect_uri" to oauthRedirectUri, - "response_type" to "code", - "scope" to OAUTH_SCOPES + "client_id" to clientId, + "redirect_uri" to oauthRedirectUri, + "response_type" to "code", + "scope" to OAUTH_SCOPES ) val url = "https://" + domain + endpoint + "?" + toQueryString(parameters) val uri = Uri.parse(url) @@ -224,31 +231,27 @@ class LoginActivity : BaseActivity(), Injectable { } else { setLoading(false) binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token) - Log.e(TAG, String.format("%s %s", - getString(R.string.error_retrieving_oauth_token), - response.message())) + Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), response.message())) } } override fun onFailure(call: Call, t: Throwable) { setLoading(false) binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token) - Log.e(TAG, String.format("%s %s", - getString(R.string.error_retrieving_oauth_token), - t.message)) + Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), t.message)) } } - mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code, - "authorization_code").enqueue(callback) + mastodonApi.fetchOAuthToken( + domain, clientId, clientSecret, redirectUri, code, + "authorization_code" + ).enqueue(callback) } else if (error != null) { /* Authorization failed. Put the error response where the user can read it and they * can try again. */ setLoading(false) binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied) - Log.e(TAG, String.format("%s %s", - getString(R.string.error_authorization_denied), - error)) + Log.e(TAG, "%s %s".format(getString(R.string.error_authorization_denied), error)) } else { // This case means a junk response was received somehow. setLoading(false) @@ -340,14 +343,14 @@ class LoginActivity : BaseActivity(), Injectable { val navigationbarDividerColor = ThemeUtils.getColor(context, R.attr.dividerColor) val colorSchemeParams = CustomTabColorSchemeParams.Builder() - .setToolbarColor(toolbarColor) - .setNavigationBarColor(navigationbarColor) - .setNavigationBarDividerColor(navigationbarDividerColor) - .build() + .setToolbarColor(toolbarColor) + .setNavigationBarColor(navigationbarColor) + .setNavigationBarDividerColor(navigationbarDividerColor) + .build() val customTabsIntent = CustomTabsIntent.Builder() - .setDefaultColorSchemeParams(colorSchemeParams) - .build() + .setDefaultColorSchemeParams(colorSchemeParams) + .build() try { customTabsIntent.launchUrl(context, uri) diff --git a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt index 000cf3a9f..dbcde89dc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ModalTimelineActivity.kt @@ -4,9 +4,9 @@ import android.content.Context import android.content.Intent import android.os.Bundle import com.google.android.material.floatingactionbutton.FloatingActionButton -import com.keylesspalace.tusky.databinding.ActivityModalTimelineBinding import com.keylesspalace.tusky.components.timeline.TimelineFragment import com.keylesspalace.tusky.components.timeline.TimelineViewModel +import com.keylesspalace.tusky.databinding.ActivityModalTimelineBinding import com.keylesspalace.tusky.interfaces.ActionButtonActivity import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector @@ -31,11 +31,11 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn if (supportFragmentManager.findFragmentById(R.id.contentFrame) == null) { val kind = intent?.getSerializableExtra(ARG_KIND) as? TimelineViewModel.Kind - ?: TimelineViewModel.Kind.HOME + ?: TimelineViewModel.Kind.HOME val argument = intent?.getStringExtra(ARG_ARG) supportFragmentManager.beginTransaction() - .replace(R.id.contentFrame, TimelineFragment.newInstance(kind, argument)) - .commit() + .replace(R.id.contentFrame, TimelineFragment.newInstance(kind, argument)) + .commit() } } @@ -48,13 +48,15 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn private const val ARG_ARG = "arg" @JvmStatic - fun newIntent(context: Context, kind: TimelineViewModel.Kind, - argument: String?): Intent { + fun newIntent( + context: Context, + kind: TimelineViewModel.Kind, + argument: String? + ): Intent { val intent = Intent(context, ModalTimelineActivity::class.java) intent.putExtra(ARG_KIND, kind) intent.putExtra(ARG_ARG, argument) return intent } - } } diff --git a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt index 07c54e973..1b7e29947 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/SplashActivity.kt @@ -18,10 +18,9 @@ package com.keylesspalace.tusky import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.di.Injectable - -import com.keylesspalace.tusky.components.notifications.NotificationHelper import javax.inject.Inject class SplashActivity : AppCompatActivity(), Injectable { @@ -46,5 +45,4 @@ class SplashActivity : AppCompatActivity(), Injectable { startActivity(intent) finish() } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt index 219d47df5..ac9dba6bd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/StatusListActivity.kt @@ -19,15 +19,12 @@ import android.content.Context import android.content.Intent import android.os.Bundle import androidx.fragment.app.commit -import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding - import com.keylesspalace.tusky.components.timeline.TimelineFragment import com.keylesspalace.tusky.components.timeline.TimelineViewModel.Kind - -import javax.inject.Inject - +import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector +import javax.inject.Inject class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { @@ -44,7 +41,7 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { setSupportActionBar(binding.includedToolbar.toolbar) - val title = if(kind == Kind.FAVOURITES) { + val title = if (kind == Kind.FAVOURITES) { R.string.title_favourites } else { R.string.title_bookmarks @@ -60,7 +57,6 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { val fragment = TimelineFragment.newInstance(kind) replace(R.id.fragment_container, fragment) } - } override fun androidInjector() = dispatchingAndroidInjector @@ -71,15 +67,14 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector { @JvmStatic fun newFavouritesIntent(context: Context) = - Intent(context, StatusListActivity::class.java).apply { - putExtra(EXTRA_KIND, Kind.FAVOURITES.name) - } + Intent(context, StatusListActivity::class.java).apply { + putExtra(EXTRA_KIND, Kind.FAVOURITES.name) + } @JvmStatic fun newBookmarksIntent(context: Context) = - Intent(context, StatusListActivity::class.java).apply { - putExtra(EXTRA_KIND, Kind.BOOKMARKS.name) - } + Intent(context, StatusListActivity::class.java).apply { + putExtra(EXTRA_KIND, Kind.BOOKMARKS.name) + } } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/TabData.kt b/app/src/main/java/com/keylesspalace/tusky/TabData.kt index 5eabec5f4..fa1876ce3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabData.kt @@ -20,9 +20,9 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.fragment.app.Fragment import com.keylesspalace.tusky.components.conversation.ConversationsFragment -import com.keylesspalace.tusky.fragment.NotificationsFragment import com.keylesspalace.tusky.components.timeline.TimelineFragment import com.keylesspalace.tusky.components.timeline.TimelineViewModel +import com.keylesspalace.tusky.fragment.NotificationsFragment /** this would be a good case for a sealed class, but that does not work nice with Room */ @@ -34,71 +34,72 @@ const val DIRECT = "Direct" const val HASHTAG = "Hashtag" const val LIST = "List" -data class TabData(val id: String, - @StringRes val text: Int, - @DrawableRes val icon: Int, - val fragment: (List) -> Fragment, - val arguments: List = emptyList(), - val title: (Context) -> String = { context -> context.getString(text)} - ) +data class TabData( + val id: String, + @StringRes val text: Int, + @DrawableRes val icon: Int, + val fragment: (List) -> Fragment, + val arguments: List = emptyList(), + val title: (Context) -> String = { context -> context.getString(text) } +) fun createTabDataFromId(id: String, arguments: List = emptyList()): TabData { return when (id) { HOME -> TabData( - HOME, - R.string.title_home, - R.drawable.ic_home_24dp, - { TimelineFragment.newInstance(TimelineViewModel.Kind.HOME) } + HOME, + R.string.title_home, + R.drawable.ic_home_24dp, + { TimelineFragment.newInstance(TimelineViewModel.Kind.HOME) } ) NOTIFICATIONS -> TabData( - NOTIFICATIONS, - R.string.title_notifications, - R.drawable.ic_notifications_24dp, - { NotificationsFragment.newInstance() } + NOTIFICATIONS, + R.string.title_notifications, + R.drawable.ic_notifications_24dp, + { NotificationsFragment.newInstance() } ) LOCAL -> TabData( - LOCAL, - R.string.title_public_local, - R.drawable.ic_local_24dp, - { TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_LOCAL) } + LOCAL, + R.string.title_public_local, + R.drawable.ic_local_24dp, + { TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_LOCAL) } ) FEDERATED -> TabData( - FEDERATED, - R.string.title_public_federated, - R.drawable.ic_public_24dp, - { TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_FEDERATED) } + FEDERATED, + R.string.title_public_federated, + R.drawable.ic_public_24dp, + { TimelineFragment.newInstance(TimelineViewModel.Kind.PUBLIC_FEDERATED) } ) DIRECT -> TabData( - DIRECT, - R.string.title_direct_messages, - R.drawable.ic_reblog_direct_24dp, - { ConversationsFragment.newInstance() } + DIRECT, + R.string.title_direct_messages, + R.drawable.ic_reblog_direct_24dp, + { ConversationsFragment.newInstance() } ) HASHTAG -> TabData( - HASHTAG, - R.string.hashtags, - R.drawable.ic_hashtag, - { args -> TimelineFragment.newHashtagInstance(args) }, - arguments, - { context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) }} + HASHTAG, + R.string.hashtags, + R.drawable.ic_hashtag, + { args -> TimelineFragment.newHashtagInstance(args) }, + arguments, + { context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) } } ) LIST -> TabData( - LIST, - R.string.list, - R.drawable.ic_list, - { args -> TimelineFragment.newInstance(TimelineViewModel.Kind.LIST, args.getOrNull(0).orEmpty()) }, - arguments, - { arguments.getOrNull(1).orEmpty() } - ) + LIST, + R.string.list, + R.drawable.ic_list, + { args -> TimelineFragment.newInstance(TimelineViewModel.Kind.LIST, args.getOrNull(0).orEmpty()) }, + arguments, + { arguments.getOrNull(1).orEmpty() } + ) else -> throw IllegalArgumentException("unknown tab type") } } fun defaultTabs(): List { return listOf( - createTabDataFromId(HOME), - createTabDataFromId(NOTIFICATIONS), - createTabDataFromId(LOCAL), - createTabDataFromId(FEDERATED) + createTabDataFromId(HOME), + createTabDataFromId(NOTIFICATIONS), + createTabDataFromId(LOCAL), + createTabDataFromId(FEDERATED) ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt index acb5529e8..720664bde 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TabPreferenceActivity.kt @@ -221,26 +221,26 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene frameLayout.addView(editText) val dialog = AlertDialog.Builder(this) - .setTitle(R.string.add_hashtag_title) - .setView(frameLayout) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.action_save) { _, _ -> - val input = editText.text.toString().trim() - if (tab == null) { - val newTab = createTabDataFromId(HASHTAG, listOf(input)) - currentTabs.add(newTab) - currentTabsAdapter.notifyItemInserted(currentTabs.size - 1) - } else { - val newTab = tab.copy(arguments = tab.arguments + input) - currentTabs[tabPosition] = newTab + .setTitle(R.string.add_hashtag_title) + .setView(frameLayout) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.action_save) { _, _ -> + val input = editText.text.toString().trim() + if (tab == null) { + val newTab = createTabDataFromId(HASHTAG, listOf(input)) + currentTabs.add(newTab) + currentTabsAdapter.notifyItemInserted(currentTabs.size - 1) + } else { + val newTab = tab.copy(arguments = tab.arguments + input) + currentTabs[tabPosition] = newTab - currentTabsAdapter.notifyItemChanged(tabPosition) - } - - updateAvailableTabs() - saveTabs() + currentTabsAdapter.notifyItemChanged(tabPosition) } - .create() + + updateAvailableTabs() + saveTabs() + } + .create() editText.onTextChanged { s, _, _, _ -> dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = validateHashtag(s) @@ -254,28 +254,28 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene private fun showSelectListDialog() { val adapter = ListSelectionAdapter(this) mastodonApi.getLists() - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe ( - { lists -> - adapter.addAll(lists) - }, - { throwable -> - Log.e("TabPreferenceActivity", "failed to load lists", throwable) - } - ) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe( + { lists -> + adapter.addAll(lists) + }, + { throwable -> + Log.e("TabPreferenceActivity", "failed to load lists", throwable) + } + ) AlertDialog.Builder(this) - .setTitle(R.string.select_list_title) - .setAdapter(adapter) { _, position -> - val list = adapter.getItem(position) - val newTab = createTabDataFromId(LIST, listOf(list!!.id, list.title)) - currentTabs.add(newTab) - currentTabsAdapter.notifyItemInserted(currentTabs.size - 1) - updateAvailableTabs() - saveTabs() - } - .show() + .setTitle(R.string.select_list_title) + .setAdapter(adapter) { _, position -> + val list = adapter.getItem(position) + val newTab = createTabDataFromId(LIST, listOf(list!!.id, list.title)) + currentTabs.add(newTab) + currentTabsAdapter.notifyItemInserted(currentTabs.size - 1) + updateAvailableTabs() + saveTabs() + } + .show() } private fun validateHashtag(input: CharSequence?): Boolean { @@ -330,10 +330,9 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene it.tabPreferences = currentTabs accountManager.saveAccount(it) } - .subscribeOn(Schedulers.io()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe() - + .subscribeOn(Schedulers.io()) + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe() } tabsChanged = true } @@ -357,5 +356,4 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene private const val MIN_TAB_COUNT = 2 private const val MAX_TAB_COUNT = 5 } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt index 629696bfc..0339a7bcc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt +++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt @@ -68,8 +68,8 @@ class TuskyApplication : Application(), HasAndroidInjector { // init the custom emoji fonts val emojiSelection = preferences.getInt(PrefKeys.EMOJI, 0) val emojiConfig = EmojiCompatFont.byId(emojiSelection) - .getConfig(this) - .setReplaceAll(true) + .getConfig(this) + .setReplaceAll(true) EmojiCompat.init(emojiConfig) // init night mode @@ -81,10 +81,10 @@ class TuskyApplication : Application(), HasAndroidInjector { } WorkManager.initialize( - this, - androidx.work.Configuration.Builder() - .setWorkerFactory(notificationWorkerFactory) - .build() + this, + androidx.work.Configuration.Builder() + .setWorkerFactory(notificationWorkerFactory) + .build() ) } @@ -104,4 +104,4 @@ class TuskyApplication : Application(), HasAndroidInjector { @JvmStatic lateinit var localeManager: LocaleManager } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt index b266444e3..2598b5e22 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaActivity.kt @@ -61,7 +61,7 @@ import java.io.File import java.io.FileNotFoundException import java.io.FileOutputStream import java.io.IOException -import java.util.* +import java.util.Locale typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit @@ -102,17 +102,16 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener val realAttachs = attachments!!.map(AttachmentViewData::attachment) // Setup the view pager. ImagePagerAdapter(this, realAttachs, initialPosition) - } else { imageUrl = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL) - ?: throw IllegalArgumentException("attachment list or image url has to be set") + ?: throw IllegalArgumentException("attachment list or image url has to be set") SingleImagePagerAdapter(this, imageUrl!!) } binding.viewPager.adapter = adapter binding.viewPager.setCurrentItem(initialPosition, false) - binding.viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() { + binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { binding.toolbar.title = getPageTitle(position) } @@ -183,17 +182,17 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener } binding.toolbar.animate().alpha(alpha) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - binding.toolbar.visibility = visibility - animation.removeListener(this) - } - }) - .start() + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + binding.toolbar.visibility = visibility + animation.removeListener(this) + } + }) + .start() } private fun getPageTitle(position: Int): CharSequence { - if(attachments == null) { + if (attachments == null) { return "" } return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size) @@ -206,8 +205,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val request = DownloadManager.Request(Uri.parse(url)) - request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, - getString(R.string.app_name) + "/" + filename) + request.setDestinationInExternalPublicDir( + Environment.DIRECTORY_PICTURES, + getString(R.string.app_name) + "/" + filename + ) downloadManager.enqueue(request) } @@ -261,7 +262,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to))) } - private var isCreating: Boolean = false private fun shareImage(directory: File, url: String) { @@ -270,7 +270,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener invalidateOptionsMenu() val file = File(directory, getTemporaryMediaFilename("png")) val futureTask: FutureTarget = - Glide.with(applicationContext).asBitmap().load(Uri.parse(url)).submit() + Glide.with(applicationContext).asBitmap().load(Uri.parse(url)).submit() Single.fromCallable { val bitmap = futureTask.get() try { @@ -284,32 +284,30 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener Log.e(TAG, "Error writing temporary media.") } return@fromCallable false - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnDispose { - futureTask.cancel(true) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnDispose { + futureTask.cancel(true) + } + .autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe( + { result -> + Log.d(TAG, "Download image result: $result") + isCreating = false + invalidateOptionsMenu() + binding.progressBarShare.visibility = View.GONE + if (result) + shareFile(file, "image/png") + }, + { error -> + isCreating = false + invalidateOptionsMenu() + binding.progressBarShare.visibility = View.GONE + Log.e(TAG, "Failed to download image", error) } - .autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe( - { result -> - Log.d(TAG, "Download image result: $result") - isCreating = false - invalidateOptionsMenu() - binding.progressBarShare.visibility = View.GONE - if (result) - shareFile(file, "image/png") - }, - { error -> - isCreating = false - invalidateOptionsMenu() - binding.progressBarShare.visibility = View.GONE - Log.e(TAG, "Failed to download image", error) - } - ) - + ) } private fun shareMediaFile(directory: File, url: String) { @@ -352,7 +350,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener } } -abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) { +abstract class ViewMediaAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { abstract fun onTransitionEnd(position: Int) } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt index f68d022f1..320f8126f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountAdapter.kt @@ -121,5 +121,4 @@ abstract class AccountAdapter internal constructo const val VIEW_TYPE_ACCOUNT = 0 const val VIEW_TYPE_FOOTER = 1 } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt index fe3b15f82..d6dd668a5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldAdapter.kt @@ -16,20 +16,23 @@ package com.keylesspalace.tusky.adapter import android.text.method.LinkMovementMethod -import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ItemAccountFieldBinding import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Field import com.keylesspalace.tusky.entity.IdentityProof import com.keylesspalace.tusky.interfaces.LinkListener -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.BindingHolder +import com.keylesspalace.tusky.util.Either +import com.keylesspalace.tusky.util.LinkHelper +import com.keylesspalace.tusky.util.emojify class AccountFieldAdapter( - private val linkListener: LinkListener, - private val animateEmojis: Boolean + private val linkListener: LinkListener, + private val animateEmojis: Boolean ) : RecyclerView.Adapter>() { var emojis: List = emptyList() @@ -47,7 +50,7 @@ class AccountFieldAdapter( val nameTextView = holder.binding.accountFieldName val valueTextView = holder.binding.accountFieldValue - if(proofOrField.isLeft()) { + if (proofOrField.isLeft()) { val identityProof = proofOrField.asLeft() nameTextView.text = identityProof.provider @@ -55,7 +58,7 @@ class AccountFieldAdapter( valueTextView.movementMethod = LinkMovementMethod.getInstance() - valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0) + valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0) } else { val field = proofOrField.asRight() val emojifiedName = field.name.emojify(emojis, nameTextView, animateEmojis) @@ -64,12 +67,11 @@ class AccountFieldAdapter( val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis) LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener) - if(field.verifiedAt != null) { - valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0) + if (field.verifiedAt != null) { + valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0) } else { - valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0 ) + valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) } } - } } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt index f7f4553a3..7ba5537b8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/AccountFieldEditAdapter.kt @@ -34,7 +34,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter fieldData.add(MutableStringPair(field.name, field.value)) } - if(fieldData.isEmpty()) { + if (fieldData.isEmpty()) { fieldData.add(MutableStringPair("", "")) } @@ -63,7 +63,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter(context, R.layout.item_autocomplete_account) { @@ -48,9 +49,8 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter(co val animateAvatar = pm.getBoolean("animateGifAvatars", false) loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar) - } return binding.root } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt index fc7cec24d..33a236056 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/BlocksAdapter.kt @@ -77,4 +77,4 @@ class BlocksAdapter( itemView.setOnClickListener { listener.onViewAccount(id) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiAdapter.kt index c5656115f..dc9ec70dc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/EmojiAdapter.kt @@ -22,15 +22,15 @@ import com.bumptech.glide.Glide import com.keylesspalace.tusky.databinding.ItemEmojiButtonBinding import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.util.BindingHolder -import java.util.* +import java.util.Locale class EmojiAdapter( - emojiList: List, - private val onEmojiSelectedListener: OnEmojiSelectedListener + emojiList: List, + private val onEmojiSelectedListener: OnEmojiSelectedListener ) : RecyclerView.Adapter>() { - private val emojiList : List = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker } - .sortedBy { it.shortcode.lowercase(Locale.ROOT) } + private val emojiList: List = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker } + .sortedBy { it.shortcode.lowercase(Locale.ROOT) } override fun getItemCount() = emojiList.size @@ -44,8 +44,8 @@ class EmojiAdapter( val emojiImageView = holder.binding.root Glide.with(emojiImageView) - .load(emoji.url) - .into(emojiImageView) + .load(emoji.url) + .into(emojiImageView) emojiImageView.setOnClickListener { onEmojiSelectedListener.onEmojiSelected(emoji.shortcode) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt index 74797b3a7..672f1fcac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowAdapter.kt @@ -16,7 +16,6 @@ package com.keylesspalace.tusky.adapter import android.view.LayoutInflater import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.interfaces.AccountActionListener @@ -36,4 +35,4 @@ class FollowAdapter( viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis) viewHolder.setupActionListener(accountActionListener) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt index a7e927433..2be8b7621 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestViewHolder.kt @@ -24,11 +24,14 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.AccountActionListener -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.emojify +import com.keylesspalace.tusky.util.loadAvatar +import com.keylesspalace.tusky.util.unicodeWrap +import com.keylesspalace.tusky.util.visible class FollowRequestViewHolder( - private val binding: ItemFollowRequestBinding, - private val showHeader: Boolean + private val binding: ItemFollowRequestBinding, + private val showHeader: Boolean ) : RecyclerView.ViewHolder(binding.root) { fun setupWithAccount(account: Account, animateAvatar: Boolean, animateEmojis: Boolean) { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt index 11089cd42..9b0a5dd90 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsAdapter.kt @@ -16,8 +16,6 @@ package com.keylesspalace.tusky.adapter import android.view.LayoutInflater import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.keylesspalace.tusky.R import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding import com.keylesspalace.tusky.interfaces.AccountActionListener @@ -38,4 +36,4 @@ class FollowRequestsAdapter( viewHolder.setupWithAccount(accountList[position], animateAvatar, animateEmojis) viewHolder.setupActionListener(accountActionListener, accountList[position].id) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsHeaderAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsHeaderAdapter.kt index 60ab40086..2480086e4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsHeaderAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/FollowRequestsHeaderAdapter.kt @@ -25,7 +25,7 @@ class FollowRequestsHeaderAdapter(private val instanceName: String, private val override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder { val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_follow_requests_header, parent, false) as TextView + .inflate(R.layout.item_follow_requests_header, parent, false) as TextView return HeaderViewHolder(view) } @@ -34,7 +34,6 @@ class FollowRequestsHeaderAdapter(private val instanceName: String, private val } override fun getItemCount() = if (accountLocked) 0 else 1 - } class HeaderViewHolder(var textView: TextView) : RecyclerView.ViewHolder(textView) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt index ebff5c5f1..6d5ddbd81 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/LoadingFooterViewHolder.kt @@ -15,7 +15,7 @@ package com.keylesspalace.tusky.adapter -import androidx.recyclerview.widget.RecyclerView import android.view.View +import androidx.recyclerview.widget.RecyclerView -class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) \ No newline at end of file +class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt index d20f783ce..9fca33e8f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/MutesAdapter.kt @@ -13,7 +13,7 @@ import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.AccountActionListener import com.keylesspalace.tusky.util.emojify import com.keylesspalace.tusky.util.loadAvatar -import java.util.* +import java.util.HashMap /** * Displays a list of muted accounts with mute/unmute account and mute/unmute notifications @@ -129,4 +129,4 @@ class MutesAdapter( itemView.setOnClickListener { listener.onViewAccount(id) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/NetworkStateViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/NetworkStateViewHolder.kt index b991def5e..cf7559908 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/NetworkStateViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/NetworkStateViewHolder.kt @@ -20,9 +20,10 @@ import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding import com.keylesspalace.tusky.util.visible -class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding, - private val retryCallback: () -> Unit) -: RecyclerView.ViewHolder(binding.root) { +class NetworkStateViewHolder( + private val binding: ItemNetworkStateBinding, + private val retryCallback: () -> Unit +) : RecyclerView.ViewHolder(binding.root) { fun setUpWithNetworkState(state: LoadState) { binding.progressBar.visible(state == LoadState.Loading) @@ -38,5 +39,4 @@ class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding, retryCallback() } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt index df05b6ae0..e80e3746d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PlaceholderViewHolder.kt @@ -38,4 +38,4 @@ class PlaceholderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) listener.onLoadMore(bindingAdapterPosition) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt index 1f57cc4e0..89b3915e6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PollAdapter.kt @@ -29,7 +29,7 @@ import com.keylesspalace.tusky.viewdata.PollOptionViewData import com.keylesspalace.tusky.viewdata.buildDescription import com.keylesspalace.tusky.viewdata.calculatePercent -class PollAdapter: RecyclerView.Adapter>() { +class PollAdapter : RecyclerView.Adapter>() { private var pollOptions: List = emptyList() private var voteCount: Int = 0 @@ -40,13 +40,14 @@ class PollAdapter: RecyclerView.Adapter>() { private var animateEmojis = false fun setup( - options: List, - voteCount: Int, - votersCount: Int?, - emojis: List, - mode: Int, - resultClickListener: View.OnClickListener?, - animateEmojis: Boolean) { + options: List, + voteCount: Int, + votersCount: Int?, + emojis: List, + mode: Int, + resultClickListener: View.OnClickListener?, + animateEmojis: Boolean + ) { this.pollOptions = options this.voteCount = voteCount this.votersCount = votersCount @@ -57,12 +58,11 @@ class PollAdapter: RecyclerView.Adapter>() { notifyDataSetChanged() } - fun getSelected() : List { + fun getSelected(): List { return pollOptions.filter { it.selected } - .map { pollOptions.indexOf(it) } + .map { pollOptions.indexOf(it) } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false) return BindingHolder(binding) @@ -82,12 +82,12 @@ class PollAdapter: RecyclerView.Adapter>() { radioButton.visible(mode == SINGLE) checkBox.visible(mode == MULTIPLE) - when(mode) { + when (mode) { RESULT -> { val percent = calculatePercent(option.votesCount, votersCount, voteCount) val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context) - .emojify(emojis, resultTextView, animateEmojis) - resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) + .emojify(emojis, resultTextView, animateEmojis) + resultTextView.text = EmojiCompat.get().process(emojifiedPollOptionText) val level = percent * 100 @@ -114,7 +114,6 @@ class PollAdapter: RecyclerView.Adapter>() { } } } - } companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt index 4206f7cfe..6b59672d1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/PreviewPollOptionsAdapter.kt @@ -23,7 +23,7 @@ import androidx.core.widget.TextViewCompat import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R -class PreviewPollOptionsAdapter: RecyclerView.Adapter() { +class PreviewPollOptionsAdapter : RecyclerView.Adapter() { private var options: List = emptyList() private var multiple: Boolean = false @@ -60,7 +60,6 @@ class PreviewPollOptionsAdapter: RecyclerView.Adapter() { textView.setOnClickListener(clickListener) } - } -class PreviewViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) +class PreviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/TabAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/TabAdapter.kt index bec07f067..994630a14 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/TabAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/TabAdapter.kt @@ -43,10 +43,11 @@ interface ItemInteractionListener { fun onChipClicked(tab: TabData, tabPosition: Int, chipPosition: Int) } -class TabAdapter(private var data: List, - private val small: Boolean, - private val listener: ItemInteractionListener, - private var removeButtonEnabled: Boolean = false +class TabAdapter( + private var data: List, + private val small: Boolean, + private val listener: ItemInteractionListener, + private var removeButtonEnabled: Boolean = false ) : RecyclerView.Adapter>() { fun updateData(newData: List) { @@ -77,7 +78,6 @@ class TabAdapter(private var data: List, binding.textView.setOnClickListener { listener.onTabAdded(tab) } - } else { val binding = holder.binding as ItemTabPreferenceBinding @@ -102,9 +102,9 @@ class TabAdapter(private var data: List, } binding.removeButton.isEnabled = removeButtonEnabled ThemeUtils.setDrawableTint( - holder.itemView.context, - binding.removeButton.drawable, - (if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled) + holder.itemView.context, + binding.removeButton.drawable, + (if (removeButtonEnabled) android.R.attr.textColorTertiary else R.attr.textColorDisabled) ) if (tab.id == HASHTAG) { @@ -118,14 +118,14 @@ class TabAdapter(private var data: List, tab.arguments.forEachIndexed { i, arg -> val chip = binding.chipGroup.getChildAt(i).takeUnless { it.id == R.id.actionChip } as Chip? - ?: Chip(context).apply { - binding.chipGroup.addView(this, binding.chipGroup.size - 1) - chipIconTint = ColorStateList.valueOf(ThemeUtils.getColor(context, android.R.attr.textColorPrimary)) - } + ?: Chip(context).apply { + binding.chipGroup.addView(this, binding.chipGroup.size - 1) + chipIconTint = ColorStateList.valueOf(ThemeUtils.getColor(context, android.R.attr.textColorPrimary)) + } chip.text = arg - if(tab.arguments.size <= 1) { + if (tab.arguments.size <= 1) { chip.chipIcon = null chip.setOnClickListener(null) } else { @@ -136,14 +136,13 @@ class TabAdapter(private var data: List, } } - while(binding.chipGroup.size - 1 > tab.arguments.size) { + while (binding.chipGroup.size - 1 > tab.arguments.size) { binding.chipGroup.removeViewAt(tab.arguments.size) } binding.actionChip.setOnClickListener { listener.onActionChipClicked(tab, holder.bindingAdapterPosition) } - } else { binding.chipGroup.hide() } diff --git a/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.kt index 1d05dff11..8abbbd5f6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/adapter/ThreadAdapter.kt @@ -111,8 +111,8 @@ class ThreadAdapter( fun getItem(position: Int): StatusViewData.Concrete? = statuses.getOrNull(position) fun setDetailedStatusPosition(position: Int) { - if (position != detailedStatusPosition - && detailedStatusPosition != RecyclerView.NO_POSITION + if (position != detailedStatusPosition && + detailedStatusPosition != RecyclerView.NO_POSITION ) { val prior = detailedStatusPosition detailedStatusPosition = position @@ -126,4 +126,4 @@ class ThreadAdapter( private const val VIEW_TYPE_STATUS = 0 private const val VIEW_TYPE_STATUS_DETAILED = 1 } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt index d418321fe..7b5ecf77a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/CacheUpdater.kt @@ -9,10 +9,10 @@ import io.reactivex.rxjava3.schedulers.Schedulers import javax.inject.Inject class CacheUpdater @Inject constructor( - eventHub: EventHub, - accountManager: AccountManager, - private val appDatabase: AppDatabase, - gson: Gson + eventHub: EventHub, + accountManager: AccountManager, + private val appDatabase: AppDatabase, + gson: Gson ) { private val disposable: Disposable @@ -27,7 +27,7 @@ class CacheUpdater @Inject constructor( is ReblogEvent -> timelineDao.setReblogged(accountId, event.statusId, event.reblog) is BookmarkEvent -> - timelineDao.setBookmarked(accountId, event.statusId, event.bookmark ) + timelineDao.setBookmarked(accountId, event.statusId, event.bookmark) is UnfollowEvent -> timelineDao.removeAllByUser(accountId, event.accountId) is StatusDeletedEvent -> @@ -49,7 +49,7 @@ class CacheUpdater @Inject constructor( appDatabase.timelineDao().removeAllForAccount(accountId) appDatabase.timelineDao().removeAllUsersForAccount(accountId) } - .subscribeOn(Schedulers.io()) - .subscribe() + .subscribeOn(Schedulers.io()) + .subscribe() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt index 13baf07f0..aef4525cb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/Events.kt @@ -19,6 +19,6 @@ data class ProfileEditedEvent(val newProfileData: Account) : Dispatchable data class PreferenceChangedEvent(val preferenceKey: String) : Dispatchable data class MainTabsChangedEvent(val newTabs: List) : Dispatchable data class PollVoteEvent(val statusId: String, val poll: Poll) : Dispatchable -data class DomainMuteEvent(val instance: String): Dispatchable -data class AnnouncementReadEvent(val announcementId: String): Dispatchable -data class PinEvent(val statusId: String, val pinned: Boolean): Dispatchable +data class DomainMuteEvent(val instance: String) : Dispatchable +data class AnnouncementReadEvent(val announcementId: String) : Dispatchable +data class PinEvent(val statusId: String, val pinned: Boolean) : Dispatchable diff --git a/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt b/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt index d2f182d81..316974935 100644 --- a/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt +++ b/app/src/main/java/com/keylesspalace/tusky/appstore/EventsHub.kt @@ -19,4 +19,4 @@ object EventHubImpl : EventHub { override fun dispatch(event: Dispatchable) { eventsSubject.onNext(event) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt index 5014b52e2..2b05ee088 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementAdapter.kt @@ -31,17 +31,17 @@ import com.keylesspalace.tusky.util.BindingHolder import com.keylesspalace.tusky.util.LinkHelper import com.keylesspalace.tusky.util.emojify -interface AnnouncementActionListener: LinkListener { +interface AnnouncementActionListener : LinkListener { fun openReactionPicker(announcementId: String, target: View) fun addReaction(announcementId: String, name: String) fun removeReaction(announcementId: String, name: String) } class AnnouncementAdapter( - private var items: List = emptyList(), - private val listener: AnnouncementActionListener, - private val wellbeingEnabled: Boolean = false, - private val animateEmojis: Boolean = false + private var items: List = emptyList(), + private val listener: AnnouncementActionListener, + private val wellbeingEnabled: Boolean = false, + private val animateEmojis: Boolean = false ) : RecyclerView.Adapter>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { @@ -67,12 +67,12 @@ class AnnouncementAdapter( } item.reactions.forEachIndexed { i, reaction -> - (chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip? - ?: Chip(ContextThemeWrapper(chips.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply { - isCheckable = true - checkedIcon = null - chips.addView(this, i) - }) + chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip? + ?: Chip(ContextThemeWrapper(chips.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply { + isCheckable = true + checkedIcon = null + chips.addView(this, i) + } .apply { val emojiText = if (reaction.url == null) { reaction.name @@ -80,16 +80,18 @@ class AnnouncementAdapter( context.getString(R.string.emoji_shortcode_format, reaction.name) } this.text = ("$emojiText ${reaction.count}") - .emojify( - listOf(Emoji( - reaction.name, - reaction.url ?: "", - reaction.staticUrl ?: "", - null - )), - this, - animateEmojis - ) + .emojify( + listOf( + Emoji( + reaction.name, + reaction.url ?: "", + reaction.staticUrl ?: "", + null + ) + ), + this, + animateEmojis + ) isChecked = reaction.me diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt index 9f61bea86..738476f5d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsActivity.kt @@ -34,7 +34,12 @@ import com.keylesspalace.tusky.databinding.ActivityAnnouncementsBinding import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.Error +import com.keylesspalace.tusky.util.Loading +import com.keylesspalace.tusky.util.Success +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.viewBinding import com.keylesspalace.tusky.view.EmojiPicker import javax.inject.Inject @@ -52,13 +57,13 @@ class AnnouncementsActivity : BottomSheetActivity(), AnnouncementActionListener, private val picker by lazy { EmojiPicker(this) } private val pickerDialog by lazy { PopupWindow(this) - .apply { - contentView = picker - isFocusable = true - setOnDismissListener { - currentAnnouncementId = null - } + .apply { + contentView = picker + isFocusable = true + setOnDismissListener { + currentAnnouncementId = null } + } } private var currentAnnouncementId: String? = null diff --git a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt index 2b4d61a40..fef8f9326 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/announcements/AnnouncementsViewModel.kt @@ -27,15 +27,20 @@ import com.keylesspalace.tusky.entity.Announcement import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Instance import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.Either +import com.keylesspalace.tusky.util.Error +import com.keylesspalace.tusky.util.Loading +import com.keylesspalace.tusky.util.Resource +import com.keylesspalace.tusky.util.RxAwareViewModel +import com.keylesspalace.tusky.util.Success import io.reactivex.rxjava3.core.Single import javax.inject.Inject class AnnouncementsViewModel @Inject constructor( - accountManager: AccountManager, - private val appDatabase: AppDatabase, - private val mastodonApi: MastodonApi, - private val eventHub: EventHub + accountManager: AccountManager, + private val appDatabase: AppDatabase, + private val mastodonApi: MastodonApi, + private val eventHub: EventHub ) : RxAwareViewModel() { private val announcementsMutable = MutableLiveData>>() @@ -45,139 +50,153 @@ class AnnouncementsViewModel @Inject constructor( val emojis: LiveData> = emojisMutable init { - Single.zip(mastodonApi.getCustomEmojis(), - appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!) - .map> { Either.Left(it) } - .onErrorResumeNext { - mastodonApi.getInstance() - .map { Either.Right(it) } - }, - { emojis, either -> - either.asLeftOrNull()?.copy(emojiList = emojis) - ?: InstanceEntity( - accountManager.activeAccount?.domain!!, - emojis, - either.asRight().maxTootChars, - either.asRight().pollLimits?.maxOptions, - either.asRight().pollLimits?.maxOptionChars, - either.asRight().version - ) - }) - .doOnSuccess { - appDatabase.instanceDao().insertOrReplace(it) - } - .subscribe({ + Single.zip( + mastodonApi.getCustomEmojis(), + appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!) + .map> { Either.Left(it) } + .onErrorResumeNext { + mastodonApi.getInstance() + .map { Either.Right(it) } + }, + { emojis, either -> + either.asLeftOrNull()?.copy(emojiList = emojis) + ?: InstanceEntity( + accountManager.activeAccount?.domain!!, + emojis, + either.asRight().maxTootChars, + either.asRight().pollLimits?.maxOptions, + either.asRight().pollLimits?.maxOptionChars, + either.asRight().version + ) + } + ) + .doOnSuccess { + appDatabase.instanceDao().insertOrReplace(it) + } + .subscribe( + { emojisMutable.postValue(it.emojiList.orEmpty()) - }, { + }, + { Log.w(TAG, "Failed to get custom emojis.", it) - }) - .autoDispose() + } + ) + .autoDispose() } fun load() { announcementsMutable.postValue(Loading()) mastodonApi.listAnnouncements() - .subscribe({ + .subscribe( + { announcementsMutable.postValue(Success(it)) it.filter { announcement -> !announcement.read } - .forEach { announcement -> - mastodonApi.dismissAnnouncement(announcement.id) - .subscribe( - { - eventHub.dispatch(AnnouncementReadEvent(announcement.id)) - }, - { throwable -> - Log.d(TAG, "Failed to mark announcement as read.", throwable) - } - ) - .autoDispose() - } - }, { + .forEach { announcement -> + mastodonApi.dismissAnnouncement(announcement.id) + .subscribe( + { + eventHub.dispatch(AnnouncementReadEvent(announcement.id)) + }, + { throwable -> + Log.d(TAG, "Failed to mark announcement as read.", throwable) + } + ) + .autoDispose() + } + }, + { announcementsMutable.postValue(Error(cause = it)) - }) - .autoDispose() + } + ) + .autoDispose() } fun addReaction(announcementId: String, name: String) { mastodonApi.addAnnouncementReaction(announcementId, name) - .subscribe({ + .subscribe( + { announcementsMutable.postValue( - Success( - announcements.value!!.data!!.map { announcement -> - if (announcement.id == announcementId) { - announcement.copy( - reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) { - announcement.reactions.map { reaction -> - if (reaction.name == name) { - reaction.copy( - count = reaction.count + 1, - me = true - ) - } else { - reaction - } - } - } else { - listOf( - *announcement.reactions.toTypedArray(), - emojis.value!!.find { emoji -> emoji.shortcode == name } - !!.run { - Announcement.Reaction( - name, - 1, - true, - url, - staticUrl - ) - } - ) - } - ) + Success( + announcements.value!!.data!!.map { announcement -> + if (announcement.id == announcementId) { + announcement.copy( + reactions = if (announcement.reactions.find { reaction -> reaction.name == name } != null) { + announcement.reactions.map { reaction -> + if (reaction.name == name) { + reaction.copy( + count = reaction.count + 1, + me = true + ) + } else { + reaction + } + } } else { - announcement + listOf( + *announcement.reactions.toTypedArray(), + emojis.value!!.find { emoji -> emoji.shortcode == name } + !!.run { + Announcement.Reaction( + name, + 1, + true, + url, + staticUrl + ) + } + ) } - } - ) + ) + } else { + announcement + } + } + ) ) - }, { + }, + { Log.w(TAG, "Failed to add reaction to the announcement.", it) - }) - .autoDispose() + } + ) + .autoDispose() } fun removeReaction(announcementId: String, name: String) { mastodonApi.removeAnnouncementReaction(announcementId, name) - .subscribe({ + .subscribe( + { announcementsMutable.postValue( - Success( - announcements.value!!.data!!.map { announcement -> - if (announcement.id == announcementId) { - announcement.copy( - reactions = announcement.reactions.mapNotNull { reaction -> - if (reaction.name == name) { - if (reaction.count > 1) { - reaction.copy( - count = reaction.count - 1, - me = false - ) - } else { - null - } - } else { - reaction - } - } - ) - } else { - announcement + Success( + announcements.value!!.data!!.map { announcement -> + if (announcement.id == announcementId) { + announcement.copy( + reactions = announcement.reactions.mapNotNull { reaction -> + if (reaction.name == name) { + if (reaction.count > 1) { + reaction.copy( + count = reaction.count - 1, + me = false + ) + } else { + null + } + } else { + reaction + } } - } - ) + ) + } else { + announcement + } + } + ) ) - }, { + }, + { Log.w(TAG, "Failed to remove reaction from the announcement.", it) - }) - .autoDispose() + } + ) + .autoDispose() } companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 9458b26a3..bfce01304 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -32,7 +32,10 @@ import android.view.KeyEvent import android.view.MenuItem import android.view.View import android.view.ViewGroup -import android.widget.* +import android.widget.ImageButton +import android.widget.LinearLayout +import android.widget.PopupMenu +import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.annotation.ColorInt @@ -70,7 +73,20 @@ import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.NewPoll import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.ComposeTokenizer +import com.keylesspalace.tusky.util.PickMediaFiles +import com.keylesspalace.tusky.util.ThemeUtils +import com.keylesspalace.tusky.util.afterTextChanged +import com.keylesspalace.tusky.util.combineLiveData +import com.keylesspalace.tusky.util.combineOptionalLiveData +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.highlightSpans +import com.keylesspalace.tusky.util.loadAvatar +import com.keylesspalace.tusky.util.onTextChanged +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.viewBinding +import com.keylesspalace.tusky.util.visible +import com.keylesspalace.tusky.util.withLifecycleContext import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.utils.colorInt @@ -83,7 +99,8 @@ import javax.inject.Inject import kotlin.math.max import kotlin.math.min -class ComposeActivity : BaseActivity(), +class ComposeActivity : + BaseActivity(), ComposeOptionsListener, ComposeAutoCompleteAdapter.AutocompletionProvider, OnEmojiSelectedListener, @@ -288,8 +305,9 @@ class ComposeActivity : BaseActivity(), } // work around Android platform bug -> https://issuetracker.google.com/issues/67102093 - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O - || Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O || + Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1 + ) { binding.composeEditField.setLayerType(View.LAYER_TYPE_SOFTWARE, null) } } @@ -330,9 +348,9 @@ class ComposeActivity : BaseActivity(), updateScheduleButton() } combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll -> - val active = poll == null - && media!!.size != 4 - && (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE) + val active = poll == null && + media!!.size != 4 && + (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE) enableButton(binding.composeAddMediaButton, active, active) enablePollButton(media.isNullOrEmpty()) }.subscribe() @@ -393,7 +411,6 @@ class ComposeActivity : BaseActivity(), setDisplayShowHomeEnabled(true) setHomeAsUpIndicator(R.drawable.ic_close_24dp) } - } private fun setupAvatar(preferences: SharedPreferences, activeAccount: AccountEntity) { @@ -409,8 +426,10 @@ class ComposeActivity : BaseActivity(), avatarSize / 8, animateAvatars ) - binding.composeAvatar.contentDescription = getString(R.string.compose_active_account_description, - activeAccount.fullName) + binding.composeAvatar.contentDescription = getString( + R.string.compose_active_account_description, + activeAccount.fullName + ) } private fun replaceTextAtCaret(text: CharSequence) { @@ -468,7 +487,6 @@ class ComposeActivity : BaseActivity(), } } - private fun atButtonClicked() { prependSelectedWordsWith("@") } @@ -484,7 +502,7 @@ class ComposeActivity : BaseActivity(), private fun displayTransientError(@StringRes stringId: Int) { val bar = Snackbar.make(binding.activityCompose, stringId, Snackbar.LENGTH_LONG) - //necessary so snackbar is shown over everything + // necessary so snackbar is shown over everything bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation) bar.show() } @@ -502,7 +520,6 @@ class ComposeActivity : BaseActivity(), binding.composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp) binding.composeHideMediaButton.isClickable = false ContextCompat.getColor(this, R.color.transparent_tusky_blue) - } else { binding.composeHideMediaButton.isClickable = true if (markMediaSensitive) { @@ -611,13 +628,15 @@ class ComposeActivity : BaseActivity(), private fun onMediaPick() { addMediaBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { - //Wait until bottom sheet is not collapsed and show next screen after + // Wait until bottom sheet is not collapsed and show next screen after if (newState == BottomSheetBehavior.STATE_COLLAPSED) { addMediaBehavior.removeBottomSheetCallback(this) if (ContextCompat.checkSelfPermission(this@ComposeActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this@ComposeActivity, + ActivityCompat.requestPermissions( + this@ComposeActivity, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), - PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) + PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE + ) } else { pickMediaFile.launch(true) } @@ -633,8 +652,10 @@ class ComposeActivity : BaseActivity(), private fun openPollDialog() { addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED val instanceParams = viewModel.instanceParams.value!! - showAddPollDialog(this, viewModel.poll.value, instanceParams.pollMaxOptions, - instanceParams.pollMaxLength, viewModel::updatePoll) + showAddPollDialog( + this, viewModel.poll.value, instanceParams.pollMaxOptions, + instanceParams.pollMaxLength, viewModel::updatePoll + ) } private fun setupPollView() { @@ -755,14 +776,17 @@ class ComposeActivity : BaseActivity(), if (viewModel.media.value!!.isNotEmpty()) { finishingUploadDialog = ProgressDialog.show( 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 + ) } - viewModel.sendStatus(contentText, spoilerText).observe(this, { - finishingUploadDialog?.dismiss() - deleteDraftAndFinish() - }) - + viewModel.sendStatus(contentText, spoilerText).observe( + this, + { + finishingUploadDialog?.dismiss() + deleteDraftAndFinish() + } + ) } else { binding.composeEditField.error = getString(R.string.error_compose_character_limit) enableButtons(true) @@ -776,10 +800,12 @@ class ComposeActivity : BaseActivity(), if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { pickMediaFile.launch(true) } else { - Snackbar.make(binding.activityCompose, R.string.error_media_upload_permission, - Snackbar.LENGTH_SHORT).apply { + Snackbar.make( + binding.activityCompose, R.string.error_media_upload_permission, + Snackbar.LENGTH_SHORT + ).apply { setAction(R.string.action_retry) { onMediaPick() } - //necessary so snackbar is shown over everything + // necessary so snackbar is shown over everything view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation) show() } @@ -798,24 +824,30 @@ class ComposeActivity : BaseActivity(), } // Continue only if the File was successfully created - photoUploadUri = FileProvider.getUriForFile(this, + photoUploadUri = FileProvider.getUriForFile( + this, BuildConfig.APPLICATION_ID + ".fileprovider", - photoFile) + photoFile + ) takePicture.launch(photoUploadUri) } private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) { button.isEnabled = clickable - ThemeUtils.setDrawableTint(this, button.drawable, + ThemeUtils.setDrawableTint( + this, button.drawable, if (colorActive) android.R.attr.textColorTertiary - else R.attr.textColorDisabled) + else R.attr.textColorDisabled + ) } private fun enablePollButton(enable: Boolean) { binding.addPollTextActionTextView.isEnabled = enable - val textColor = ThemeUtils.getColor(this, + val textColor = ThemeUtils.getColor( + this, if (enable) android.R.attr.textColorTertiary - else R.attr.textColorDisabled) + else R.attr.textColorDisabled + ) binding.addPollTextActionTextView.setTextColor(textColor) binding.addPollTextActionTextView.compoundDrawablesRelative[0].colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN) } @@ -847,7 +879,6 @@ class ComposeActivity : BaseActivity(), } displayTransientError(errorId) } - } } } @@ -881,7 +912,8 @@ class ComposeActivity : BaseActivity(), if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED || addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED || emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED || - scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { + scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED + ) { composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN emojiBehavior.state = BottomSheetBehavior.STATE_HIDDEN diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt index a08aebc08..0b1fa8c41 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt @@ -30,9 +30,9 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.compose.view.ProgressImageView class MediaPreviewAdapter( - context: Context, - private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit, - private val onRemove: (ComposeActivity.QueuedMedia) -> Unit + context: Context, + private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit, + private val onRemove: (ComposeActivity.QueuedMedia) -> Unit ) : RecyclerView.Adapter() { fun submitList(list: List) { @@ -57,7 +57,7 @@ class MediaPreviewAdapter( } private val thumbnailViewSize = - context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size) + context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size) override fun getItemCount(): Int = differ.currentList.size @@ -74,31 +74,34 @@ class MediaPreviewAdapter( holder.progressImageView.setImageResource(R.drawable.ic_music_box_preview_24dp) } else { Glide.with(holder.itemView.context) - .load(item.uri) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .dontAnimate() - .into(holder.progressImageView) + .load(item.uri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .dontAnimate() + .into(holder.progressImageView) } } - private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean { - return oldItem.localId == newItem.localId - } + private val differ = AsyncListDiffer( + this, + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean { + return oldItem.localId == newItem.localId + } - override fun areContentsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean { - return oldItem == newItem + override fun areContentsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean { + return oldItem == newItem + } } - }) + ) - inner class PreviewViewHolder(val progressImageView: ProgressImageView) - : RecyclerView.ViewHolder(progressImageView) { + inner class PreviewViewHolder(val progressImageView: ProgressImageView) : + RecyclerView.ViewHolder(progressImageView) { init { val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize) val margin = itemView.context.resources - .getDimensionPixelSize(R.dimen.compose_media_preview_margin) + .getDimensionPixelSize(R.dimen.compose_media_preview_margin) val marginBottom = itemView.context.resources - .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) + .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) layoutParams.setMargins(margin, 0, margin, marginBottom) progressImageView.layoutParams = layoutParams progressImageView.scaleType = ImageView.ScaleType.CENTER_CROP @@ -107,4 +110,4 @@ class MediaPreviewAdapter( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt index cf5da80d0..6ff361a9b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt @@ -28,7 +28,10 @@ import com.keylesspalace.tusky.components.compose.ComposeActivity.QueuedMedia import com.keylesspalace.tusky.entity.Attachment import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.ProgressRequestBody -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.MEDIA_SIZE_UNKNOWN +import com.keylesspalace.tusky.util.getImageSquarePixels +import com.keylesspalace.tusky.util.getMediaSize +import com.keylesspalace.tusky.util.randomAlphanumericString import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers @@ -37,7 +40,7 @@ import okhttp3.MultipartBody import java.io.File import java.io.FileOutputStream import java.io.IOException -import java.util.* +import java.util.Date sealed class UploadEvent { data class ProgressEvent(val percentage: Int) : UploadEvent() @@ -50,9 +53,9 @@ fun createNewImageFile(context: Context): File { val imageFileName = "Tusky_${randomId}_" val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) return File.createTempFile( - imageFileName, /* prefix */ - ".jpg", /* suffix */ - storageDir /* directory */ + imageFileName, /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ ) } @@ -69,18 +72,18 @@ class MediaTypeException : Exception() class CouldNotOpenFileException : Exception() class MediaUploaderImpl( - private val context: Context, - private val mastodonApi: MastodonApi + private val context: Context, + private val mastodonApi: MastodonApi ) : MediaUploader { override fun uploadMedia(media: QueuedMedia): Observable { return Observable - .fromCallable { - if (shouldResizeMedia(media)) { - downsize(media) - } else media - } - .switchMap { upload(it) } - .subscribeOn(Schedulers.io()) + .fromCallable { + if (shouldResizeMedia(media)) { + downsize(media) + } else media + } + .switchMap { upload(it) } + .subscribeOn(Schedulers.io()) } override fun prepareMedia(inUri: Uri): Single { @@ -101,12 +104,13 @@ class MediaUploaderImpl( val file = File.createTempFile("randomTemp1", suffix, context.cacheDir) FileOutputStream(file.absoluteFile).use { out -> input.copyTo(out) - uri = FileProvider.getUriForFile(context, - BuildConfig.APPLICATION_ID + ".fileprovider", - file) + uri = FileProvider.getUriForFile( + context, + BuildConfig.APPLICATION_ID + ".fileprovider", + file + ) mediaSize = getMediaSize(contentResolver, uri) } - } } catch (e: IOException) { Log.w(TAG, e) @@ -151,20 +155,22 @@ class MediaUploaderImpl( var mimeType = contentResolver.getType(media.uri) val map = MimeTypeMap.getSingleton() val fileExtension = map.getExtensionFromMimeType(mimeType) - val filename = String.format("%s_%s_%s.%s", - context.getString(R.string.app_name), - Date().time.toString(), - randomAlphanumericString(10), - fileExtension) + val filename = "%s_%s_%s.%s".format( + context.getString(R.string.app_name), + Date().time.toString(), + randomAlphanumericString(10), + fileExtension + ) val stream = contentResolver.openInputStream(media.uri) if (mimeType == null) mimeType = "multipart/form-data" - var lastProgress = -1 - val fileBody = ProgressRequestBody(stream, media.mediaSize, - mimeType.toMediaTypeOrNull()) { percentage -> + val fileBody = ProgressRequestBody( + stream, media.mediaSize, + mimeType.toMediaTypeOrNull() + ) { percentage -> if (percentage != lastProgress) { emitter.onNext(UploadEvent.ProgressEvent(percentage)) } @@ -180,12 +186,15 @@ class MediaUploaderImpl( } val uploadDisposable = mastodonApi.uploadMedia(body, description) - .subscribe({ attachment -> + .subscribe( + { attachment -> emitter.onNext(UploadEvent.FinishedEvent(attachment)) emitter.onComplete() - }, { e -> + }, + { e -> emitter.onError(e) - }) + } + ) // Cancel the request when our observable is cancelled emitter.setDisposable(uploadDisposable) @@ -194,15 +203,16 @@ class MediaUploaderImpl( private fun downsize(media: QueuedMedia): QueuedMedia { val file = createNewImageFile(context) - DownsizeImageTask.resize(arrayOf(media.uri), - STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file) + DownsizeImageTask.resize( + arrayOf(media.uri), + STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file + ) return media.copy(uri = file.toUri(), mediaSize = file.length()) } private fun shouldResizeMedia(media: QueuedMedia): Boolean { - return media.type == QueuedMedia.Type.IMAGE - && (media.mediaSize > STATUS_IMAGE_SIZE_LIMIT - || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT) + return media.type == QueuedMedia.Type.IMAGE && + (media.mediaSize > STATUS_IMAGE_SIZE_LIMIT || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT) } private companion object { @@ -211,6 +221,5 @@ class MediaUploaderImpl( private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels - } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollDialog.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollDialog.kt index 6ace77bc3..7a4f73898 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollDialog.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollDialog.kt @@ -26,33 +26,33 @@ import com.keylesspalace.tusky.databinding.DialogAddPollBinding import com.keylesspalace.tusky.entity.NewPoll fun showAddPollDialog( - context: Context, - poll: NewPoll?, - maxOptionCount: Int, - maxOptionLength: Int, - onUpdatePoll: (NewPoll) -> Unit + context: Context, + poll: NewPoll?, + maxOptionCount: Int, + maxOptionLength: Int, + onUpdatePoll: (NewPoll) -> Unit ) { val binding = DialogAddPollBinding.inflate(LayoutInflater.from(context)) val dialog = AlertDialog.Builder(context) - .setIcon(R.drawable.ic_poll_24dp) - .setTitle(R.string.create_poll_title) - .setView(binding.root) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, null) - .create() + .setIcon(R.drawable.ic_poll_24dp) + .setTitle(R.string.create_poll_title) + .setView(binding.root) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, null) + .create() val adapter = AddPollOptionsAdapter( - options = poll?.options?.toMutableList() ?: mutableListOf("", ""), - maxOptionLength = maxOptionLength, - onOptionRemoved = { valid -> - binding.addChoiceButton.isEnabled = true - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid - }, - onOptionChanged = { valid -> - dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid - } + options = poll?.options?.toMutableList() ?: mutableListOf("", ""), + maxOptionLength = maxOptionLength, + onOptionRemoved = { valid -> + binding.addChoiceButton.isEnabled = true + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid + }, + onOptionChanged = { valid -> + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = valid + } ) binding.pollChoices.adapter = adapter @@ -80,13 +80,15 @@ fun showAddPollDialog( val selectedPollDurationId = binding.pollDurationSpinner.selectedItemPosition val pollDuration = context.resources - .getIntArray(R.array.poll_duration_values)[selectedPollDurationId] + .getIntArray(R.array.poll_duration_values)[selectedPollDurationId] - onUpdatePoll(NewPoll( + onUpdatePoll( + NewPoll( options = adapter.pollOptions, expiresIn = pollDuration, multiple = binding.multipleChoicesCheckBox.isChecked - )) + ) + ) dialog.dismiss() } @@ -96,4 +98,4 @@ fun showAddPollDialog( // make the dialog focusable so the keyboard does not stay behind it dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt index c3da2c1c2..3640ffa97 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/AddPollOptionsAdapter.kt @@ -27,11 +27,11 @@ import com.keylesspalace.tusky.util.onTextChanged import com.keylesspalace.tusky.util.visible class AddPollOptionsAdapter( - private var options: MutableList, - private val maxOptionLength: Int, - private val onOptionRemoved: (Boolean) -> Unit, - private val onOptionChanged: (Boolean) -> Unit -): RecyclerView.Adapter>() { + private var options: MutableList, + private val maxOptionLength: Int, + private val onOptionRemoved: (Boolean) -> Unit, + private val onOptionChanged: (Boolean) -> Unit +) : RecyclerView.Adapter>() { val pollOptions: List get() = options.toList() @@ -48,7 +48,7 @@ class AddPollOptionsAdapter( binding.optionEditText.onTextChanged { s, _, _, _ -> val pos = holder.bindingAdapterPosition - if(pos != RecyclerView.NO_POSITION) { + if (pos != RecyclerView.NO_POSITION) { options[pos] = s.toString() onOptionChanged(validateInput()) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt index 13601c1a8..0c15eff0d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/dialog/CaptionDialog.kt @@ -40,9 +40,10 @@ import com.keylesspalace.tusky.util.withLifecycleContext // https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32 private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500 -fun T.makeCaptionDialog(existingDescription: String?, - previewUri: Uri, - onUpdateDescription: (String) -> LiveData +fun T.makeCaptionDialog( + existingDescription: String?, + previewUri: Uri, + onUpdateDescription: (String) -> LiveData ) where T : Activity, T : LifecycleOwner { val dialogLayout = LinearLayout(this) val padding = Utils.dpToPx(this, 8) @@ -60,14 +61,18 @@ fun T.makeCaptionDialog(existingDescription: String?, (imageView.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, 0) val input = EditText(this) - input.hint = resources.getQuantityString(R.plurals.hint_describe_for_visually_impaired, - MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT) + input.hint = resources.getQuantityString( + R.plurals.hint_describe_for_visually_impaired, + MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT + ) dialogLayout.addView(input) (input.layoutParams as LinearLayout.LayoutParams).setMargins(margin, margin, margin, margin) input.setLines(2) - input.inputType = (InputType.TYPE_CLASS_TEXT - or InputType.TYPE_TEXT_FLAG_MULTI_LINE - or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) + input.inputType = ( + InputType.TYPE_CLASS_TEXT + or InputType.TYPE_TEXT_FLAG_MULTI_LINE + or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES + ) input.setText(existingDescription) input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT)) @@ -75,41 +80,40 @@ fun T.makeCaptionDialog(existingDescription: String?, onUpdateDescription(input.text.toString()) withLifecycleContext { onUpdateDescription(input.text.toString()) - .observe { success -> if (!success) showFailedCaptionMessage() } - + .observe { success -> if (!success) showFailedCaptionMessage() } } dialog.dismiss() } val dialog = AlertDialog.Builder(this) - .setView(dialogLayout) - .setPositiveButton(android.R.string.ok, okListener) - .setNegativeButton(android.R.string.cancel, null) - .create() + .setView(dialogLayout) + .setPositiveButton(android.R.string.ok, okListener) + .setNegativeButton(android.R.string.cancel, null) + .create() val window = dialog.window window?.setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + ) dialog.show() // Load the image and manually set it into the ImageView because it doesn't have a fixed size. Glide.with(this) - .load(previewUri) - .downsample(DownsampleStrategy.CENTER_INSIDE) - .into(object : CustomTarget(4096, 4096) { - override fun onLoadCleared(placeholder: Drawable?) { - imageView.setImageDrawable(placeholder) - } + .load(previewUri) + .downsample(DownsampleStrategy.CENTER_INSIDE) + .into(object : CustomTarget(4096, 4096) { + override fun onLoadCleared(placeholder: Drawable?) { + imageView.setImageDrawable(placeholder) + } - override fun onResourceReady(resource: Drawable, transition: Transition?) { - imageView.setImageDrawable(resource) - } - }) + override fun onResourceReady(resource: Drawable, transition: Transition?) { + imageView.setImageDrawable(resource) + } + }) } - private fun Activity.showFailedCaptionMessage() { Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeOptionsView.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeOptionsView.kt index 8f80c76df..02ec9a9bc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeOptionsView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeOptionsView.kt @@ -57,12 +57,10 @@ class ComposeOptionsView @JvmOverloads constructor(context: Context, attrs: Attr R.id.directRadioButton else -> R.id.directRadioButton - } check(selectedButton) } - } interface ComposeOptionsListener { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt index 0a5e1c33a..a8403c954 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/EditTextTyped.kt @@ -16,25 +16,27 @@ package com.keylesspalace.tusky.components.compose.view import android.content.Context -import androidx.emoji.widget.EmojiEditTextHelper -import androidx.core.view.inputmethod.EditorInfoCompat -import androidx.core.view.inputmethod.InputConnectionCompat -import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView import android.text.InputType import android.text.method.KeyListener import android.util.AttributeSet import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection +import androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView +import androidx.core.view.inputmethod.EditorInfoCompat +import androidx.core.view.inputmethod.InputConnectionCompat +import androidx.emoji.widget.EmojiEditTextHelper -class EditTextTyped @JvmOverloads constructor(context: Context, - attributeSet: AttributeSet? = null) - : AppCompatMultiAutoCompleteTextView(context, attributeSet) { +class EditTextTyped @JvmOverloads constructor( + context: Context, + attributeSet: AttributeSet? = null +) : + AppCompatMultiAutoCompleteTextView(context, attributeSet) { private var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this) init { - //fix a bug with autocomplete and some keyboards + // fix a bug with autocomplete and some keyboards val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) inputType = newInputType super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener)) @@ -52,8 +54,13 @@ class EditTextTyped @JvmOverloads constructor(context: Context, val connection = super.onCreateInputConnection(editorInfo) return if (onCommitContentListener != null) { EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) - getEmojiEditTextHelper().onCreateInputConnection(InputConnectionCompat.createWrapper(connection, editorInfo, - onCommitContentListener!!), editorInfo)!! + getEmojiEditTextHelper().onCreateInputConnection( + InputConnectionCompat.createWrapper( + connection, editorInfo, + onCommitContentListener!! + ), + editorInfo + )!! } else { connection } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/PollPreviewView.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/PollPreviewView.kt index 1126047d8..c55e8fce7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/PollPreviewView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/PollPreviewView.kt @@ -25,10 +25,11 @@ import com.keylesspalace.tusky.databinding.ViewPollPreviewBinding import com.keylesspalace.tusky.entity.NewPoll class PollPreviewView @JvmOverloads constructor( - context: Context?, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0) - : LinearLayout(context, attrs, defStyleAttr) { + context: Context?, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : + LinearLayout(context, attrs, defStyleAttr) { private val adapter = PreviewPollOptionsAdapter() @@ -46,7 +47,7 @@ class PollPreviewView @JvmOverloads constructor( binding.pollPreviewOptions.adapter = adapter } - fun setPoll(poll: NewPoll){ + fun setPoll(poll: NewPoll) { adapter.update(poll.options, poll.multiple) val pollDurationId = resources.getIntArray(R.array.poll_duration_values).indexOfLast { @@ -59,4 +60,4 @@ class PollPreviewView @JvmOverloads constructor( super.setOnClickListener(l) adapter.setOnClickListener(l) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/TootButton.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/TootButton.kt index f7ba7ee69..24f4130b2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/TootButton.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/TootButton.kt @@ -28,15 +28,15 @@ import com.mikepenz.iconics.utils.sizeDp class TootButton @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : MaterialButton(context, attrs, defStyleAttr) { private val smallStyle: Boolean = context.resources.getBoolean(R.bool.show_small_toot_button) init { - if(smallStyle) { + if (smallStyle) { setIconResource(R.drawable.ic_send_24dp) } else { setText(R.string.action_send) @@ -47,7 +47,7 @@ class TootButton } fun setStatusVisibility(visibility: Status.Visibility) { - if(!smallStyle) { + if (!smallStyle) { icon = when (visibility) { Status.Visibility.PUBLIC -> { @@ -68,8 +68,5 @@ class TootButton } } } - } - } - diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt index 7caa91144..0a4698227 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationEntity.kt @@ -30,7 +30,7 @@ import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.util.shouldTrimStatus import java.util.Date -@Entity(primaryKeys = ["id","accountId"]) +@Entity(primaryKeys = ["id", "accountId"]) @TypeConverters(Converters::class) data class ConversationEntity( val accountId: Long, @@ -98,7 +98,7 @@ data class ConversationStatusEntity( if (inReplyToId != other.inReplyToId) return false if (inReplyToAccountId != other.inReplyToAccountId) return false if (account != other.account) return false - if (content.toString() != other.content.toString()) return false //TODO find a better method to compare two spanned strings + if (content.toString() != other.content.toString()) return false // TODO find a better method to compare two spanned strings if (createdAt != other.createdAt) return false if (emojis != other.emojis) return false if (favouritesCount != other.favouritesCount) return false @@ -157,7 +157,7 @@ data class ConversationStatusEntity( reblogged = false, favourited = favourited, bookmarked = bookmarked, - sensitive= sensitive, + sensitive = sensitive, spoilerText = spoilerText, visibility = Status.Visibility.DIRECT, attachments = attachments, @@ -166,7 +166,8 @@ data class ConversationStatusEntity( pinned = false, muted = muted, poll = poll, - card = null) + card = null + ) } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationLoadStateAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationLoadStateAdapter.kt index d5c0983a0..c7224c4d2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationLoadStateAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationLoadStateAdapter.kt @@ -37,5 +37,4 @@ class ConversationLoadStateAdapter( val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false) return NetworkStateViewHolder(binding, retryCallback) } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt index a484c6d06..4272c1bd6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsFragment.kt @@ -40,15 +40,16 @@ import com.keylesspalace.tusky.fragment.SFragment import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import java.io.IOException import com.keylesspalace.tusky.util.CardViewMode import com.keylesspalace.tusky.util.StatusDisplayOptions import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.util.viewBinding +import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.viewdata.AttachmentViewData +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import java.io.IOException import javax.inject.Inject class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsRepository.kt index 2156b0189..12c5eb0bb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsRepository.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/conversation/ConversationsRepository.kt @@ -32,7 +32,6 @@ class ConversationsRepository @Inject constructor( Single.fromCallable { db.conversationDao().deleteForAccount(accountId) }.subscribeOn(Schedulers.io()) - .subscribe() + .subscribe() } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt index 69403fdb5..acee683b6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftMediaAdapter.kt @@ -28,18 +28,17 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.DraftAttachment class DraftMediaAdapter( - private val attachmentClick: () -> Unit + private val attachmentClick: () -> Unit ) : ListAdapter( - object: DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { - return oldItem == newItem - } - - override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { - return oldItem == newItem - } - + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { + return oldItem == newItem } + + override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean { + return oldItem == newItem + } + } ) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DraftMediaViewHolder { @@ -52,24 +51,24 @@ class DraftMediaAdapter( holder.imageView.setImageResource(R.drawable.ic_music_box_preview_24dp) } else { Glide.with(holder.itemView.context) - .load(attachment.uri) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .dontAnimate() - .into(holder.imageView) + .load(attachment.uri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .dontAnimate() + .into(holder.imageView) } } } - inner class DraftMediaViewHolder(val imageView: ImageView) - : RecyclerView.ViewHolder(imageView) { + inner class DraftMediaViewHolder(val imageView: ImageView) : + RecyclerView.ViewHolder(imageView) { init { val thumbnailViewSize = - imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size) + imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size) val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize) val margin = itemView.context.resources - .getDimensionPixelSize(R.dimen.compose_media_preview_margin) + .getDimensionPixelSize(R.dimen.compose_media_preview_margin) val marginBottom = itemView.context.resources - .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) + .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) layoutParams.setMargins(margin, 0, margin, marginBottom) imageView.layoutParams = layoutParams imageView.scaleType = ImageView.ScaleType.CENTER_CROP @@ -78,4 +77,4 @@ class DraftMediaAdapter( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt index 8ca00491a..ce0048011 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/drafts/DraftsActivity.kt @@ -91,27 +91,28 @@ class DraftsActivity : BaseActivity(), DraftActionListener { if (draft.inReplyToId != null) { bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED viewModel.getToot(draft.inReplyToId) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this)) - .subscribe({ status -> + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this)) + .subscribe( + { status -> val composeOptions = ComposeActivity.ComposeOptions( - draftId = draft.id, - tootText = draft.content, - contentWarning = draft.contentWarning, - inReplyToId = draft.inReplyToId, - replyingStatusContent = status.content.toString(), - replyingStatusAuthor = status.account.localUsername, - draftAttachments = draft.attachments, - poll = draft.poll, - sensitive = draft.sensitive, - visibility = draft.visibility + draftId = draft.id, + tootText = draft.content, + contentWarning = draft.contentWarning, + inReplyToId = draft.inReplyToId, + replyingStatusContent = status.content.toString(), + replyingStatusAuthor = status.account.localUsername, + draftAttachments = draft.attachments, + poll = draft.poll, + sensitive = draft.sensitive, + visibility = draft.visibility ) bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN startActivity(ComposeActivity.startIntent(this, composeOptions)) - - }, { throwable -> + }, + { throwable -> bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN @@ -124,9 +125,10 @@ class DraftsActivity : BaseActivity(), DraftActionListener { openDraftWithoutReply(draft) } else { Snackbar.make(binding.root, getString(R.string.drafts_failed_loading_reply), Snackbar.LENGTH_SHORT) - .show() + .show() } - }) + } + ) } else { openDraftWithoutReply(draft) } @@ -134,13 +136,13 @@ class DraftsActivity : BaseActivity(), DraftActionListener { private fun openDraftWithoutReply(draft: DraftEntity) { val composeOptions = ComposeActivity.ComposeOptions( - draftId = draft.id, - tootText = draft.content, - contentWarning = draft.contentWarning, - draftAttachments = draft.attachments, - poll = draft.poll, - sensitive = draft.sensitive, - visibility = draft.visibility + draftId = draft.id, + tootText = draft.content, + contentWarning = draft.contentWarning, + draftAttachments = draft.attachments, + poll = draft.poll, + sensitive = draft.sensitive, + visibility = draft.visibility ) startActivity(ComposeActivity.startIntent(this, composeOptions)) @@ -149,10 +151,10 @@ class DraftsActivity : BaseActivity(), DraftActionListener { override fun onDeleteDraft(draft: DraftEntity) { viewModel.deleteDraft(draft) Snackbar.make(binding.root, getString(R.string.draft_deleted), Snackbar.LENGTH_LONG) - .setAction(R.string.action_undo) { - viewModel.restoreDraft(draft) - } - .show() + .setAction(R.string.action_undo) { + viewModel.restoreDraft(draft) + } + .show() } companion object { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt index 7253112a1..83a2191fe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/InstanceListActivity.kt @@ -9,7 +9,7 @@ import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import javax.inject.Inject -class InstanceListActivity: BaseActivity(), HasAndroidInjector { +class InstanceListActivity : BaseActivity(), HasAndroidInjector { @Inject lateinit var androidInjector: DispatchingAndroidInjector @@ -27,11 +27,10 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector { } supportFragmentManager - .beginTransaction() - .replace(R.id.fragment_container, InstanceListFragment()) - .commit() + .beginTransaction() + .replace(R.id.fragment_container, InstanceListFragment()) + .commit() } override fun androidInjector() = androidInjector - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt index f475f3942..509c9561d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/adapter/DomainMutesAdapter.kt @@ -8,8 +8,8 @@ import com.keylesspalace.tusky.databinding.ItemMutedDomainBinding import com.keylesspalace.tusky.util.BindingHolder class DomainMutesAdapter( - private val actionListener: InstanceActionListener -): RecyclerView.Adapter>() { + private val actionListener: InstanceActionListener +) : RecyclerView.Adapter>() { var instances: MutableList = mutableListOf() var bottomLoading: Boolean = false diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt index 1a1392d42..ccfe52b3c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/fragment/InstanceListFragment.kt @@ -29,7 +29,7 @@ import retrofit2.Response import java.io.IOException import javax.inject.Inject -class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener { +class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener { @Inject lateinit var api: MastodonApi @@ -65,7 +65,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl override fun mute(mute: Boolean, instance: String, position: Int) { if (mute) { - api.blockDomain(instance).enqueue(object: Callback { + api.blockDomain(instance).enqueue(object : Callback { override fun onFailure(call: Call, t: Throwable) { Log.e(TAG, "Error muting domain $instance") } @@ -79,7 +79,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl } }) } else { - api.unblockDomain(instance).enqueue(object: Callback { + api.unblockDomain(instance).enqueue(object : Callback { override fun onFailure(call: Call, t: Throwable) { Log.e(TAG, "Error unmuting domain $instance") } @@ -88,10 +88,10 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl if (response.isSuccessful) { adapter.removeItem(position) Snackbar.make(binding.recyclerView, getString(R.string.confirmation_domain_unmuted, instance), Snackbar.LENGTH_LONG) - .setAction(R.string.action_undo) { - mute(true, instance, position) - } - .show() + .setAction(R.string.action_undo) { + mute(true, instance, position) + } + .show() } else { Log.e(TAG, "Error unmuting domain $instance") } @@ -112,9 +112,10 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl } api.domainBlocks(id, bottomId) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe({ response -> + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe( + { response -> val instances = response.body() if (response.isSuccessful && instances != null) { @@ -122,9 +123,11 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl } else { onFetchInstancesFailure(Exception(response.message())) } - }, {throwable -> + }, + { throwable -> onFetchInstancesFailure(throwable) - }) + } + ) } private fun onFetchInstancesSuccess(instances: List, linkHeader: String?) { @@ -141,9 +144,9 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl if (adapter.itemCount == 0) { binding.messageView.show() binding.messageView.setup( - R.drawable.elephant_friend_empty, - R.string.message_empty, - null + R.drawable.elephant_friend_empty, + R.string.message_empty, + null ) } else { binding.messageView.hide() @@ -174,4 +177,4 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl companion object { private const val TAG = "InstanceList" // logging tag } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt index 97d59cc96..9b88ad966 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/instancemute/interfaces/InstanceActionListener.kt @@ -2,4 +2,4 @@ package com.keylesspalace.tusky.components.instancemute.interfaces interface InstanceActionListener { fun mute(mute: Boolean, instance: String, position: Int) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt index 394a68466..fe48a16b3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt @@ -10,9 +10,9 @@ import com.keylesspalace.tusky.util.isLessThan import javax.inject.Inject class NotificationFetcher @Inject constructor( - private val mastodonApi: MastodonApi, - private val accountManager: AccountManager, - private val notifier: Notifier + private val mastodonApi: MastodonApi, + private val accountManager: AccountManager, + private val notifier: Notifier ) { fun fetchAndShow() { for (account in accountManager.getAllAccountsOrderedByActive()) { @@ -39,9 +39,9 @@ class NotificationFetcher @Inject constructor( } Log.d(TAG, "getting Notifications for " + account.fullName) val notifications = mastodonApi.notificationsWithAuth( - authHeader, - account.domain, - account.lastNotificationId + authHeader, + account.domain, + account.lastNotificationId ).blockingGet() val newId = account.lastNotificationId @@ -63,9 +63,9 @@ class NotificationFetcher @Inject constructor( private fun fetchMarker(authHeader: String, account: AccountEntity): Marker? { return try { val allMarkers = mastodonApi.markersWithAuth( - authHeader, - account.domain, - listOf("notifications") + authHeader, + account.domain, + listOf("notifications") ).blockingGet() val notificationMarker = allMarkers["notifications"] Log.d(TAG, "Fetched marker: $notificationMarker") @@ -79,4 +79,4 @@ class NotificationFetcher @Inject constructor( companion object { const val TAG = "NotificationFetcher" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationWorker.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationWorker.kt index ae7d4d3fb..42b9c869e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationWorker.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationWorker.kt @@ -23,9 +23,9 @@ import androidx.work.WorkerParameters import javax.inject.Inject class NotificationWorker( - context: Context, - params: WorkerParameters, - private val notificationsFetcher: NotificationFetcher + context: Context, + params: WorkerParameters, + private val notificationsFetcher: NotificationFetcher ) : Worker(context, params) { override fun doWork(): Result { @@ -35,13 +35,13 @@ class NotificationWorker( } class NotificationWorkerFactory @Inject constructor( - private val notificationsFetcher: NotificationFetcher + private val notificationsFetcher: NotificationFetcher ) : WorkerFactory() { override fun createWorker( - appContext: Context, - workerClassName: String, - workerParameters: WorkerParameters + appContext: Context, + workerClassName: String, + workerParameters: WorkerParameters ): ListenableWorker? { if (workerClassName == NotificationWorker::class.java.name) { return NotificationWorker(appContext, workerParameters, notificationsFetcher) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/Notifier.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/Notifier.kt index 35c33a9b8..5092530bd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/Notifier.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/Notifier.kt @@ -12,9 +12,9 @@ interface Notifier { } class SystemNotifier( - private val context: Context + private val context: Context ) : Notifier { override fun show(notification: Notification, account: AccountEntity, isFirstInBatch: Boolean) { NotificationHelper.make(context, notification, account, isFirstInBatch) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt index 286b49b56..e6bf83fbc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/AccountPreferencesFragment.kt @@ -22,7 +22,11 @@ import android.util.Log import androidx.annotation.DrawableRes import androidx.preference.PreferenceFragmentCompat import com.google.android.material.snackbar.Snackbar -import com.keylesspalace.tusky.* +import com.keylesspalace.tusky.AccountListActivity +import com.keylesspalace.tusky.BuildConfig +import com.keylesspalace.tusky.FiltersActivity +import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.TabPreferenceActivity import com.keylesspalace.tusky.appstore.EventHub import com.keylesspalace.tusky.appstore.PreferenceChangedEvent import com.keylesspalace.tusky.components.instancemute.InstanceListActivity @@ -33,7 +37,12 @@ import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.settings.* +import com.keylesspalace.tusky.settings.PrefKeys +import com.keylesspalace.tusky.settings.listPreference +import com.keylesspalace.tusky.settings.makePreferenceScreen +import com.keylesspalace.tusky.settings.preference +import com.keylesspalace.tusky.settings.preferenceCategory +import com.keylesspalace.tusky.settings.switchPreference import com.keylesspalace.tusky.util.ThemeUtils import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial @@ -75,8 +84,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setOnPreferenceClickListener { val intent = Intent(context, TabPreferenceActivity::class.java) activity?.startActivity(intent) - activity?.overridePendingTransition(R.anim.slide_from_right, - R.anim.slide_to_left) + activity?.overridePendingTransition( + R.anim.slide_from_right, + R.anim.slide_to_left + ) true } } @@ -88,8 +99,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { val intent = Intent(context, AccountListActivity::class.java) intent.putExtra("type", AccountListActivity.Type.MUTES) activity?.startActivity(intent) - activity?.overridePendingTransition(R.anim.slide_from_right, - R.anim.slide_to_left) + activity?.overridePendingTransition( + R.anim.slide_from_right, + R.anim.slide_to_left + ) true } } @@ -104,8 +117,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { val intent = Intent(context, AccountListActivity::class.java) intent.putExtra("type", AccountListActivity.Type.BLOCKS) activity?.startActivity(intent) - activity?.overridePendingTransition(R.anim.slide_from_right, - R.anim.slide_to_left) + activity?.overridePendingTransition( + R.anim.slide_from_right, + R.anim.slide_to_left + ) true } } @@ -116,8 +131,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { setOnPreferenceClickListener { val intent = Intent(context, InstanceListActivity::class.java) activity?.startActivity(intent) - activity?.overridePendingTransition(R.anim.slide_from_right, - R.anim.slide_to_left) + activity?.overridePendingTransition( + R.anim.slide_from_right, + R.anim.slide_to_left + ) true } } @@ -130,7 +147,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { key = PrefKeys.DEFAULT_POST_PRIVACY setSummaryProvider { entry } val visibility = accountManager.activeAccount?.defaultPostPrivacy - ?: Status.Visibility.PUBLIC + ?: Status.Visibility.PUBLIC value = visibility.serverString() setIcon(getIconForVisibility(visibility)) setOnPreferenceChangeListener { _, newValue -> @@ -147,7 +164,7 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { key = PrefKeys.DEFAULT_MEDIA_SENSITIVITY isSingleLineTitle = false val sensitivity = accountManager.activeAccount?.defaultMediaSensitivity - ?: false + ?: false setDefaultValue(sensitivity) setIcon(getIconForSensitivity(sensitivity)) setOnPreferenceChangeListener { _, newValue -> @@ -201,8 +218,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { preference { setTitle(R.string.pref_title_public_filter_keywords) setOnPreferenceClickListener { - launchFilterActivity(Filter.PUBLIC, - R.string.pref_title_public_filter_keywords) + launchFilterActivity( + Filter.PUBLIC, + R.string.pref_title_public_filter_keywords + ) true } } @@ -226,8 +245,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { preference { setTitle(R.string.pref_title_thread_filter_keywords) setOnPreferenceClickListener { - launchFilterActivity(Filter.THREAD, - R.string.pref_title_thread_filter_keywords) + launchFilterActivity( + Filter.THREAD, + R.string.pref_title_thread_filter_keywords + ) true } } @@ -255,7 +276,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { it.startActivity(intent) it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left) } - } } @@ -268,36 +288,35 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable { private fun syncWithServer(visibility: String? = null, sensitive: Boolean? = null) { mastodonApi.accountUpdateSource(visibility, sensitive) - .enqueue(object : Callback { - override fun onResponse(call: Call, response: Response) { - val account = response.body() - if (response.isSuccessful && account != null) { + .enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + val account = response.body() + if (response.isSuccessful && account != null) { - accountManager.activeAccount?.let { - it.defaultPostPrivacy = account.source?.privacy - ?: Status.Visibility.PUBLIC - it.defaultMediaSensitivity = account.source?.sensitive ?: false - accountManager.saveAccount(it) - } - } else { - Log.e("AccountPreferences", "failed updating settings on server") - showErrorSnackbar(visibility, sensitive) + accountManager.activeAccount?.let { + it.defaultPostPrivacy = account.source?.privacy + ?: Status.Visibility.PUBLIC + it.defaultMediaSensitivity = account.source?.sensitive ?: false + accountManager.saveAccount(it) } - } - - override fun onFailure(call: Call, t: Throwable) { - Log.e("AccountPreferences", "failed updating settings on server", t) + } else { + Log.e("AccountPreferences", "failed updating settings on server") showErrorSnackbar(visibility, sensitive) } + } - }) + override fun onFailure(call: Call, t: Throwable) { + Log.e("AccountPreferences", "failed updating settings on server", t) + showErrorSnackbar(visibility, sensitive) + } + }) } private fun showErrorSnackbar(visibility: String?, sensitive: Boolean?) { view?.let { view -> Snackbar.make(view, R.string.pref_failed_to_sync, Snackbar.LENGTH_LONG) - .setAction(R.string.action_retry) { syncWithServer(visibility, sensitive) } - .show() + .setAction(R.string.action_retry) { syncWithServer(visibility, sensitive) } + .show() } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt index d045350f3..e793f17f1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/EmojiPreference.kt @@ -34,8 +34,8 @@ import kotlin.system.exitProcess * This Preference lets the user select their preferred emoji font */ class EmojiPreference( - context: Context, - private val okHttpClient: OkHttpClient + context: Context, + private val okHttpClient: OkHttpClient ) : Preference(context) { private lateinit var selected: EmojiCompatFont @@ -51,7 +51,7 @@ class EmojiPreference( // Find out which font is currently active selected = EmojiCompatFont.byId( - PreferenceManager.getDefaultSharedPreferences(context).getInt(key, 0) + PreferenceManager.getDefaultSharedPreferences(context).getInt(key, 0) ) // We'll use this later to determine if anything has changed original = selected @@ -67,10 +67,10 @@ class EmojiPreference( setupItem(SYSTEM_DEFAULT, binding.itemNomoji) AlertDialog.Builder(context) - .setView(binding.root) - .setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setView(binding.root) + .setPositiveButton(android.R.string.ok) { _, _ -> onDialogOk() } + .setNegativeButton(android.R.string.cancel, null) + .show() } private fun setupItem(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) { @@ -100,32 +100,30 @@ class EmojiPreference( binding.emojiProgress.progress = 0 binding.emojiDownloadCancel.show() font.downloadFontFile(context, okHttpClient) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { progress -> - // The progress is returned as a float between 0 and 1, or -1 if it could not determined - if (progress >= 0) { - binding.emojiProgress.isIndeterminate = false - val max = binding.emojiProgress.max.toFloat() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - binding.emojiProgress.setProgress((max * progress).toInt(), true) - } else { - binding.emojiProgress.progress = (max * progress).toInt() - } - } else { - binding.emojiProgress.isIndeterminate = true - } - }, - { - Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show() - updateItem(font, binding) - }, - { - finishDownload(font, binding) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { progress -> + // The progress is returned as a float between 0 and 1, or -1 if it could not determined + if (progress >= 0) { + binding.emojiProgress.isIndeterminate = false + val max = binding.emojiProgress.max.toFloat() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + binding.emojiProgress.setProgress((max * progress).toInt(), true) + } else { + binding.emojiProgress.progress = (max * progress).toInt() } - ).also { downloadDisposables[font.id] = it } - - + } else { + binding.emojiProgress.isIndeterminate = true + } + }, + { + Toast.makeText(context, R.string.download_failed, Toast.LENGTH_SHORT).show() + updateItem(font, binding) + }, + { + finishDownload(font, binding) + } + ).also { downloadDisposables[font.id] = it } } private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) { @@ -197,10 +195,10 @@ class EmojiPreference( val index = selected.id Log.i(TAG, "saveSelectedFont: Font ID: $index") PreferenceManager - .getDefaultSharedPreferences(context) - .edit() - .putInt(key, index) - .apply() + .getDefaultSharedPreferences(context) + .edit() + .putInt(key, index) + .apply() summary = selected.getDisplay(context) } @@ -211,29 +209,31 @@ class EmojiPreference( saveSelectedFont() if (selected !== original || updated) { AlertDialog.Builder(context) - .setTitle(R.string.restart_required) - .setMessage(R.string.restart_emoji) - .setNegativeButton(R.string.later, null) - .setPositiveButton(R.string.restart) { _, _ -> - // Restart the app - // From https://stackoverflow.com/a/17166729/5070653 - val launchIntent = Intent(context, SplashActivity::class.java) - val mPendingIntent = PendingIntent.getActivity( - context, - 0x1f973, // This is the codepoint of the party face emoji :D - launchIntent, - PendingIntent.FLAG_CANCEL_CURRENT) - val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager - mgr.set( - AlarmManager.RTC, - System.currentTimeMillis() + 100, - mPendingIntent) - exitProcess(0) - }.show() + .setTitle(R.string.restart_required) + .setMessage(R.string.restart_emoji) + .setNegativeButton(R.string.later, null) + .setPositiveButton(R.string.restart) { _, _ -> + // Restart the app + // From https://stackoverflow.com/a/17166729/5070653 + val launchIntent = Intent(context, SplashActivity::class.java) + val mPendingIntent = PendingIntent.getActivity( + context, + 0x1f973, // This is the codepoint of the party face emoji :D + launchIntent, + PendingIntent.FLAG_CANCEL_CURRENT + ) + val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + mgr.set( + AlarmManager.RTC, + System.currentTimeMillis() + 100, + mPendingIntent + ) + exitProcess(0) + }.show() } } companion object { private const val TAG = "EmojiPreference" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt index 1e90abc85..4d8ba84f3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/NotificationPreferencesFragment.kt @@ -111,7 +111,7 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable { true } } - + switchPreference { setTitle(R.string.pref_title_notification_filter_subscriptions) key = PrefKeys.NOTIFICATION_FILTER_SUBSCRIPTIONS @@ -176,5 +176,4 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable { return NotificationPreferencesFragment() } } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt index f1a076159..8297fae48 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesActivity.kt @@ -36,8 +36,10 @@ import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import javax.inject.Inject -class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener, - HasAndroidInjector { +class PreferencesActivity : + BaseActivity(), + SharedPreferences.OnSharedPreferenceChangeListener, + HasAndroidInjector { @Inject lateinit var eventHub: EventHub @@ -62,36 +64,35 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference val fragmentTag = "preference_fragment_$EXTRA_PREFERENCE_TYPE" val fragment: Fragment = supportFragmentManager.findFragmentByTag(fragmentTag) - ?: when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) { - GENERAL_PREFERENCES -> { - setTitle(R.string.action_view_preferences) - PreferencesFragment.newInstance() - } - ACCOUNT_PREFERENCES -> { - setTitle(R.string.action_view_account_preferences) - AccountPreferencesFragment.newInstance() - } - NOTIFICATION_PREFERENCES -> { - setTitle(R.string.pref_title_edit_notification_settings) - NotificationPreferencesFragment.newInstance() - } - TAB_FILTER_PREFERENCES -> { - setTitle(R.string.pref_title_status_tabs) - TabFilterPreferencesFragment.newInstance() - } - PROXY_PREFERENCES -> { - setTitle(R.string.pref_title_http_proxy_settings) - ProxyPreferencesFragment.newInstance() - } - else -> throw IllegalArgumentException("preferenceType not known") + ?: when (intent.getIntExtra(EXTRA_PREFERENCE_TYPE, 0)) { + GENERAL_PREFERENCES -> { + setTitle(R.string.action_view_preferences) + PreferencesFragment.newInstance() } + ACCOUNT_PREFERENCES -> { + setTitle(R.string.action_view_account_preferences) + AccountPreferencesFragment.newInstance() + } + NOTIFICATION_PREFERENCES -> { + setTitle(R.string.pref_title_edit_notification_settings) + NotificationPreferencesFragment.newInstance() + } + TAB_FILTER_PREFERENCES -> { + setTitle(R.string.pref_title_status_tabs) + TabFilterPreferencesFragment.newInstance() + } + PROXY_PREFERENCES -> { + setTitle(R.string.pref_title_http_proxy_settings) + ProxyPreferencesFragment.newInstance() + } + else -> throw IllegalArgumentException("preferenceType not known") + } supportFragmentManager.commit { replace(R.id.fragment_container, fragment, fragmentTag) } restartActivitiesOnExit = intent.getBooleanExtra("restart", false) - } override fun onResume() { @@ -122,7 +123,6 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference restartActivitiesOnExit = true this.restartCurrentActivity() - } "statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars", "useBlurhash", "showCardsInTimelines", "confirmReblogs", "enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> { @@ -179,5 +179,4 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference return intent } } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt index fa4ac3b29..d3f44e322 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/PreferencesFragment.kt @@ -22,7 +22,14 @@ import com.keylesspalace.tusky.R import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.Notification -import com.keylesspalace.tusky.settings.* +import com.keylesspalace.tusky.settings.AppTheme +import com.keylesspalace.tusky.settings.PrefKeys +import com.keylesspalace.tusky.settings.emojiPreference +import com.keylesspalace.tusky.settings.listPreference +import com.keylesspalace.tusky.settings.makePreferenceScreen +import com.keylesspalace.tusky.settings.preference +import com.keylesspalace.tusky.settings.preferenceCategory +import com.keylesspalace.tusky.settings.switchPreference import com.keylesspalace.tusky.util.ThemeUtils import com.keylesspalace.tusky.util.deserialize import com.keylesspalace.tusky.util.getNonNullString @@ -122,7 +129,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { setTitle(R.string.pref_title_bot_overlay) isSingleLineTitle = false setIcon(R.drawable.ic_bot_24dp) - } switchPreference { @@ -259,7 +265,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { sizePx = iconSize colorInt = ThemeUtils.getColor(context, R.attr.iconColor) } - } override fun onResume() { @@ -274,7 +279,7 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable { try { val httpPort = sharedPreferences.getNonNullString(PrefKeys.HTTP_PROXY_PORT, "-1") - .toInt() + .toInt() if (httpProxyEnabled && httpServer.isNotBlank() && httpPort > 0 && httpPort < 65535) { httpProxyPref?.summary = "$httpServer:$httpPort" diff --git a/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt index 922d5a7a1..322b0c1da 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/preference/ProxyPreferencesFragment.kt @@ -50,7 +50,6 @@ class ProxyPreferencesFragment : PreferenceFragmentCompat() { setSummaryProvider { text } } } - } override fun onPause() { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt index 4c53588a3..82526706e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportActivity.kt @@ -126,12 +126,12 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector { @JvmStatic fun getIntent(context: Context, accountId: String, userName: String, statusId: String? = null) = - Intent(context, ReportActivity::class.java) - .apply { - putExtra(ACCOUNT_ID, accountId) - putExtra(ACCOUNT_USERNAME, userName) - putExtra(STATUS_ID, statusId) - } + Intent(context, ReportActivity::class.java) + .apply { + putExtra(ACCOUNT_ID, accountId) + putExtra(ACCOUNT_USERNAME, userName) + putExtra(STATUS_ID, statusId) + } } override fun androidInjector() = androidInjector diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt index 7b51e91ce..f8991282d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/ReportViewModel.kt @@ -43,8 +43,8 @@ import kotlinx.coroutines.launch import javax.inject.Inject class ReportViewModel @Inject constructor( - private val mastodonApi: MastodonApi, - private val eventHub: EventHub + private val mastodonApi: MastodonApi, + private val eventHub: EventHub ) : RxAwareViewModel() { private val navigationMutable = MutableLiveData() @@ -121,18 +121,17 @@ class ReportViewModel @Inject constructor( muteStateMutable.value = Loading() blockStateMutable.value = Loading() mastodonApi.relationships(ids) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { data -> - updateRelationship(data.getOrNull(0)) - - }, - { - updateRelationship(null) - } - ) - .autoDispose() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { data -> + updateRelationship(data.getOrNull(0)) + }, + { + updateRelationship(null) + } + ) + .autoDispose() } private fun updateRelationship(relationship: Relationship?) { @@ -152,20 +151,20 @@ class ReportViewModel @Inject constructor( } else { mastodonApi.muteAccount(accountId) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { relationship -> - val muting = relationship?.muting == true - muteStateMutable.value = Success(muting) - if (muting) { - eventHub.dispatch(MuteEvent(accountId)) - } - }, - { error -> - muteStateMutable.value = Error(false, error.message) - } - ).autoDispose() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { relationship -> + val muting = relationship?.muting == true + muteStateMutable.value = Success(muting) + if (muting) { + eventHub.dispatch(MuteEvent(accountId)) + } + }, + { error -> + muteStateMutable.value = Error(false, error.message) + } + ).autoDispose() muteStateMutable.value = Loading() } @@ -177,21 +176,21 @@ class ReportViewModel @Inject constructor( } else { mastodonApi.blockAccount(accountId) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { relationship -> - val blocking = relationship?.blocking == true - blockStateMutable.value = Success(blocking) - if (blocking) { - eventHub.dispatch(BlockEvent(accountId)) - } - }, - { error -> - blockStateMutable.value = Error(false, error.message) - } - ) - .autoDispose() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { relationship -> + val blocking = relationship?.blocking == true + blockStateMutable.value = Success(blocking) + if (blocking) { + eventHub.dispatch(BlockEvent(accountId)) + } + }, + { error -> + blockStateMutable.value = Error(false, error.message) + } + ) + .autoDispose() blockStateMutable.value = Loading() } @@ -199,18 +198,17 @@ class ReportViewModel @Inject constructor( fun doReport() { reportingStateMutable.value = Loading() mastodonApi.reportObservable(accountId, selectedIds.toList(), reportNote, if (isRemoteAccount) isRemoteNotify else null) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - reportingStateMutable.value = Success(true) - }, - { error -> - reportingStateMutable.value = Error(cause = error) - } - ) - .autoDispose() - + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + reportingStateMutable.value = Success(true) + }, + { error -> + reportingStateMutable.value = Error(cause = error) + } + ) + .autoDispose() } fun checkClickedUrl(url: String?) { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/Screen.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/Screen.kt index 643c46c18..fb0b15cae 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/Screen.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/Screen.kt @@ -21,4 +21,4 @@ enum class Screen { Done, Back, Finish -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/AdapterHandler.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/AdapterHandler.kt index 957d5b325..fd150f91a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/AdapterHandler.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/AdapterHandler.kt @@ -19,8 +19,8 @@ import android.view.View import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.interfaces.LinkListener -interface AdapterHandler: LinkListener { +interface AdapterHandler : LinkListener { fun showMedia(v: View?, status: Status?, idx: Int) fun setStatusChecked(status: Status, isChecked: Boolean) fun isStatusChecked(id: String): Boolean -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/ReportPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/ReportPagerAdapter.kt index 506d99afe..fa5acc2d6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/ReportPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/ReportPagerAdapter.kt @@ -33,4 +33,4 @@ class ReportPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(acti } override fun getItemCount() = 3 -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt index 5ac3dd6a1..41486506c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusViewHolder.kt @@ -25,18 +25,25 @@ import com.keylesspalace.tusky.databinding.ItemReportStatusBinding import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.interfaces.LinkListener -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.LinkHelper +import com.keylesspalace.tusky.util.StatusDisplayOptions +import com.keylesspalace.tusky.util.StatusViewHelper import com.keylesspalace.tusky.util.StatusViewHelper.Companion.COLLAPSE_INPUT_FILTER import com.keylesspalace.tusky.util.StatusViewHelper.Companion.NO_INPUT_FILTER +import com.keylesspalace.tusky.util.TimestampUtils +import com.keylesspalace.tusky.util.emojify +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.shouldTrimStatus +import com.keylesspalace.tusky.util.show import com.keylesspalace.tusky.viewdata.toViewData -import java.util.* +import java.util.Date class StatusViewHolder( - private val binding: ItemReportStatusBinding, - private val statusDisplayOptions: StatusDisplayOptions, - private val viewState: StatusViewState, - private val adapterHandler: AdapterHandler, - private val getStatusForPosition: (Int) -> Status? + private val binding: ItemReportStatusBinding, + private val statusDisplayOptions: StatusDisplayOptions, + private val viewState: StatusViewState, + private val adapterHandler: AdapterHandler, + private val getStatusForPosition: (Int) -> Status? ) : RecyclerView.ViewHolder(binding.root) { private val mediaViewHeight = itemView.context.resources.getDimensionPixelSize(R.dimen.status_media_preview_height) private val statusViewHelper = StatusViewHelper(itemView) @@ -71,9 +78,11 @@ class StatusViewHolder( val sensitive = status.sensitive - statusViewHelper.setMediasPreview(statusDisplayOptions, status.attachments, - sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive), - mediaViewHeight) + statusViewHelper.setMediasPreview( + statusDisplayOptions, status.attachments, + sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive), + mediaViewHeight + ) statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions) setCreatedAt(status.createdAt) @@ -81,8 +90,10 @@ class StatusViewHolder( private fun updateTextView() { status()?.let { status -> - setupCollapsedState(shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true), - viewState.isContentShow(status.id, status.sensitive), status.spoilerText) + setupCollapsedState( + shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true), + viewState.isContentShow(status.id, status.sensitive), status.spoilerText + ) if (status.spoilerText.isBlank()) { setTextVisible(true, status.content, status.mentions, status.emojis, adapterHandler) @@ -109,18 +120,20 @@ class StatusViewHolder( } private fun setContentWarningButtonText(contentShown: Boolean) { - if(contentShown) { + if (contentShown) { binding.statusContentWarningButton.setText(R.string.status_content_warning_show_less) } else { binding.statusContentWarningButton.setText(R.string.status_content_warning_show_more) } } - private fun setTextVisible(expanded: Boolean, - content: Spanned, - mentions: List?, - emojis: List, - listener: LinkListener) { + private fun setTextVisible( + expanded: Boolean, + content: Spanned, + mentions: List?, + emojis: List, + listener: LinkListener + ) { if (expanded) { val emojifiedText = content.emojify(emojis, binding.statusContent, statusDisplayOptions.animateEmojis) LinkHelper.setClickableText(binding.statusContent, emojifiedText, mentions, listener) @@ -152,7 +165,7 @@ class StatusViewHolder( private fun setupCollapsedState(collapsible: Boolean, collapsed: Boolean, expanded: Boolean, spoilerText: String) { /* input filter for TextViews have to be set before text */ if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) { - binding.buttonToggleContent.setOnClickListener{ + binding.buttonToggleContent.setOnClickListener { status()?.let { status -> viewState.setCollapsed(status.id, !collapsed) updateTextView() @@ -174,4 +187,4 @@ class StatusViewHolder( } private fun status() = getStatusForPosition(bindingAdapterPosition) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt index d472995d4..76ed2ebea 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesAdapter.kt @@ -26,9 +26,9 @@ import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.util.StatusDisplayOptions class StatusesAdapter( - private val statusDisplayOptions: StatusDisplayOptions, - private val statusViewState: StatusViewState, - private val adapterHandler: AdapterHandler + private val statusDisplayOptions: StatusDisplayOptions, + private val statusViewState: StatusViewState, + private val adapterHandler: AdapterHandler ) : PagingDataAdapter(STATUS_COMPARATOR) { private val statusForPosition: (Int) -> Status? = { position: Int -> @@ -37,8 +37,10 @@ class StatusesAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatusViewHolder { val binding = ItemReportStatusBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return StatusViewHolder(binding, statusDisplayOptions, statusViewState, adapterHandler, - statusForPosition) + return StatusViewHolder( + binding, statusDisplayOptions, statusViewState, adapterHandler, + statusForPosition + ) } override fun onBindViewHolder(holder: StatusViewHolder, position: Int) { @@ -50,10 +52,10 @@ class StatusesAdapter( companion object { val STATUS_COMPARATOR = object : DiffUtil.ItemCallback() { override fun areContentsTheSame(oldItem: Status, newItem: Status): Boolean = - oldItem == newItem + oldItem == newItem override fun areItemsTheSame(oldItem: Status, newItem: Status): Boolean = - oldItem.id == newItem.id + oldItem.id == newItem.id } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesPagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesPagingSource.kt index 964e23e27..c007239d8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesPagingSource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/adapter/StatusesPagingSource.kt @@ -32,7 +32,7 @@ class StatusesPagingSource( override fun getRefreshKey(state: PagingState): String? { return state.anchorPosition?.let { anchorPosition -> - state.closestItemToPosition(anchorPosition)?.id + state.closestItemToPosition(anchorPosition)?.id } } @@ -65,7 +65,6 @@ class StatusesPagingSource( prevKey = result.firstOrNull()?.id, nextKey = result.lastOrNull()?.id ) - } catch (e: Exception) { Log.w("StatusesPagingSource", "failed to load statuses", e) return LoadResult.Error(e) @@ -86,4 +85,4 @@ class StatusesPagingSource( excludeReblogs = true ).await() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt index 794cb287b..0f8065776 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportDoneFragment.kt @@ -56,27 +56,29 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable { binding.progressMute.hide() } - binding.buttonMute.setText(when (it.data) { - true -> R.string.action_unmute - else -> R.string.action_mute - }) + binding.buttonMute.setText( + when (it.data) { + true -> R.string.action_unmute + else -> R.string.action_mute + } + ) } viewModel.blockState.observe(viewLifecycleOwner) { if (it !is Loading) { binding.buttonBlock.show() binding.progressBlock.show() - } - else { + } else { binding.buttonBlock.hide() binding.progressBlock.hide() } - binding.buttonBlock.setText(when (it.data) { - true -> R.string.action_unblock - else -> R.string.action_block - }) + binding.buttonBlock.setText( + when (it.data) { + true -> R.string.action_unblock + else -> R.string.action_block + } + ) } - } private fun handleClicks() { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt index aa3559355..56f812a05 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportNoteFragment.kt @@ -64,11 +64,10 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable { private fun fillViews() { binding.editNote.setText(viewModel.reportNote) - if (viewModel.isRemoteAccount){ + if (viewModel.isRemoteAccount) { binding.checkIsNotifyRemote.show() binding.reportDescriptionRemoteInstance.show() - } - else{ + } else { binding.checkIsNotifyRemote.hide() binding.reportDescriptionRemoteInstance.hide() } @@ -84,7 +83,6 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable { is Success -> viewModel.navigateTo(Screen.Done) is Loading -> showLoading() is Error -> showError(it.cause) - } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt index 33cd2ece2..98de3c94b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/fragments/ReportStatusesFragment.kt @@ -107,15 +107,15 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje private fun initStatusesView() { val preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) val statusDisplayOptions = StatusDisplayOptions( - animateAvatars = false, - mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true, - useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false), - showBotOverlay = false, - useBlurhash = preferences.getBoolean("useBlurhash", true), - cardViewMode = CardViewMode.NONE, - confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), - animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + animateAvatars = false, + mediaPreviewEnabled = accountManager.activeAccount?.mediaPreviewEnabled ?: true, + useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false), + showBotOverlay = false, + useBlurhash = preferences.getBoolean("useBlurhash", true), + cardViewMode = CardViewMode.NONE, + confirmReblogs = preferences.getBoolean("confirmReblogs", true), + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) adapter = StatusesAdapter(statusDisplayOptions, viewModel.statusViewState, this) @@ -132,9 +132,10 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje } adapter.addLoadStateListener { loadState -> - if (loadState.refresh is LoadState.Error - || loadState.append is LoadState.Error - || loadState.prepend is LoadState.Error) { + if (loadState.refresh is LoadState.Error || + loadState.append is LoadState.Error || + loadState.prepend is LoadState.Error + ) { showError() } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/report/model/StatusViewState.kt b/app/src/main/java/com/keylesspalace/tusky/components/report/model/StatusViewState.kt index 664ddc6a5..2bcade2fe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/report/model/StatusViewState.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/report/model/StatusViewState.kt @@ -30,7 +30,7 @@ class StatusViewState { fun setCollapsed(id: String, isCollapsed: Boolean) = setStateEnabled(longContentCollapsedState, id, isCollapsed) private fun isStateEnabled(map: Map, id: String, def: Boolean): Boolean = map[id] - ?: def + ?: def private fun setStateEnabled(map: MutableMap, id: String, state: Boolean) = map.put(id, state) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootViewModel.kt index 6890b15b3..14f012ba6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/scheduled/ScheduledTootViewModel.kt @@ -29,9 +29,9 @@ import kotlinx.coroutines.rx3.await import javax.inject.Inject class ScheduledTootViewModel @Inject constructor( - val mastodonApi: MastodonApi, - val eventHub: EventHub -): ViewModel() { + val mastodonApi: MastodonApi, + val eventHub: EventHub +) : ViewModel() { private val pagingSourceFactory = ScheduledTootPagingSourceFactory(mastodonApi) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt index 7208b0388..2326bf17e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchActivity.kt @@ -76,7 +76,7 @@ class SearchActivity : BottomSheetActivity(), HasAndroidInjector { menuInflater.inflate(R.menu.search_toolbar, menu) val searchView = menu.findItem(R.id.action_search) - .actionView as SearchView + .actionView as SearchView setupSearchView(searchView) searchView.setQuery(viewModel.currentQuery, false) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchType.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchType.kt index df98b9ab0..235f8ce03 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchType.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchType.kt @@ -19,4 +19,4 @@ enum class SearchType(val apiParameter: String) { Status("statuses"), Account("accounts"), Hashtag("hashtags") -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt index 4ec51413b..8682b5a27 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/SearchViewModel.kt @@ -95,13 +95,17 @@ class SearchViewModel @Inject constructor( fun removeItem(status: Pair) { timelineCases.delete(status.first.id) - .subscribe({ + .subscribe( + { if (loadedStatuses.remove(status)) statusesPagingSourceFactory.invalidate() - }, { - err -> Log.d(TAG, "Failed to delete status", err) - }) - .autoDispose() + }, + { + err -> + Log.d(TAG, "Failed to delete status", err) + } + ) + .autoDispose() } fun expandedChange(status: Pair, expanded: Boolean) { @@ -225,4 +229,4 @@ class SearchViewModel @Inject constructor( private const val TAG = "SearchViewModel" private const val DEFAULT_LOAD_SIZE = 20 } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt index 7056d5e29..71d582680 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchAccountsAdapter.kt @@ -24,12 +24,12 @@ import com.keylesspalace.tusky.adapter.AccountViewHolder import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.interfaces.LinkListener -class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) - : PagingDataAdapter(ACCOUNT_COMPARATOR) { +class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) : + PagingDataAdapter(ACCOUNT_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_account, parent, false) + .inflate(R.layout.item_account, parent, false) return AccountViewHolder(view) } @@ -46,10 +46,10 @@ class SearchAccountsAdapter(private val linkListener: LinkListener, private val val ACCOUNT_COMPARATOR = object : DiffUtil.ItemCallback() { override fun areContentsTheSame(oldItem: Account, newItem: Account): Boolean = - oldItem.deepEquals(newItem) + oldItem.deepEquals(newItem) override fun areItemsTheSame(oldItem: Account, newItem: Account): Boolean = - oldItem.id == newItem.id + oldItem.id == newItem.id } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchHashtagsAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchHashtagsAdapter.kt index cf7e7c7c5..50bd0f933 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchHashtagsAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchHashtagsAdapter.kt @@ -24,8 +24,8 @@ import com.keylesspalace.tusky.entity.HashTag import com.keylesspalace.tusky.interfaces.LinkListener import com.keylesspalace.tusky.util.BindingHolder -class SearchHashtagsAdapter(private val linkListener: LinkListener) - : PagingDataAdapter>(HASHTAG_COMPARATOR) { +class SearchHashtagsAdapter(private val linkListener: LinkListener) : + PagingDataAdapter>(HASHTAG_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -43,10 +43,10 @@ class SearchHashtagsAdapter(private val linkListener: LinkListener) val HASHTAG_COMPARATOR = object : DiffUtil.ItemCallback() { override fun areContentsTheSame(oldItem: HashTag, newItem: HashTag): Boolean = - oldItem.name == newItem.name + oldItem.name == newItem.name override fun areItemsTheSame(oldItem: HashTag, newItem: HashTag): Boolean = - oldItem.name == newItem.name + oldItem.name == newItem.name } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt index 845abaf89..9f30f9c55 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagerAdapter.kt @@ -34,5 +34,4 @@ class SearchPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(acti } override fun getItemCount() = 3 - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSource.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSource.kt index 315edba69..5ced44037 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSource.kt @@ -22,12 +22,13 @@ import com.keylesspalace.tusky.entity.SearchResult import com.keylesspalace.tusky.network.MastodonApi import kotlinx.coroutines.rx3.await -class SearchPagingSource( +class SearchPagingSource( private val mastodonApi: MastodonApi, private val searchType: SearchType, private val searchRequest: String, private val initialItems: List?, - private val parser: (SearchResult) -> List) : PagingSource() { + private val parser: (SearchResult) -> List +) : PagingSource() { override fun getRefreshKey(state: PagingState): Int? { return null @@ -80,4 +81,4 @@ class SearchPagingSource( return LoadResult.Error(e) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSourceFactory.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSourceFactory.kt index fb3760ca4..f995d029b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSourceFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/adapter/SearchPagingSourceFactory.kt @@ -50,4 +50,4 @@ class SearchPagingSourceFactory( fun invalidate() { currentSource?.invalidate() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt index 8a0d54162..d5e2a7aba 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchAccountsFragment.kt @@ -28,9 +28,9 @@ class SearchAccountsFragment : SearchFragment() { val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context) return SearchAccountsAdapter( - this, - preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), - preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + this, + preferences.getBoolean(PrefKeys.ANIMATE_GIF_AVATARS, false), + preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt index e18cd5cb1..10ef2713f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchFragment.kt @@ -29,8 +29,11 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject -abstract class SearchFragment : Fragment(R.layout.fragment_search), - LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener { +abstract class SearchFragment : + Fragment(R.layout.fragment_search), + LinkListener, + Injectable, + SwipeRefreshLayout.OnRefreshListener { @Inject lateinit var viewModelFactory: ViewModelFactory diff --git a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt index c6fe2c4e0..3ade751f1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/search/fragments/SearchStatusesFragment.kt @@ -74,15 +74,15 @@ class SearchStatusesFragment : SearchFragment, *> { val preferences = PreferenceManager.getDefaultSharedPreferences(binding.searchRecyclerView.context) val statusDisplayOptions = StatusDisplayOptions( - animateAvatars = preferences.getBoolean("animateGifAvatars", false), - mediaPreviewEnabled = viewModel.mediaPreviewEnabled, - useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false), - showBotOverlay = preferences.getBoolean("showBotOverlay", true), - useBlurhash = preferences.getBoolean("useBlurhash", true), - cardViewMode = CardViewMode.NONE, - confirmReblogs = preferences.getBoolean("confirmReblogs", true), - hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), - animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) + animateAvatars = preferences.getBoolean("animateGifAvatars", false), + mediaPreviewEnabled = viewModel.mediaPreviewEnabled, + useAbsoluteTime = preferences.getBoolean("absoluteTimeView", false), + showBotOverlay = preferences.getBoolean("showBotOverlay", true), + useBlurhash = preferences.getBoolean("useBlurhash", true), + cardViewMode = CardViewMode.NONE, + confirmReblogs = preferences.getBoolean("confirmReblogs", true), + hideStats = preferences.getBoolean(PrefKeys.WELLBEING_HIDE_STATS_POSTS, false), + animateEmojis = preferences.getBoolean(PrefKeys.ANIMATE_CUSTOM_EMOJIS, false) ) binding.searchRecyclerView.addItemDecoration(DividerItemDecoration(binding.searchRecyclerView.context, DividerItemDecoration.VERTICAL)) @@ -125,13 +125,17 @@ class SearchStatusesFragment : SearchFragment { val attachments = AttachmentViewData.list(actionable) - val intent = ViewMediaActivity.newIntent(context, attachments, - attachmentIndex) + val intent = ViewMediaActivity.newIntent( + context, attachments, + attachmentIndex + ) if (view != null) { val url = actionable.attachments[attachmentIndex].url ViewCompat.setTransitionName(view, url) - val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), - view, url) + val options = ActivityOptionsCompat.makeSceneTransitionAnimation( + requireActivity(), + view, url + ) startActivity(intent, options.toBundle()) } else { startActivity(intent) @@ -198,20 +202,23 @@ class SearchStatusesFragment : SearchFragment { - } //Ignore + } // Ignore } } else { popup.inflate(R.menu.status_more) @@ -271,11 +278,12 @@ class SearchStatusesFragment : SearchFragment @@ -287,8 +295,8 @@ class SearchStatusesFragment : SearchFragment viewModel.blockAccount(accountId) } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setMessage(getString(R.string.dialog_block_warning, accountUsername)) + .setPositiveButton(android.R.string.ok) { _, _ -> viewModel.blockAccount(accountId) } + .setNegativeButton(android.R.string.cancel, null) + .show() } private fun onMute(accountId: String, accountUsername: String) { @@ -383,11 +391,14 @@ class SearchStatusesFragment : SearchFragment - viewModel.deleteStatus(id) - removeItem(position) - } - .setNegativeButton(android.R.string.cancel, null) - .show() + .setMessage(R.string.dialog_delete_toot_warning) + .setPositiveButton(android.R.string.ok) { _, _ -> + viewModel.deleteStatus(id) + removeItem(position) + } + .setNegativeButton(android.R.string.cancel, null) + .show() } } private fun showConfirmEditDialog(id: String, position: Int, status: Status) { activity?.let { AlertDialog.Builder(it) - .setMessage(R.string.dialog_redraft_toot_warning) - .setPositiveButton(android.R.string.ok) { _, _ -> - viewModel.deleteStatus(id) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe({ deletedStatus -> - removeItem(position) + .setMessage(R.string.dialog_redraft_toot_warning) + .setPositiveButton(android.R.string.ok) { _, _ -> + viewModel.deleteStatus(id) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe( + { deletedStatus -> + removeItem(position) - val redraftStatus = if (deletedStatus.isEmpty()) { - status.toDeletedStatus() - } else { - deletedStatus - } + val redraftStatus = if (deletedStatus.isEmpty()) { + status.toDeletedStatus() + } else { + deletedStatus + } - val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions( - tootText = redraftStatus.text ?: "", - inReplyToId = redraftStatus.inReplyToId, - visibility = redraftStatus.visibility, - contentWarning = redraftStatus.spoilerText, - mediaAttachments = redraftStatus.attachments, - sensitive = redraftStatus.sensitive, - poll = redraftStatus.poll?.toNewPoll(status.createdAt) - )) - startActivity(intent) - }, { error -> - Log.w("SearchStatusesFragment", "error deleting status", error) - Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show() - }) - - } - .setNegativeButton(android.R.string.cancel, null) - .show() + val intent = ComposeActivity.startIntent( + requireContext(), + ComposeOptions( + tootText = redraftStatus.text ?: "", + inReplyToId = redraftStatus.inReplyToId, + visibility = redraftStatus.visibility, + contentWarning = redraftStatus.spoilerText, + mediaAttachments = redraftStatus.attachments, + sensitive = redraftStatus.sensitive, + poll = redraftStatus.poll?.toNewPoll(status.createdAt) + ) + ) + startActivity(intent) + }, + { error -> + Log.w("SearchStatusesFragment", "error deleting status", error) + Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show() + } + ) + } + .setNegativeButton(android.R.string.cancel, null) + .show() } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt index b0fc5d14d..50d6abcac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineFragment.kt @@ -25,10 +25,16 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.* +import androidx.recyclerview.widget.AsyncDifferConfig +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListUpdateCallback +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.SimpleItemAnimator import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import at.connyduck.sparkbutton.helpers.Utils -import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.* import autodispose2.androidx.lifecycle.autoDispose import com.keylesspalace.tusky.AccountListActivity import com.keylesspalace.tusky.AccountListActivity.Companion.newIntent @@ -47,7 +53,13 @@ import com.keylesspalace.tusky.interfaces.RefreshableFragment import com.keylesspalace.tusky.interfaces.ReselectableFragment import com.keylesspalace.tusky.interfaces.StatusActionListener import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.CardViewMode +import com.keylesspalace.tusky.util.ListStatusAccessibilityDelegate +import com.keylesspalace.tusky.util.StatusDisplayOptions +import com.keylesspalace.tusky.util.hide +import com.keylesspalace.tusky.util.show +import com.keylesspalace.tusky.util.viewBinding +import com.keylesspalace.tusky.util.visible import com.keylesspalace.tusky.view.EndlessOnScrollListener import com.keylesspalace.tusky.viewdata.AttachmentViewData import com.keylesspalace.tusky.viewdata.StatusViewData @@ -56,8 +68,13 @@ import io.reactivex.rxjava3.core.Observable import java.util.concurrent.TimeUnit import javax.inject.Inject -class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, Injectable, - ReselectableFragment, RefreshableFragment { +class TimelineFragment : + SFragment(), + OnRefreshListener, + StatusActionListener, + Injectable, + ReselectableFragment, + RefreshableFragment { @Inject lateinit var viewModelFactory: ViewModelFactory @@ -161,8 +178,7 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I private fun setupRecyclerView() { binding.recyclerView.setAccessibilityDelegateCompat( - ListStatusAccessibilityDelegate(binding.recyclerView, this) - { pos -> viewModel.statuses.getOrNull(pos) } + ListStatusAccessibilityDelegate(binding.recyclerView, this) { pos -> viewModel.statuses.getOrNull(pos) } ) binding.recyclerView.setHasFixedSize(true) layoutManager = LinearLayoutManager(context) @@ -330,8 +346,10 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I } override fun onViewAccount(id: String) { - if ((viewModel.kind == TimelineViewModel.Kind.USER || - viewModel.kind == TimelineViewModel.Kind.USER_WITH_REPLIES) && + if (( + viewModel.kind == TimelineViewModel.Kind.USER || + viewModel.kind == TimelineViewModel.Kind.USER_WITH_REPLIES + ) && viewModel.id == id ) { /* If already viewing an account page, then any requests to view that account page @@ -369,9 +387,9 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I private fun actionButtonPresent(): Boolean { return viewModel.kind != TimelineViewModel.Kind.TAG && - viewModel.kind != TimelineViewModel.Kind.FAVOURITES && - viewModel.kind != TimelineViewModel.Kind.BOOKMARKS && - activity is ActionButtonActivity + viewModel.kind != TimelineViewModel.Kind.FAVOURITES && + viewModel.kind != TimelineViewModel.Kind.BOOKMARKS && + activity is ActionButtonActivity } private fun updateViews() { @@ -505,7 +523,6 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I private const val HASHTAGS_ARG = "hashtags" private const val ARG_ENABLE_SWIPE_TO_REFRESH = "enableSwipeToRefresh" - fun newInstance( kind: TimelineViewModel.Kind, hashtagOrId: String? = null, @@ -531,7 +548,6 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I return fragment } - private val diffCallback: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( @@ -555,9 +571,9 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I return if (oldItem === newItem) { // If items are equal - update timestamp only listOf(StatusBaseViewHolder.Key.KEY_CREATED) - } else // If items are different - update the whole view holder + } else // If items are different - update the whole view holder null } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineRepository.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineRepository.kt index dac285593..1f7d32e74 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineRepository.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineRepository.kt @@ -5,11 +5,19 @@ import androidx.core.text.parseAsHtml import androidx.core.text.toHtml import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import com.keylesspalace.tusky.db.* -import com.keylesspalace.tusky.entity.* -import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.components.timeline.TimelineRequestMode.DISK import com.keylesspalace.tusky.components.timeline.TimelineRequestMode.NETWORK +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.db.TimelineAccountEntity +import com.keylesspalace.tusky.db.TimelineDao +import com.keylesspalace.tusky.db.TimelineStatusEntity +import com.keylesspalace.tusky.db.TimelineStatusWithAccount +import com.keylesspalace.tusky.entity.Account +import com.keylesspalace.tusky.entity.Attachment +import com.keylesspalace.tusky.entity.Emoji +import com.keylesspalace.tusky.entity.Poll +import com.keylesspalace.tusky.entity.Status +import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.util.Either import com.keylesspalace.tusky.util.dec import com.keylesspalace.tusky.util.inc @@ -17,9 +25,8 @@ import com.keylesspalace.tusky.util.trimTrailingWhitespace import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers import java.io.IOException -import java.util.* +import java.util.Date import java.util.concurrent.TimeUnit -import kotlin.collections.ArrayList data class Placeholder(val id: String) @@ -31,7 +38,10 @@ enum class TimelineRequestMode { interface TimelineRepository { fun getStatuses( - maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, + maxId: String?, + sinceId: String?, + sincedIdMinusOne: String?, + limit: Int, requestMode: TimelineRequestMode ): Single> @@ -52,8 +62,11 @@ class TimelineRepositoryImpl( } override fun getStatuses( - maxId: String?, sinceId: String?, sincedIdMinusOne: String?, - limit: Int, requestMode: TimelineRequestMode + maxId: String?, + sinceId: String?, + sincedIdMinusOne: String?, + limit: Int, + requestMode: TimelineRequestMode ): Single> { val acc = accountManager.activeAccount ?: throw IllegalStateException() val accountId = acc.id @@ -66,9 +79,12 @@ class TimelineRepositoryImpl( } private fun getStatusesFromNetwork( - maxId: String?, sinceId: String?, - sinceIdMinusOne: String?, limit: Int, - accountId: Long, requestMode: TimelineRequestMode + maxId: String?, + sinceId: String?, + sinceIdMinusOne: String?, + limit: Int, + accountId: Long, + requestMode: TimelineRequestMode ): Single> { return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1) .map { response -> @@ -87,8 +103,11 @@ class TimelineRepositoryImpl( } private fun addFromDbIfNeeded( - accountId: Long, statuses: List>, - maxId: String?, sinceId: String?, limit: Int, + accountId: Long, + statuses: List>, + maxId: String?, + sinceId: String?, + limit: Int, requestMode: TimelineRequestMode ): Single> { return if (requestMode != NETWORK && statuses.size < 2) { @@ -113,7 +132,9 @@ class TimelineRepositoryImpl( } private fun getStatusesFromDb( - accountId: Long, maxId: String?, sinceId: String?, + accountId: Long, + maxId: String?, + sinceId: String?, limit: Int ): Single> { return timelineDao.getStatusesForAccount(accountId, maxId, sinceId, limit) @@ -124,8 +145,10 @@ class TimelineRepositoryImpl( } private fun saveStatusesToDb( - accountId: Long, statuses: List, - maxId: String?, sinceId: String? + accountId: Long, + statuses: List, + maxId: String?, + sinceId: String? ): List> { var placeholderToInsert: Placeholder? = null @@ -347,7 +370,6 @@ fun TimelineAccountEntity.toAccount(gson: Gson): Account { ) } - fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity { return TimelineStatusEntity( serverId = this.id, diff --git a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineViewModel.kt index 74ff7163d..5509b9294 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/timeline/TimelineViewModel.kt @@ -3,7 +3,20 @@ package com.keylesspalace.tusky.components.timeline import android.content.SharedPreferences import android.util.Log import androidx.lifecycle.viewModelScope -import com.keylesspalace.tusky.appstore.* +import com.keylesspalace.tusky.appstore.BlockEvent +import com.keylesspalace.tusky.appstore.BookmarkEvent +import com.keylesspalace.tusky.appstore.DomainMuteEvent +import com.keylesspalace.tusky.appstore.Event +import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.appstore.FavoriteEvent +import com.keylesspalace.tusky.appstore.MuteConversationEvent +import com.keylesspalace.tusky.appstore.MuteEvent +import com.keylesspalace.tusky.appstore.PinEvent +import com.keylesspalace.tusky.appstore.PreferenceChangedEvent +import com.keylesspalace.tusky.appstore.ReblogEvent +import com.keylesspalace.tusky.appstore.StatusComposedEvent +import com.keylesspalace.tusky.appstore.StatusDeletedEvent +import com.keylesspalace.tusky.appstore.UnfollowEvent import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Filter import com.keylesspalace.tusky.entity.Poll @@ -12,7 +25,15 @@ import com.keylesspalace.tusky.network.FilterModel import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.TimelineCases import com.keylesspalace.tusky.settings.PrefKeys -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.Either +import com.keylesspalace.tusky.util.HttpHeaderLink +import com.keylesspalace.tusky.util.LinkHelper +import com.keylesspalace.tusky.util.RxAwareViewModel +import com.keylesspalace.tusky.util.dec +import com.keylesspalace.tusky.util.firstIsInstanceOrNull +import com.keylesspalace.tusky.util.inc +import com.keylesspalace.tusky.util.isLessThan +import com.keylesspalace.tusky.util.toViewData import com.keylesspalace.tusky.viewdata.StatusViewData import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single @@ -238,8 +259,8 @@ class TimelineViewModel @Inject constructor( private fun addStatusesBelow(statuses: MutableList>) { val fullFetch = isFullFetch(statuses) // Remove placeholder in the bottom if it's there - if (this.statuses.isNotEmpty() - && this.statuses.last() !is StatusViewData.Concrete + if (this.statuses.isNotEmpty() && + this.statuses.last() !is StatusViewData.Concrete ) { this.statuses.removeAt(this.statuses.lastIndex) } @@ -264,7 +285,7 @@ class TimelineViewModel @Inject constructor( fun loadGap(position: Int): Job { return viewModelScope.launch { - //check bounds before accessing list, + // check bounds before accessing list, if (statuses.size < position || position <= 0) { Log.e(TAG, "Wrong gap position: $position") return@launch @@ -318,7 +339,6 @@ class TimelineViewModel @Inject constructor( } catch (t: Exception) { ifExpected(t) { Log.d(TAG, "Failed to reblog status " + status.id, t) - } } } @@ -485,9 +505,9 @@ class TimelineViewModel @Inject constructor( } private fun shouldFilterStatus(status: Status): Boolean { - return status.inReplyToId != null && filterRemoveReplies - || status.reblog != null && filterRemoveReblogs - || filterModel.shouldFilterStatus(status.actionableStatus) + return status.inReplyToId != null && filterRemoveReplies || + status.reblog != null && filterRemoveReblogs || + filterModel.shouldFilterStatus(status.actionableStatus) } private fun extractNextId(response: Response<*>): String? { @@ -644,7 +664,8 @@ class TimelineViewModel @Inject constructor( private fun replacePlaceholderWithStatuses( newStatuses: MutableList>, - fullFetch: Boolean, pos: Int + fullFetch: Boolean, + pos: Int ) { val placeholder = statuses[pos] if (placeholder is StatusViewData.Placeholder) { @@ -873,9 +894,11 @@ class TimelineViewModel @Inject constructor( Log.e(TAG, "Failed to fetch filters", t) return@launch } - filterModel.initWithFilters(filters.filter { - filterContextMatchesKind(kind, it.context) - }) + filterModel.initWithFilters( + filters.filter { + filterContextMatchesKind(kind, it.context) + } + ) filterViewData(this@TimelineViewModel.statuses) } } @@ -891,7 +914,6 @@ class TimelineViewModel @Inject constructor( } } - companion object { private const val TAG = "TimelineVM" internal const val LOAD_AT_ONCE = 30 @@ -900,4 +922,4 @@ class TimelineViewModel @Inject constructor( enum class Kind { HOME, PUBLIC_LOCAL, PUBLIC_FEDERATED, TAG, USER, USER_PINNED, USER_WITH_REPLIES, FAVOURITES, LIST, BOOKMARKS } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountDao.kt index e1c64e28d..218c9b8f4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountDao.kt @@ -15,7 +15,11 @@ package com.keylesspalace.tusky.db -import androidx.room.* +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query @Dao interface AccountDao { @@ -27,5 +31,4 @@ interface AccountDao { @Query("SELECT * FROM AccountEntity ORDER BY id ASC") fun loadAll(): List - } diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt index ab6dbb7eb..0c25cbbc9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountEntity.kt @@ -21,42 +21,49 @@ import androidx.room.PrimaryKey import androidx.room.TypeConverters import com.keylesspalace.tusky.TabData import com.keylesspalace.tusky.defaultTabs - import com.keylesspalace.tusky.entity.Emoji import com.keylesspalace.tusky.entity.Status -@Entity(indices = [Index(value = ["domain", "accountId"], - unique = true)]) +@Entity( + indices = [ + Index( + value = ["domain", "accountId"], + unique = true + ) + ] +) @TypeConverters(Converters::class) -data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long, - val domain: String, - var accessToken: String, - var isActive: Boolean, - var accountId: String = "", - var username: String = "", - var displayName: String = "", - var profilePictureUrl: String = "", - var notificationsEnabled: Boolean = true, - var notificationsMentioned: Boolean = true, - var notificationsFollowed: Boolean = true, - var notificationsFollowRequested: Boolean = false, - var notificationsReblogged: Boolean = true, - var notificationsFavorited: Boolean = true, - var notificationsPolls: Boolean = true, - var notificationsSubscriptions: Boolean = true, - var notificationSound: Boolean = true, - var notificationVibration: Boolean = true, - var notificationLight: Boolean = true, - var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC, - var defaultMediaSensitivity: Boolean = false, - var alwaysShowSensitiveMedia: Boolean = false, - var alwaysOpenSpoiler: Boolean = false, - var mediaPreviewEnabled: Boolean = true, - var lastNotificationId: String = "0", - var activeNotifications: String = "[]", - var emojis: List = emptyList(), - var tabPreferences: List = defaultTabs(), - var notificationsFilter: String = "[\"follow_request\"]") { +data class AccountEntity( + @field:PrimaryKey(autoGenerate = true) var id: Long, + val domain: String, + var accessToken: String, + var isActive: Boolean, + var accountId: String = "", + var username: String = "", + var displayName: String = "", + var profilePictureUrl: String = "", + var notificationsEnabled: Boolean = true, + var notificationsMentioned: Boolean = true, + var notificationsFollowed: Boolean = true, + var notificationsFollowRequested: Boolean = false, + var notificationsReblogged: Boolean = true, + var notificationsFavorited: Boolean = true, + var notificationsPolls: Boolean = true, + var notificationsSubscriptions: Boolean = true, + var notificationSound: Boolean = true, + var notificationVibration: Boolean = true, + var notificationLight: Boolean = true, + var defaultPostPrivacy: Status.Visibility = Status.Visibility.PUBLIC, + var defaultMediaSensitivity: Boolean = false, + var alwaysShowSensitiveMedia: Boolean = false, + var alwaysOpenSpoiler: Boolean = false, + var mediaPreviewEnabled: Boolean = true, + var lastNotificationId: String = "0", + var activeNotifications: String = "[]", + var emojis: List = emptyList(), + var tabPreferences: List = defaultTabs(), + var notificationsFilter: String = "[\"follow_request\"]" +) { val identifier: String get() = "$domain:$accountId" diff --git a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt index 52650f77a..3de34f55e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/AccountManager.kt @@ -18,7 +18,7 @@ package com.keylesspalace.tusky.db import android.util.Log import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Status -import java.util.* +import java.util.Locale import javax.inject.Inject import javax.inject.Singleton @@ -66,7 +66,6 @@ class AccountManager @Inject constructor(db: AppDatabase) { val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0 val newAccountId = maxAccountId + 1 activeAccount = AccountEntity(id = newAccountId, domain = domain.lowercase(Locale.ROOT), accessToken = accessToken, isActive = true) - } /** @@ -79,7 +78,6 @@ class AccountManager @Inject constructor(db: AppDatabase) { Log.d(TAG, "saveAccount: saving account with id " + account.id) accountDao.insertOrReplace(account) } - } /** @@ -103,9 +101,7 @@ class AccountManager @Inject constructor(db: AppDatabase) { activeAccount = null } return activeAccount - } - } /** @@ -129,13 +125,12 @@ class AccountManager @Inject constructor(db: AppDatabase) { val accountIndex = accounts.indexOf(it) if (accountIndex != -1) { - //in case the user was already logged in with this account, remove the old information + // in case the user was already logged in with this account, remove the old information accounts.removeAt(accountIndex) accounts.add(accountIndex, it) } else { accounts.add(it) } - } } @@ -194,5 +189,4 @@ class AccountManager @Inject constructor(db: AppDatabase) { id == accountId } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt index 2d54e6746..393a23925 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/ConversationsDao.kt @@ -35,9 +35,8 @@ interface ConversationsDao { suspend fun delete(conversation: ConversationEntity): Int @Query("SELECT * FROM ConversationEntity WHERE accountId = :accountId ORDER BY s_createdAt DESC") - fun conversationsForAccount(accountId: Long) : PagingSource + fun conversationsForAccount(accountId: Long): PagingSource @Query("DELETE FROM ConversationEntity WHERE accountId = :accountId") fun deleteForAccount(accountId: Long) - } diff --git a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt index 48792a1be..a59133dea 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/Converters.kt @@ -25,18 +25,23 @@ import com.google.gson.reflect.TypeToken import com.keylesspalace.tusky.TabData import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity import com.keylesspalace.tusky.createTabDataFromId -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.Poll +import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.util.trimTrailingWhitespace import java.net.URLDecoder import java.net.URLEncoder -import java.util.* +import java.util.ArrayList +import java.util.Date import javax.inject.Inject import javax.inject.Singleton @ProvidedTypeConverter @Singleton class Converters @Inject constructor ( - private val gson: Gson + private val gson: Gson ) { @TypeConverter @@ -62,10 +67,10 @@ class Converters @Inject constructor ( @TypeConverter fun stringToTabData(str: String?): List? { return str?.split(";") - ?.map { - val data = it.split(":") - createTabDataFromId(data[0], data.drop(1).map { s -> URLDecoder.decode(s, "UTF-8") }) - } + ?.map { + val data = it.split(":") + createTabDataFromId(data[0], data.drop(1).map { s -> URLDecoder.decode(s, "UTF-8") }) + } } @TypeConverter @@ -126,7 +131,7 @@ class Converters @Inject constructor ( @TypeConverter fun spannedToString(spanned: Spanned?): String? { - if(spanned == null) { + if (spanned == null) { return null } return spanned.toHtml() @@ -134,7 +139,7 @@ class Converters @Inject constructor ( @TypeConverter fun stringToSpanned(spannedString: String?): Spanned? { - if(spannedString == null) { + if (spannedString == null) { return null } return spannedString.parseAsHtml().trimTrailingWhitespace() diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt index 88f683d8a..8029dd236 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftDao.kt @@ -38,5 +38,4 @@ interface DraftDao { @Query("SELECT * FROM DraftEntity WHERE id = :id") suspend fun find(id: Int): DraftEntity? - } diff --git a/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt index 184ff2c30..0df30040e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/DraftEntity.kt @@ -28,24 +28,24 @@ import kotlinx.parcelize.Parcelize @Entity @TypeConverters(Converters::class) data class DraftEntity( - @PrimaryKey(autoGenerate = true) val id: Int = 0, - val accountId: Long, - val inReplyToId: String?, - val content: String?, - val contentWarning: String?, - val sensitive: Boolean, - val visibility: Status.Visibility, - val attachments: List, - val poll: NewPoll?, - val failedToSend: Boolean + @PrimaryKey(autoGenerate = true) val id: Int = 0, + val accountId: Long, + val inReplyToId: String?, + val content: String?, + val contentWarning: String?, + val sensitive: Boolean, + val visibility: Status.Visibility, + val attachments: List, + val poll: NewPoll?, + val failedToSend: Boolean ) @Parcelize data class DraftAttachment( - val uriString: String, - val description: String?, - val type: Type -): Parcelable { + val uriString: String, + val description: String?, + val type: Type +) : Parcelable { val uri: Uri get() = uriString.toUri() diff --git a/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt index 1e2adaf04..ac4464f2d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/InstanceEntity.kt @@ -23,10 +23,10 @@ import com.keylesspalace.tusky.entity.Emoji @Entity @TypeConverters(Converters::class) data class InstanceEntity( - @field:PrimaryKey var instance: String, - val emojiList: List?, - val maximumTootCharacters: Int?, - val maxPollOptions: Int?, - val maxPollOptionLength: Int?, - val version: String? + @field:PrimaryKey var instance: String, + val emojiList: List?, + val maximumTootCharacters: Int?, + val maxPollOptions: Int?, + val maxPollOptionLength: Int?, + val version: String? ) diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt b/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt index 82e97aed1..6bbc08047 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineDao.kt @@ -17,11 +17,11 @@ abstract class TimelineDao { @Insert(onConflict = REPLACE) abstract fun insertStatus(timelineAccountEntity: TimelineStatusEntity): Long - @Insert(onConflict = IGNORE) abstract fun insertStatusIfNotThere(timelineAccountEntity: TimelineStatusEntity): Long - @Query(""" + @Query( + """ SELECT s.serverId, s.url, s.timelineUserId, s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt, s.emojis, s.reblogsCount, s.favouritesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive, @@ -46,47 +46,62 @@ AND (CASE WHEN :sinceId IS NOT NULL THEN (LENGTH(s.serverId) > LENGTH(:sinceId) OR LENGTH(s.serverId) == LENGTH(:sinceId) AND s.serverId > :sinceId) ELSE 1 END) ORDER BY LENGTH(s.serverId) DESC, s.serverId DESC -LIMIT :limit""") +LIMIT :limit""" + ) abstract fun getStatusesForAccount(account: Long, maxId: String?, sinceId: String?, limit: Int): Single> - @Transaction - open fun insertInTransaction(status: TimelineStatusEntity, account: TimelineAccountEntity, - reblogAccount: TimelineAccountEntity?) { + open fun insertInTransaction( + status: TimelineStatusEntity, + account: TimelineAccountEntity, + reblogAccount: TimelineAccountEntity? + ) { insertAccount(account) reblogAccount?.let(this::insertAccount) insertStatus(status) } - @Query("""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND + @Query( + """DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND (LENGTH(serverId) < LENGTH(:maxId) OR LENGTH(serverId) == LENGTH(:maxId) AND serverId < :maxId) AND (LENGTH(serverId) > LENGTH(:minId) OR LENGTH(serverId) == LENGTH(:minId) AND serverId > :minId) - """) + """ + ) abstract fun deleteRange(accountId: Long, minId: String, maxId: String) - @Query("""DELETE FROM TimelineStatusEntity WHERE authorServerId = null + @Query( + """DELETE FROM TimelineStatusEntity WHERE authorServerId = null AND timelineUserId = :account AND (LENGTH(serverId) < LENGTH(:maxId) OR LENGTH(serverId) == LENGTH(:maxId) AND serverId < :maxId) AND (LENGTH(serverId) > LENGTH(:sinceId) OR LENGTH(serverId) == LENGTH(:sinceId) AND serverId > :sinceId) -""") +""" + ) abstract fun removeAllPlaceholdersBetween(account: Long, maxId: String, sinceId: String) - @Query("""UPDATE TimelineStatusEntity SET favourited = :favourited -WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""") + @Query( + """UPDATE TimelineStatusEntity SET favourited = :favourited +WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""" + ) abstract fun setFavourited(accountId: Long, statusId: String, favourited: Boolean) - @Query("""UPDATE TimelineStatusEntity SET bookmarked = :bookmarked -WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""") + @Query( + """UPDATE TimelineStatusEntity SET bookmarked = :bookmarked +WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""" + ) abstract fun setBookmarked(accountId: Long, statusId: String, bookmarked: Boolean) - @Query("""UPDATE TimelineStatusEntity SET reblogged = :reblogged -WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""") + @Query( + """UPDATE TimelineStatusEntity SET reblogged = :reblogged +WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""" + ) abstract fun setReblogged(accountId: Long, statusId: String, reblogged: Boolean) - @Query("""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND -(authorServerId = :userId OR reblogAccountId = :userId)""") + @Query( + """DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND +(authorServerId = :userId OR reblogAccountId = :userId)""" + ) abstract fun removeAllByUser(accountId: Long, userId: String) @Query("DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId") @@ -95,14 +110,18 @@ WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = @Query("DELETE FROM TimelineAccountEntity WHERE timelineUserId = :accountId") abstract fun removeAllUsersForAccount(accountId: Long) - @Query("""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId -AND serverId = :statusId""") + @Query( + """DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId +AND serverId = :statusId""" + ) abstract fun delete(accountId: Long, statusId: String) @Query("""DELETE FROM TimelineStatusEntity WHERE createdAt < :olderThan""") abstract fun cleanup(olderThan: Long) - @Query("""UPDATE TimelineStatusEntity SET poll = :poll -WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""") + @Query( + """UPDATE TimelineStatusEntity SET poll = :poll +WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""" + ) abstract fun setVoted(accountId: Long, statusId: String, poll: String) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt b/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt index 296111d30..4e2db4ff3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/db/TimelineStatusEntity.kt @@ -1,6 +1,10 @@ package com.keylesspalace.tusky.db -import androidx.room.* +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.TypeConverters import com.keylesspalace.tusky.entity.Status /** @@ -15,62 +19,63 @@ import com.keylesspalace.tusky.entity.Status * fields. */ @Entity( - primaryKeys = ["serverId", "timelineUserId"], - foreignKeys = ([ + primaryKeys = ["serverId", "timelineUserId"], + foreignKeys = ( + [ ForeignKey( - entity = TimelineAccountEntity::class, - parentColumns = ["serverId", "timelineUserId"], - childColumns = ["authorServerId", "timelineUserId"] + entity = TimelineAccountEntity::class, + parentColumns = ["serverId", "timelineUserId"], + childColumns = ["authorServerId", "timelineUserId"] ) - ]), - // Avoiding rescanning status table when accounts table changes. Recommended by Room(c). - indices = [Index("authorServerId", "timelineUserId")] + ] + ), + // Avoiding rescanning status table when accounts table changes. Recommended by Room(c). + indices = [Index("authorServerId", "timelineUserId")] ) @TypeConverters(Converters::class) data class TimelineStatusEntity( - val serverId: String, // id never flips: we need it for sorting so it's a real id - val url: String?, - // our local id for the logged in user in case there are multiple accounts per instance - val timelineUserId: Long, - val authorServerId: String?, - val inReplyToId: String?, - val inReplyToAccountId: String?, - val content: String?, - val createdAt: Long, - val emojis: String?, - val reblogsCount: Int, - val favouritesCount: Int, - val reblogged: Boolean, - val bookmarked: Boolean, - val favourited: Boolean, - val sensitive: Boolean, - val spoilerText: String?, - val visibility: Status.Visibility?, - val attachments: String?, - val mentions: String?, - val application: String?, - val reblogServerId: String?, // if it has a reblogged status, it's id is stored here - val reblogAccountId: String?, - val poll: String?, - val muted: Boolean? + val serverId: String, // id never flips: we need it for sorting so it's a real id + val url: String?, + // our local id for the logged in user in case there are multiple accounts per instance + val timelineUserId: Long, + val authorServerId: String?, + val inReplyToId: String?, + val inReplyToAccountId: String?, + val content: String?, + val createdAt: Long, + val emojis: String?, + val reblogsCount: Int, + val favouritesCount: Int, + val reblogged: Boolean, + val bookmarked: Boolean, + val favourited: Boolean, + val sensitive: Boolean, + val spoilerText: String?, + val visibility: Status.Visibility?, + val attachments: String?, + val mentions: String?, + val application: String?, + val reblogServerId: String?, // if it has a reblogged status, it's id is stored here + val reblogAccountId: String?, + val poll: String?, + val muted: Boolean? ) @Entity( - primaryKeys = ["serverId", "timelineUserId"] + primaryKeys = ["serverId", "timelineUserId"] ) data class TimelineAccountEntity( - val serverId: String, - val timelineUserId: Long, - val localUsername: String, - val username: String, - val displayName: String, - val url: String, - val avatar: String, - val emojis: String, - val bot: Boolean + val serverId: String, + val timelineUserId: Long, + val localUsername: String, + val username: String, + val displayName: String, + val url: String, + val avatar: String, + val emojis: String, + val bot: Boolean ) - class TimelineStatusWithAccount { @Embedded lateinit var status: TimelineStatusEntity @@ -78,4 +83,4 @@ class TimelineStatusWithAccount { lateinit var account: TimelineAccountEntity @Embedded(prefix = "rb_") var reblogAccount: TimelineAccountEntity? = null -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt index cdf10224b..cbeda2f41 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ActivitiesModule.kt @@ -15,7 +15,23 @@ package com.keylesspalace.tusky.di -import com.keylesspalace.tusky.* +import com.keylesspalace.tusky.AboutActivity +import com.keylesspalace.tusky.AccountActivity +import com.keylesspalace.tusky.AccountListActivity +import com.keylesspalace.tusky.BaseActivity +import com.keylesspalace.tusky.EditProfileActivity +import com.keylesspalace.tusky.FiltersActivity +import com.keylesspalace.tusky.LicenseActivity +import com.keylesspalace.tusky.ListsActivity +import com.keylesspalace.tusky.LoginActivity +import com.keylesspalace.tusky.MainActivity +import com.keylesspalace.tusky.ModalTimelineActivity +import com.keylesspalace.tusky.SplashActivity +import com.keylesspalace.tusky.StatusListActivity +import com.keylesspalace.tusky.TabPreferenceActivity +import com.keylesspalace.tusky.ViewMediaActivity +import com.keylesspalace.tusky.ViewTagActivity +import com.keylesspalace.tusky.ViewThreadActivity import com.keylesspalace.tusky.components.announcements.AnnouncementsActivity import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.drafts.DraftsActivity diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt index ff3d02669..51596ac71 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppComponent.kt @@ -21,23 +21,24 @@ import dagger.Component import dagger.android.support.AndroidSupportInjectionModule import javax.inject.Singleton - /** * Created by charlag on 3/21/18. */ @Singleton -@Component(modules = [ - AppModule::class, - NetworkModule::class, - AndroidSupportInjectionModule::class, - ActivitiesModule::class, - ServicesModule::class, - BroadcastReceiverModule::class, - ViewModelModule::class, - RepositoryModule::class, - MediaUploaderModule::class -]) +@Component( + modules = [ + AppModule::class, + NetworkModule::class, + AndroidSupportInjectionModule::class, + ActivitiesModule::class, + ServicesModule::class, + BroadcastReceiverModule::class, + ViewModelModule::class, + RepositoryModule::class, + MediaUploaderModule::class + ] +) interface AppComponent { @Component.Builder interface Builder { @@ -48,4 +49,4 @@ interface AppComponent { } fun inject(app: TuskyApplication) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt index 21fd184a6..6446a7357 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppInjector.kt @@ -34,7 +34,7 @@ import dagger.android.support.AndroidSupportInjection object AppInjector { fun init(app: TuskyApplication) { DaggerAppComponent.builder().application(app) - .build().inject(app) + .build().inject(app) app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { @@ -58,7 +58,6 @@ object AppInjector { override fun onActivityStopped(activity: Activity) { } - }) } @@ -68,13 +67,15 @@ object AppInjector { } if (activity is FragmentActivity) { activity.supportFragmentManager.registerFragmentLifecycleCallbacks( - object : FragmentManager.FragmentLifecycleCallbacks() { - override fun onFragmentPreAttached(fm: FragmentManager, f: Fragment, context: Context) { - if (f is Injectable) { - AndroidSupportInjection.inject(f) - } + object : FragmentManager.FragmentLifecycleCallbacks() { + override fun onFragmentPreAttached(fm: FragmentManager, f: Fragment, context: Context) { + if (f is Injectable) { + AndroidSupportInjection.inject(f) } - }, true) + } + }, + true + ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt index faf0a3863..4cce3f447 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/AppModule.kt @@ -13,7 +13,6 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ - package com.keylesspalace.tusky.di import android.app.Application @@ -60,8 +59,10 @@ class AppModule { } @Provides - fun providesTimelineUseCases(api: MastodonApi, - eventHub: EventHub): TimelineCases { + fun providesTimelineUseCases( + api: MastodonApi, + eventHub: EventHub + ): TimelineCases { return TimelineCasesImpl(api, eventHub) } @@ -73,24 +74,24 @@ class AppModule { @Singleton fun providesDatabase(appContext: Context, converters: Converters): AppDatabase { return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB") - .addTypeConverter(converters) - .allowMainThreadQueries() - .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, - AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8, - AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, - AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, - AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, - AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, - AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22, - AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24, AppDatabase.MIGRATION_24_25, - AppDatabase.MIGRATION_26_27, - AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")) - ) - .build() + .addTypeConverter(converters) + .allowMainThreadQueries() + .addMigrations( + AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, + AppDatabase.MIGRATION_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8, + AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, + AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13, + AppDatabase.MIGRATION_13_14, AppDatabase.MIGRATION_14_15, AppDatabase.MIGRATION_15_16, + AppDatabase.MIGRATION_16_17, AppDatabase.MIGRATION_17_18, AppDatabase.MIGRATION_18_19, + AppDatabase.MIGRATION_19_20, AppDatabase.MIGRATION_20_21, AppDatabase.MIGRATION_21_22, + AppDatabase.MIGRATION_22_23, AppDatabase.MIGRATION_23_24, AppDatabase.MIGRATION_24_25, + AppDatabase.MIGRATION_26_27, + AppDatabase.Migration25_26(appContext.getExternalFilesDir("Tusky")) + ) + .build() } @Provides @Singleton fun notifier(context: Context): Notifier = SystemNotifier(context) - } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt index edf95341c..b7213fa64 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/BroadcastReceiverModule.kt @@ -16,16 +16,16 @@ package com.keylesspalace.tusky.di -import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver +import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver import dagger.Module import dagger.android.ContributesAndroidInjector @Module abstract class BroadcastReceiverModule { @ContributesAndroidInjector - abstract fun contributeSendStatusBroadcastReceiver() : SendStatusBroadcastReceiver + abstract fun contributeSendStatusBroadcastReceiver(): SendStatusBroadcastReceiver @ContributesAndroidInjector - abstract fun contributeNotificationClearBroadcastReceiver() : NotificationClearBroadcastReceiver -} \ No newline at end of file + abstract fun contributeNotificationClearBroadcastReceiver(): NotificationClearBroadcastReceiver +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt index 16ed59cc2..704252aea 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/FragmentBuildersModule.kt @@ -13,23 +13,25 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ - package com.keylesspalace.tusky.di import com.keylesspalace.tusky.AccountsInListFragment import com.keylesspalace.tusky.components.conversation.ConversationsFragment import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment -import com.keylesspalace.tusky.fragment.* import com.keylesspalace.tusky.components.preference.AccountPreferencesFragment import com.keylesspalace.tusky.components.preference.NotificationPreferencesFragment +import com.keylesspalace.tusky.components.preference.PreferencesFragment import com.keylesspalace.tusky.components.report.fragments.ReportDoneFragment import com.keylesspalace.tusky.components.report.fragments.ReportNoteFragment import com.keylesspalace.tusky.components.report.fragments.ReportStatusesFragment import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragment import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment -import com.keylesspalace.tusky.components.preference.PreferencesFragment import com.keylesspalace.tusky.components.timeline.TimelineFragment +import com.keylesspalace.tusky.fragment.AccountListFragment +import com.keylesspalace.tusky.fragment.AccountMediaFragment +import com.keylesspalace.tusky.fragment.NotificationsFragment +import com.keylesspalace.tusky.fragment.ViewThreadFragment import dagger.Module import dagger.android.ContributesAndroidInjector @@ -89,5 +91,4 @@ abstract class FragmentBuildersModule { @ContributesAndroidInjector abstract fun preferencesFragment(): PreferencesFragment - } diff --git a/app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt b/app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt index 1df715e70..f3b4e8105 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/Injectable.kt @@ -13,11 +13,10 @@ * You should have received a copy of the GNU General Public License along with Tusky; if not, * see . */ - package com.keylesspalace.tusky.di /** * Created by charlag on 3/24/18. */ -interface Injectable \ No newline at end of file +interface Injectable diff --git a/app/src/main/java/com/keylesspalace/tusky/di/MediaUploaderModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/MediaUploaderModule.kt index 66dc27110..00ec1b73e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/MediaUploaderModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/MediaUploaderModule.kt @@ -26,5 +26,5 @@ import dagger.Provides class MediaUploaderModule { @Provides fun providesMediaUploder(context: Context, mastodonApi: MastodonApi): MediaUploader = - MediaUploaderImpl(context, mastodonApi) -} \ No newline at end of file + MediaUploaderImpl(context, mastodonApi) +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt index 89e28553c..7bda6ef74 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/NetworkModule.kt @@ -53,16 +53,16 @@ class NetworkModule { @Singleton fun providesGson(): Gson { return GsonBuilder() - .registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter()) - .create() + .registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter()) + .create() } @Provides @Singleton fun providesHttpClient( - accountManager: AccountManager, - context: Context, - preferences: SharedPreferences + accountManager: AccountManager, + context: Context, + preferences: SharedPreferences ): OkHttpClient { val httpProxyEnabled = preferences.getBoolean("httpProxyEnabled", false) val httpServer = preferences.getNonNullString("httpProxyServer", "") @@ -92,30 +92,29 @@ class NetworkModule { builder.proxy(Proxy(Proxy.Type.HTTP, address)) } return builder - .apply { - addInterceptor(InstanceSwitchAuthInterceptor(accountManager)) - if (BuildConfig.DEBUG) { - addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }) - } + .apply { + addInterceptor(InstanceSwitchAuthInterceptor(accountManager)) + if (BuildConfig.DEBUG) { + addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }) } - .build() + } + .build() } @Provides @Singleton fun providesRetrofit( - httpClient: OkHttpClient, - gson: Gson + httpClient: OkHttpClient, + gson: Gson ): Retrofit { return Retrofit.Builder().baseUrl("https://" + MastodonApi.PLACEHOLDER_DOMAIN) - .client(httpClient) - .addConverterFactory(GsonConverterFactory.create(gson)) - .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) - .build() - + .client(httpClient) + .addConverterFactory(GsonConverterFactory.create(gson)) + .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) + .build() } @Provides @Singleton fun providesApi(retrofit: Retrofit): MastodonApi = retrofit.create() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/RepositoryModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/RepositoryModule.kt index 5086c752d..e94c55d19 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/RepositoryModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/RepositoryModule.kt @@ -1,11 +1,11 @@ package com.keylesspalace.tusky.di import com.google.gson.Gson +import com.keylesspalace.tusky.components.timeline.TimelineRepository +import com.keylesspalace.tusky.components.timeline.TimelineRepositoryImpl import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.db.AppDatabase import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.components.timeline.TimelineRepository -import com.keylesspalace.tusky.components.timeline.TimelineRepositoryImpl import dagger.Module import dagger.Provides @@ -13,11 +13,11 @@ import dagger.Provides class RepositoryModule { @Provides fun providesTimelineRepository( - db: AppDatabase, - mastodonApi: MastodonApi, - accountManager: AccountManager, - gson: Gson + db: AppDatabase, + mastodonApi: MastodonApi, + accountManager: AccountManager, + gson: Gson ): TimelineRepository { return TimelineRepositoryImpl(db.timelineDao(), mastodonApi, accountManager, gson) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ServicesModule.kt b/app/src/main/java/com/keylesspalace/tusky/di/ServicesModule.kt index 5f6495543..f34dc0750 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ServicesModule.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ServicesModule.kt @@ -36,4 +36,4 @@ abstract class ServicesModule { return ServiceClientImpl(context) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt index a71817ecb..8cb0a172d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt +++ b/app/src/main/java/com/keylesspalace/tusky/di/ViewModelFactory.kt @@ -11,7 +11,6 @@ import com.keylesspalace.tusky.components.drafts.DraftsViewModel import com.keylesspalace.tusky.components.report.ReportViewModel import com.keylesspalace.tusky.components.scheduled.ScheduledTootViewModel import com.keylesspalace.tusky.components.search.SearchViewModel -import com.keylesspalace.tusky.components.timeline.TimelineFragment import com.keylesspalace.tusky.components.timeline.TimelineViewModel import com.keylesspalace.tusky.viewmodel.AccountViewModel import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel @@ -63,7 +62,6 @@ abstract class ViewModelModule { @ViewModelKey(ListsViewModel::class) internal abstract fun listsViewModel(viewModel: ListsViewModel): ViewModel - @Binds @IntoMap @ViewModelKey(AccountsInListViewModel::class) @@ -104,5 +102,5 @@ abstract class ViewModelModule { @ViewModelKey(TimelineViewModel::class) internal abstract fun timelineViewModel(viewModel: TimelineViewModel): ViewModel - //Add more ViewModels here + // Add more ViewModels here } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.kt b/app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.kt index 181078839..e974ce196 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/AccessToken.kt @@ -18,5 +18,5 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName data class AccessToken( - @SerializedName("access_token") val accessToken: String + @SerializedName("access_token") val accessToken: String ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt index d4940fe0e..49df50734 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Account.kt @@ -20,23 +20,23 @@ import com.google.gson.annotations.SerializedName import java.util.Date data class Account( - val id: String, - @SerializedName("username") val localUsername: String, - @SerializedName("acct") val username: String, - @SerializedName("display_name") val displayName: String?, // should never be null per Api definition, but some servers break the contract - val note: Spanned, - val url: String, - val avatar: String, - val header: String, - val locked: Boolean = false, - @SerializedName("followers_count") val followersCount: Int = 0, - @SerializedName("following_count") val followingCount: Int = 0, - @SerializedName("statuses_count") val statusesCount: Int = 0, - val source: AccountSource? = null, - val bot: Boolean = false, - val emojis: List? = emptyList(), // nullable for backward compatibility - val fields: List? = emptyList(), //nullable for backward compatibility - val moved: Account? = null + val id: String, + @SerializedName("username") val localUsername: String, + @SerializedName("acct") val username: String, + @SerializedName("display_name") val displayName: String?, // should never be null per Api definition, but some servers break the contract + val note: Spanned, + val url: String, + val avatar: String, + val header: String, + val locked: Boolean = false, + @SerializedName("followers_count") val followersCount: Int = 0, + @SerializedName("following_count") val followingCount: Int = 0, + @SerializedName("statuses_count") val statusesCount: Int = 0, + val source: AccountSource? = null, + val bot: Boolean = false, + val emojis: List? = emptyList(), // nullable for backward compatibility + val fields: List? = emptyList(), // nullable for backward compatibility + val moved: Account? = null ) { @@ -57,41 +57,41 @@ data class Account( } fun deepEquals(other: Account): Boolean { - return id == other.id - && localUsername == other.localUsername - && displayName == other.displayName - && note == other.note - && url == other.url - && avatar == other.avatar - && header == other.header - && locked == other.locked - && followersCount == other.followersCount - && followingCount == other.followingCount - && statusesCount == other.statusesCount - && source == other.source - && bot == other.bot - && emojis == other.emojis - && fields == other.fields - && moved == other.moved + return id == other.id && + localUsername == other.localUsername && + displayName == other.displayName && + note == other.note && + url == other.url && + avatar == other.avatar && + header == other.header && + locked == other.locked && + followersCount == other.followersCount && + followingCount == other.followingCount && + statusesCount == other.statusesCount && + source == other.source && + bot == other.bot && + emojis == other.emojis && + fields == other.fields && + moved == other.moved } fun isRemote(): Boolean = this.username != this.localUsername } data class AccountSource( - val privacy: Status.Visibility, - val sensitive: Boolean, - val note: String, - val fields: List? + val privacy: Status.Visibility, + val sensitive: Boolean, + val note: String, + val fields: List? ) -data class Field ( - val name: String, - val value: Spanned, - @SerializedName("verified_at") val verifiedAt: Date? +data class Field( + val name: String, + val value: Spanned, + @SerializedName("verified_at") val verifiedAt: Date? ) -data class StringField ( - val name: String, - val value: String +data class StringField( + val name: String, + val value: String ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt index 5cd32fe8d..400e9764d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Announcement.kt @@ -17,22 +17,22 @@ package com.keylesspalace.tusky.entity import android.text.Spanned import com.google.gson.annotations.SerializedName -import java.util.* +import java.util.Date data class Announcement( - val id: String, - val content: Spanned, - @SerializedName("starts_at") val startsAt: Date?, - @SerializedName("ends_at") val endsAt: Date?, - @SerializedName("all_day") val allDay: Boolean, - @SerializedName("published_at") val publishedAt: Date, - @SerializedName("updated_at") val updatedAt: Date, - val read: Boolean, - val mentions: List, - val statuses: List, - val tags: List, - val emojis: List, - val reactions: List + val id: String, + val content: Spanned, + @SerializedName("starts_at") val startsAt: Date?, + @SerializedName("ends_at") val endsAt: Date?, + @SerializedName("all_day") val allDay: Boolean, + @SerializedName("published_at") val publishedAt: Date, + @SerializedName("updated_at") val updatedAt: Date, + val read: Boolean, + val mentions: List, + val statuses: List, + val tags: List, + val emojis: List, + val reactions: List ) { override fun equals(other: Any?): Boolean { @@ -48,10 +48,10 @@ data class Announcement( } data class Reaction( - val name: String, - var count: Int, - var me: Boolean, - val url: String?, - @SerializedName("static_url") val staticUrl: String? + val name: String, + var count: Int, + var me: Boolean, + val url: String?, + @SerializedName("static_url") val staticUrl: String? ) } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.kt b/app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.kt index 95a829c14..fe6b0c3ce 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/AppCredentials.kt @@ -18,6 +18,6 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName data class AppCredentials( - @SerializedName("client_id") val clientId: String, - @SerializedName("client_secret") val clientSecret: String + @SerializedName("client_id") val clientId: String, + @SerializedName("client_secret") val clientSecret: String ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt index 3e14519ac..27fdc8be6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Attachment.kt @@ -26,13 +26,13 @@ import kotlinx.parcelize.Parcelize @Parcelize data class Attachment( - val id: String, - val url: String, - @SerializedName("preview_url") val previewUrl: String?, // can be null for e.g. audio attachments - val meta: MetaData?, - val type: Type, - val description: String?, - val blurhash: String? + val id: String, + val url: String, + @SerializedName("preview_url") val previewUrl: String?, // can be null for e.g. audio attachments + val meta: MetaData?, + val type: Type, + val description: String?, + val blurhash: String? ) : Parcelable { @JsonAdapter(MediaTypeDeserializer::class) @@ -66,9 +66,9 @@ data class Attachment( * The meta data of an [Attachment]. */ @Parcelize - data class MetaData ( - val focus: Focus?, - val duration: Float? + data class MetaData( + val focus: Focus?, + val duration: Float? ) : Parcelable /** @@ -78,8 +78,8 @@ data class Attachment( * https://github.com/jonom/jquery-focuspoint#1-calculate-your-images-focus-point */ @Parcelize - data class Focus ( - val x: Float, - val y: Float + data class Focus( + val x: Float, + val y: Float ) : Parcelable } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt index ada9ec205..52011f3d1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Card.kt @@ -19,16 +19,16 @@ import android.text.Spanned import com.google.gson.annotations.SerializedName data class Card( - val url: String, - val title: Spanned, - val description: Spanned, - @SerializedName("author_name") val authorName: String, - val image: String, - val type: String, - val width: Int, - val height: Int, - val blurhash: String?, - val embed_url: String? + val url: String, + val title: Spanned, + val description: Spanned, + @SerializedName("author_name") val authorName: String, + val image: String, + val type: String, + val width: Int, + val height: Int, + val blurhash: String?, + val embed_url: String? ) { override fun hashCode(): Int { diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Conversation.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Conversation.kt index 0e66385fd..cb09981db 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Conversation.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Conversation.kt @@ -18,8 +18,8 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName data class Conversation( - val id: String, - val accounts: List, - @SerializedName("last_status") val lastStatus: Status?, // should never be null, but apparently its possible https://github.com/tuskyapp/Tusky/issues/1038 - val unread: Boolean -) \ No newline at end of file + val id: String, + val accounts: List, + @SerializedName("last_status") val lastStatus: Status?, // should never be null, but apparently its possible https://github.com/tuskyapp/Tusky/issues/1038 + val unread: Boolean +) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt b/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt index 289a93fb7..92a35b69c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/DeletedStatus.kt @@ -16,19 +16,20 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName -import java.util.* +import java.util.ArrayList +import java.util.Date data class DeletedStatus( - var text: String?, - @SerializedName("in_reply_to_id") var inReplyToId: String?, - @SerializedName("spoiler_text") val spoilerText: String, - val visibility: Status.Visibility, - val sensitive: Boolean, - @SerializedName("media_attachments") var attachments: ArrayList?, - val poll: Poll?, - @SerializedName("created_at") val createdAt: Date + var text: String?, + @SerializedName("in_reply_to_id") var inReplyToId: String?, + @SerializedName("spoiler_text") val spoilerText: String, + val visibility: Status.Visibility, + val sensitive: Boolean, + @SerializedName("media_attachments") var attachments: ArrayList?, + val poll: Poll?, + @SerializedName("created_at") val createdAt: Date ) { fun isEmpty(): Boolean { return text == null && attachments == null } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Emoji.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Emoji.kt index 42bb99e93..130831a2d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Emoji.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Emoji.kt @@ -21,8 +21,8 @@ import kotlinx.parcelize.Parcelize @Parcelize data class Emoji( - val shortcode: String, - val url: String, - @SerializedName("static_url") val staticUrl: String, - @SerializedName("visible_in_picker") val visibleInPicker: Boolean? + val shortcode: String, + val url: String, + @SerializedName("static_url") val staticUrl: String, + @SerializedName("visible_in_picker") val visibleInPicker: Boolean? ) : Parcelable diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Filter.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Filter.kt index 58bdc79a9..34b80e83b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Filter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Filter.kt @@ -17,7 +17,7 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName -data class Filter ( +data class Filter( val id: String, val phrase: String, val context: List, @@ -45,4 +45,3 @@ data class Filter ( return filter?.id.equals(id) } } - diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/HashTag.kt b/app/src/main/java/com/keylesspalace/tusky/entity/HashTag.kt index 1eaaf68f9..a334257a1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/HashTag.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/HashTag.kt @@ -1,3 +1,3 @@ package com.keylesspalace.tusky.entity -data class HashTag(val name: String) \ No newline at end of file +data class HashTag(val name: String) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt b/app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt index 9473f0372..98af734bf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/IdentityProof.kt @@ -3,7 +3,7 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName data class IdentityProof( - val provider: String, - @SerializedName("provider_username") val username: String, - @SerializedName("profile_url") val profileUrl: String + val provider: String, + @SerializedName("provider_username") val username: String, + @SerializedName("profile_url") val profileUrl: String ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt index a9f6f499c..d1e2aca90 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Instance.kt @@ -17,20 +17,20 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName -data class Instance ( - val uri: String, - val title: String, - val description: String, - val email: String, - val version: String, - val urls: Map, - val stats: Map?, - val thumbnail: String?, - val languages: List, - @SerializedName("contact_account") val contactAccount: Account, - @SerializedName("max_toot_chars") val maxTootChars: Int?, - @SerializedName("max_bio_chars") val maxBioChars: Int?, - @SerializedName("poll_limits") val pollLimits: PollLimits? +data class Instance( + val uri: String, + val title: String, + val description: String, + val email: String, + val version: String, + val urls: Map, + val stats: Map?, + val thumbnail: String?, + val languages: List, + @SerializedName("contact_account") val contactAccount: Account, + @SerializedName("max_toot_chars") val maxTootChars: Int?, + @SerializedName("max_bio_chars") val maxBioChars: Int?, + @SerializedName("poll_limits") val pollLimits: PollLimits? ) { override fun hashCode(): Int { return uri.hashCode() @@ -45,7 +45,7 @@ data class Instance ( } } -data class PollLimits ( - @SerializedName("max_options") val maxOptions: Int?, - @SerializedName("max_option_chars") val maxOptionChars: Int? +data class PollLimits( + @SerializedName("max_options") val maxOptions: Int?, + @SerializedName("max_option_chars") val maxOptionChars: Int? ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Marker.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Marker.kt index 16fd9e318..78572054d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Marker.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Marker.kt @@ -1,15 +1,15 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName -import java.util.* +import java.util.Date /** * API type for saving the scroll position of a timeline. */ data class Marker( - @SerializedName("last_read_id") - val lastReadId: String, - val version: Int, - @SerializedName("updated_at") - val updatedAt: Date -) \ No newline at end of file + @SerializedName("last_read_id") + val lastReadId: String, + val version: Int, + @SerializedName("updated_at") + val updatedAt: Date +) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/MastoList.kt b/app/src/main/java/com/keylesspalace/tusky/entity/MastoList.kt index 2f8eecf3c..bfec7cc52 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/MastoList.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/MastoList.kt @@ -21,6 +21,6 @@ package com.keylesspalace.tusky.entity */ data class MastoList( - val id: String, - val title: String -) \ No newline at end of file + val id: String, + val title: String +) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/NewStatus.kt b/app/src/main/java/com/keylesspalace/tusky/entity/NewStatus.kt index 16cbc6a7c..83ed56e95 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/NewStatus.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/NewStatus.kt @@ -20,19 +20,19 @@ import com.google.gson.annotations.SerializedName import kotlinx.parcelize.Parcelize data class NewStatus( - val status: String, - @SerializedName("spoiler_text") val warningText: String, - @SerializedName("in_reply_to_id") val inReplyToId: String?, - val visibility: String, - val sensitive: Boolean, - @SerializedName("media_ids") val mediaIds: List?, - @SerializedName("scheduled_at") val scheduledAt: String?, - val poll: NewPoll? + val status: String, + @SerializedName("spoiler_text") val warningText: String, + @SerializedName("in_reply_to_id") val inReplyToId: String?, + val visibility: String, + val sensitive: Boolean, + @SerializedName("media_ids") val mediaIds: List?, + @SerializedName("scheduled_at") val scheduledAt: String?, + val poll: NewPoll? ) @Parcelize data class NewPoll( - val options: List, - @SerializedName("expires_in") val expiresIn: Int, - val multiple: Boolean -): Parcelable \ No newline at end of file + val options: List, + @SerializedName("expires_in") val expiresIn: Int, + val multiple: Boolean +) : Parcelable diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt index cb4ce3cbd..6198867d9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt @@ -79,7 +79,6 @@ data class Notification( ): Type { return Type.byString(json.asString) } - } /** Helper for Java */ @@ -89,8 +88,9 @@ data class Notification( fun rewriteToStatusTypeIfNeeded(accountId: String): Notification { if (type == Type.MENTION && status != null) { return if (status.mentions.any { - it.id == accountId - }) this else copy(type = Type.STATUS) + it.id == accountId + } + ) this else copy(type = Type.STATUS) } return this } diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt index 02c236c48..1a4c23548 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Poll.kt @@ -1,22 +1,22 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName -import java.util.* +import java.util.Date data class Poll( - val id: String, - @SerializedName("expires_at") val expiresAt: Date?, - val expired: Boolean, - val multiple: Boolean, - @SerializedName("votes_count") val votesCount: Int, - @SerializedName("voters_count") val votersCount: Int?, // nullable for compatibility with Pleroma - val options: List, - val voted: Boolean + val id: String, + @SerializedName("expires_at") val expiresAt: Date?, + val expired: Boolean, + val multiple: Boolean, + @SerializedName("votes_count") val votesCount: Int, + @SerializedName("voters_count") val votersCount: Int?, // nullable for compatibility with Pleroma + val options: List, + val voted: Boolean ) { fun votedCopy(choices: List): Poll { val newOptions = options.mapIndexed { index, option -> - if(choices.contains(index)) { + if (choices.contains(index)) { option.copy(votesCount = option.votesCount + 1) } else { option @@ -24,24 +24,23 @@ data class Poll( } return copy( - options = newOptions, - votesCount = votesCount + choices.size, - votersCount = votersCount?.plus(1), - voted = true + options = newOptions, + votesCount = votesCount + choices.size, + votersCount = votersCount?.plus(1), + voted = true ) } fun toNewPoll(creationDate: Date) = NewPoll( - options.map { it.title }, - expiresAt?.let { - ((it.time - creationDate.time) / 1000).toInt() + 1 - }?: 3600, - multiple + options.map { it.title }, + expiresAt?.let { + ((it.time - creationDate.time) / 1000).toInt() + 1 + } ?: 3600, + multiple ) - } data class PollOption( - val title: String, - @SerializedName("votes_count") val votesCount: Int -) \ No newline at end of file + val title: String, + @SerializedName("votes_count") val votesCount: Int +) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt index e25a3d10d..17bddccaf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Relationship.kt @@ -17,7 +17,7 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName -data class Relationship ( +data class Relationship( val id: String, val following: Boolean, @SerializedName("followed_by") val followedBy: Boolean, diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/ScheduledStatus.kt b/app/src/main/java/com/keylesspalace/tusky/entity/ScheduledStatus.kt index 2621bd5ed..dfaeb499c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/ScheduledStatus.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/ScheduledStatus.kt @@ -18,8 +18,8 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName data class ScheduledStatus( - val id: String, - @SerializedName("scheduled_at") val scheduledAt: String, - val params: StatusParams, - @SerializedName("media_attachments") val mediaAttachments: ArrayList + val id: String, + @SerializedName("scheduled_at") val scheduledAt: String, + val params: StatusParams, + @SerializedName("media_attachments") val mediaAttachments: ArrayList ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/SearchResult.kt b/app/src/main/java/com/keylesspalace/tusky/entity/SearchResult.kt index 4307380ca..18e3d71b0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/SearchResult.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/SearchResult.kt @@ -15,7 +15,7 @@ package com.keylesspalace.tusky.entity -data class SearchResult ( +data class SearchResult( val accounts: List, val statuses: List, val hashtags: List diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt b/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt index 728cc1b40..1be5c2cb9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt @@ -19,33 +19,34 @@ import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.URLSpan import com.google.gson.annotations.SerializedName -import java.util.* +import java.util.ArrayList +import java.util.Date data class Status( - val id: String, - val url: String?, // not present if it's reblog - val account: Account, - @SerializedName("in_reply_to_id") var inReplyToId: String?, - @SerializedName("in_reply_to_account_id") val inReplyToAccountId: String?, - val reblog: Status?, - val content: Spanned, - @SerializedName("created_at") val createdAt: Date, - val emojis: List, - @SerializedName("reblogs_count") val reblogsCount: Int, - @SerializedName("favourites_count") val favouritesCount: Int, - var reblogged: Boolean, - var favourited: Boolean, - var bookmarked: Boolean, - var sensitive: Boolean, - @SerializedName("spoiler_text") val spoilerText: String, - val visibility: Visibility, - @SerializedName("media_attachments") var attachments: ArrayList, - val mentions: List, - val application: Application?, - val pinned: Boolean?, - val muted: Boolean?, - val poll: Poll?, - val card: Card? + val id: String, + val url: String?, // not present if it's reblog + val account: Account, + @SerializedName("in_reply_to_id") var inReplyToId: String?, + @SerializedName("in_reply_to_account_id") val inReplyToAccountId: String?, + val reblog: Status?, + val content: Spanned, + @SerializedName("created_at") val createdAt: Date, + val emojis: List, + @SerializedName("reblogs_count") val reblogsCount: Int, + @SerializedName("favourites_count") val favouritesCount: Int, + var reblogged: Boolean, + var favourited: Boolean, + var bookmarked: Boolean, + var sensitive: Boolean, + @SerializedName("spoiler_text") val spoilerText: String, + val visibility: Visibility, + @SerializedName("media_attachments") var attachments: ArrayList, + val mentions: List, + val application: Application?, + val pinned: Boolean?, + val muted: Boolean?, + val poll: Poll?, + val card: Card? ) { val actionableId: String @@ -119,14 +120,14 @@ data class Status( fun toDeletedStatus(): DeletedStatus { return DeletedStatus( - text = getEditableText(), - inReplyToId = inReplyToId, - spoilerText = spoilerText, - visibility = visibility, - sensitive = sensitive, - attachments = attachments, - poll = poll, - createdAt = createdAt + text = getEditableText(), + inReplyToId = inReplyToId, + spoilerText = spoilerText, + visibility = visibility, + sensitive = sensitive, + attachments = attachments, + poll = poll, + createdAt = createdAt ) } @@ -158,15 +159,14 @@ data class Status( return id.hashCode() } - - data class Mention ( + data class Mention( val id: String, val url: String, @SerializedName("acct") val username: String, @SerializedName("username") val localUsername: String ) - data class Application ( + data class Application( val name: String, val website: String? ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.kt b/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.kt index 1287619b9..ce5bb1440 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/StatusContext.kt @@ -15,7 +15,7 @@ package com.keylesspalace.tusky.entity -data class StatusContext ( +data class StatusContext( val ancestors: List, val descendants: List ) diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/StatusParams.kt b/app/src/main/java/com/keylesspalace/tusky/entity/StatusParams.kt index 0e25e6c16..d3235337b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/StatusParams.kt +++ b/app/src/main/java/com/keylesspalace/tusky/entity/StatusParams.kt @@ -18,9 +18,9 @@ package com.keylesspalace.tusky.entity import com.google.gson.annotations.SerializedName data class StatusParams( - val text: String, - val sensitive: Boolean, - val visibility: Status.Visibility, - @SerializedName("spoiler_text") val spoilerText: String, - @SerializedName("in_reply_to_id") val inReplyToId: String? -) \ No newline at end of file + val text: String, + val sensitive: Boolean, + val visibility: Status.Visibility, + @SerializedName("spoiler_text") val spoilerText: String, + @SerializedName("in_reply_to_id") val inReplyToId: String? +) diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt index cd37f8425..16e6523e4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountListFragment.kt @@ -33,9 +33,14 @@ import com.keylesspalace.tusky.AccountActivity import com.keylesspalace.tusky.AccountListActivity.Type import com.keylesspalace.tusky.BaseActivity import com.keylesspalace.tusky.R -import com.keylesspalace.tusky.adapter.* -import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.adapter.AccountAdapter +import com.keylesspalace.tusky.adapter.BlocksAdapter +import com.keylesspalace.tusky.adapter.FollowAdapter +import com.keylesspalace.tusky.adapter.FollowRequestsAdapter +import com.keylesspalace.tusky.adapter.FollowRequestsHeaderAdapter +import com.keylesspalace.tusky.adapter.MutesAdapter import com.keylesspalace.tusky.databinding.FragmentAccountListBinding +import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Relationship @@ -51,7 +56,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import retrofit2.Response import java.io.IOException -import java.util.* +import java.util.HashMap import javax.inject.Inject class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountActionListener, Injectable { @@ -133,12 +138,15 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct } else { api.muteAccount(id, notifications) } - .autoDispose(from(this)) - .subscribe({ + .autoDispose(from(this)) + .subscribe( + { onMuteSuccess(mute, id, position, notifications) - }, { + }, + { onMuteFailure(mute, id, notifications) - }) + } + ) } private fun onMuteSuccess(muted: Boolean, id: String, position: Int, notifications: Boolean) { @@ -151,11 +159,11 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct if (unmutedUser != null) { Snackbar.make(binding.recyclerView, R.string.confirmation_unmuted, Snackbar.LENGTH_LONG) - .setAction(R.string.action_undo) { - mutesAdapter.addItem(unmutedUser, position) - onMute(true, id, position, notifications) - } - .show() + .setAction(R.string.action_undo) { + mutesAdapter.addItem(unmutedUser, position) + onMute(true, id, position, notifications) + } + .show() } } @@ -178,12 +186,15 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct } else { api.blockAccount(id) } - .autoDispose(from(this)) - .subscribe({ + .autoDispose(from(this)) + .subscribe( + { onBlockSuccess(block, id, position) - }, { + }, + { onBlockFailure(block, id) - }) + } + ) } private fun onBlockSuccess(blocked: Boolean, id: String, position: Int) { @@ -195,11 +206,11 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct if (unblockedUser != null) { Snackbar.make(binding.recyclerView, R.string.confirmation_unblocked, Snackbar.LENGTH_LONG) - .setAction(R.string.action_undo) { - blocksAdapter.addItem(unblockedUser, position) - onBlock(true, id, position) - } - .show() + .setAction(R.string.action_undo) { + blocksAdapter.addItem(unblockedUser, position) + onBlock(true, id, position) + } + .show() } } @@ -212,26 +223,31 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct Log.e(TAG, "Failed to $verb account accountId $accountId") } - override fun onRespondToFollowRequest(accept: Boolean, accountId: String, - position: Int) { + override fun onRespondToFollowRequest( + accept: Boolean, + accountId: String, + position: Int + ) { if (accept) { api.authorizeFollowRequest(accountId) } else { api.rejectFollowRequest(accountId) }.observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe({ + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe( + { onRespondToFollowRequestSuccess(position) - }, { throwable -> + }, + { throwable -> val verb = if (accept) { "accept" } else { "reject" } Log.e(TAG, "Failed to $verb account id $accountId.", throwable) - }) - + } + ) } private fun onRespondToFollowRequestSuccess(position: Int) { @@ -264,7 +280,7 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct } private fun requireId(type: Type, id: String?): String { - return requireNotNull(id) { "id must not be null for type "+type.name } + return requireNotNull(id) { "id must not be null for type " + type.name } } private fun fetchAccounts(fromId: String? = null) { @@ -278,9 +294,10 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct } getFetchCallByListType(fromId) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) - .subscribe({ response -> + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this, Lifecycle.Event.ON_DESTROY)) + .subscribe( + { response -> val accountList = response.body() if (response.isSuccessful && accountList != null) { @@ -289,10 +306,11 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct } else { onFetchAccountsFailure(Exception(response.message())) } - }, {throwable -> + }, + { throwable -> onFetchAccountsFailure(throwable) - }) - + } + ) } private fun onFetchAccountsSuccess(accounts: List, linkHeader: String?) { @@ -319,9 +337,9 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct if (adapter.itemCount == 0) { binding.messageView.show() binding.messageView.setup( - R.drawable.elephant_friend_empty, - R.string.message_empty, - null + R.drawable.elephant_friend_empty, + R.string.message_empty, + null ) } else { binding.messageView.hide() @@ -330,11 +348,11 @@ class AccountListFragment : Fragment(R.layout.fragment_account_list), AccountAct private fun fetchRelationships(ids: List) { api.relationships(ids) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(from(this)) - .subscribe(::onFetchRelationshipsSuccess) { - onFetchRelationshipsFailure(ids) - } + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(from(this)) + .subscribe(::onFetchRelationshipsSuccess) { + onFetchRelationshipsFailure(ids) + } } private fun onFetchRelationshipsSuccess(relationships: List) { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt index 588e22b6a..053299a53 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/AccountMediaFragment.kt @@ -49,7 +49,7 @@ import io.reactivex.rxjava3.core.SingleObserver import io.reactivex.rxjava3.disposables.Disposable import retrofit2.Response import java.io.IOException -import java.util.* +import java.util.Random import javax.inject.Inject /** @@ -156,18 +156,17 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - isSwipeToRefreshEnabled = arguments?.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,true) == true - accountId = arguments?.getString(ACCOUNT_ID_ARG)!! + isSwipeToRefreshEnabled = arguments?.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true) == true + accountId = arguments?.getString(ACCOUNT_ID_ARG)!! } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val columnCount = view.context.resources.getInteger(R.integer.profile_media_column_count) val layoutManager = GridLayoutManager(view.context, columnCount) - adapter.baseItemColor = ThemeUtils.getColor(view.context, android.R.attr.windowBackground) + adapter.baseItemColor = ThemeUtils.getColor(view.context, android.R.attr.windowBackground) binding.recyclerView.layoutManager = layoutManager binding.recyclerView.adapter = adapter @@ -188,12 +187,12 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr val lastItem = layoutManager.findLastCompletelyVisibleItemPosition() if (itemCount <= lastItem + 3 && fetchingStatus == FetchingStatus.NOT_FETCHING) { statuses.lastOrNull()?.let { (id) -> - Log.d(TAG, "Requesting statuses with max_id: ${id}, (bottom)") + Log.d(TAG, "Requesting statuses with max_id: $id, (bottom)") fetchingStatus = FetchingStatus.FETCHING_BOTTOM api.accountStatuses(accountId, id, null, null, null, true, null) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) - .subscribe(bottomCallback) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) + .subscribe(bottomCallback) } } } @@ -213,8 +212,8 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr fetchingStatus = FetchingStatus.REFRESHING api.accountStatuses(accountId, null, statuses[0].id, null, null, true, null) }.observeOn(AndroidSchedulers.mainThread()) - .autoDispose(this, Lifecycle.Event.ON_DESTROY) - .subscribe(callback) + .autoDispose(this, Lifecycle.Event.ON_DESTROY) + .subscribe(callback) if (!isSwipeToRefreshEnabled) binding.topProgressBar.show() @@ -227,11 +226,10 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr if (fetchingStatus == FetchingStatus.NOT_FETCHING && statuses.isEmpty()) { fetchingStatus = FetchingStatus.INITIAL_FETCHING api.accountStatuses(accountId, null, null, null, null, true, null) - .observeOn(AndroidSchedulers.mainThread()) - .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) - .subscribe(callback) - } - else if (needToRefresh) + .observeOn(AndroidSchedulers.mainThread()) + .autoDispose(this@AccountMediaFragment, Lifecycle.Event.ON_DESTROY) + .subscribe(callback) + } else if (needToRefresh) refresh() needToRefresh = false } @@ -264,7 +262,7 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr } inner class MediaGridAdapter : - RecyclerView.Adapter() { + RecyclerView.Adapter() { var baseItemColor = Color.BLACK @@ -305,15 +303,14 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr val item = items[position] Glide.with(holder.imageView) - .load(item.attachment.previewUrl) - .centerInside() - .into(holder.imageView) + .load(item.attachment.previewUrl) + .centerInside() + .into(holder.imageView) } - - inner class MediaViewHolder(val imageView: ImageView) - : RecyclerView.ViewHolder(imageView), - View.OnClickListener { + inner class MediaViewHolder(val imageView: ImageView) : + RecyclerView.ViewHolder(imageView), + View.OnClickListener { init { itemView.setOnClickListener(this) } @@ -334,11 +331,11 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr companion object { @JvmStatic - fun newInstance(accountId: String, enableSwipeToRefresh:Boolean=true): AccountMediaFragment { + fun newInstance(accountId: String, enableSwipeToRefresh: Boolean = true): AccountMediaFragment { val fragment = AccountMediaFragment() val args = Bundle() args.putString(ACCOUNT_ID_ARG, accountId) - args.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH,enableSwipeToRefresh) + args.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, enableSwipeToRefresh) fragment.arguments = args return fragment } @@ -347,4 +344,4 @@ class AccountMediaFragment : Fragment(R.layout.fragment_timeline), RefreshableFr private const val TAG = "AccountMediaFragment" private const val ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt index ceb2f365d..0362da9c7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewImageFragment.kt @@ -66,12 +66,11 @@ class ViewImageFragment : ViewMediaFragment() { photoActionsListener = context as PhotoActionsListener } - override fun setupMediaView( - url: String, - previewUrl: String?, - description: String?, - showingDescription: Boolean + url: String, + previewUrl: String?, + description: String?, + showingDescription: Boolean ) { binding.photoView.transitionName = url binding.mediaDescription.text = description @@ -136,9 +135,9 @@ class ViewImageFragment : ViewMediaFragment() { if (event.action == MotionEvent.ACTION_DOWN) { lastY = event.rawY - } else if (event.pointerCount == 1 - && attacher.scale == 1f - && event.action == MotionEvent.ACTION_MOVE + } else if (event.pointerCount == 1 && + attacher.scale == 1f && + event.action == MotionEvent.ACTION_MOVE ) { val diff = event.rawY - lastY // This code is to prevent transformations during page scrolling @@ -176,21 +175,21 @@ class ViewImageFragment : ViewMediaFragment() { } override fun onToolbarVisibilityChange(visible: Boolean) { - if (_binding == null || !userVisibleHint ) { + if (_binding == null || !userVisibleHint) { return } isDescriptionVisible = showingDescription && visible val alpha = if (isDescriptionVisible) 1.0f else 0.0f binding.captionSheet.animate().alpha(alpha) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - if (_binding != null) { - binding.captionSheet.visible(isDescriptionVisible) - } - animation.removeListener(this) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + if (_binding != null) { + binding.captionSheet.visible(isDescriptionVisible) } - }) - .start() + animation.removeListener(this) + } + }) + .start() } override fun onDestroyView() { @@ -204,27 +203,30 @@ class ViewImageFragment : ViewMediaFragment() { val glide = Glide.with(this) // Request image from the any cache glide - .load(url) - .dontAnimate() - .onlyRetrieveFromCache(true) - .let { - if (previewUrl != null) - it.thumbnail(glide - .load(previewUrl) - .dontAnimate() - .onlyRetrieveFromCache(true) - .centerInside() - .addListener(ImageRequestListener(true, isThumnailRequest = true))) - else it - } - //Request image from the network on fail load image from cache - .error(glide.load(url) - .centerInside() - .addListener(ImageRequestListener(false, isThumnailRequest = false)) - ) - .centerInside() - .addListener(ImageRequestListener(true, isThumnailRequest = false)) - .into(photoView) + .load(url) + .dontAnimate() + .onlyRetrieveFromCache(true) + .let { + if (previewUrl != null) + it.thumbnail( + glide + .load(previewUrl) + .dontAnimate() + .onlyRetrieveFromCache(true) + .centerInside() + .addListener(ImageRequestListener(true, isThumnailRequest = true)) + ) + else it + } + // Request image from the network on fail load image from cache + .error( + glide.load(url) + .centerInside() + .addListener(ImageRequestListener(false, isThumnailRequest = false)) + ) + .centerInside() + .addListener(ImageRequestListener(true, isThumnailRequest = false)) + .into(photoView) } /** @@ -248,14 +250,20 @@ class ViewImageFragment : ViewMediaFragment() { * @param isCacheRequest - is this listener for request image from cache or from the network */ private inner class ImageRequestListener( - private val isCacheRequest: Boolean, - private val isThumnailRequest: Boolean) : RequestListener { + private val isCacheRequest: Boolean, + private val isThumnailRequest: Boolean + ) : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any, target: Target, - isFirstResource: Boolean): Boolean { + override fun onLoadFailed( + e: GlideException?, + model: Any, + target: Target, + isFirstResource: Boolean + ): Boolean { // If cache for full image failed complete transition - if (isCacheRequest && !isThumnailRequest && shouldStartTransition - && !startedTransition) { + if (isCacheRequest && !isThumnailRequest && shouldStartTransition && + !startedTransition + ) { photoActionsListener.onBringUp() } // Hide progress bar only on fail request from internet @@ -265,8 +273,13 @@ class ViewImageFragment : ViewMediaFragment() { } @SuppressLint("CheckResult") - override fun onResourceReady(resource: Drawable, model: Any, target: Target, - dataSource: DataSource, isFirstResource: Boolean): Boolean { + override fun onResourceReady( + resource: Drawable, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { if (_binding != null) { binding.progressBar.hide() // Always hide the progress bar on success } @@ -284,14 +297,14 @@ class ViewImageFragment : ViewMediaFragment() { // This wait for transition. If there's no transition then we should hit // another branch. take() will unsubscribe after we have it to not leak menmory transition - .take(1) - .subscribe { - target.onResourceReady(resource, null) - // It's needed. Don't ask why, I don't know, setImageDrawable() should - // do it by itself but somehow it doesn't work automatically. - // Just do it. If you don't, image will jump around when touched. - attacher.update() - } + .take(1) + .subscribe { + target.onResourceReady(resource, null) + // It's needed. Don't ask why, I don't know, setImageDrawable() should + // do it by itself but somehow it doesn't work automatically. + // Just do it. If you don't, image will jump around when touched. + attacher.update() + } } return true } diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt index b25fec26f..89c65e10a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewMediaFragment.kt @@ -25,10 +25,10 @@ abstract class ViewMediaFragment : Fragment() { private var toolbarVisibiltyDisposable: Function0? = null abstract fun setupMediaView( - url: String, - previewUrl: String?, - description: String?, - showingDescription: Boolean + url: String, + previewUrl: String?, + description: String?, + showingDescription: Boolean ) abstract fun onToolbarVisibilityChange(visible: Boolean) @@ -56,7 +56,7 @@ abstract class ViewMediaFragment : Fragment() { Attachment.Type.VIDEO, Attachment.Type.GIFV, Attachment.Type.AUDIO -> ViewVideoFragment() - else -> ViewImageFragment() // it probably won't show anything, but its better than crashing + else -> ViewImageFragment() // it probably won't show anything, but its better than crashing } fragment.arguments = arguments return fragment @@ -84,9 +84,9 @@ abstract class ViewMediaFragment : Fragment() { setupMediaView(url, previewUrl, description, showingDescription && mediaActivity.isToolbarVisible) toolbarVisibiltyDisposable = (activity as ViewMediaActivity) - .addToolbarVisibilityListener { isVisible -> - onToolbarVisibilityChange(isVisible) - } + .addToolbarVisibilityListener { isVisible -> + onToolbarVisibilityChange(isVisible) + } } override fun onDestroyView() { diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt index a0912837d..35b98ef71 100644 --- a/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/fragment/ViewVideoFragment.kt @@ -48,7 +48,7 @@ class ViewVideoFragment : ViewMediaFragment() { } private lateinit var mediaActivity: ViewMediaActivity private val TOOLBAR_HIDE_DELAY_MS = 3000L - private lateinit var mediaController : MediaController + private lateinit var mediaController: MediaController private var isAudio = false override fun setUserVisibleHint(isVisibleToUser: Boolean) { @@ -72,10 +72,10 @@ class ViewVideoFragment : ViewMediaFragment() { @SuppressLint("ClickableViewAccessibility") override fun setupMediaView( - url: String, - previewUrl: String?, - description: String?, - showingDescription: Boolean + url: String, + previewUrl: String?, + description: String?, + showingDescription: Boolean ) { binding.mediaDescription.text = description binding.mediaDescription.visible(showingDescription) @@ -105,7 +105,7 @@ class ViewVideoFragment : ViewMediaFragment() { mediaController.setMediaPlayer(binding.videoView) binding.videoView.setMediaController(mediaController) binding.videoView.requestFocus() - binding.videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener { + binding.videoView.setPlayPauseListener(object : ExposedPlayPauseVideoView.PlayPauseListener { override fun onPause() { handler.removeCallbacks(hideToolbar) } @@ -125,7 +125,7 @@ class ViewVideoFragment : ViewMediaFragment() { val videoWidth = mp.videoWidth.toFloat() val videoHeight = mp.videoHeight.toFloat() - if(containerWidth/containerHeight > videoWidth/videoHeight) { + if (containerWidth / containerHeight > videoWidth / videoHeight) { binding.videoView.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT binding.videoView.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT } else { @@ -190,15 +190,15 @@ class ViewVideoFragment : ViewMediaFragment() { } binding.mediaDescription.animate().alpha(alpha) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - if (_binding != null) { - binding.mediaDescription.visible(isDescriptionVisible) - } - animation.removeListener(this) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + if (_binding != null) { + binding.mediaDescription.visible(isDescriptionVisible) } - }) - .start() + animation.removeListener(this) + } + }) + .start() if (visible && binding.videoView.isPlaying && !isAudio) { hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS) diff --git a/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountSelectionListener.kt b/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountSelectionListener.kt index 04b1ebd2a..b86c55c76 100644 --- a/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountSelectionListener.kt +++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/AccountSelectionListener.kt @@ -19,4 +19,4 @@ import com.keylesspalace.tusky.db.AccountEntity interface AccountSelectionListener { fun onAccountSelected(account: AccountEntity) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/interfaces/RefreshableFragment.kt b/app/src/main/java/com/keylesspalace/tusky/interfaces/RefreshableFragment.kt index 5032774f4..83fc20c93 100644 --- a/app/src/main/java/com/keylesspalace/tusky/interfaces/RefreshableFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/RefreshableFragment.kt @@ -8,4 +8,4 @@ interface RefreshableFragment { * Call this method to refresh fragment content */ fun refreshContent() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/interfaces/ReselectableFragment.kt b/app/src/main/java/com/keylesspalace/tusky/interfaces/ReselectableFragment.kt index c50178c10..598894f6b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/interfaces/ReselectableFragment.kt +++ b/app/src/main/java/com/keylesspalace/tusky/interfaces/ReselectableFragment.kt @@ -8,4 +8,4 @@ interface ReselectableFragment { * Call this method when tab reselected */ fun onReselect() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt index 6eabea524..ceb96f4a2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/json/SpannedTypeAdapter.kt @@ -19,7 +19,13 @@ import android.text.Spanned import android.text.SpannedString import androidx.core.text.HtmlCompat import androidx.core.text.parseAsHtml -import com.google.gson.* +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import com.google.gson.JsonPrimitive +import com.google.gson.JsonSerializationContext +import com.google.gson.JsonSerializer import com.keylesspalace.tusky.util.trimTrailingWhitespace import java.lang.reflect.Type @@ -34,4 +40,4 @@ class SpannedTypeAdapter : JsonDeserializer, JsonSerializer { override fun serialize(src: Spanned?, typeOfSrc: Type, context: JsonSerializationContext): JsonElement { return JsonPrimitive(HtmlCompat.toHtml(src!!, HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt b/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt index 3be20c1a9..d4c7464f1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/FilterModel.kt @@ -29,8 +29,10 @@ class FilterModel @Inject constructor() { } val spoilerText = status.actionableStatus.spoilerText - return (matcher.reset(status.actionableStatus.content).find() || - spoilerText.isNotEmpty() && matcher.reset(spoilerText).find()) + return ( + matcher.reset(status.actionableStatus.content).find() || + spoilerText.isNotEmpty() && matcher.reset(spoilerText).find() + ) } private fun filterToRegexToken(filter: Filter): String? { @@ -47,10 +49,10 @@ class FilterModel @Inject constructor() { if (filters.isEmpty()) return null val tokens = filters.map { filterToRegexToken(it) } - return Pattern.compile(TextUtils.join("|", tokens), Pattern.CASE_INSENSITIVE); + return Pattern.compile(TextUtils.join("|", tokens), Pattern.CASE_INSENSITIVE) } companion object { private val ALPHANUMERIC = Pattern.compile("^\\w+$") } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt index 96f05349d..dc5fd8f38 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/MastodonApi.kt @@ -85,57 +85,57 @@ interface MastodonApi { @GET("api/v1/timelines/home") fun homeTimeline( - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int? ): Single>> @GET("api/v1/timelines/public") fun publicTimeline( - @Query("local") local: Boolean?, - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? + @Query("local") local: Boolean?, + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int? ): Single>> @GET("api/v1/timelines/tag/{hashtag}") fun hashtagTimeline( - @Path("hashtag") hashtag: String, - @Query("any[]") any: List?, - @Query("local") local: Boolean?, - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? + @Path("hashtag") hashtag: String, + @Query("any[]") any: List?, + @Query("local") local: Boolean?, + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int? ): Single>> @GET("api/v1/timelines/list/{listId}") fun listTimeline( - @Path("listId") listId: String, - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? + @Path("listId") listId: String, + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int? ): Single>> @GET("api/v1/notifications") fun notifications( - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int?, - @Query("exclude_types[]") excludes: Set? + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int?, + @Query("exclude_types[]") excludes: Set? ): Single>> @GET("api/v1/markers") fun markersWithAuth( - @Header("Authorization") auth: String, - @Header(DOMAIN_HEADER) domain: String, - @Query("timeline[]") timelines: List + @Header("Authorization") auth: String, + @Header(DOMAIN_HEADER) domain: String, + @Query("timeline[]") timelines: List ): Single> @GET("api/v1/notifications") fun notificationsWithAuth( - @Header("Authorization") auth: String, - @Header(DOMAIN_HEADER) domain: String, - @Query("since_id") sinceId: String? + @Header("Authorization") auth: String, + @Header(DOMAIN_HEADER) domain: String, + @Query("since_id") sinceId: String? ): Single> @POST("api/v1/notifications/clear") @@ -144,111 +144,111 @@ interface MastodonApi { @Multipart @POST("api/v1/media") fun uploadMedia( - @Part file: MultipartBody.Part, - @Part description: MultipartBody.Part? = null + @Part file: MultipartBody.Part, + @Part description: MultipartBody.Part? = null ): Single @FormUrlEncoded @PUT("api/v1/media/{mediaId}") fun updateMedia( - @Path("mediaId") mediaId: String, - @Field("description") description: String + @Path("mediaId") mediaId: String, + @Field("description") description: String ): Single @POST("api/v1/statuses") fun createStatus( - @Header("Authorization") auth: String, - @Header(DOMAIN_HEADER) domain: String, - @Header("Idempotency-Key") idempotencyKey: String, - @Body status: NewStatus + @Header("Authorization") auth: String, + @Header(DOMAIN_HEADER) domain: String, + @Header("Idempotency-Key") idempotencyKey: String, + @Body status: NewStatus ): Call @GET("api/v1/statuses/{id}") fun status( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @GET("api/v1/statuses/{id}/context") fun statusContext( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @GET("api/v1/statuses/{id}/reblogged_by") fun statusRebloggedBy( - @Path("id") statusId: String, - @Query("max_id") maxId: String? + @Path("id") statusId: String, + @Query("max_id") maxId: String? ): Single>> @GET("api/v1/statuses/{id}/favourited_by") fun statusFavouritedBy( - @Path("id") statusId: String, - @Query("max_id") maxId: String? + @Path("id") statusId: String, + @Query("max_id") maxId: String? ): Single>> @DELETE("api/v1/statuses/{id}") fun deleteStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/reblog") fun reblogStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/unreblog") fun unreblogStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/favourite") fun favouriteStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/unfavourite") fun unfavouriteStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/bookmark") fun bookmarkStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/unbookmark") fun unbookmarkStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/pin") fun pinStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/unpin") fun unpinStatus( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/mute") fun muteConversation( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @POST("api/v1/statuses/{id}/unmute") fun unmuteConversation( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @GET("api/v1/scheduled_statuses") fun scheduledStatuses( - @Query("limit") limit: Int? = null, - @Query("max_id") maxId: String? = null + @Query("limit") limit: Int? = null, + @Query("max_id") maxId: String? = null ): Single> @DELETE("api/v1/scheduled_statuses/{id}") fun deleteScheduledStatus( - @Path("id") scheduledStatusId: String + @Path("id") scheduledStatusId: String ): Single @GET("api/v1/accounts/verify_credentials") @@ -257,39 +257,39 @@ interface MastodonApi { @FormUrlEncoded @PATCH("api/v1/accounts/update_credentials") fun accountUpdateSource( - @Field("source[privacy]") privacy: String?, - @Field("source[sensitive]") sensitive: Boolean? + @Field("source[privacy]") privacy: String?, + @Field("source[sensitive]") sensitive: Boolean? ): Call @Multipart @PATCH("api/v1/accounts/update_credentials") fun accountUpdateCredentials( - @Part(value = "display_name") displayName: RequestBody?, - @Part(value = "note") note: RequestBody?, - @Part(value = "locked") locked: RequestBody?, - @Part avatar: MultipartBody.Part?, - @Part header: MultipartBody.Part?, - @Part(value = "fields_attributes[0][name]") fieldName0: RequestBody?, - @Part(value = "fields_attributes[0][value]") fieldValue0: RequestBody?, - @Part(value = "fields_attributes[1][name]") fieldName1: RequestBody?, - @Part(value = "fields_attributes[1][value]") fieldValue1: RequestBody?, - @Part(value = "fields_attributes[2][name]") fieldName2: RequestBody?, - @Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?, - @Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?, - @Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody? + @Part(value = "display_name") displayName: RequestBody?, + @Part(value = "note") note: RequestBody?, + @Part(value = "locked") locked: RequestBody?, + @Part avatar: MultipartBody.Part?, + @Part header: MultipartBody.Part?, + @Part(value = "fields_attributes[0][name]") fieldName0: RequestBody?, + @Part(value = "fields_attributes[0][value]") fieldValue0: RequestBody?, + @Part(value = "fields_attributes[1][name]") fieldName1: RequestBody?, + @Part(value = "fields_attributes[1][value]") fieldValue1: RequestBody?, + @Part(value = "fields_attributes[2][name]") fieldName2: RequestBody?, + @Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?, + @Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?, + @Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody? ): Call @GET("api/v1/accounts/search") fun searchAccounts( - @Query("q") query: String, - @Query("resolve") resolve: Boolean? = null, - @Query("limit") limit: Int? = null, - @Query("following") following: Boolean? = null + @Query("q") query: String, + @Query("resolve") resolve: Boolean? = null, + @Query("limit") limit: Int? = null, + @Query("following") following: Boolean? = null ): Single> @GET("api/v1/accounts/{id}") fun account( - @Path("id") accountId: String + @Path("id") accountId: String ): Single /** @@ -303,71 +303,71 @@ interface MastodonApi { */ @GET("api/v1/accounts/{id}/statuses") fun accountStatuses( - @Path("id") accountId: String, - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int?, - @Query("exclude_replies") excludeReplies: Boolean?, - @Query("only_media") onlyMedia: Boolean?, - @Query("pinned") pinned: Boolean? + @Path("id") accountId: String, + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int?, + @Query("exclude_replies") excludeReplies: Boolean?, + @Query("only_media") onlyMedia: Boolean?, + @Query("pinned") pinned: Boolean? ): Single>> @GET("api/v1/accounts/{id}/followers") fun accountFollowers( - @Path("id") accountId: String, - @Query("max_id") maxId: String? + @Path("id") accountId: String, + @Query("max_id") maxId: String? ): Single>> @GET("api/v1/accounts/{id}/following") fun accountFollowing( - @Path("id") accountId: String, - @Query("max_id") maxId: String? + @Path("id") accountId: String, + @Query("max_id") maxId: String? ): Single>> @FormUrlEncoded @POST("api/v1/accounts/{id}/follow") fun followAccount( - @Path("id") accountId: String, - @Field("reblogs") showReblogs: Boolean? = null, - @Field("notify") notify: Boolean? = null + @Path("id") accountId: String, + @Field("reblogs") showReblogs: Boolean? = null, + @Field("notify") notify: Boolean? = null ): Single @POST("api/v1/accounts/{id}/unfollow") fun unfollowAccount( - @Path("id") accountId: String + @Path("id") accountId: String ): Single @POST("api/v1/accounts/{id}/block") fun blockAccount( - @Path("id") accountId: String + @Path("id") accountId: String ): Single @POST("api/v1/accounts/{id}/unblock") fun unblockAccount( - @Path("id") accountId: String + @Path("id") accountId: String ): Single @FormUrlEncoded @POST("api/v1/accounts/{id}/mute") fun muteAccount( - @Path("id") accountId: String, - @Field("notifications") notifications: Boolean? = null, - @Field("duration") duration: Int? = null + @Path("id") accountId: String, + @Field("notifications") notifications: Boolean? = null, + @Field("duration") duration: Int? = null ): Single @POST("api/v1/accounts/{id}/unmute") fun unmuteAccount( - @Path("id") accountId: String + @Path("id") accountId: String ): Single @GET("api/v1/accounts/relationships") fun relationships( - @Query("id[]") accountIds: List + @Query("id[]") accountIds: List ): Single> @GET("api/v1/accounts/{id}/identity_proofs") fun identityProofs( - @Path("id") accountId: String + @Path("id") accountId: String ): Single> @POST("api/v1/pleroma/accounts/{id}/subscribe") @@ -382,25 +382,25 @@ interface MastodonApi { @GET("api/v1/blocks") fun blocks( - @Query("max_id") maxId: String? + @Query("max_id") maxId: String? ): Single>> @GET("api/v1/mutes") fun mutes( - @Query("max_id") maxId: String? + @Query("max_id") maxId: String? ): Single>> @GET("api/v1/domain_blocks") fun domainBlocks( - @Query("max_id") maxId: String? = null, - @Query("since_id") sinceId: String? = null, - @Query("limit") limit: Int? = null + @Query("max_id") maxId: String? = null, + @Query("since_id") sinceId: String? = null, + @Query("limit") limit: Int? = null ): Single>> @FormUrlEncoded @POST("api/v1/domain_blocks") fun blockDomain( - @Field("domain") domain: String + @Field("domain") domain: String ): Call @FormUrlEncoded @@ -410,97 +410,97 @@ interface MastodonApi { @GET("api/v1/favourites") fun favourites( - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int? ): Single>> @GET("api/v1/bookmarks") fun bookmarks( - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("limit") limit: Int? + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("limit") limit: Int? ): Single>> @GET("api/v1/follow_requests") fun followRequests( - @Query("max_id") maxId: String? + @Query("max_id") maxId: String? ): Single>> @POST("api/v1/follow_requests/{id}/authorize") fun authorizeFollowRequest( - @Path("id") accountId: String + @Path("id") accountId: String ): Single @POST("api/v1/follow_requests/{id}/reject") fun rejectFollowRequest( - @Path("id") accountId: String + @Path("id") accountId: String ): Single @FormUrlEncoded @POST("api/v1/apps") fun authenticateApp( - @Header(DOMAIN_HEADER) domain: String, - @Field("client_name") clientName: String, - @Field("redirect_uris") redirectUris: String, - @Field("scopes") scopes: String, - @Field("website") website: String + @Header(DOMAIN_HEADER) domain: String, + @Field("client_name") clientName: String, + @Field("redirect_uris") redirectUris: String, + @Field("scopes") scopes: String, + @Field("website") website: String ): Call @FormUrlEncoded @POST("oauth/token") fun fetchOAuthToken( - @Header(DOMAIN_HEADER) domain: String, - @Field("client_id") clientId: String, - @Field("client_secret") clientSecret: String, - @Field("redirect_uri") redirectUri: String, - @Field("code") code: String, - @Field("grant_type") grantType: String + @Header(DOMAIN_HEADER) domain: String, + @Field("client_id") clientId: String, + @Field("client_secret") clientSecret: String, + @Field("redirect_uri") redirectUri: String, + @Field("code") code: String, + @Field("grant_type") grantType: String ): Call @FormUrlEncoded @POST("api/v1/lists") fun createList( - @Field("title") title: String + @Field("title") title: String ): Single @FormUrlEncoded @PUT("api/v1/lists/{listId}") fun updateList( - @Path("listId") listId: String, - @Field("title") title: String + @Path("listId") listId: String, + @Field("title") title: String ): Single @DELETE("api/v1/lists/{listId}") fun deleteList( - @Path("listId") listId: String + @Path("listId") listId: String ): Completable @GET("api/v1/lists/{listId}/accounts") fun getAccountsInList( - @Path("listId") listId: String, - @Query("limit") limit: Int + @Path("listId") listId: String, + @Query("limit") limit: Int ): Single> @FormUrlEncoded // @DELETE doesn't support fields @HTTP(method = "DELETE", path = "api/v1/lists/{listId}/accounts", hasBody = true) fun deleteAccountFromList( - @Path("listId") listId: String, - @Field("account_ids[]") accountIds: List + @Path("listId") listId: String, + @Field("account_ids[]") accountIds: List ): Completable @FormUrlEncoded @POST("api/v1/lists/{listId}/accounts") fun addCountToList( - @Path("listId") listId: String, - @Field("account_ids[]") accountIds: List + @Path("listId") listId: String, + @Field("account_ids[]") accountIds: List ): Completable @GET("/api/v1/conversations") suspend fun getConversations( - @Query("max_id") maxId: String? = null, - @Query("limit") limit: Int + @Query("max_id") maxId: String? = null, + @Query("limit") limit: Int ): List @DELETE("/api/v1/conversations/{id}") @@ -511,97 +511,96 @@ interface MastodonApi { @FormUrlEncoded @POST("api/v1/filters") fun createFilter( - @Field("phrase") phrase: String, - @Field("context[]") context: List, - @Field("irreversible") irreversible: Boolean?, - @Field("whole_word") wholeWord: Boolean?, - @Field("expires_in") expiresIn: String? + @Field("phrase") phrase: String, + @Field("context[]") context: List, + @Field("irreversible") irreversible: Boolean?, + @Field("whole_word") wholeWord: Boolean?, + @Field("expires_in") expiresIn: String? ): Call @FormUrlEncoded @PUT("api/v1/filters/{id}") fun updateFilter( - @Path("id") id: String, - @Field("phrase") phrase: String, - @Field("context[]") context: List, - @Field("irreversible") irreversible: Boolean?, - @Field("whole_word") wholeWord: Boolean?, - @Field("expires_in") expiresIn: String? + @Path("id") id: String, + @Field("phrase") phrase: String, + @Field("context[]") context: List, + @Field("irreversible") irreversible: Boolean?, + @Field("whole_word") wholeWord: Boolean?, + @Field("expires_in") expiresIn: String? ): Call @DELETE("api/v1/filters/{id}") fun deleteFilter( - @Path("id") id: String + @Path("id") id: String ): Call @FormUrlEncoded @POST("api/v1/polls/{id}/votes") fun voteInPoll( - @Path("id") id: String, - @Field("choices[]") choices: List + @Path("id") id: String, + @Field("choices[]") choices: List ): Single @GET("api/v1/announcements") fun listAnnouncements( - @Query("with_dismissed") withDismissed: Boolean = true + @Query("with_dismissed") withDismissed: Boolean = true ): Single> @POST("api/v1/announcements/{id}/dismiss") fun dismissAnnouncement( - @Path("id") announcementId: String + @Path("id") announcementId: String ): Single @PUT("api/v1/announcements/{id}/reactions/{name}") fun addAnnouncementReaction( - @Path("id") announcementId: String, - @Path("name") name: String + @Path("id") announcementId: String, + @Path("name") name: String ): Single @DELETE("api/v1/announcements/{id}/reactions/{name}") fun removeAnnouncementReaction( - @Path("id") announcementId: String, - @Path("name") name: String + @Path("id") announcementId: String, + @Path("name") name: String ): Single @FormUrlEncoded @POST("api/v1/reports") fun reportObservable( - @Field("account_id") accountId: String, - @Field("status_ids[]") statusIds: List, - @Field("comment") comment: String, - @Field("forward") isNotifyRemote: Boolean? + @Field("account_id") accountId: String, + @Field("status_ids[]") statusIds: List, + @Field("comment") comment: String, + @Field("forward") isNotifyRemote: Boolean? ): Single @GET("api/v1/accounts/{id}/statuses") fun accountStatusesObservable( - @Path("id") accountId: String, - @Query("max_id") maxId: String?, - @Query("since_id") sinceId: String?, - @Query("min_id") minId: String?, - @Query("limit") limit: Int?, - @Query("exclude_reblogs") excludeReblogs: Boolean? + @Path("id") accountId: String, + @Query("max_id") maxId: String?, + @Query("since_id") sinceId: String?, + @Query("min_id") minId: String?, + @Query("limit") limit: Int?, + @Query("exclude_reblogs") excludeReblogs: Boolean? ): Single> @GET("api/v1/statuses/{id}") fun statusObservable( - @Path("id") statusId: String + @Path("id") statusId: String ): Single @GET("api/v2/search") fun searchObservable( - @Query("q") query: String?, - @Query("type") type: String? = null, - @Query("resolve") resolve: Boolean? = null, - @Query("limit") limit: Int? = null, - @Query("offset") offset: Int? = null, - @Query("following") following: Boolean? = null + @Query("q") query: String?, + @Query("type") type: String? = null, + @Query("resolve") resolve: Boolean? = null, + @Query("limit") limit: Int? = null, + @Query("offset") offset: Int? = null, + @Query("following") following: Boolean? = null ): Single @FormUrlEncoded @POST("api/v1/accounts/{id}/note") fun updateAccountNote( - @Path("id") accountId: String, - @Field("comment") note: String + @Path("id") accountId: String, + @Field("comment") note: String ): Single - } diff --git a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt index 96fff6f80..ea51d8c3e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt +++ b/app/src/main/java/com/keylesspalace/tusky/network/TimelineCases.kt @@ -16,14 +16,22 @@ package com.keylesspalace.tusky.network import android.util.Log -import com.keylesspalace.tusky.appstore.* +import com.keylesspalace.tusky.appstore.BlockEvent +import com.keylesspalace.tusky.appstore.BookmarkEvent +import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.appstore.FavoriteEvent +import com.keylesspalace.tusky.appstore.MuteConversationEvent +import com.keylesspalace.tusky.appstore.MuteEvent +import com.keylesspalace.tusky.appstore.PinEvent +import com.keylesspalace.tusky.appstore.PollVoteEvent +import com.keylesspalace.tusky.appstore.ReblogEvent +import com.keylesspalace.tusky.appstore.StatusDeletedEvent import com.keylesspalace.tusky.entity.DeletedStatus import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.Status import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.addTo -import java.lang.IllegalStateException /** * Created by charlag on 3/24/18. @@ -98,21 +106,27 @@ class TimelineCasesImpl( override fun mute(statusId: String, notifications: Boolean, duration: Int?) { mastodonApi.muteAccount(statusId, notifications, duration) - .subscribe({ - eventHub.dispatch(MuteEvent(statusId)) - }, { t -> - Log.w("Failed to mute account", t) - }) + .subscribe( + { + eventHub.dispatch(MuteEvent(statusId)) + }, + { t -> + Log.w("Failed to mute account", t) + } + ) .addTo(cancelDisposable) } override fun block(statusId: String) { mastodonApi.blockAccount(statusId) - .subscribe({ - eventHub.dispatch(BlockEvent(statusId)) - }, { t -> - Log.w("Failed to block account", t) - }) + .subscribe( + { + eventHub.dispatch(BlockEvent(statusId)) + }, + { t -> + Log.w("Failed to block account", t) + } + ) .addTo(cancelDisposable) } @@ -140,5 +154,4 @@ class TimelineCasesImpl( eventHub.dispatch(PollVoteEvent(statusId, it)) } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.kt index e2e13c66b..2d01a32db 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/AccountPagerAdapter.kt @@ -15,18 +15,17 @@ package com.keylesspalace.tusky.pager -import androidx.fragment.app.* - -import com.keylesspalace.tusky.fragment.AccountMediaFragment +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity import com.keylesspalace.tusky.components.timeline.TimelineFragment import com.keylesspalace.tusky.components.timeline.TimelineViewModel +import com.keylesspalace.tusky.fragment.AccountMediaFragment import com.keylesspalace.tusky.interfaces.RefreshableFragment - import com.keylesspalace.tusky.util.CustomFragmentStateAdapter class AccountPagerAdapter( - activity: FragmentActivity, - private val accountId: String + activity: FragmentActivity, + private val accountId: String ) : CustomFragmentStateAdapter(activity) { override fun getItemCount() = TAB_COUNT diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt index 4f813d8b1..26c5fc05a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/ImagePagerAdapter.kt @@ -8,9 +8,9 @@ import com.keylesspalace.tusky.fragment.ViewMediaFragment import java.lang.ref.WeakReference class ImagePagerAdapter( - activity: FragmentActivity, - private val attachments: List, - private val initialPosition: Int + activity: FragmentActivity, + private val attachments: List, + private val initialPosition: Int ) : ViewMediaAdapter(activity) { private var didTransition = false @@ -25,8 +25,8 @@ class ImagePagerAdapter( // forth photo and then back to the first. The first fragment will try to start the // transition and wait until it's over and it will never take place. val fragment = ViewMediaFragment.newInstance( - attachment = attachments[position], - shouldStartPostponedTransition = !didTransition && position == initialPosition + attachment = attachments[position], + shouldStartPostponedTransition = !didTransition && position == initialPosition ) fragments[position] = WeakReference(fragment) return fragment @@ -35,7 +35,7 @@ class ImagePagerAdapter( } } - override fun onTransitionEnd(position: Int) { + override fun onTransitionEnd(position: Int) { this.didTransition = true fragments[position]?.get()?.onTransitionEnd() } diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt index 1e1029410..4fe92660c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/MainPagerAdapter.kt @@ -28,5 +28,4 @@ class MainPagerAdapter(val tabs: List, activity: FragmentActivity) : Cu } override fun getItemCount() = tabs.size - } diff --git a/app/src/main/java/com/keylesspalace/tusky/pager/SingleImagePagerAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/pager/SingleImagePagerAdapter.kt index c8306f70e..c1f5342a3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/pager/SingleImagePagerAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/pager/SingleImagePagerAdapter.kt @@ -6,8 +6,8 @@ import com.keylesspalace.tusky.ViewMediaAdapter import com.keylesspalace.tusky.fragment.ViewMediaFragment class SingleImagePagerAdapter( - activity: FragmentActivity, - private val imageUrl: String + activity: FragmentActivity, + private val imageUrl: String ) : ViewMediaAdapter(activity) { override fun createFragment(position: Int): Fragment { diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt index d9b94857c..6d4e97193 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/NotificationClearBroadcastReceiver.kt @@ -18,9 +18,8 @@ package com.keylesspalace.tusky.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent - -import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.components.notifications.NotificationHelper +import com.keylesspalace.tusky.db.AccountManager import dagger.android.AndroidInjection import javax.inject.Inject @@ -40,5 +39,4 @@ class NotificationClearBroadcastReceiver : BroadcastReceiver() { accountManager.saveAccount(account) } } - } diff --git a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt index c69de81c3..fb1d78673 100644 --- a/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt +++ b/app/src/main/java/com/keylesspalace/tusky/receiver/SendStatusBroadcastReceiver.kt @@ -26,11 +26,11 @@ import androidx.core.content.ContextCompat import com.keylesspalace.tusky.R import com.keylesspalace.tusky.components.compose.ComposeActivity import com.keylesspalace.tusky.components.compose.ComposeActivity.ComposeOptions +import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.service.SendTootService import com.keylesspalace.tusky.service.TootToSend -import com.keylesspalace.tusky.components.notifications.NotificationHelper import com.keylesspalace.tusky.util.randomAlphanumericString import dagger.android.AndroidInjection import javax.inject.Inject @@ -68,10 +68,10 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { Log.w(TAG, "Account \"$senderId\" not found in database. Aborting quick reply!") val builder = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MENTION + senderIdentifier) - .setSmallIcon(R.drawable.ic_notify) - .setColor(ContextCompat.getColor(context, R.color.tusky_blue)) - .setGroup(senderFullName) - .setDefaults(0) // So it doesn't ring twice, notify only in Target callback + .setSmallIcon(R.drawable.ic_notify) + .setColor(ContextCompat.getColor(context, R.color.tusky_blue)) + .setGroup(senderFullName) + .setDefaults(0) // So it doesn't ring twice, notify only in Target callback builder.setContentTitle(context.getString(R.string.error_generic)) builder.setContentText(context.getString(R.string.error_sender_account_gone)) @@ -86,34 +86,34 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { val text = mentions.joinToString(" ", postfix = " ") { "@$it" } + message.toString() val sendIntent = SendTootService.sendTootIntent( - context, - TootToSend( - text = text, - warningText = spoiler, - visibility = visibility.serverString(), - sensitive = false, - mediaIds = emptyList(), - mediaUris = emptyList(), - mediaDescriptions = emptyList(), - scheduledAt = null, - inReplyToId = citedStatusId, - poll = null, - replyingStatusContent = null, - replyingStatusAuthorUsername = null, - accountId = account.id, - draftId = -1, - idempotencyKey = randomAlphanumericString(16), - retries = 0 - ) + context, + TootToSend( + text = text, + warningText = spoiler, + visibility = visibility.serverString(), + sensitive = false, + mediaIds = emptyList(), + mediaUris = emptyList(), + mediaDescriptions = emptyList(), + scheduledAt = null, + inReplyToId = citedStatusId, + poll = null, + replyingStatusContent = null, + replyingStatusAuthorUsername = null, + accountId = account.id, + draftId = -1, + idempotencyKey = randomAlphanumericString(16), + retries = 0 + ) ) context.startService(sendIntent) val builder = NotificationCompat.Builder(context, NotificationHelper.CHANNEL_MENTION + senderIdentifier) - .setSmallIcon(R.drawable.ic_notify) - .setColor(ContextCompat.getColor(context, (R.color.tusky_blue))) - .setGroup(senderFullName) - .setDefaults(0) // So it doesn't ring twice, notify only in Target callback + .setSmallIcon(R.drawable.ic_notify) + .setColor(ContextCompat.getColor(context, (R.color.tusky_blue))) + .setGroup(senderFullName) + .setDefaults(0) // So it doesn't ring twice, notify only in Target callback builder.setContentTitle(context.getString(R.string.status_sent)) builder.setContentText(context.getString(R.string.status_sent_long)) @@ -133,14 +133,17 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { accountManager.setActiveAccount(senderId) - val composeIntent = ComposeActivity.startIntent(context, ComposeOptions( + val composeIntent = ComposeActivity.startIntent( + context, + ComposeOptions( inReplyToId = citedStatusId, replyVisibility = visibility, contentWarning = spoiler, mentionedUsernames = mentions.toSet(), replyingStatusAuthor = localAuthorId, replyingStatusContent = citedText - )) + ) + ) composeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -153,5 +156,4 @@ class SendStatusBroadcastReceiver : BroadcastReceiver() { return remoteInput.getCharSequence(NotificationHelper.KEY_REPLY, "") } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt index 20ae8f476..ed69a49d4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/SendTootService.kt @@ -35,7 +35,8 @@ import kotlinx.parcelize.Parcelize import retrofit2.Call import retrofit2.Callback import retrofit2.Response -import java.util.* +import java.util.Timer +import java.util.TimerTask import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -76,12 +77,11 @@ class SendTootService : Service(), Injectable { if (intent.hasExtra(KEY_TOOT)) { val tootToSend = intent.getParcelableExtra(KEY_TOOT) - ?: throw IllegalStateException("SendTootService started without $KEY_TOOT extra") + ?: throw IllegalStateException("SendTootService started without $KEY_TOOT extra") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel(CHANNEL_ID, getString(R.string.send_toot_notification_channel_name), NotificationManager.IMPORTANCE_LOW) notificationManager.createNotificationChannel(channel) - } var notificationText = tootToSend.warningText @@ -90,13 +90,13 @@ class SendTootService : Service(), Injectable { } val builder = NotificationCompat.Builder(this, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_notify) - .setContentTitle(getString(R.string.send_toot_notification_title)) - .setContentText(notificationText) - .setProgress(1, 0, true) - .setOngoing(true) - .setColor(ContextCompat.getColor(this, R.color.tusky_blue)) - .addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId)) + .setSmallIcon(R.drawable.ic_notify) + .setContentTitle(getString(R.string.send_toot_notification_title)) + .setContentText(notificationText) + .setProgress(1, 0, true) + .setOngoing(true) + .setColor(ContextCompat.getColor(this, R.color.tusky_blue)) + .addAction(0, getString(android.R.string.cancel), cancelSendingIntent(sendingNotificationId)) if (tootsToSend.size == 0 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH) @@ -107,17 +107,14 @@ class SendTootService : Service(), Injectable { tootsToSend[sendingNotificationId] = tootToSend sendToot(sendingNotificationId--) - } else { if (intent.hasExtra(KEY_CANCEL)) { cancelSending(intent.getIntExtra(KEY_CANCEL, 0)) } - } return START_NOT_STICKY - } private fun sendToot(tootId: Int) { @@ -138,21 +135,21 @@ class SendTootService : Service(), Injectable { tootToSend.retries++ val newStatus = NewStatus( - tootToSend.text, - tootToSend.warningText, - tootToSend.inReplyToId, - tootToSend.visibility, - tootToSend.sensitive, - tootToSend.mediaIds, - tootToSend.scheduledAt, - tootToSend.poll + tootToSend.text, + tootToSend.warningText, + tootToSend.inReplyToId, + tootToSend.visibility, + tootToSend.sensitive, + tootToSend.mediaIds, + tootToSend.scheduledAt, + tootToSend.poll ) val sendCall = mastodonApi.createStatus( - "Bearer " + account.accessToken, - account.domain, - tootToSend.idempotencyKey, - newStatus + "Bearer " + account.accessToken, + account.domain, + tootToSend.idempotencyKey, + newStatus ) sendCalls[tootId] = sendCall @@ -178,24 +175,21 @@ class SendTootService : Service(), Injectable { } notificationManager.cancel(tootId) - } else { // the server refused to accept the toot, save toot & show error message saveTootToDrafts(tootToSend) val builder = NotificationCompat.Builder(this@SendTootService, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_notify) - .setContentTitle(getString(R.string.send_toot_notification_error_title)) - .setContentText(getString(R.string.send_toot_notification_saved_content)) - .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) + .setSmallIcon(R.drawable.ic_notify) + .setContentTitle(getString(R.string.send_toot_notification_error_title)) + .setContentText(getString(R.string.send_toot_notification_saved_content)) + .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) notificationManager.cancel(tootId) notificationManager.notify(errorNotificationId--, builder.build()) - } stopSelfWhenDone() - } override fun onFailure(call: Call, t: Throwable) { @@ -204,16 +198,18 @@ class SendTootService : Service(), Injectable { backoff = MAX_RETRY_INTERVAL } - timer.schedule(object : TimerTask() { - override fun run() { - sendToot(tootId) - } - }, backoff) + timer.schedule( + object : TimerTask() { + override fun run() { + sendToot(tootId) + } + }, + backoff + ) } } sendCall.enqueue(callback) - } private fun stopSelfWhenDone() { @@ -233,20 +229,22 @@ class SendTootService : Service(), Injectable { saveTootToDrafts(tootToCancel) val builder = NotificationCompat.Builder(this@SendTootService, CHANNEL_ID) - .setSmallIcon(R.drawable.ic_notify) - .setContentTitle(getString(R.string.send_toot_notification_cancel_title)) - .setContentText(getString(R.string.send_toot_notification_saved_content)) - .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) + .setSmallIcon(R.drawable.ic_notify) + .setContentTitle(getString(R.string.send_toot_notification_cancel_title)) + .setContentText(getString(R.string.send_toot_notification_saved_content)) + .setColor(ContextCompat.getColor(this@SendTootService, R.color.tusky_blue)) notificationManager.notify(tootId, builder.build()) - timer.schedule(object : TimerTask() { - override fun run() { - notificationManager.cancel(tootId) - stopSelfWhenDone() - } - }, 5000) - + timer.schedule( + object : TimerTask() { + override fun run() { + notificationManager.cancel(tootId) + stopSelfWhenDone() + } + }, + 5000 + ) } } @@ -294,8 +292,9 @@ class SendTootService : Service(), Injectable { private var errorNotificationId = Int.MIN_VALUE // use even more negative ids to not clash with other notis @JvmStatic - fun sendTootIntent(context: Context, - tootToSend: TootToSend + fun sendTootIntent( + context: Context, + tootToSend: TootToSend ): Intent { val intent = Intent(context, SendTootService::class.java) intent.putExtra(KEY_TOOT, tootToSend) @@ -304,41 +303,39 @@ class SendTootService : Service(), Injectable { // forward uri permissions intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) val uriClip = ClipData( - ClipDescription("Toot Media", arrayOf("image/*", "video/*")), - ClipData.Item(tootToSend.mediaUris[0]) + ClipDescription("Toot Media", arrayOf("image/*", "video/*")), + ClipData.Item(tootToSend.mediaUris[0]) ) tootToSend.mediaUris - .drop(1) - .forEach { mediaUri -> - uriClip.addItem(ClipData.Item(mediaUri)) - } + .drop(1) + .forEach { mediaUri -> + uriClip.addItem(ClipData.Item(mediaUri)) + } intent.clipData = uriClip - } return intent } - } } @Parcelize data class TootToSend( - val text: String, - val warningText: String, - val visibility: String, - val sensitive: Boolean, - val mediaIds: List, - val mediaUris: List, - val mediaDescriptions: List, - val scheduledAt: String?, - val inReplyToId: String?, - val poll: NewPoll?, - val replyingStatusContent: String?, - val replyingStatusAuthorUsername: String?, - val accountId: Long, - val draftId: Int, - val idempotencyKey: String, - var retries: Int + val text: String, + val warningText: String, + val visibility: String, + val sensitive: Boolean, + val mediaIds: List, + val mediaUris: List, + val mediaDescriptions: List, + val scheduledAt: String?, + val inReplyToId: String?, + val poll: NewPoll?, + val replyingStatusContent: String?, + val replyingStatusAuthorUsername: String?, + val accountId: Long, + val draftId: Int, + val idempotencyKey: String, + var retries: Int ) : Parcelable diff --git a/app/src/main/java/com/keylesspalace/tusky/service/ServiceClient.kt b/app/src/main/java/com/keylesspalace/tusky/service/ServiceClient.kt index b60377f52..5b9e3298d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/service/ServiceClient.kt +++ b/app/src/main/java/com/keylesspalace/tusky/service/ServiceClient.kt @@ -31,4 +31,4 @@ class ServiceClientImpl(private val context: Context) : ServiceClient { context.startService(intent) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt index 82dfa14e2..1569cb151 100644 --- a/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt +++ b/app/src/main/java/com/keylesspalace/tusky/settings/SettingsDSL.kt @@ -2,13 +2,20 @@ package com.keylesspalace.tusky.settings import android.content.Context import androidx.annotation.StringRes -import androidx.preference.* +import androidx.preference.CheckBoxPreference +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreference import com.keylesspalace.tusky.components.preference.EmojiPreference import okhttp3.OkHttpClient class PreferenceParent( - val context: Context, - val addPref: (pref: Preference) -> Unit + val context: Context, + val addPref: (pref: Preference) -> Unit ) inline fun PreferenceParent.preference(builder: Preference.() -> Unit): Preference { @@ -33,7 +40,7 @@ inline fun PreferenceParent.emojiPreference(okHttpClient: OkHttpClient, builder: } inline fun PreferenceParent.switchPreference( - builder: SwitchPreference.() -> Unit + builder: SwitchPreference.() -> Unit ): SwitchPreference { val pref = SwitchPreference(context) builder(pref) @@ -42,7 +49,7 @@ inline fun PreferenceParent.switchPreference( } inline fun PreferenceParent.editTextPreference( - builder: EditTextPreference.() -> Unit + builder: EditTextPreference.() -> Unit ): EditTextPreference { val pref = EditTextPreference(context) builder(pref) @@ -51,7 +58,7 @@ inline fun PreferenceParent.editTextPreference( } inline fun PreferenceParent.checkBoxPreference( - builder: CheckBoxPreference.() -> Unit + builder: CheckBoxPreference.() -> Unit ): CheckBoxPreference { val pref = CheckBoxPreference(context) builder(pref) @@ -60,8 +67,8 @@ inline fun PreferenceParent.checkBoxPreference( } inline fun PreferenceParent.preferenceCategory( - @StringRes title: Int, - builder: PreferenceParent.(PreferenceCategory) -> Unit + @StringRes title: Int, + builder: PreferenceParent.(PreferenceCategory) -> Unit ) { val category = PreferenceCategory(context) addPref(category) @@ -71,7 +78,7 @@ inline fun PreferenceParent.preferenceCategory( } inline fun PreferenceFragmentCompat.makePreferenceScreen( - builder: PreferenceParent.() -> Unit + builder: PreferenceParent.() -> Unit ): PreferenceScreen { val context = requireContext() val screen = preferenceManager.createPreferenceScreen(context) @@ -81,4 +88,4 @@ inline fun PreferenceFragmentCompat.makePreferenceScreen( preferenceScreen = screen builder(parent) return screen -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/BindingHolder.kt b/app/src/main/java/com/keylesspalace/tusky/util/BindingHolder.kt index a7a4c9720..62167ee6a 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/BindingHolder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/BindingHolder.kt @@ -4,5 +4,5 @@ import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding class BindingHolder( - val binding: T + val binding: T ) : RecyclerView.ViewHolder(binding.root) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/BlurHashDecoder.kt b/app/src/main/java/com/keylesspalace/tusky/util/BlurHashDecoder.kt index bd5f9007c..117f59c09 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/BlurHashDecoder.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/BlurHashDecoder.kt @@ -74,18 +74,20 @@ object BlurHashDecoder { val g = (value / 19) % 19 val b = value % 19 return floatArrayOf( - signedPow2((r - 9) / 9.0f) * maxAc, - signedPow2((g - 9) / 9.0f) * maxAc, - signedPow2((b - 9) / 9.0f) * maxAc + signedPow2((r - 9) / 9.0f) * maxAc, + signedPow2((g - 9) / 9.0f) * maxAc, + signedPow2((b - 9) / 9.0f) * maxAc ) } private fun signedPow2(value: Float) = value.pow(2f).withSign(value) private fun composeBitmap( - width: Int, height: Int, - numCompX: Int, numCompY: Int, - colors: Array + width: Int, + height: Int, + numCompX: Int, + numCompY: Int, + colors: Array ): Bitmap { val imageArray = IntArray(width * height) for (y in 0 until height) { @@ -118,13 +120,12 @@ object BlurHashDecoder { } private val charMap = listOf( - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', - 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',', - '-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~' + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',', + '-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~' ) - .mapIndexed { i, c -> c to i } - .toMap() - + .mapIndexed { i, c -> c to i } + .toMap() } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CardViewMode.kt b/app/src/main/java/com/keylesspalace/tusky/util/CardViewMode.kt index 2cf2348cd..81c2216b0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CardViewMode.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/CardViewMode.kt @@ -4,4 +4,4 @@ enum class CardViewMode { NONE, FULL_WIDTH, INDENTED -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt b/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt index c0da4275e..6fee42edf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ComposeTokenizer.kt @@ -22,10 +22,10 @@ import android.widget.MultiAutoCompleteTextView class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer { - private fun isMentionOrHashtagAllowedCharacter(character: Char) : Boolean { - return Character.isLetterOrDigit(character) || character == '_' // simple usernames - || character == '-' // extended usernames - || character == '.' // domain dot + private fun isMentionOrHashtagAllowedCharacter(character: Char): Boolean { + return Character.isLetterOrDigit(character) || character == '_' || // simple usernames + character == '-' || // extended usernames + character == '.' // domain dot } override fun findTokenStart(text: CharSequence, cursor: Int): Int { @@ -36,8 +36,8 @@ class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer { var character = text[i - 1] // go up to first illegal character or character we're looking for (@, # or :) - while(i > 0 && !(character == '@' || character == '#' || character == ':')) { - if(!isMentionOrHashtagAllowedCharacter(character)) { + while (i > 0 && !(character == '@' || character == '#' || character == ':')) { + if (!isMentionOrHashtagAllowedCharacter(character)) { return cursor } @@ -46,13 +46,13 @@ class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer { } // maybe caught domain name? try search username - if(i > 2 && character == '@') { + if (i > 2 && character == '@') { var j = i - 1 var character2 = text[i - 2] // again go up to first illegal character or tag "@" - while(j > 0 && character2 != '@') { - if(!isMentionOrHashtagAllowedCharacter(character2)) { + while (j > 0 && character2 != '@') { + if (!isMentionOrHashtagAllowedCharacter(character2)) { break } @@ -61,15 +61,16 @@ class ComposeTokenizer : MultiAutoCompleteTextView.Tokenizer { } // found mention symbol, override cursor - if(character2 == '@') { + if (character2 == '@') { i = j character = character2 } } - if (i < 1 - || (character != '@' && character != '#' && character != ':') - || i > 1 && !Character.isWhitespace(text[i - 2])) { + if (i < 1 || + (character != '@' && character != '#' && character != ':') || + i > 1 && !Character.isWhitespace(text[i - 2]) + ) { return cursor } return i - 1 diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt index f63c44b84..2aee7384b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomEmojiHelper.kt @@ -18,17 +18,16 @@ package com.keylesspalace.tusky.util import android.graphics.Canvas import android.graphics.Paint -import android.graphics.drawable.* +import android.graphics.drawable.Animatable +import android.graphics.drawable.Drawable import android.text.SpannableStringBuilder import android.text.style.ReplacementSpan import android.view.View - import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.transition.Transition import com.keylesspalace.tusky.entity.Emoji - import java.lang.ref.WeakReference import java.util.regex.Pattern @@ -39,8 +38,8 @@ import java.util.regex.Pattern * @param view a reference to the a view the emojis will be shown in (should be the TextView, but parents of the TextView are also acceptable) * @return the text with the shortcodes replaced by EmojiSpans */ -fun CharSequence.emojify(emojis: List?, view: View, animate: Boolean) : CharSequence { - if(emojis.isNullOrEmpty()) +fun CharSequence.emojify(emojis: List?, view: View, animate: Boolean): CharSequence { + if (emojis.isNullOrEmpty()) return this val builder = SpannableStringBuilder.valueOf(this) @@ -49,7 +48,7 @@ fun CharSequence.emojify(emojis: List?, view: View, animate: Boolean) : C val matcher = Pattern.compile(":$shortcode:", Pattern.LITERAL) .matcher(this) - while(matcher.find()) { + while (matcher.find()) { val span = EmojiSpan(WeakReference(view)) builder.setSpan(span, matcher.start(), matcher.end(), 0) @@ -64,8 +63,8 @@ fun CharSequence.emojify(emojis: List?, view: View, animate: Boolean) : C class EmojiSpan(val viewWeakReference: WeakReference) : ReplacementSpan() { var imageDrawable: Drawable? = null - - override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?) : Int { + + override fun getSize(paint: Paint, text: CharSequence, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { if (fm != null) { /* update FontMetricsInt or otherwise span does not get drawn when * it covers the whole text */ @@ -75,10 +74,10 @@ class EmojiSpan(val viewWeakReference: WeakReference) : ReplacementSpan() fm.descent = metrics.descent fm.bottom = metrics.bottom } - + return (paint.textSize * 1.2).toInt() } - + override fun draw(canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { imageDrawable?.let { drawable -> canvas.save() @@ -94,15 +93,15 @@ class EmojiSpan(val viewWeakReference: WeakReference) : ReplacementSpan() canvas.restore() } } - - fun getTarget(animate : Boolean): Target { + + fun getTarget(animate: Boolean): Target { return object : CustomTarget() { override fun onResourceReady(resource: Drawable, transition: Transition?) { viewWeakReference.get()?.let { view -> - if(animate && resource is Animatable) { + if (animate && resource is Animatable) { val callback = resource.callback - resource.callback = object: Drawable.Callback { + resource.callback = object : Drawable.Callback { override fun unscheduleDrawable(p0: Drawable, p1: Runnable) { callback?.unscheduleDrawable(p0, p1) } @@ -121,7 +120,7 @@ class EmojiSpan(val viewWeakReference: WeakReference) : ReplacementSpan() view.invalidate() } } - + override fun onLoadCleared(placeholder: Drawable?) {} } } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/CustomFragmentStateAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/util/CustomFragmentStateAdapter.kt index bda206144..eb31032f5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/CustomFragmentStateAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/CustomFragmentStateAdapter.kt @@ -20,9 +20,9 @@ import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter abstract class CustomFragmentStateAdapter( - private val activity: FragmentActivity -): FragmentStateAdapter(activity) { + private val activity: FragmentActivity +) : FragmentStateAdapter(activity) { - fun getFragment(position: Int): Fragment? - = activity.supportFragmentManager.findFragmentByTag("f" + getItemId(position)) + fun getFragment(position: Int): Fragment? = + activity.supportFragmentManager.findFragmentByTag("f" + getItemId(position)) } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/Either.kt b/app/src/main/java/com/keylesspalace/tusky/util/Either.kt index f0955cfa8..728ccd0e0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/Either.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/Either.kt @@ -44,4 +44,4 @@ sealed class Either { Right(mapper(this.asRight())) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt b/app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt index 4bce7b8f4..f513feeef 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/EmojiCompatFont.kt @@ -29,13 +29,14 @@ import kotlin.math.max * This class bundles information about an emoji font as well as many convenient actions. */ class EmojiCompatFont( - val name: String, - private val display: String, - @StringRes val caption: Int, - @DrawableRes val img: Int, - val url: String, - // The version is stored as a String in the x.xx.xx format (to be able to compare versions) - val version: String) { + val name: String, + private val display: String, + @StringRes val caption: Int, + @DrawableRes val img: Int, + val url: String, + // The version is stored as a String in the x.xx.xx format (to be able to compare versions) + val version: String +) { private val versionCode = getVersionCode(version) @@ -102,8 +103,13 @@ class EmojiCompatFont( if (compareVersions(fileExists.second, versionCode) < 0) { val file = fileExists.first // Uses side effects! - Log.d(TAG, String.format("Deleted %s successfully: %s", file.absolutePath, - file.delete())) + Log.d( + TAG, + String.format( + "Deleted %s successfully: %s", file.absolutePath, + file.delete() + ) + ) } } } @@ -131,8 +137,13 @@ class EmojiCompatFont( val fontRegex = "$name(\\d+(\\.\\d+)*)?\\.ttf".toPattern() val ttfFilter = FilenameFilter { _, name: String -> name.endsWith(".ttf") } val foundFontFiles = directory.listFiles(ttfFilter).orEmpty() - Log.d(TAG, String.format("loadExistingFontFiles: %d other font files found", - foundFontFiles.size)) + Log.d( + TAG, + String.format( + "loadExistingFontFiles: %d other font files found", + foundFontFiles.size + ) + ) return foundFontFiles.map { file -> val matcher = fontRegex.matcher(file.name) @@ -170,8 +181,10 @@ class EmojiCompatFont( } } - fun downloadFontFile(context: Context, - okHttpClient: OkHttpClient): Observable { + fun downloadFontFile( + context: Context, + okHttpClient: OkHttpClient + ): Observable { return Observable.create { emitter: ObservableEmitter -> // It is possible (and very likely) that the file does not exist yet val downloadFile = getFontFile(context)!! @@ -180,7 +193,7 @@ class EmojiCompatFont( downloadFile.createNewFile() } val request = Request.Builder().url(url) - .build() + .build() val sink = downloadFile.sink().buffer() var source: Source? = null @@ -197,7 +210,7 @@ class EmojiCompatFont( while (!emitter.isDisposed) { sink.write(source, CHUNK_SIZE) progress += CHUNK_SIZE.toFloat() - if(size > 0) { + if (size > 0) { emitter.onNext(progress / size) } else { emitter.onNext(-1f) @@ -213,7 +226,6 @@ class EmojiCompatFont( Log.e(TAG, "Downloading $url failed. Status code: ${response.code}") emitter.tryOnError(Exception()) } - } catch (ex: IOException) { Log.e(TAG, "Downloading $url failed.", ex) downloadFile.deleteIfExists() @@ -228,10 +240,8 @@ class EmojiCompatFont( emitter.onComplete() } } - } - .subscribeOn(Schedulers.io()) - + .subscribeOn(Schedulers.io()) } /** @@ -256,32 +266,37 @@ class EmojiCompatFont( private const val CHUNK_SIZE = 4096L // The system font gets some special behavior... - val SYSTEM_DEFAULT = EmojiCompatFont("system-default", - "System Default", - R.string.caption_systememoji, - R.drawable.ic_emoji_34dp, - "", - "0") - val BLOBMOJI = EmojiCompatFont("Blobmoji", - "Blobmoji", - R.string.caption_blobmoji, - R.drawable.ic_blobmoji, - "https://tusky.app/hosted/emoji/BlobmojiCompat.ttf", - "12.0.0" + val SYSTEM_DEFAULT = EmojiCompatFont( + "system-default", + "System Default", + R.string.caption_systememoji, + R.drawable.ic_emoji_34dp, + "", + "0" ) - val TWEMOJI = EmojiCompatFont("Twemoji", - "Twemoji", - R.string.caption_twemoji, - R.drawable.ic_twemoji, - "https://tusky.app/hosted/emoji/TwemojiCompat.ttf", - "12.0.0" + val BLOBMOJI = EmojiCompatFont( + "Blobmoji", + "Blobmoji", + R.string.caption_blobmoji, + R.drawable.ic_blobmoji, + "https://tusky.app/hosted/emoji/BlobmojiCompat.ttf", + "12.0.0" ) - val NOTOEMOJI = EmojiCompatFont("NotoEmoji", - "Noto Emoji", - R.string.caption_notoemoji, - R.drawable.ic_notoemoji, - "https://tusky.app/hosted/emoji/NotoEmojiCompat.ttf", - "11.0.0" + val TWEMOJI = EmojiCompatFont( + "Twemoji", + "Twemoji", + R.string.caption_twemoji, + R.drawable.ic_twemoji, + "https://tusky.app/hosted/emoji/TwemojiCompat.ttf", + "12.0.0" + ) + val NOTOEMOJI = EmojiCompatFont( + "NotoEmoji", + "Noto Emoji", + R.string.caption_notoemoji, + R.drawable.ic_notoemoji, + "https://tusky.app/hosted/emoji/NotoEmojiCompat.ttf", + "11.0.0" ) /** @@ -341,11 +356,9 @@ class EmojiCompatFont( } private fun File.deleteIfExists() { - if(exists() && !delete()) { + if (exists() && !delete()) { Log.e(TAG, "Could not delete file $this") } } - } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/FocalPointUtil.kt b/app/src/main/java/com/keylesspalace/tusky/util/FocalPointUtil.kt index 6f2542b50..41d1034c7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/FocalPointUtil.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/FocalPointUtil.kt @@ -16,7 +16,6 @@ package com.keylesspalace.tusky.util import android.graphics.Matrix - import com.keylesspalace.tusky.entity.Attachment.Focus /** @@ -54,12 +53,14 @@ object FocalPointUtil { * * @return The matrix which correctly crops the image */ - fun updateFocalPointMatrix(viewWidth: Float, - viewHeight: Float, - imageWidth: Float, - imageHeight: Float, - focus: Focus, - mat: Matrix) { + fun updateFocalPointMatrix( + viewWidth: Float, + viewHeight: Float, + imageWidth: Float, + imageHeight: Float, + focus: Focus, + mat: Matrix + ) { // Reset the cached matrix: mat.reset() @@ -84,11 +85,15 @@ object FocalPointUtil { * * The scaling used depends on if we need a vertical of horizontal crop. */ - fun calculateScaling(viewWidth: Float, viewHeight: Float, - imageWidth: Float, imageHeight: Float): Float { + fun calculateScaling( + viewWidth: Float, + viewHeight: Float, + imageWidth: Float, + imageHeight: Float + ): Float { return if (isVerticalCrop(viewWidth, viewHeight, imageWidth, imageHeight)) { viewWidth / imageWidth - } else { // horizontal crop: + } else { // horizontal crop: viewHeight / imageHeight } } @@ -96,8 +101,12 @@ object FocalPointUtil { /** * Return true if we need a vertical crop, false for a horizontal crop. */ - fun isVerticalCrop(viewWidth: Float, viewHeight: Float, - imageWidth: Float, imageHeight: Float): Boolean { + fun isVerticalCrop( + viewWidth: Float, + viewHeight: Float, + imageWidth: Float, + imageHeight: Float + ): Boolean { val viewRatio = viewWidth / viewHeight val imageRatio = imageWidth / imageHeight @@ -135,8 +144,12 @@ object FocalPointUtil { * the image. So it won't put the very edge of the image in center, because that would * leave part of the view empty. */ - fun focalOffset(view: Float, image: Float, - scale: Float, focal: Float): Float { + fun focalOffset( + view: Float, + image: Float, + scale: Float, + focal: Float + ): Float { // The fraction of the image that will be in view: val inView = view / (scale * image) var offset = 0f diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt index 9daf16f80..1cd9b99ac 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ImageLoadingHelper.kt @@ -11,41 +11,38 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.keylesspalace.tusky.R - private val centerCropTransformation = CenterCrop() fun loadAvatar(url: String?, imageView: ImageView, @Px radius: Int, animate: Boolean) { if (url.isNullOrBlank()) { Glide.with(imageView) - .load(R.drawable.avatar_default) - .into(imageView) + .load(R.drawable.avatar_default) + .into(imageView) } else { if (animate) { Glide.with(imageView) - .load(url) - .transform( - centerCropTransformation, - RoundedCorners(radius) - ) - .placeholder(R.drawable.avatar_default) - .into(imageView) - + .load(url) + .transform( + centerCropTransformation, + RoundedCorners(radius) + ) + .placeholder(R.drawable.avatar_default) + .into(imageView) } else { Glide.with(imageView) - .asBitmap() - .load(url) - .transform( - centerCropTransformation, - RoundedCorners(radius) - ) - .placeholder(R.drawable.avatar_default) - .into(imageView) + .asBitmap() + .load(url) + .transform( + centerCropTransformation, + RoundedCorners(radius) + ) + .placeholder(R.drawable.avatar_default) + .into(imageView) } - } } fun decodeBlurHash(context: Context, blurhash: String): BitmapDrawable { return BitmapDrawable(context.resources, BlurHashDecoder.decode(blurhash, 32, 32, 1f)) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ListStatusAccessibilityDelegate.kt b/app/src/main/java/com/keylesspalace/tusky/util/ListStatusAccessibilityDelegate.kt index 93e0c67bb..879fccc30 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ListStatusAccessibilityDelegate.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ListStatusAccessibilityDelegate.kt @@ -32,7 +32,7 @@ class ListStatusAccessibilityDelegate( private val statusProvider: StatusProvider ) : RecyclerViewAccessibilityDelegate(recyclerView) { private val a11yManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) - as AccessibilityManager + as AccessibilityManager override fun getItemDelegate(): AccessibilityDelegateCompat = itemDelegate @@ -92,11 +92,11 @@ class ListStatusAccessibilityDelegate( info.addAction(moreAction) } - } override fun performAccessibilityAction( - host: View, action: Int, + host: View, + action: Int, args: Bundle? ): Boolean { val pos = recyclerView.getChildAdapterPosition(host) @@ -170,7 +170,6 @@ class ListStatusAccessibilityDelegate( return true } - private fun showLinksDialog(host: View) { val status = getStatus(host) as? StatusViewData.Concrete ?: return val links = getLinks(status).toList() @@ -228,7 +227,6 @@ class ListStatusAccessibilityDelegate( } } - private fun getLinks(status: StatusViewData.Concrete): Sequence { val content = status.content return if (content is Spannable) { @@ -268,7 +266,6 @@ class ListStatusAccessibilityDelegate( a11yManager.interrupt() } - private fun isHashtag(text: CharSequence) = text.startsWith("#") private val collapseCwAction = AccessibilityActionCompat( @@ -357,4 +354,4 @@ class ListStatusAccessibilityDelegate( ) private data class LinkSpanInfo(val text: String, val link: String) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ListUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/ListUtils.kt index 28ef0c63a..7cdc12e83 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ListUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ListUtils.kt @@ -17,9 +17,8 @@ package com.keylesspalace.tusky.util -import java.util.LinkedHashSet import java.util.ArrayList - +import java.util.LinkedHashSet /** * @return true if list is null or else return list.isEmpty() @@ -56,4 +55,4 @@ inline fun List.replacedFirstWhich(replacement: T, predicate: (T) -> Bool inline fun Iterable<*>.firstIsInstanceOrNull(): R? { return firstOrNull { it is R }?.let { it as R } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LiveDataUtil.kt b/app/src/main/java/com/keylesspalace/tusky/util/LiveDataUtil.kt index 822a8da6b..21c4307c6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LiveDataUtil.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/LiveDataUtil.kt @@ -15,17 +15,21 @@ package com.keylesspalace.tusky.util -import androidx.lifecycle.* +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.LiveDataReactiveStreams +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.Transformations import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single - inline fun LiveData.map(crossinline mapFunction: (X) -> Y): LiveData = - Transformations.map(this) { input -> mapFunction(input) } + Transformations.map(this) { input -> mapFunction(input) } inline fun LiveData.switchMap( - crossinline switchMapFunction: (X) -> LiveData + crossinline switchMapFunction: (X) -> LiveData ): LiveData = Transformations.switchMap(this) { input -> switchMapFunction(input) } inline fun LiveData.filter(crossinline predicate: (X) -> Boolean): LiveData { @@ -39,17 +43,17 @@ inline fun LiveData.filter(crossinline predicate: (X) -> Boolean): LiveDa } fun LifecycleOwner.withLifecycleContext(body: LifecycleContext.() -> Unit) = - LifecycleContext(this).apply(body) + LifecycleContext(this).apply(body) class LifecycleContext(val lifecycleOwner: LifecycleOwner) { inline fun LiveData.observe(crossinline observer: (T) -> Unit) = - this.observe(lifecycleOwner, Observer { observer(it) }) + this.observe(lifecycleOwner, Observer { observer(it) }) /** * Just hold a subscription, */ fun LiveData.subscribe() = - this.observe(lifecycleOwner, Observer { }) + this.observe(lifecycleOwner, Observer { }) } /** @@ -90,5 +94,5 @@ fun combineOptionalLiveData(a: LiveData, b: LiveData, combiner: fun Single.toLiveData() = LiveDataReactiveStreams.fromPublisher(this.toFlowable()) fun Observable.toLiveData( - backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST -) = LiveDataReactiveStreams.fromPublisher(this.toFlowable(BackpressureStrategy.LATEST)) \ No newline at end of file + backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST +) = LiveDataReactiveStreams.fromPublisher(this.toFlowable(BackpressureStrategy.LATEST)) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt b/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt index 4a80bca20..45f3ab371 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/LocaleManager.kt @@ -19,7 +19,7 @@ import android.content.Context import android.content.SharedPreferences import android.content.res.Configuration import androidx.preference.PreferenceManager -import java.util.* +import java.util.Locale class LocaleManager(context: Context) { diff --git a/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.kt index 43f05e9cb..5482b292b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/MediaUtils.kt @@ -22,11 +22,13 @@ import android.graphics.BitmapFactory import android.graphics.Matrix import android.net.Uri import android.provider.OpenableColumns +import android.util.Log import androidx.annotation.Px import androidx.exifinterface.media.ExifInterface -import android.util.Log -import java.io.* - +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.io.InputStream import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date @@ -46,7 +48,7 @@ const val MEDIA_SIZE_UNKNOWN = -1L * @return the size of the media in bytes or {@link MediaUtils#MEDIA_SIZE_UNKNOWN} */ fun getMediaSize(contentResolver: ContentResolver, uri: Uri?): Long { - if(uri == null) { + if (uri == null) { return MEDIA_SIZE_UNKNOWN } @@ -165,8 +167,10 @@ fun reorientBitmap(bitmap: Bitmap?, orientation: Int): Bitmap? { } return try { - val result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, - bitmap.height, matrix, true) + val result = Bitmap.createBitmap( + bitmap, 0, 0, bitmap.width, + bitmap.height, matrix, true + ) if (!bitmap.sameAs(result)) { bitmap.recycle() } @@ -210,7 +214,7 @@ fun deleteStaleCachedMedia(mediaDirectory: File?) { twentyfourHoursAgo.add(Calendar.HOUR, -24) val unixTime = twentyfourHoursAgo.timeInMillis - val files = mediaDirectory.listFiles{ file -> unixTime > file.lastModified() && file.name.contains(MEDIA_TEMP_PREFIX) } + val files = mediaDirectory.listFiles { file -> unixTime > file.lastModified() && file.name.contains(MEDIA_TEMP_PREFIX) } if (files == null || files.isEmpty()) { // Nothing to do return diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NetworkState.kt b/app/src/main/java/com/keylesspalace/tusky/util/NetworkState.kt index 09a00339a..c7eea3a65 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NetworkState.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/NetworkState.kt @@ -24,11 +24,12 @@ enum class Status { @Suppress("DataClassPrivateConstructor") data class NetworkState private constructor( - val status: Status, - val msg: String? = null) { + val status: Status, + val msg: String? = null +) { companion object { val LOADED = NetworkState(Status.SUCCESS) val LOADING = NetworkState(Status.RUNNING) fun error(msg: String?) = NetworkState(Status.FAILED, msg) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt b/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt index 65c8f6c08..34e8924f8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationTypeConverter.kt @@ -42,4 +42,4 @@ fun deserialize(data: String?): Set { } } return ret -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/PickMediaFiles.kt b/app/src/main/java/com/keylesspalace/tusky/util/PickMediaFiles.kt index ae09d9e4f..4d3fcd5b4 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/PickMediaFiles.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/PickMediaFiles.kt @@ -49,4 +49,4 @@ class PickMediaFiles : ActivityResultContract>() { } return emptyList() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/Resource.kt b/app/src/main/java/com/keylesspalace/tusky/util/Resource.kt index 1f9f35d20..ddc88f45d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/Resource.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/Resource.kt @@ -6,8 +6,9 @@ class Loading (override val data: T? = null) : Resource(data) class Success (override val data: T? = null) : Resource(data) -class Error (override val data: T? = null, - val errorMessage: String? = null, - var consumed: Boolean = false, - val cause: Throwable? = null -): Resource(data) \ No newline at end of file +class Error ( + override val data: T? = null, + val errorMessage: String? = null, + var consumed: Boolean = false, + val cause: Throwable? = null +) : Resource(data) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/RickRoll.kt b/app/src/main/java/com/keylesspalace/tusky/util/RickRoll.kt index 03c3339fb..788786ed0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/RickRoll.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/RickRoll.kt @@ -6,9 +6,9 @@ import android.net.Uri import com.keylesspalace.tusky.R fun shouldRickRoll(context: Context, domain: String) = - context.resources.getStringArray(R.array.rick_roll_domains).any { candidate -> - domain.equals(candidate, true) || domain.endsWith(".$candidate", true) - } + context.resources.getStringArray(R.array.rick_roll_domains).any { candidate -> + domain.equals(candidate, true) || domain.endsWith(".$candidate", true) + } fun rickRoll(context: Context) { val uri = Uri.parse(context.getString(R.string.rick_roll_url)) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt index 62af3ba56..0f3267436 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/RxAwareViewModel.kt @@ -15,4 +15,4 @@ open class RxAwareViewModel : ViewModel() { super.onCleared() disposables.clear() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt index 11b7e2ccd..ee6874964 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ShareShortcutHelper.kt @@ -43,17 +43,17 @@ fun updateShortcut(context: Context, account: AccountEntity) { val bmp = if (TextUtils.isEmpty(account.profilePictureUrl)) { Glide.with(context) - .asBitmap() - .load(R.drawable.avatar_default) - .submit(innerSize, innerSize) - .get() + .asBitmap() + .load(R.drawable.avatar_default) + .submit(innerSize, innerSize) + .get() } else { Glide.with(context) - .asBitmap() - .load(account.profilePictureUrl) - .error(R.drawable.avatar_default) - .submit(innerSize, innerSize) - .get() + .asBitmap() + .load(account.profilePictureUrl) + .error(R.drawable.avatar_default) + .submit(innerSize, innerSize) + .get() } // inset the loaded bitmap inside a 108dp transparent canvas so it looks good as adaptive icon @@ -65,10 +65,10 @@ fun updateShortcut(context: Context, account: AccountEntity) { val icon = IconCompat.createWithAdaptiveBitmap(outBmp) val person = Person.Builder() - .setIcon(icon) - .setName(account.displayName) - .setKey(account.identifier) - .build() + .setIcon(icon) + .setName(account.displayName) + .setKey(account.identifier) + .build() // This intent will be sent when the user clicks on one of the launcher shortcuts. Intent from share sheet will be different val intent = Intent(context, MainActivity::class.java).apply { @@ -78,26 +78,22 @@ fun updateShortcut(context: Context, account: AccountEntity) { } val shortcutInfo = ShortcutInfoCompat.Builder(context, account.id.toString()) - .setIntent(intent) - .setCategories(setOf("com.keylesspalace.tusky.Share")) - .setShortLabel(account.displayName) - .setPerson(person) - .setLongLived(true) - .setIcon(icon) - .build() + .setIntent(intent) + .setCategories(setOf("com.keylesspalace.tusky.Share")) + .setShortLabel(account.displayName) + .setPerson(person) + .setLongLived(true) + .setIcon(icon) + .build() ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcutInfo)) - } - .subscribeOn(Schedulers.io()) - .onErrorReturnItem(false) - .subscribe() - - + .subscribeOn(Schedulers.io()) + .onErrorReturnItem(false) + .subscribe() } fun removeShortcut(context: Context, account: AccountEntity) { ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(account.id.toString())) - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SmartLengthInputFilter.kt b/app/src/main/java/com/keylesspalace/tusky/util/SmartLengthInputFilter.kt index ba9c42039..078639acd 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SmartLengthInputFilter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/SmartLengthInputFilter.kt @@ -35,10 +35,10 @@ private const val LENGTH_DEFAULT = 500 * be hidden will not be enough to justify the operation. * * @param message The message to trim. - * @return Whether the message should be trimmed or not. + * @return Whether the message should be trimmed or not. */ fun shouldTrimStatus(message: Spanned): Boolean { - return message.isNotEmpty() && LENGTH_DEFAULT.toFloat() / message.length < 0.75 + return message.isNotEmpty() && LENGTH_DEFAULT.toFloat() / message.length < 0.75 } /** @@ -53,59 +53,59 @@ fun shouldTrimStatus(message: Spanned): Boolean { * */ object SmartLengthInputFilter : InputFilter { - /** {@inheritDoc} */ - override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? { - // Code originally imported from InputFilter.LengthFilter but heavily customized and converted to Kotlin. - // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/InputFilter.java#175 + /** {@inheritDoc} */ + override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? { + // Code originally imported from InputFilter.LengthFilter but heavily customized and converted to Kotlin. + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/InputFilter.java#175 - val sourceLength = source.length - var keep = LENGTH_DEFAULT - (dest.length - (dend - dstart)) - if (keep <= 0) return "" - if (keep >= end - start) return null // Keep original + val sourceLength = source.length + var keep = LENGTH_DEFAULT - (dest.length - (dend - dstart)) + if (keep <= 0) return "" + if (keep >= end - start) return null // Keep original - keep += start + keep += start - // Skip trimming if the ratio doesn't warrant it - if (keep.toDouble() / sourceLength > 0.75) return null + // Skip trimming if the ratio doesn't warrant it + if (keep.toDouble() / sourceLength > 0.75) return null - // Enable trimming at the end of the closest word if possible - if (source[keep].isLetterOrDigit()) { - var boundary: Int + // Enable trimming at the end of the closest word if possible + if (source[keep].isLetterOrDigit()) { + var boundary: Int - // Android N+ offer a clone of the ICU APIs in Java for better internationalization and - // unicode support. Using the ICU version of BreakIterator grants better support for - // those without having to add the ICU4J library at a minimum Api trade-off. - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - val iterator = android.icu.text.BreakIterator.getWordInstance() - iterator.setText(source.toString()) - boundary = iterator.following(keep) - if (keep - boundary > RUNWAY) boundary = iterator.preceding(keep) - } else { - val iterator = java.text.BreakIterator.getWordInstance() - iterator.setText(source.toString()) - boundary = iterator.following(keep) - if (keep - boundary > RUNWAY) boundary = iterator.preceding(keep) - } + // Android N+ offer a clone of the ICU APIs in Java for better internationalization and + // unicode support. Using the ICU version of BreakIterator grants better support for + // those without having to add the ICU4J library at a minimum Api trade-off. + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + val iterator = android.icu.text.BreakIterator.getWordInstance() + iterator.setText(source.toString()) + boundary = iterator.following(keep) + if (keep - boundary > RUNWAY) boundary = iterator.preceding(keep) + } else { + val iterator = java.text.BreakIterator.getWordInstance() + iterator.setText(source.toString()) + boundary = iterator.following(keep) + if (keep - boundary > RUNWAY) boundary = iterator.preceding(keep) + } - keep = boundary - } else { + keep = boundary + } else { - // If no runway is allowed simply remove whitespaces if present - while(source[keep - 1].isWhitespace()) { - --keep - if (keep == start) return "" - } - } + // If no runway is allowed simply remove whitespaces if present + while (source[keep - 1].isWhitespace()) { + --keep + if (keep == start) return "" + } + } - if (source[keep - 1].isHighSurrogate()) { - --keep - if (keep == start) return "" - } + if (source[keep - 1].isHighSurrogate()) { + --keep + if (keep == start) return "" + } - return if (source is Spanned) { - SpannableStringBuilder(source, start, keep).append("…") - } else { - "${source.subSequence(start, keep)}…" - } - } -} \ No newline at end of file + return if (source is Spanned) { + SpannableStringBuilder(source, start, keep).append("…") + } else { + "${source.subSequence(start, keep)}…" + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt index b0c3850f7..7734d9d7f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/SpanUtils.kt @@ -49,13 +49,17 @@ private class FindCharsResult { var end: Int = -1 } -private class PatternFinder(val searchCharacter: Char, regex: String, val searchPrefixWidth: Int, - val prefixValidator: (Int) -> Boolean) { +private class PatternFinder( + val searchCharacter: Char, + regex: String, + val searchPrefixWidth: Int, + val prefixValidator: (Int) -> Boolean +) { val pattern: Pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE) } private fun clearSpans(text: Spannable, spanClass: Class) { - for(span in text.getSpans(0, text.length, spanClass)) { + for (span in text.getSpans(0, text.length, spanClass)) { text.removeSpan(span) } } @@ -66,14 +70,18 @@ private fun findPattern(string: String, fromIndex: Int): FindCharsResult { val c = string[i] for (matchType in FoundMatchType.values()) { val finder = finders[matchType] - if (finder!!.searchCharacter == c - && ((i - fromIndex) < finder.searchPrefixWidth || - finder.prefixValidator(string.codePointAt(i - finder.searchPrefixWidth)))) { + if (finder!!.searchCharacter == c && + ( + (i - fromIndex) < finder.searchPrefixWidth || + finder.prefixValidator(string.codePointAt(i - finder.searchPrefixWidth)) + ) + ) { result.matchType = matchType result.start = max(0, i - finder.searchPrefixWidth) findEndOfPattern(string, result, finder.pattern) if (result.start + finder.searchPrefixWidth <= i + 1 && // The found result is actually triggered by the correct search character - result.end >= result.start) { // ...and we actually found a valid result + result.end >= result.start + ) { // ...and we actually found a valid result return result } } @@ -92,7 +100,8 @@ private fun findEndOfPattern(string: String, result: FindCharsResult, pattern: P FoundMatchType.TAG -> { if (isValidForTagPrefix(string.codePointAt(result.start))) { if (string[result.start] != '#' || - (string[result.start] == '#' && string[result.start + 1] == '#')) { + (string[result.start] == '#' && string[result.start + 1] == '#') + ) { ++result.start } } @@ -116,7 +125,7 @@ private fun findEndOfPattern(string: String, result: FindCharsResult, pattern: P } private fun getSpan(matchType: FoundMatchType, string: String, colour: Int, start: Int, end: Int): CharacterStyle { - return when(matchType) { + return when (matchType) { FoundMatchType.HTTP_URL -> NoUnderlineURLSpan(string.substring(start, end)) FoundMatchType.HTTPS_URL -> NoUnderlineURLSpan(string.substring(start, end)) else -> ForegroundColorSpan(colour) @@ -149,13 +158,15 @@ fun highlightSpans(text: Spannable, colour: Int) { private fun isWordCharacters(codePoint: Int): Boolean { return (codePoint in 0x30..0x39) || // [0-9] - (codePoint in 0x41..0x5a) || // [A-Z] - (codePoint == 0x5f) || // _ - (codePoint in 0x61..0x7a) // [a-z] + (codePoint in 0x41..0x5a) || // [A-Z] + (codePoint == 0x5f) || // _ + (codePoint in 0x61..0x7a) // [a-z] } private fun isValidForTagPrefix(codePoint: Int): Boolean { - return !(isWordCharacters(codePoint) || // \w + return !( + isWordCharacters(codePoint) || // \w (codePoint == 0x2f) || // / - (codePoint == 0x29)) // ) + (codePoint == 0x29) + ) // ) } diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt index ce19e00e8..9112919e2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusDisplayOptions.kt @@ -1,22 +1,22 @@ package com.keylesspalace.tusky.util data class StatusDisplayOptions( - @get:JvmName("animateAvatars") - val animateAvatars: Boolean, - @get:JvmName("mediaPreviewEnabled") - val mediaPreviewEnabled: Boolean, - @get:JvmName("useAbsoluteTime") - val useAbsoluteTime: Boolean, - @get:JvmName("showBotOverlay") - val showBotOverlay: Boolean, - @get:JvmName("useBlurhash") - val useBlurhash: Boolean, - @get:JvmName("cardViewMode") - val cardViewMode: CardViewMode, - @get:JvmName("confirmReblogs") - val confirmReblogs: Boolean, - @get:JvmName("hideStats") - val hideStats: Boolean, - @get:JvmName("animateEmojis") - val animateEmojis: Boolean -) \ No newline at end of file + @get:JvmName("animateAvatars") + val animateAvatars: Boolean, + @get:JvmName("mediaPreviewEnabled") + val mediaPreviewEnabled: Boolean, + @get:JvmName("useAbsoluteTime") + val useAbsoluteTime: Boolean, + @get:JvmName("showBotOverlay") + val showBotOverlay: Boolean, + @get:JvmName("useBlurhash") + val useBlurhash: Boolean, + @get:JvmName("cardViewMode") + val cardViewMode: CardViewMode, + @get:JvmName("confirmReblogs") + val confirmReblogs: Boolean, + @get:JvmName("hideStats") + val hideStats: Boolean, + @get:JvmName("animateEmojis") + val animateEmojis: Boolean +) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt index 822100290..00f6699da 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StatusViewHelper.kt @@ -34,7 +34,8 @@ import com.keylesspalace.tusky.viewdata.buildDescription import com.keylesspalace.tusky.viewdata.calculatePercent import java.text.NumberFormat import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale import kotlin.math.min class StatusViewHelper(private val itemView: View) { @@ -47,25 +48,28 @@ class StatusViewHelper(private val itemView: View) { private val longSdf = SimpleDateFormat("MM/dd HH:mm:ss", Locale.getDefault()) fun setMediasPreview( - statusDisplayOptions: StatusDisplayOptions, - attachments: List, - sensitive: Boolean, - previewListener: MediaPreviewListener, - showingContent: Boolean, - mediaPreviewHeight: Int) { + statusDisplayOptions: StatusDisplayOptions, + attachments: List, + sensitive: Boolean, + previewListener: MediaPreviewListener, + showingContent: Boolean, + mediaPreviewHeight: Int + ) { val context = itemView.context val mediaPreviews = arrayOf( - itemView.findViewById(R.id.status_media_preview_0), - itemView.findViewById(R.id.status_media_preview_1), - itemView.findViewById(R.id.status_media_preview_2), - itemView.findViewById(R.id.status_media_preview_3)) + itemView.findViewById(R.id.status_media_preview_0), + itemView.findViewById(R.id.status_media_preview_1), + itemView.findViewById(R.id.status_media_preview_2), + itemView.findViewById(R.id.status_media_preview_3) + ) val mediaOverlays = arrayOf( - itemView.findViewById(R.id.status_media_overlay_0), - itemView.findViewById(R.id.status_media_overlay_1), - itemView.findViewById(R.id.status_media_overlay_2), - itemView.findViewById(R.id.status_media_overlay_3)) + itemView.findViewById(R.id.status_media_overlay_0), + itemView.findViewById(R.id.status_media_overlay_1), + itemView.findViewById(R.id.status_media_overlay_2), + itemView.findViewById(R.id.status_media_overlay_3) + ) val sensitiveMediaWarning = itemView.findViewById(R.id.status_sensitive_media_warning) val sensitiveMediaShow = itemView.findViewById(R.id.status_sensitive_media_button) @@ -85,7 +89,6 @@ class StatusViewHelper(private val itemView: View) { return } - val mediaPreviewUnloaded = ColorDrawable(ThemeUtils.getColor(context, R.attr.colorBackgroundAccent)) val n = min(attachments.size, Status.MAX_MEDIA_ATTACHMENTS) @@ -105,9 +108,9 @@ class StatusViewHelper(private val itemView: View) { if (TextUtils.isEmpty(previewUrl)) { Glide.with(mediaPreviews[i]) - .load(mediaPreviewUnloaded) - .centerInside() - .into(mediaPreviews[i]) + .load(mediaPreviewUnloaded) + .centerInside() + .into(mediaPreviews[i]) } else { val placeholder = if (attachment.blurhash != null) decodeBlurHash(context, attachment.blurhash) @@ -119,19 +122,19 @@ class StatusViewHelper(private val itemView: View) { mediaPreviews[i].setFocalPoint(focus) Glide.with(mediaPreviews[i]) - .load(previewUrl) - .placeholder(placeholder) - .centerInside() - .addListener(mediaPreviews[i]) - .into(mediaPreviews[i]) + .load(previewUrl) + .placeholder(placeholder) + .centerInside() + .addListener(mediaPreviews[i]) + .into(mediaPreviews[i]) } else { mediaPreviews[i].removeFocalPoint() Glide.with(mediaPreviews[i]) - .load(previewUrl) - .placeholder(placeholder) - .centerInside() - .into(mediaPreviews[i]) + .load(previewUrl) + .placeholder(placeholder) + .centerInside() + .into(mediaPreviews[i]) } } else { mediaPreviews[i].removeFocalPoint() @@ -145,8 +148,9 @@ class StatusViewHelper(private val itemView: View) { } val type = attachment.type - if (showingContent - && (type === Attachment.Type.VIDEO) or (type === Attachment.Type.GIFV)) { + if (showingContent && + (type === Attachment.Type.VIDEO) or (type === Attachment.Type.GIFV) + ) { mediaOverlays[i].visibility = View.VISIBLE } else { mediaOverlays[i].visibility = View.GONE @@ -170,7 +174,7 @@ class StatusViewHelper(private val itemView: View) { sensitiveMediaWarning.visibility = View.GONE sensitiveMediaShow.visibility = View.GONE } else { - sensitiveMediaWarning.text = if (sensitive) { + sensitiveMediaWarning.text = if (sensitive) { context.getString(R.string.status_sensitive_media_title) } else { context.getString(R.string.status_media_hidden_title) @@ -182,15 +186,19 @@ class StatusViewHelper(private val itemView: View) { previewListener.onContentHiddenChange(false) v.visibility = View.GONE sensitiveMediaWarning.visibility = View.VISIBLE - setMediasPreview(statusDisplayOptions, attachments, sensitive, previewListener, - false, mediaPreviewHeight) + setMediasPreview( + statusDisplayOptions, attachments, sensitive, previewListener, + false, mediaPreviewHeight + ) } sensitiveMediaWarning.setOnClickListener { v -> previewListener.onContentHiddenChange(true) v.visibility = View.GONE sensitiveMediaShow.visibility = View.VISIBLE - setMediasPreview(statusDisplayOptions, attachments, sensitive, previewListener, - true, mediaPreviewHeight) + setMediasPreview( + statusDisplayOptions, attachments, sensitive, previewListener, + true, mediaPreviewHeight + ) } } @@ -200,8 +208,12 @@ class StatusViewHelper(private val itemView: View) { } } - private fun setMediaLabel(mediaLabel: TextView, attachments: List, sensitive: Boolean, - listener: MediaPreviewListener) { + private fun setMediaLabel( + mediaLabel: TextView, + attachments: List, + sensitive: Boolean, + listener: MediaPreviewListener + ) { if (attachments.isEmpty()) { mediaLabel.visibility = View.GONE return @@ -245,10 +257,11 @@ class StatusViewHelper(private val itemView: View) { fun setupPollReadonly(poll: PollViewData?, emojis: List, statusDisplayOptions: StatusDisplayOptions) { val pollResults = listOf( - itemView.findViewById(R.id.status_poll_option_result_0), - itemView.findViewById(R.id.status_poll_option_result_1), - itemView.findViewById(R.id.status_poll_option_result_2), - itemView.findViewById(R.id.status_poll_option_result_3)) + itemView.findViewById(R.id.status_poll_option_result_0), + itemView.findViewById(R.id.status_poll_option_result_1), + itemView.findViewById(R.id.status_poll_option_result_2), + itemView.findViewById(R.id.status_poll_option_result_3) + ) val pollDescription = itemView.findViewById(R.id.status_poll_description) @@ -260,7 +273,6 @@ class StatusViewHelper(private val itemView: View) { } else { val timestamp = System.currentTimeMillis() - setupPollResult(poll, emojis, pollResults, statusDisplayOptions.animateEmojis) pollDescription.visibility = View.VISIBLE @@ -271,7 +283,7 @@ class StatusViewHelper(private val itemView: View) { private fun getPollInfoText(timestamp: Long, poll: PollViewData, pollDescription: TextView, useAbsoluteTime: Boolean): CharSequence { val context = pollDescription.context - val votesText = if(poll.votersCount == null) { + val votesText = if (poll.votersCount == null) { val votes = NumberFormat.getNumberInstance().format(poll.votesCount.toLong()) context.resources.getQuantityString(R.plurals.poll_info_votes, poll.votesCount, votes) } else { @@ -291,7 +303,6 @@ class StatusViewHelper(private val itemView: View) { return context.getString(R.string.poll_info_format, votesText, pollDurationInfo) } - private fun setupPollResult(poll: PollViewData, emojis: List, pollResults: List, animateEmojis: Boolean) { val options = poll.options @@ -306,7 +317,6 @@ class StatusViewHelper(private val itemView: View) { val level = percent * 100 pollResults[i].background.level = level - } else { pollResults[i].visibility = View.GONE } @@ -329,4 +339,4 @@ class StatusViewHelper(private val itemView: View) { val COLLAPSE_INPUT_FILTER = arrayOf(SmartLengthInputFilter) val NO_INPUT_FILTER = arrayOfNulls(0) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/StringUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/StringUtils.kt index 23423b591..57b87f92c 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/StringUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/StringUtils.kt @@ -3,8 +3,7 @@ package com.keylesspalace.tusky.util import android.text.Spanned -import java.util.* - +import java.util.Random private const val POSSIBLE_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -30,7 +29,6 @@ fun String.inc(): String { return String(builder) } - /** * "Decrement" string so that during sorting it's smaller than [this]. */ @@ -97,4 +95,4 @@ fun Spanned.trimTrailingWhitespace(): Spanned { */ fun CharSequence.unicodeWrap(): String { return "\u2068${this}\u2069" -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt index 5fa80fcd4..e2db79c6e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt @@ -16,35 +16,35 @@ import kotlin.reflect.KProperty */ inline fun AppCompatActivity.viewBinding( - crossinline bindingInflater: (LayoutInflater) -> T + crossinline bindingInflater: (LayoutInflater) -> T ) = lazy(LazyThreadSafetyMode.NONE) { bindingInflater(layoutInflater) } class FragmentViewBindingDelegate( - val fragment: Fragment, - val viewBindingFactory: (View) -> T + val fragment: Fragment, + val viewBindingFactory: (View) -> T ) : ReadOnlyProperty { private var binding: T? = null init { fragment.lifecycle.addObserver( - object : DefaultLifecycleObserver { - override fun onCreate(owner: LifecycleOwner) { - fragment.viewLifecycleOwnerLiveData.observe( - fragment, - { t -> - t?.lifecycle?.addObserver( - object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { - binding = null - } - } - ) + object : DefaultLifecycleObserver { + override fun onCreate(owner: LifecycleOwner) { + fragment.viewLifecycleOwnerLiveData.observe( + fragment, + { t -> + t?.lifecycle?.addObserver( + object : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + binding = null + } } - ) - } + ) + } + ) } + } ) } @@ -64,4 +64,4 @@ class FragmentViewBindingDelegate( } fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = - FragmentViewBindingDelegate(this, viewBindingFactory) + FragmentViewBindingDelegate(this, viewBindingFactory) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt index 21e522f03..9b86db6d7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.kt @@ -20,8 +20,6 @@ import com.keylesspalace.tusky.entity.Notification import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.viewdata.NotificationViewData import com.keylesspalace.tusky.viewdata.StatusViewData -import com.keylesspalace.tusky.viewdata.toViewData -import java.util.* @JvmName("statusToViewData") fun Status.toViewData( @@ -50,4 +48,4 @@ fun Notification.toViewData( this.account, this.status?.toViewData(alwaysShowSensitiveData, alwaysOpenSpoiler) ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt index 389995ae2..07a9539f5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewExtensions.kt @@ -45,7 +45,8 @@ open class DefaultTextWatcher : TextWatcher { } inline fun EditText.onTextChanged( - crossinline callback: (s: CharSequence, start: Int, before: Int, count: Int) -> Unit) { + crossinline callback: (s: CharSequence, start: Int, before: Int, count: Int) -> Unit +) { addTextChangedListener(object : DefaultTextWatcher() { override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { callback(s, start, before, count) @@ -54,10 +55,11 @@ inline fun EditText.onTextChanged( } inline fun EditText.afterTextChanged( - crossinline callback: (s: Editable) -> Unit) { + crossinline callback: (s: Editable) -> Unit +) { addTextChangedListener(object : DefaultTextWatcher() { override fun afterTextChanged(s: Editable) { callback(s) } }) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt b/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt index 32a7d6b31..82860f9fc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/BackgroundMessageView.kt @@ -17,9 +17,9 @@ import com.keylesspalace.tusky.util.visible * Can show an image, text and button below them. */ class BackgroundMessageView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr) { private val binding = ViewBackgroundMessageBinding.inflate(LayoutInflater.from(context), this) @@ -38,13 +38,13 @@ class BackgroundMessageView @JvmOverloads constructor( * If [clickListener] is `null` then the button will be hidden. */ fun setup( - @DrawableRes imageRes: Int, - @StringRes messageRes: Int, - clickListener: ((v: View) -> Unit)? = null + @DrawableRes imageRes: Int, + @StringRes messageRes: Int, + clickListener: ((v: View) -> Unit)? = null ) { binding.messageTextView.setText(messageRes) binding.imageView.setImageResource(imageRes) binding.button.setOnClickListener(clickListener) binding.button.visible(clickListener != null) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/ConversationLineItemDecoration.kt b/app/src/main/java/com/keylesspalace/tusky/view/ConversationLineItemDecoration.kt index 7be8d06e5..c291bd019 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/ConversationLineItemDecoration.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/ConversationLineItemDecoration.kt @@ -18,10 +18,9 @@ package com.keylesspalace.tusky.view import android.content.Context import android.graphics.Canvas import android.graphics.drawable.Drawable -import androidx.recyclerview.widget.RecyclerView import android.view.View import androidx.core.content.ContextCompat - +import androidx.recyclerview.widget.RecyclerView import com.keylesspalace.tusky.R import com.keylesspalace.tusky.adapter.ThreadAdapter @@ -54,7 +53,8 @@ class ConversationLineItemDecoration(private val context: Context) : RecyclerVie } val below = adapter.getItem(position + 1) dividerBottom = if (below != null && current.id == below.status.inReplyToId && - adapter.detailedStatusPosition != position) { + adapter.detailedStatusPosition != position + ) { child.bottom } else { child.top + avatarMargin @@ -66,7 +66,6 @@ class ConversationLineItemDecoration(private val context: Context) : RecyclerVie divider.setBounds(canvas.width - dividerEnd, dividerTop, canvas.width - dividerStart, dividerBottom) } divider.draw(canvas) - } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/view/EmojiPicker.kt b/app/src/main/java/com/keylesspalace/tusky/view/EmojiPicker.kt index 09e648adf..0a9bfbf7e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/EmojiPicker.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/EmojiPicker.kt @@ -6,8 +6,8 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView class EmojiPicker @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null + context: Context, + attrs: AttributeSet? = null ) : RecyclerView(context, attrs) { init { diff --git a/app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt b/app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt index ec748e040..444e71dc1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/ExposedPlayPauseVideoView.kt @@ -5,10 +5,11 @@ import android.util.AttributeSet import android.widget.VideoView class ExposedPlayPauseVideoView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0) - : VideoView(context, attrs, defStyleAttr) { + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : + VideoView(context, attrs, defStyleAttr) { private var listener: PlayPauseListener? = null @@ -30,4 +31,4 @@ class ExposedPlayPauseVideoView @JvmOverloads constructor( fun onPlay() fun onPause() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/LicenseCard.kt b/app/src/main/java/com/keylesspalace/tusky/view/LicenseCard.kt index ad9ae52c2..116f01703 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/LicenseCard.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/LicenseCard.kt @@ -27,9 +27,9 @@ import com.keylesspalace.tusky.util.hide class LicenseCard @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 ) : MaterialCardView(context, attrs, defStyleAttr) { init { @@ -46,14 +46,11 @@ class LicenseCard binding.licenseCardName.text = name binding.licenseCardLicense.text = license - if(link.isNullOrBlank()) { + if (link.isNullOrBlank()) { binding.licenseCardLink.hide() } else { binding.licenseCardLink.text = link setOnClickListener { LinkHelper.openLink(link, context) } } - } - } - diff --git a/app/src/main/java/com/keylesspalace/tusky/view/MediaPreviewImageView.kt b/app/src/main/java/com/keylesspalace/tusky/view/MediaPreviewImageView.kt index 42bfc276c..8922fafd5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/MediaPreviewImageView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/MediaPreviewImageView.kt @@ -24,7 +24,6 @@ import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.target.Target import com.keylesspalace.tusky.entity.Attachment - import com.keylesspalace.tusky.util.FocalPointUtil /** @@ -40,10 +39,10 @@ import com.keylesspalace.tusky.util.FocalPointUtil */ class MediaPreviewImageView @JvmOverloads constructor( -context: Context, -attrs: AttributeSet? = null, -defStyleAttr: Int = 0 -) : AppCompatImageView(context, attrs, defStyleAttr),RequestListener { + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AppCompatImageView(context, attrs, defStyleAttr), RequestListener { private var focus: Attachment.Focus? = null private var focalMatrix: Matrix? = null @@ -106,7 +105,6 @@ defStyleAttr: Int = 0 return false } - /** * Called when the size of the view changes, it calls the FocalPointUtil to update the * matrix if we have a set focal point. It then reassigns the matrix to this imageView. @@ -120,9 +118,11 @@ defStyleAttr: Int = 0 private fun recalculateMatrix(width: Int, height: Int, drawable: Drawable?) { if (drawable != null && focus != null && focalMatrix != null) { scaleType = ScaleType.MATRIX - FocalPointUtil.updateFocalPointMatrix(width.toFloat(), height.toFloat(), - drawable.intrinsicWidth.toFloat(), drawable.intrinsicHeight.toFloat(), - focus as Attachment.Focus, focalMatrix as Matrix) + FocalPointUtil.updateFocalPointMatrix( + width.toFloat(), height.toFloat(), + drawable.intrinsicWidth.toFloat(), drawable.intrinsicHeight.toFloat(), + focus as Attachment.Focus, focalMatrix as Matrix + ) imageMatrix = focalMatrix } } diff --git a/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt b/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt index 022927e61..715fa6033 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/MuteAccountDialog.kt @@ -17,20 +17,20 @@ fun showMuteAccountDialog( binding.checkbox.isChecked = true AlertDialog.Builder(activity) - .setView(binding.root) - .setPositiveButton(android.R.string.ok) { _, _ -> - val durationValues = activity.resources.getIntArray(R.array.mute_duration_values) + .setView(binding.root) + .setPositiveButton(android.R.string.ok) { _, _ -> + val durationValues = activity.resources.getIntArray(R.array.mute_duration_values) - // workaround to make indefinite muting work with Mastodon 3.3.0 - // https://github.com/tuskyapp/Tusky/issues/2107 - val duration = if(binding.duration.selectedItemPosition == 0) { - null - } else { - durationValues[binding.duration.selectedItemPosition] - } - - onOk(binding.checkbox.isChecked, duration) + // workaround to make indefinite muting work with Mastodon 3.3.0 + // https://github.com/tuskyapp/Tusky/issues/2107 + val duration = if (binding.duration.selectedItemPosition == 0) { + null + } else { + durationValues[binding.duration.selectedItemPosition] } - .setNegativeButton(android.R.string.cancel, null) - .show() -} \ No newline at end of file + + onOk(binding.checkbox.isChecked, duration) + } + .setNegativeButton(android.R.string.cancel, null) + .show() +} diff --git a/app/src/main/java/com/keylesspalace/tusky/view/SquareImageView.kt b/app/src/main/java/com/keylesspalace/tusky/view/SquareImageView.kt index d0e730522..d7e753bbb 100644 --- a/app/src/main/java/com/keylesspalace/tusky/view/SquareImageView.kt +++ b/app/src/main/java/com/keylesspalace/tusky/view/SquareImageView.kt @@ -1,8 +1,8 @@ package com.keylesspalace.tusky.view import android.content.Context -import androidx.appcompat.widget.AppCompatImageView import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView /** * Created by charlag on 26/10/2017. @@ -13,12 +13,12 @@ class SquareImageView : AppCompatImageView { constructor(context: Context, attributes: AttributeSet) : super(context, attributes) - constructor(context: Context, attributes: AttributeSet, defStyleAttr: Int) - : super(context, attributes, defStyleAttr) + constructor(context: Context, attributes: AttributeSet, defStyleAttr: Int) : + super(context, attributes, defStyleAttr) override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val width = measuredWidth setMeasuredDimension(width, width) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt index f2e42e404..b0a8062f6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/AttachmentViewData.kt @@ -7,9 +7,9 @@ import kotlinx.parcelize.Parcelize @Parcelize data class AttachmentViewData( - val attachment: Attachment, - val statusId: String, - val statusUrl: String + val attachment: Attachment, + val statusId: String, + val statusUrl: String ) : Parcelable { companion object { @JvmStatic @@ -20,4 +20,4 @@ data class AttachmentViewData( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt index b6eefd713..0cd73bc98 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewdata/PollViewData.kt @@ -22,24 +22,24 @@ import androidx.core.text.parseAsHtml import com.keylesspalace.tusky.R import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.PollOption -import java.util.* +import java.util.Date import kotlin.math.roundToInt data class PollViewData( - val id: String, - val expiresAt: Date?, - val expired: Boolean, - val multiple: Boolean, - val votesCount: Int, - val votersCount: Int?, - val options: List, - var voted: Boolean + val id: String, + val expiresAt: Date?, + val expired: Boolean, + val multiple: Boolean, + val votesCount: Int, + val votersCount: Int?, + val options: List, + var voted: Boolean ) data class PollOptionViewData( - val title: String, - var votesCount: Int, - var selected: Boolean + val title: String, + var votesCount: Int, + var selected: Boolean ) fun calculatePercent(fraction: Int, totalVoters: Int?, totalVotes: Int): Int { @@ -60,21 +60,21 @@ fun buildDescription(title: String, percent: Int, context: Context): Spanned { fun Poll?.toViewData(): PollViewData? { if (this == null) return null return PollViewData( - id = id, - expiresAt = expiresAt, - expired = expired, - multiple = multiple, - votesCount = votesCount, - votersCount = votersCount, - options = options.map { it.toViewData() }, - voted = voted + id = id, + expiresAt = expiresAt, + expired = expired, + multiple = multiple, + votesCount = votesCount, + votersCount = votersCount, + options = options.map { it.toViewData() }, + voted = voted ) } fun PollOption.toViewData(): PollOptionViewData { return PollOptionViewData( - title = title, - votesCount = votesCount, - selected = false + title = title, + votesCount = votesCount, + selected = false ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt index 129959f92..5d8fcd32b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountViewModel.kt @@ -2,14 +2,25 @@ package com.keylesspalace.tusky.viewmodel import android.util.Log import androidx.lifecycle.MutableLiveData -import com.keylesspalace.tusky.appstore.* +import com.keylesspalace.tusky.appstore.BlockEvent +import com.keylesspalace.tusky.appstore.DomainMuteEvent +import com.keylesspalace.tusky.appstore.EventHub +import com.keylesspalace.tusky.appstore.MuteEvent +import com.keylesspalace.tusky.appstore.ProfileEditedEvent +import com.keylesspalace.tusky.appstore.UnfollowEvent import com.keylesspalace.tusky.db.AccountManager import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Field import com.keylesspalace.tusky.entity.IdentityProof import com.keylesspalace.tusky.entity.Relationship import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.Either +import com.keylesspalace.tusky.util.Error +import com.keylesspalace.tusky.util.Loading +import com.keylesspalace.tusky.util.Resource +import com.keylesspalace.tusky.util.RxAwareViewModel +import com.keylesspalace.tusky.util.Success +import com.keylesspalace.tusky.util.combineOptionalLiveData import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.Disposable import retrofit2.Call @@ -19,9 +30,9 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class AccountViewModel @Inject constructor( - private val mastodonApi: MastodonApi, - private val eventHub: EventHub, - private val accountManager: AccountManager + private val mastodonApi: MastodonApi, + private val eventHub: EventHub, + private val accountManager: AccountManager ) : RxAwareViewModel() { val accountData = MutableLiveData>() @@ -33,7 +44,7 @@ class AccountViewModel @Inject constructor( val accountFieldData = combineOptionalLiveData(accountData, identityProofData) { accountRes, identityProofs -> identityProofs.orEmpty().map { Either.Left(it) } - .plus(accountRes?.data?.fields.orEmpty().map { Either.Right(it) }) + .plus(accountRes?.data?.fields.orEmpty().map { Either.Right(it) }) } val isRefreshing = MutableLiveData().apply { value = false } @@ -46,11 +57,11 @@ class AccountViewModel @Inject constructor( init { eventHub.events - .subscribe { event -> - if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) { - accountData.postValue(Success(event.newProfileData)) - } - }.autoDispose() + .subscribe { event -> + if (event is ProfileEditedEvent && event.newProfileData.id == accountData.value?.data?.id) { + accountData.postValue(Success(event.newProfileData)) + } + }.autoDispose() } private fun obtainAccount(reload: Boolean = false) { @@ -59,17 +70,20 @@ class AccountViewModel @Inject constructor( accountData.postValue(Loading()) mastodonApi.account(accountId) - .subscribe({ account -> + .subscribe( + { account -> accountData.postValue(Success(account)) isDataLoading = false isRefreshing.postValue(false) - }, {t -> + }, + { t -> Log.w(TAG, "failed obtaining account", t) accountData.postValue(Error()) isDataLoading = false isRefreshing.postValue(false) - }) - .autoDispose() + } + ) + .autoDispose() } } @@ -79,13 +93,16 @@ class AccountViewModel @Inject constructor( relationshipData.postValue(Loading()) mastodonApi.relationships(listOf(accountId)) - .subscribe({ relationships -> + .subscribe( + { relationships -> relationshipData.postValue(Success(relationships[0])) - }, { t -> + }, + { t -> Log.w(TAG, "failed obtaining relationships", t) relationshipData.postValue(Error()) - }) - .autoDispose() + } + ) + .autoDispose() } } @@ -93,12 +110,15 @@ class AccountViewModel @Inject constructor( if (identityProofData.value == null || reload) { mastodonApi.identityProofs(accountId) - .subscribe({ proofs -> + .subscribe( + { proofs -> identityProofData.postValue(proofs) - }, { t -> + }, + { t -> Log.w(TAG, "failed obtaining identity proofs", t) - }) - .autoDispose() + } + ) + .autoDispose() } } @@ -126,11 +146,12 @@ class AccountViewModel @Inject constructor( fun unmuteAccount() { changeRelationship(RelationShipAction.UNMUTE) } - + fun changeSubscribingState() { val relationship = relationshipData.value?.data - if(relationship?.notifying == true /* Mastodon 3.3.0rc1 */ - || relationship?.subscribing == true /* Pleroma */ ) { + if (relationship?.notifying == true || /* Mastodon 3.3.0rc1 */ + relationship?.subscribing == true /* Pleroma */ + ) { changeRelationship(RelationShipAction.UNSUBSCRIBE) } else { changeRelationship(RelationShipAction.SUBSCRIBE) @@ -138,12 +159,12 @@ class AccountViewModel @Inject constructor( } fun blockDomain(instance: String) { - mastodonApi.blockDomain(instance).enqueue(object: Callback { + mastodonApi.blockDomain(instance).enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { eventHub.dispatch(DomainMuteEvent(instance)) val relation = relationshipData.value?.data - if(relation != null) { + if (relation != null) { relationshipData.postValue(Success(relation.copy(blockingDomain = true))) } } else { @@ -158,11 +179,11 @@ class AccountViewModel @Inject constructor( } fun unblockDomain(instance: String) { - mastodonApi.unblockDomain(instance).enqueue(object: Callback { + mastodonApi.unblockDomain(instance).enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { if (response.isSuccessful) { val relation = relationshipData.value?.data - if(relation != null) { + if (relation != null) { relationshipData.postValue(Success(relation.copy(blockingDomain = false))) } } else { @@ -209,12 +230,12 @@ class AccountViewModel @Inject constructor( RelationShipAction.MUTE -> relation.copy(muting = true) RelationShipAction.UNMUTE -> relation.copy(muting = false) RelationShipAction.SUBSCRIBE -> { - if(isMastodon) + if (isMastodon) relation.copy(notifying = true) else relation.copy(subscribing = true) } RelationShipAction.UNSUBSCRIBE -> { - if(isMastodon) + if (isMastodon) relation.copy(notifying = false) else relation.copy(subscribing = false) } @@ -230,50 +251,53 @@ class AccountViewModel @Inject constructor( RelationShipAction.MUTE -> mastodonApi.muteAccount(accountId, parameter ?: true, duration) RelationShipAction.UNMUTE -> mastodonApi.unmuteAccount(accountId) RelationShipAction.SUBSCRIBE -> { - if(isMastodon) + if (isMastodon) mastodonApi.followAccount(accountId, notify = true) else mastodonApi.subscribeAccount(accountId) } RelationShipAction.UNSUBSCRIBE -> { - if(isMastodon) + if (isMastodon) mastodonApi.followAccount(accountId, notify = false) else mastodonApi.unsubscribeAccount(accountId) } }.subscribe( - { relationship -> - relationshipData.postValue(Success(relationship)) + { relationship -> + relationshipData.postValue(Success(relationship)) - when (relationshipAction) { - RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(accountId)) - RelationShipAction.BLOCK -> eventHub.dispatch(BlockEvent(accountId)) - RelationShipAction.MUTE -> eventHub.dispatch(MuteEvent(accountId)) - else -> { - } + when (relationshipAction) { + RelationShipAction.UNFOLLOW -> eventHub.dispatch(UnfollowEvent(accountId)) + RelationShipAction.BLOCK -> eventHub.dispatch(BlockEvent(accountId)) + RelationShipAction.MUTE -> eventHub.dispatch(MuteEvent(accountId)) + else -> { } - }, - { - relationshipData.postValue(Error(relation)) } + }, + { + relationshipData.postValue(Error(relation)) + } ) - .autoDispose() + .autoDispose() } fun noteChanged(newNote: String) { noteSaved.postValue(false) noteDisposable?.dispose() noteDisposable = Single.timer(1500, TimeUnit.MILLISECONDS) - .flatMap { - mastodonApi.updateAccountNote(accountId, newNote) - } - .doOnSuccess { - noteSaved.postValue(true) - } - .delay(4, TimeUnit.SECONDS) - .subscribe({ + .flatMap { + mastodonApi.updateAccountNote(accountId, newNote) + } + .doOnSuccess { + noteSaved.postValue(true) + } + .delay(4, TimeUnit.SECONDS) + .subscribe( + { noteSaved.postValue(false) - }, { + }, + { Log.e(TAG, "Error updating note", it) - }) + } + ) } override fun onCleared() { diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountsInListViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountsInListViewModel.kt index e65decae6..b02c2ac09 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountsInListViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/AccountsInListViewModel.kt @@ -38,39 +38,52 @@ class AccountsInListViewModel @Inject constructor(private val api: MastodonApi) fun load(listId: String) { val state = _state.value!! if (state.accounts.isLeft() || state.accounts.asRight().isEmpty()) { - api.getAccountsInList(listId, 0).subscribe({ accounts -> - updateState { copy(accounts = Right(accounts)) } - }, { e -> - updateState { copy(accounts = Left(e)) } - }).autoDispose() + api.getAccountsInList(listId, 0).subscribe( + { accounts -> + updateState { copy(accounts = Right(accounts)) } + }, + { e -> + updateState { copy(accounts = Left(e)) } + } + ).autoDispose() } } fun addAccountToList(listId: String, account: Account) { api.addCountToList(listId, listOf(account.id)) - .subscribe({ + .subscribe( + { updateState { copy(accounts = accounts.map { it + account }) } - }, { - Log.i(javaClass.simpleName, - "Failed to add account to the list: ${account.username}") - }) - .autoDispose() + }, + { + Log.i( + javaClass.simpleName, + "Failed to add account to the list: ${account.username}" + ) + } + ) + .autoDispose() } fun deleteAccountFromList(listId: String, accountId: String) { api.deleteAccountFromList(listId, listOf(accountId)) - .subscribe({ + .subscribe( + { updateState { - copy(accounts = accounts.map { accounts -> - accounts.withoutFirstWhich { it.id == accountId } - }) + copy( + accounts = accounts.map { accounts -> + accounts.withoutFirstWhich { it.id == accountId } + } + ) } - }, { + }, + { Log.i(javaClass.simpleName, "Failed to remove account from thelist: $accountId") - }) - .autoDispose() + } + ) + .autoDispose() } fun search(query: String) { @@ -78,15 +91,18 @@ class AccountsInListViewModel @Inject constructor(private val api: MastodonApi) query.isEmpty() -> updateState { copy(searchResult = null) } query.isBlank() -> updateState { copy(searchResult = listOf()) } else -> api.searchAccounts(query, null, 10, true) - .subscribe({ result -> + .subscribe( + { result -> updateState { copy(searchResult = result) } - }, { + }, + { updateState { copy(searchResult = listOf()) } - }).autoDispose() + } + ).autoDispose() } } private inline fun updateState(crossinline fn: State.() -> State) { _state.onNext(fn(_state.value!!)) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt index f75dbad5d..a51fea0de 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/EditProfileViewModel.kt @@ -15,12 +15,12 @@ package com.keylesspalace.tusky.viewmodel -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel import android.content.Context import android.graphics.Bitmap import android.net.Uri import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel import com.keylesspalace.tusky.EditProfileActivity.Companion.AVATAR_SIZE import com.keylesspalace.tusky.EditProfileActivity.Companion.HEADER_HEIGHT import com.keylesspalace.tusky.EditProfileActivity.Companion.HEADER_WIDTH @@ -30,16 +30,22 @@ import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Instance import com.keylesspalace.tusky.entity.StringField import com.keylesspalace.tusky.network.MastodonApi -import com.keylesspalace.tusky.util.* +import com.keylesspalace.tusky.util.Error +import com.keylesspalace.tusky.util.IOUtils +import com.keylesspalace.tusky.util.Loading +import com.keylesspalace.tusky.util.Resource +import com.keylesspalace.tusky.util.Success +import com.keylesspalace.tusky.util.getSampledBitmap +import com.keylesspalace.tusky.util.randomAlphanumericString import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.addTo import io.reactivex.rxjava3.schedulers.Schedulers import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.RequestBody.Companion.asRequestBody -import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.MultipartBody import okhttp3.RequestBody +import okhttp3.RequestBody.Companion.asRequestBody +import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONException import org.json.JSONObject import retrofit2.Call @@ -56,10 +62,10 @@ private const val AVATAR_FILE_NAME = "avatar.png" private const val TAG = "EditProfileViewModel" -class EditProfileViewModel @Inject constructor( - private val mastodonApi: MastodonApi, - private val eventHub: EventHub -): ViewModel() { +class EditProfileViewModel @Inject constructor( + private val mastodonApi: MastodonApi, + private val eventHub: EventHub +) : ViewModel() { val profileData = MutableLiveData>() val avatarData = MutableLiveData>() @@ -72,21 +78,21 @@ class EditProfileViewModel @Inject constructor( private val disposeables = CompositeDisposable() fun obtainProfile() { - if(profileData.value == null || profileData.value is Error) { + if (profileData.value == null || profileData.value is Error) { profileData.postValue(Loading()) mastodonApi.accountVerifyCredentials() - .subscribe( - {profile -> - oldProfileData = profile - profileData.postValue(Success(profile)) - }, - { - profileData.postValue(Error()) - }) - .addTo(disposeables) - + .subscribe( + { profile -> + oldProfileData = profile + profileData.postValue(Success(profile)) + }, + { + profileData.postValue(Error()) + } + ) + .addTo(disposeables) } } @@ -102,12 +108,14 @@ class EditProfileViewModel @Inject constructor( resizeImage(uri, context, HEADER_WIDTH, HEADER_HEIGHT, cacheFile, headerData) } - private fun resizeImage(uri: Uri, - context: Context, - resizeWidth: Int, - resizeHeight: Int, - cacheFile: File, - imageLiveData: MutableLiveData>) { + private fun resizeImage( + uri: Uri, + context: Context, + resizeWidth: Int, + resizeHeight: Int, + cacheFile: File, + imageLiveData: MutableLiveData> + ) { Single.fromCallable { val contentResolver = context.contentResolver @@ -117,13 +125,13 @@ class EditProfileViewModel @Inject constructor( throw Exception() } - //dont upscale image if its smaller than the desired size + // dont upscale image if its smaller than the desired size val bitmap = - if (sourceBitmap.width <= resizeWidth && sourceBitmap.height <= resizeHeight) { - sourceBitmap - } else { - Bitmap.createScaledBitmap(sourceBitmap, resizeWidth, resizeHeight, true) - } + if (sourceBitmap.width <= resizeWidth && sourceBitmap.height <= resizeHeight) { + sourceBitmap + } else { + Bitmap.createScaledBitmap(sourceBitmap, resizeWidth, resizeHeight, true) + } if (!saveBitmapToFile(bitmap, cacheFile)) { throw Exception() @@ -131,17 +139,20 @@ class EditProfileViewModel @Inject constructor( bitmap }.subscribeOn(Schedulers.io()) - .subscribe({ + .subscribe( + { imageLiveData.postValue(Success(it)) - }, { + }, + { imageLiveData.postValue(Error()) - }) - .addTo(disposeables) + } + ) + .addTo(disposeables) } fun save(newDisplayName: String, newNote: String, newLocked: Boolean, newFields: List, context: Context) { - if(saveData.value is Loading || profileData.value !is Success) { + if (saveData.value is Loading || profileData.value !is Success) { return } @@ -184,21 +195,23 @@ class EditProfileViewModel @Inject constructor( val field3 = calculateFieldToUpdate(newFields.getOrNull(2), fieldsUnchanged) val field4 = calculateFieldToUpdate(newFields.getOrNull(3), fieldsUnchanged) - if (displayName == null && note == null && locked == null && avatar == null && header == null - && field1 == null && field2 == null && field3 == null && field4 == null) { + if (displayName == null && note == null && locked == null && avatar == null && header == null && + field1 == null && field2 == null && field3 == null && field4 == null + ) { /** if nothing has changed, there is no need to make a network request */ saveData.postValue(Success()) return } - mastodonApi.accountUpdateCredentials(displayName, note, locked, avatar, header, - field1?.first, field1?.second, field2?.first, field2?.second, field3?.first, field3?.second, field4?.first, field4?.second + mastodonApi.accountUpdateCredentials( + displayName, note, locked, avatar, header, + field1?.first, field1?.second, field2?.first, field2?.second, field3?.first, field3?.second, field4?.first, field4?.second ).enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { val newProfileData = response.body() if (!response.isSuccessful || newProfileData == null) { val errorResponse = response.errorBody()?.string() - val errorMsg = if(!errorResponse.isNullOrBlank()) { + val errorMsg = if (!errorResponse.isNullOrBlank()) { try { JSONObject(errorResponse).optString("error", null) } catch (e: JSONException) { @@ -218,29 +231,28 @@ class EditProfileViewModel @Inject constructor( saveData.postValue(Error()) } }) - } // cache activity state for rotation change fun updateProfile(newDisplayName: String, newNote: String, newLocked: Boolean, newFields: List) { - if(profileData.value is Success) { + if (profileData.value is Success) { val newProfileSource = profileData.value?.data?.source?.copy(note = newNote, fields = newFields) - val newProfile = profileData.value?.data?.copy(displayName = newDisplayName, - locked = newLocked, source = newProfileSource) + val newProfile = profileData.value?.data?.copy( + displayName = newDisplayName, + locked = newLocked, source = newProfileSource + ) profileData.postValue(Success(newProfile)) } - } - private fun calculateFieldToUpdate(newField: StringField?, fieldsUnchanged: Boolean): Pair? { - if(fieldsUnchanged || newField == null) { + if (fieldsUnchanged || newField == null) { return null } return Pair( - newField.name.toRequestBody(MultipartBody.FORM), - newField.value.toRequestBody(MultipartBody.FORM) + newField.name.toRequestBody(MultipartBody.FORM), + newField.value.toRequestBody(MultipartBody.FORM) ) } @@ -270,19 +282,18 @@ class EditProfileViewModel @Inject constructor( } fun obtainInstance() { - if(instanceData.value == null || instanceData.value is Error) { + if (instanceData.value == null || instanceData.value is Error) { instanceData.postValue(Loading()) mastodonApi.getInstance().subscribe( - { instance -> - instanceData.postValue(Success(instance)) - }, - { - instanceData.postValue(Error()) - }) - .addTo(disposeables) + { instance -> + instanceData.postValue(Success(instance)) + }, + { + instanceData.postValue(Error()) + } + ) + .addTo(disposeables) } } - - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt index 650636e61..682631555 100644 --- a/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/viewmodel/ListsViewModel.kt @@ -55,49 +55,63 @@ internal class ListsViewModel @Inject constructor(private val api: MastodonApi) copy(loadingState = LoadingState.LOADING) } - api.getLists().subscribe({ lists -> - updateState { - copy( + api.getLists().subscribe( + { lists -> + updateState { + copy( lists = lists, loadingState = LoadingState.LOADED - ) + ) + } + }, + { err -> + updateState { + copy( + loadingState = if (err is IOException || err is ConnectException) + LoadingState.ERROR_NETWORK else LoadingState.ERROR_OTHER + ) + } } - }, { err -> - updateState { - copy(loadingState = if (err is IOException || err is ConnectException) - LoadingState.ERROR_NETWORK else LoadingState.ERROR_OTHER) - } - }).autoDispose() + ).autoDispose() } fun createNewList(listName: String) { - api.createList(listName).subscribe({ list -> - updateState { - copy(lists = lists + list) + api.createList(listName).subscribe( + { list -> + updateState { + copy(lists = lists + list) + } + }, + { + sendEvent(Event.CREATE_ERROR) } - }, { - sendEvent(Event.CREATE_ERROR) - }).autoDispose() + ).autoDispose() } fun renameList(listId: String, listName: String) { - api.updateList(listId, listName).subscribe({ list -> - updateState { - copy(lists = lists.replacedFirstWhich(list) { it.id == listId }) + api.updateList(listId, listName).subscribe( + { list -> + updateState { + copy(lists = lists.replacedFirstWhich(list) { it.id == listId }) + } + }, + { + sendEvent(Event.RENAME_ERROR) } - }, { - sendEvent(Event.RENAME_ERROR) - }).autoDispose() + ).autoDispose() } fun deleteList(listId: String) { - api.deleteList(listId).subscribe({ - updateState { - copy(lists = lists.withoutFirstWhich { it.id == listId }) + api.deleteList(listId).subscribe( + { + updateState { + copy(lists = lists.withoutFirstWhich { it.id == listId }) + } + }, + { + sendEvent(Event.DELETE_ERROR) } - }, { - sendEvent(Event.DELETE_ERROR) - }).autoDispose() + ).autoDispose() } private inline fun updateState(crossinline fn: State.() -> State) { diff --git a/app/src/test/java/android/text/FakeSpannableString.kt b/app/src/test/java/android/text/SpannableString.kt similarity index 99% rename from app/src/test/java/android/text/FakeSpannableString.kt rename to app/src/test/java/android/text/SpannableString.kt index c4e4e4ccd..dc8cd831f 100644 --- a/app/src/test/java/android/text/FakeSpannableString.kt +++ b/app/src/test/java/android/text/SpannableString.kt @@ -23,7 +23,6 @@ class SpannableString(private val text: CharSequence) : Spannable { override val length: Int get() = text.length - override fun nextSpanTransition(start: Int, limit: Int, type: Class<*>?): Int { throw NotImplementedError() } @@ -47,4 +46,4 @@ class SpannableString(private val text: CharSequence) : Spannable { override fun getSpanStart(tag: Any?): Int { throw NotImplementedError() } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt index 3b72d2ae2..5c77de765 100644 --- a/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/BottomSheetActivityTest.kt @@ -34,17 +34,20 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.mockito.ArgumentMatchers -import org.mockito.Mockito.* -import java.util.* +import org.mockito.Mockito.`when` +import org.mockito.Mockito.eq +import org.mockito.Mockito.mock +import java.util.ArrayList +import java.util.Collections +import java.util.Date import java.util.concurrent.TimeUnit - class BottomSheetActivityTest { @get:Rule val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule() - private lateinit var activity : FakeBottomSheetActivity + private lateinit var activity: FakeBottomSheetActivity private lateinit var apiMock: MastodonApi private val accountQuery = "http://mastodon.foo.bar/@User" private val statusQuery = "http://mastodon.foo.bar/@User/345678" @@ -52,51 +55,51 @@ class BottomSheetActivityTest { private val emptyCallback = Single.just(SearchResult(emptyList(), emptyList(), emptyList())) private val testScheduler = TestScheduler() - private val account = Account ( - "1", - "admin", - "admin", - "Ad Min", - SpannedString(""), - "http://mastodon.foo.bar", - "", - "", - false, - 0, - 0, - 0, - null, - false, - emptyList(), - emptyList() + private val account = Account( + "1", + "admin", + "admin", + "Ad Min", + SpannedString(""), + "http://mastodon.foo.bar", + "", + "", + false, + 0, + 0, + 0, + null, + false, + emptyList(), + emptyList() ) private val accountSingle = Single.just(SearchResult(listOf(account), emptyList(), emptyList())) private val status = Status( - "1", - statusQuery, - account, - null, - null, - null, - SpannedString("omgwat"), - Date(), - Collections.emptyList(), - 0, - 0, - false, - false, - false, - false, - "", - Status.Visibility.PUBLIC, - ArrayList(), - listOf(), - null, - pinned = false, - muted = false, - poll = null, - card = null + "1", + statusQuery, + account, + null, + null, + null, + SpannedString("omgwat"), + Date(), + Collections.emptyList(), + 0, + 0, + false, + false, + false, + false, + "", + Status.Visibility.PUBLIC, + ArrayList(), + listOf(), + null, + pinned = false, + muted = false, + poll = null, + card = null ) private val statusSingle = Single.just(SearchResult(emptyList(), listOf(status), emptyList())) @@ -119,7 +122,7 @@ class BottomSheetActivityTest { companion object { @Parameterized.Parameters(name = "match_{0}") @JvmStatic - fun data() : Iterable { + fun data(): Iterable { return listOf( arrayOf("https://mastodon.foo.bar/@User", true), arrayOf("http://mastodon.foo.bar/@abc123", true), diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt index 6bbdc1897..156a6b413 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeActivityTest.kt @@ -25,7 +25,11 @@ import com.keylesspalace.tusky.components.compose.ComposeViewModel import com.keylesspalace.tusky.components.compose.DEFAULT_CHARACTER_LIMIT import com.keylesspalace.tusky.components.compose.MediaUploader import com.keylesspalace.tusky.components.drafts.DraftHelper -import com.keylesspalace.tusky.db.* +import com.keylesspalace.tusky.db.AccountEntity +import com.keylesspalace.tusky.db.AccountManager +import com.keylesspalace.tusky.db.AppDatabase +import com.keylesspalace.tusky.db.InstanceDao +import com.keylesspalace.tusky.db.InstanceEntity import com.keylesspalace.tusky.di.ViewModelFactory import com.keylesspalace.tusky.entity.Account import com.keylesspalace.tusky.entity.Instance @@ -34,7 +38,9 @@ import com.keylesspalace.tusky.service.ServiceClient import com.nhaarman.mockitokotlin2.any import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.SingleObserver -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -59,25 +65,25 @@ class ComposeActivityTest { private val instanceDomain = "example.domain" private val account = AccountEntity( - id = 1, - domain = instanceDomain, - accessToken = "token", - isActive = true, - accountId = "1", - username = "username", - displayName = "Display Name", - profilePictureUrl = "", - notificationsEnabled = true, - notificationsMentioned = true, - notificationsFollowed = true, - notificationsFollowRequested = false, - notificationsReblogged = true, - notificationsFavorited = true, - notificationSound = true, - notificationVibration = true, - notificationLight = true + id = 1, + domain = instanceDomain, + accessToken = "token", + isActive = true, + accountId = "1", + username = "username", + displayName = "Display Name", + profilePictureUrl = "", + notificationsEnabled = true, + notificationsMentioned = true, + notificationsFollowed = true, + notificationsFollowRequested = false, + notificationsReblogged = true, + notificationsFavorited = true, + notificationSound = true, + notificationVibration = true, + notificationLight = true ) - private var instanceResponseCallback: (()->Instance)? = null + private var instanceResponseCallback: (() -> Instance)? = null private var composeOptions: ComposeActivity.ComposeOptions? = null @Before @@ -90,7 +96,7 @@ class ComposeActivityTest { apiMock = mock(MastodonApi::class.java) `when`(apiMock.getCustomEmojis()).thenReturn(Single.just(emptyList())) - `when`(apiMock.getInstance()).thenReturn(object: Single() { + `when`(apiMock.getInstance()).thenReturn(object : Single() { override fun subscribeActual(observer: SingleObserver) { val instance = instanceResponseCallback?.invoke() if (instance == null) { @@ -103,19 +109,19 @@ class ComposeActivityTest { val instanceDaoMock = mock(InstanceDao::class.java) `when`(instanceDaoMock.loadMetadataForInstance(any())).thenReturn( - Single.just(InstanceEntity(instanceDomain, emptyList(),null, null, null, null)) + Single.just(InstanceEntity(instanceDomain, emptyList(), null, null, null, null)) ) val dbMock = mock(AppDatabase::class.java) `when`(dbMock.instanceDao()).thenReturn(instanceDaoMock) val viewModel = ComposeViewModel( - apiMock, - accountManagerMock, - mock(MediaUploader::class.java), - mock(ServiceClient::class.java), - mock(DraftHelper::class.java), - dbMock + apiMock, + accountManagerMock, + mock(MediaUploader::class.java), + mock(ServiceClient::class.java), + mock(DraftHelper::class.java), + dbMock ) activity.intent = Intent(activity, ComposeActivity::class.java).apply { putExtra(ComposeActivity.COMPOSE_OPTIONS_EXTRA, composeOptions) @@ -381,41 +387,38 @@ class ComposeActivityTest { activity.findViewById(R.id.composeEditField).setText(text ?: "Some text") } - private fun getInstanceWithMaximumTootCharacters(maximumTootCharacters: Int?): Instance - { + private fun getInstanceWithMaximumTootCharacters(maximumTootCharacters: Int?): Instance { return Instance( + "https://example.token", + "Example dot Token", + "Example instance for testing", + "admin@example.token", + "2.6.3", + HashMap(), + null, + null, + listOf("en"), + Account( + "1", + "admin", + "admin", + "admin", + SpannedString(""), "https://example.token", - "Example dot Token", - "Example instance for testing", - "admin@example.token", - "2.6.3", - HashMap(), + "", + "", + false, + 0, + 0, + 0, null, - null, - listOf("en"), - Account( - "1", - "admin", - "admin", - "admin", - SpannedString(""), - "https://example.token", - "", - "", - false, - 0, - 0, - 0, - null, - false, - emptyList(), - emptyList() - ), - maximumTootCharacters, - null, - null + false, + emptyList(), + emptyList() + ), + maximumTootCharacters, + null, + null ) } - } - diff --git a/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt b/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt index b603a4a7c..e203dde27 100644 --- a/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/ComposeTokenizerTest.kt @@ -22,64 +22,66 @@ import org.junit.runner.RunWith import org.junit.runners.Parameterized @RunWith(Parameterized::class) -class ComposeTokenizerTest(private val text: CharSequence, - private val expectedStartIndex: Int, - private val expectedEndIndex: Int) { +class ComposeTokenizerTest( + private val text: CharSequence, + private val expectedStartIndex: Int, + private val expectedEndIndex: Int +) { companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic fun data(): Iterable { return listOf( - arrayOf("@mention", 0, 8), - arrayOf("@ment10n", 0, 8), - arrayOf("@ment10n_", 0, 9), - arrayOf("@ment10n_n", 0, 10), - arrayOf("@ment10n_9", 0, 10), - arrayOf(" @mention", 1, 9), - arrayOf(" @ment10n", 1, 9), - arrayOf(" @ment10n_", 1, 10), - arrayOf(" @ment10n_ @", 11, 12), - arrayOf(" @ment10n_ @ment20n", 11, 19), - arrayOf(" @ment10n_ @ment20n_", 11, 20), - arrayOf(" @ment10n_ @ment20n_n", 11, 21), - arrayOf(" @ment10n_ @ment20n_9", 11, 21), - arrayOf(" @ment10n-", 1, 10), - arrayOf(" @ment10n- @", 11, 12), - arrayOf(" @ment10n- @ment20n", 11, 19), - arrayOf(" @ment10n- @ment20n-", 11, 20), - arrayOf(" @ment10n- @ment20n-n", 11, 21), - arrayOf(" @ment10n- @ment20n-9", 11, 21), - arrayOf("@ment10n@l0calhost", 0, 18), - arrayOf(" @ment10n@l0calhost", 1, 19), - arrayOf(" @ment10n_@l0calhost", 1, 20), - arrayOf(" @ment10n-@l0calhost", 1, 20), - arrayOf(" @ment10n_@l0calhost @ment20n@husky", 21, 35), - arrayOf(" @ment10n_@l0calhost @ment20n_@husky", 21, 36), - arrayOf(" @ment10n-@l0calhost @ment20n-@husky", 21, 36), - arrayOf(" @m@localhost", 1, 13), - arrayOf(" @m@localhost @a@localhost", 14, 26), - arrayOf("@m@", 0, 3), - arrayOf(" @m@ @a@asdf", 5, 12), - arrayOf(" @m@ @a@", 5, 8), - arrayOf(" @m@ @a@a", 5, 9), - arrayOf(" @m@a @a@m", 6, 10), - arrayOf("@m@m@", 5, 5), - arrayOf("#tusky@husky", 12, 12), - arrayOf(":tusky@husky", 12, 12), - arrayOf("mention", 7, 7), - arrayOf("ment10n", 7, 7), - arrayOf("mentio_", 7, 7), - arrayOf("#tusky", 0, 6), - arrayOf("#@tusky", 7, 7), - arrayOf("@#tusky", 7, 7), - arrayOf(" @#tusky", 8, 8), - arrayOf(":mastodon", 0, 9), - arrayOf(":@mastodon", 10, 10), - arrayOf("@:mastodon", 10, 10), - arrayOf(" @:mastodon", 11, 11), - arrayOf("#@:mastodon", 11, 11), - arrayOf(" #@:mastodon", 12, 12) + arrayOf("@mention", 0, 8), + arrayOf("@ment10n", 0, 8), + arrayOf("@ment10n_", 0, 9), + arrayOf("@ment10n_n", 0, 10), + arrayOf("@ment10n_9", 0, 10), + arrayOf(" @mention", 1, 9), + arrayOf(" @ment10n", 1, 9), + arrayOf(" @ment10n_", 1, 10), + arrayOf(" @ment10n_ @", 11, 12), + arrayOf(" @ment10n_ @ment20n", 11, 19), + arrayOf(" @ment10n_ @ment20n_", 11, 20), + arrayOf(" @ment10n_ @ment20n_n", 11, 21), + arrayOf(" @ment10n_ @ment20n_9", 11, 21), + arrayOf(" @ment10n-", 1, 10), + arrayOf(" @ment10n- @", 11, 12), + arrayOf(" @ment10n- @ment20n", 11, 19), + arrayOf(" @ment10n- @ment20n-", 11, 20), + arrayOf(" @ment10n- @ment20n-n", 11, 21), + arrayOf(" @ment10n- @ment20n-9", 11, 21), + arrayOf("@ment10n@l0calhost", 0, 18), + arrayOf(" @ment10n@l0calhost", 1, 19), + arrayOf(" @ment10n_@l0calhost", 1, 20), + arrayOf(" @ment10n-@l0calhost", 1, 20), + arrayOf(" @ment10n_@l0calhost @ment20n@husky", 21, 35), + arrayOf(" @ment10n_@l0calhost @ment20n_@husky", 21, 36), + arrayOf(" @ment10n-@l0calhost @ment20n-@husky", 21, 36), + arrayOf(" @m@localhost", 1, 13), + arrayOf(" @m@localhost @a@localhost", 14, 26), + arrayOf("@m@", 0, 3), + arrayOf(" @m@ @a@asdf", 5, 12), + arrayOf(" @m@ @a@", 5, 8), + arrayOf(" @m@ @a@a", 5, 9), + arrayOf(" @m@a @a@m", 6, 10), + arrayOf("@m@m@", 5, 5), + arrayOf("#tusky@husky", 12, 12), + arrayOf(":tusky@husky", 12, 12), + arrayOf("mention", 7, 7), + arrayOf("ment10n", 7, 7), + arrayOf("mentio_", 7, 7), + arrayOf("#tusky", 0, 6), + arrayOf("#@tusky", 7, 7), + arrayOf("@#tusky", 7, 7), + arrayOf(" @#tusky", 8, 8), + arrayOf(":mastodon", 0, 9), + arrayOf(":@mastodon", 10, 10), + arrayOf("@:mastodon", 10, 10), + arrayOf(" @:mastodon", 11, 11), + arrayOf("#@:mastodon", 11, 11), + arrayOf(" #@:mastodon", 12, 12) ) } } @@ -91,4 +93,4 @@ class ComposeTokenizerTest(private val text: CharSequence, Assert.assertEquals(expectedStartIndex, tokenizer.findTokenStart(text, text.length)) Assert.assertEquals(expectedEndIndex, tokenizer.findTokenEnd(text, text.length)) } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt index 71f0377fd..1c6a19c68 100644 --- a/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/FilterTest.kt @@ -7,17 +7,14 @@ import com.keylesspalace.tusky.entity.Poll import com.keylesspalace.tusky.entity.PollOption import com.keylesspalace.tusky.entity.Status import com.keylesspalace.tusky.network.FilterModel -import com.keylesspalace.tusky.network.MastodonApi -import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.mock -import io.reactivex.rxjava3.core.Single import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.annotation.Config -import java.util.* +import java.util.Date @Config(sdk = [28]) @RunWith(AndroidJUnit4::class) @@ -182,5 +179,4 @@ class FilterTest { card = null ) } - -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/FocalPointUtilTest.kt b/app/src/test/java/com/keylesspalace/tusky/FocalPointUtilTest.kt index f3094ee93..444da0619 100644 --- a/app/src/test/java/com/keylesspalace/tusky/FocalPointUtilTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/FocalPointUtilTest.kt @@ -17,8 +17,8 @@ package com.keylesspalace.tusky import com.keylesspalace.tusky.util.FocalPointUtil import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test class FocalPointUtilTest { @@ -45,66 +45,112 @@ class FocalPointUtilTest { // isVerticalCrop tests @Test fun isVerticalCropTest() { - assertTrue(FocalPointUtil.isVerticalCrop(2f, 1f, - 1f, 2f)) + assertTrue( + FocalPointUtil.isVerticalCrop( + 2f, 1f, + 1f, 2f + ) + ) } @Test fun isHorizontalCropTest() { - assertFalse(FocalPointUtil.isVerticalCrop(1f, 2f, - 2f,1f)) + assertFalse( + FocalPointUtil.isVerticalCrop( + 1f, 2f, + 2f, 1f + ) + ) } @Test fun isPerfectFitTest() { // Doesn't matter what it returns, just check it doesn't crash - FocalPointUtil.isVerticalCrop(3f, 1f, - 6f, 2f) + FocalPointUtil.isVerticalCrop( + 3f, 1f, + 6f, 2f + ) } // calculateScaling tests @Test fun perfectFitScaleDownTest() { - assertEquals(FocalPointUtil.calculateScaling(2f, 5f, - 5f, 12.5f), 0.4f, eps) + assertEquals( + FocalPointUtil.calculateScaling( + 2f, 5f, + 5f, 12.5f + ), + 0.4f, eps + ) } @Test fun perfectFitScaleUpTest() { - assertEquals(FocalPointUtil.calculateScaling(2f, 5f, - 1f, 2.5f), 2f, eps) + assertEquals( + FocalPointUtil.calculateScaling( + 2f, 5f, + 1f, 2.5f + ), + 2f, eps + ) } @Test fun verticalCropScaleUpTest() { - assertEquals(FocalPointUtil.calculateScaling(2f, 1f, - 1f, 2f), 2f, eps) + assertEquals( + FocalPointUtil.calculateScaling( + 2f, 1f, + 1f, 2f + ), + 2f, eps + ) } @Test fun verticalCropScaleDownTest() { - assertEquals(FocalPointUtil.calculateScaling(4f, 3f, - 8f, 24f), 0.5f, eps) + assertEquals( + FocalPointUtil.calculateScaling( + 4f, 3f, + 8f, 24f + ), + 0.5f, eps + ) } @Test fun horizontalCropScaleUpTest() { - assertEquals(FocalPointUtil.calculateScaling(1f, 2f, - 2f, 1f), 2f, eps) + assertEquals( + FocalPointUtil.calculateScaling( + 1f, 2f, + 2f, 1f + ), + 2f, eps + ) } @Test fun horizontalCropScaleDownTest() { - assertEquals(FocalPointUtil.calculateScaling(3f, 4f, - 24f, 8f), 0.5f, eps) + assertEquals( + FocalPointUtil.calculateScaling( + 3f, 4f, + 24f, 8f + ), + 0.5f, eps + ) } // focalOffset tests @Test fun toLowFocalOffsetTest() { - assertEquals(FocalPointUtil.focalOffset(2f, 8f, 1f, 0.05f), - 0f, eps) + assertEquals( + FocalPointUtil.focalOffset(2f, 8f, 1f, 0.05f), + 0f, eps + ) } @Test fun toHighFocalOffsetTest() { - assertEquals(FocalPointUtil.focalOffset(2f, 4f, 2f,0.95f), - -6f, eps) + assertEquals( + FocalPointUtil.focalOffset(2f, 4f, 2f, 0.95f), + -6f, eps + ) } @Test fun possibleFocalOffsetTest() { - assertEquals(FocalPointUtil.focalOffset(2f, 4f, 2f,0.7f), - -4.6f, eps) + assertEquals( + FocalPointUtil.focalOffset(2f, 4f, 2f, 0.7f), + -4.6f, eps + ) } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/SpanUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/SpanUtilsTest.kt index 68bdaaa97..213405603 100644 --- a/app/src/test/java/com/keylesspalace/tusky/SpanUtilsTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/SpanUtilsTest.kt @@ -36,10 +36,10 @@ class SpanUtilsTest { @JvmStatic fun data(): Iterable { return listOf( - "@mention", - "#tag", - "https://thr.ee/meh?foo=bar&wat=@at#hmm", - "http://thr.ee/meh?foo=bar&wat=@at#hmm" + "@mention", + "#tag", + "https://thr.ee/meh?foo=bar&wat=@at#hmm", + "http://thr.ee/meh?foo=bar&wat=@at#hmm" ) } } @@ -94,21 +94,23 @@ class SpanUtilsTest { } @RunWith(Parameterized::class) - class HighlightingTestsForTag(private val text: String, - private val expectedStartIndex: Int, - private val expectedEndIndex: Int) { + class HighlightingTestsForTag( + private val text: String, + private val expectedStartIndex: Int, + private val expectedEndIndex: Int + ) { companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic fun data(): Iterable { return listOf( - arrayOf("#test", 0, 5), - arrayOf(" #AfterSpace", 1, 12), - arrayOf("#BeforeSpace ", 0, 12), - arrayOf("@#after_at", 1, 10), - arrayOf("あいうえお#after_hiragana", 5, 20), - arrayOf("##DoubleHash", 1, 12), - arrayOf("###TripleHash", 2, 13) + arrayOf("#test", 0, 5), + arrayOf(" #AfterSpace", 1, 12), + arrayOf("#BeforeSpace ", 0, 12), + arrayOf("@#after_at", 1, 10), + arrayOf("あいうえお#after_hiragana", 5, 20), + arrayOf("##DoubleHash", 1, 12), + arrayOf("###TripleHash", 2, 13) ) } } @@ -133,13 +135,13 @@ class SpanUtilsTest { } override fun getSpans(start: Int, end: Int, type: Class): Array { - return spans.filter { it.start >= start && it.end <= end && type.isInstance(it)} - .map { it.span } - .toTypedArray() as Array + return spans.filter { it.start >= start && it.end <= end && type.isInstance(it) } + .map { it.span } + .toTypedArray() as Array } override fun removeSpan(what: Any?) { - spans.removeIf { span -> span.span == what} + spans.removeIf { span -> span.span == what } } override fun toString(): String { @@ -175,4 +177,4 @@ class SpanUtilsTest { throw NotImplementedError() } } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/StringUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/StringUtilsTest.kt index 7b23297e8..5966cc39e 100644 --- a/app/src/test/java/com/keylesspalace/tusky/StringUtilsTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/StringUtilsTest.kt @@ -3,21 +3,23 @@ package com.keylesspalace.tusky import com.keylesspalace.tusky.util.dec import com.keylesspalace.tusky.util.inc import com.keylesspalace.tusky.util.isLessThan -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test class StringUtilsTest { @Test fun isLessThan() { val lessList = listOf( - "abc" to "bcd", - "ab" to "abc", - "cb" to "abc", - "1" to "2" + "abc" to "bcd", + "ab" to "abc", + "cb" to "abc", + "1" to "2" ) lessList.forEach { (l, r) -> assertTrue("$l < $r", l.isLessThan(r)) } val notLessList = lessList.map { (l, r) -> r to l } + listOf( - "abc" to "abc" + "abc" to "abc" ) notLessList.forEach { (l, r) -> assertFalse("not $l < $r", l.isLessThan(r)) } } @@ -25,22 +27,22 @@ class StringUtilsTest { @Test fun inc() { listOf( - "122" to "123", - "12A" to "12B", - "1" to "2" + "122" to "123", + "12A" to "12B", + "1" to "2" ).forEach { (l, r) -> assertEquals("$l + 1 = $r", r, l.inc()) } } @Test fun dec() { listOf( - "123" to "122", - "12B" to "12A", - "120" to "11z", - "100" to "zz", - "0" to "", - "" to "", - "2" to "1" + "123" to "122", + "12B" to "12A", + "120" to "11z", + "100" to "zz", + "0" to "", + "" to "", + "2" to "1" ).forEach { (l, r) -> assertEquals("$l - 1 = $r", r, l.dec()) } } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/test/java/com/keylesspalace/tusky/TuskyApplication.kt index 7f82e2495..7724ba768 100644 --- a/app/src/test/java/com/keylesspalace/tusky/TuskyApplication.kt +++ b/app/src/test/java/com/keylesspalace/tusky/TuskyApplication.kt @@ -44,4 +44,4 @@ class TuskyApplication : Application() { @JvmStatic lateinit var localeManager: LocaleManager } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineRepositoryTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineRepositoryTest.kt index a11fda67d..55861fd4e 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineRepositoryTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineRepositoryTest.kt @@ -23,14 +23,15 @@ import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.* +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.MockitoAnnotations import org.robolectric.annotation.Config import retrofit2.Response -import java.util.* +import java.util.Date import java.util.concurrent.TimeUnit -import kotlin.collections.ArrayList @Config(sdk = [28]) @RunWith(AndroidJUnit4::class) @@ -50,7 +51,6 @@ class TimelineRepositoryTest { private lateinit var testScheduler: TestScheduler - private val limit = 30 private val account = AccountEntity( id = 2, diff --git a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt b/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt index b70972dbb..a5665de92 100644 --- a/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/components/timeline/TimelineViewModelTest.kt @@ -14,14 +14,24 @@ import com.keylesspalace.tusky.network.TimelineCases import com.keylesspalace.tusky.util.Either import com.keylesspalace.tusky.util.toViewData import com.keylesspalace.tusky.viewdata.StatusViewData -import com.nhaarman.mockitokotlin2.* +import com.nhaarman.mockitokotlin2.clearInvocations +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.eq +import com.nhaarman.mockitokotlin2.isNull +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.times +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.whenever import io.reactivex.rxjava3.annotations.NonNull import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.observers.TestObserver import io.reactivex.rxjava3.subjects.PublishSubject import kotlinx.coroutines.runBlocking -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.robolectric.annotation.Config @@ -29,7 +39,6 @@ import org.robolectric.shadows.ShadowLog import retrofit2.Response import java.io.IOException - @Config(sdk = [29]) class TimelineViewModelTest { lateinit var timelineRepository: TimelineRepository @@ -727,7 +736,6 @@ class TimelineViewModelTest { ).thenReturn(Single.just(items)) } - private fun assertHasList(aList: List) { assertEquals( aList, @@ -780,4 +788,4 @@ class TimelineViewModelTest { } private fun List.toEitherList() = map { Either.Right(it) } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/util/EmojiCompatFontTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/EmojiCompatFontTest.kt index badaa709a..5dd5ea84f 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/EmojiCompatFontTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/EmojiCompatFontTest.kt @@ -1,6 +1,6 @@ package com.keylesspalace.tusky.util -import org.junit.Assert.* +import org.junit.Assert.assertEquals import org.junit.Test class EmojiCompatFontTest { @@ -9,39 +9,39 @@ class EmojiCompatFontTest { fun testCompareVersions() { assertEquals( - -1, - EmojiCompatFont.compareVersions( - listOf(0), - listOf(1, 2, 3) - ) + -1, + EmojiCompatFont.compareVersions( + listOf(0), + listOf(1, 2, 3) + ) ) assertEquals( - 1, - EmojiCompatFont.compareVersions( - listOf(1, 2, 3), - listOf(0, 0, 0) - ) + 1, + EmojiCompatFont.compareVersions( + listOf(1, 2, 3), + listOf(0, 0, 0) + ) ) assertEquals( - -1, - EmojiCompatFont.compareVersions( - listOf(1, 0, 1), - listOf(1, 1, 0) - ) + -1, + EmojiCompatFont.compareVersions( + listOf(1, 0, 1), + listOf(1, 1, 0) + ) ) assertEquals( - 0, - EmojiCompatFont.compareVersions( - listOf(4, 5, 6), - listOf(4, 5, 6) - ) + 0, + EmojiCompatFont.compareVersions( + listOf(4, 5, 6), + listOf(4, 5, 6) + ) ) assertEquals( - 0, - EmojiCompatFont.compareVersions( - listOf(0, 0), - listOf(0) - ) + 0, + EmojiCompatFont.compareVersions( + listOf(0, 0), + listOf(0) + ) ) } -} \ No newline at end of file +} diff --git a/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt index b9b3a3740..c5bfad426 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/RickRollTest.kt @@ -23,11 +23,13 @@ class RickRollTest { @Test fun testShouldRickRoll() { listOf("gab.Com", "social.gab.ai", "whatever.GAB.com").forEach { - rollableDomain -> assertTrue(shouldRickRoll(activity, rollableDomain)) + rollableDomain -> + assertTrue(shouldRickRoll(activity, rollableDomain)) } listOf("chaos.social", "notgab.com").forEach { - notRollableDomain -> assertFalse(shouldRickRoll(activity, notRollableDomain)) + notRollableDomain -> + assertFalse(shouldRickRoll(activity, notRollableDomain)) } } } diff --git a/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt index b85d60a1f..5b6f417b7 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/SmartLengthInputFilterTest.kt @@ -12,7 +12,6 @@ import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) class SmartLengthInputFilterTest { - @Test fun shouldNotTrimStatusWithLength0() { assertFalse(shouldTrimStatus(SpannableStringBuilder(""))) @@ -25,56 +24,80 @@ class SmartLengthInputFilterTest { @Test fun shouldNotTrimStatusWithLength500() { - assertFalse(shouldTrimStatus(SpannableStringBuilder("u1Pc5TbDVYFnzIdqlQkb3xuZ2S61fFD1K4u" + - "cb3q40dnELjAsWxnSH59jqly249Spr0Vod029zfwFHYQ0PkBCNQ7tuk90h6aY661RFC7vhIKJna4yDYOBFj" + - "RR9u0CsUa6vlgEE5yUrk5LKn3bmnnzRCXmU6HyT2bFu256qoUWbmMQ6GFXUXjO28tru8Q3UiXKLgrotKdSH" + - "mmqPwQgtatbMykTW4RZdKTE46nzlbD3mXHdWQkf4uVPYhVT1CMvVbCPMaimfQ0xuU8CpxyVqA8a6lCL3YX9" + - "pNnZjD7DoCg2FCejANnjXsTF6vuqPSHjQZDjy696nSAFy95p9kBeJkc70fHzX5TcfUqSaNtvx3LUtpIkwh4" + - "q2EYmKISPsxlANaspEMPuX6r9fSACiEwmHsitZkp4RMKZq5NqRsGPCiAXcNIN3jj9fCYVGxUwVxVeCescDG" + - "5naEEszIR3FT1RO4MSn9c2ZZi0UdLizd8ciJAIuwwmcVyYyyM4"))) + assertFalse( + shouldTrimStatus( + SpannableStringBuilder( + "u1Pc5TbDVYFnzIdqlQkb3xuZ2S61fFD1K4u" + + "cb3q40dnELjAsWxnSH59jqly249Spr0Vod029zfwFHYQ0PkBCNQ7tuk90h6aY661RFC7vhIKJna4yDYOBFj" + + "RR9u0CsUa6vlgEE5yUrk5LKn3bmnnzRCXmU6HyT2bFu256qoUWbmMQ6GFXUXjO28tru8Q3UiXKLgrotKdSH" + + "mmqPwQgtatbMykTW4RZdKTE46nzlbD3mXHdWQkf4uVPYhVT1CMvVbCPMaimfQ0xuU8CpxyVqA8a6lCL3YX9" + + "pNnZjD7DoCg2FCejANnjXsTF6vuqPSHjQZDjy696nSAFy95p9kBeJkc70fHzX5TcfUqSaNtvx3LUtpIkwh4" + + "q2EYmKISPsxlANaspEMPuX6r9fSACiEwmHsitZkp4RMKZq5NqRsGPCiAXcNIN3jj9fCYVGxUwVxVeCescDG" + + "5naEEszIR3FT1RO4MSn9c2ZZi0UdLizd8ciJAIuwwmcVyYyyM4" + ) + ) + ) } @Test fun shouldNotTrimStatusWithLength666() { - assertFalse(shouldTrimStatus(SpannableStringBuilder("hIAXqY7DYynQGcr3zxcjCjNZFcdwAzwnWv" + - "NHONtT55rO3r2faeMRZLTG3JlOshq8M1mtLRn0Ca8M9w82nIjJDm1jspxhFc4uLFpOjb9Gm2BokgRftA8ih" + - "pv6wvMwF5Fg8V4qa8GcXcqt1q7S9g09S3PszCXG4wnrR6dp8GGc9TqVArgmoLSc9EVREIRcLPdzkhV1WWM9" + - "ZWw7josT27BfBdMWk0ckQkClHAyqLtlKZ84WamxK2q3NtHR5gr7ohIjU8CZoKDjv1bA8ZI8wBesyOhqbmHf" + - "0Ltypq39WKZ63VTGSf5Dd9kuTEjlXJtxZD1DXH4FFplY45DH5WuQ61Ih5dGx0WFEEVb1L3aku3Ht8rKG7YU" + - "bOPeanGMBmeI9YRdiD4MmuTUkJfVLkA9rrpRtiEYw8RS3Jf9iqDkTpES9aLQODMip5xTsT4liIcUbLo0Z1d" + - "NhHk7YKubigNQIm1mmh2iU3Q0ZEm8TraDpKu2o27gIwSKbAnTllrOokprPxWQWDVrN9bIliwGHzgTKPI5z8" + - "gUybaqewxUYe12GvxnzqpfPFvvHricyZAC9i6Fkil5VmFdae75tLFWRBfE8Wfep0dSjL751m2yzvzZTc6uZ" + - "RTcUiipvl42DaY8Z5eG2b6xPVhvXshMORvHzwhJhPkHSbnwXX5K"))) + assertFalse( + shouldTrimStatus( + SpannableStringBuilder( + "hIAXqY7DYynQGcr3zxcjCjNZFcdwAzwnWv" + + "NHONtT55rO3r2faeMRZLTG3JlOshq8M1mtLRn0Ca8M9w82nIjJDm1jspxhFc4uLFpOjb9Gm2BokgRftA8ih" + + "pv6wvMwF5Fg8V4qa8GcXcqt1q7S9g09S3PszCXG4wnrR6dp8GGc9TqVArgmoLSc9EVREIRcLPdzkhV1WWM9" + + "ZWw7josT27BfBdMWk0ckQkClHAyqLtlKZ84WamxK2q3NtHR5gr7ohIjU8CZoKDjv1bA8ZI8wBesyOhqbmHf" + + "0Ltypq39WKZ63VTGSf5Dd9kuTEjlXJtxZD1DXH4FFplY45DH5WuQ61Ih5dGx0WFEEVb1L3aku3Ht8rKG7YU" + + "bOPeanGMBmeI9YRdiD4MmuTUkJfVLkA9rrpRtiEYw8RS3Jf9iqDkTpES9aLQODMip5xTsT4liIcUbLo0Z1d" + + "NhHk7YKubigNQIm1mmh2iU3Q0ZEm8TraDpKu2o27gIwSKbAnTllrOokprPxWQWDVrN9bIliwGHzgTKPI5z8" + + "gUybaqewxUYe12GvxnzqpfPFvvHricyZAC9i6Fkil5VmFdae75tLFWRBfE8Wfep0dSjL751m2yzvzZTc6uZ" + + "RTcUiipvl42DaY8Z5eG2b6xPVhvXshMORvHzwhJhPkHSbnwXX5K" + ) + ) + ) } @Test fun shouldTrimStatusWithLength667() { - assertTrue(shouldTrimStatus(SpannableStringBuilder("hIAXqY7DYynQGcr3zxcjCjNZFcdwAzwnWv" + - "NHONtT55rO3r2faeMRZLTG3JlOshq8M1mtLRn0Ca8M9w82nIjJDm1jspxhFc4uLFpOjb9Gm2BokgRftA8ih" + - "pv6wvMwF5Fg8V4qa8GcXcqt1q7S9g09S3PszCXG4wnrR6dp8GGc9TqVArgmoLSc9EVREIRcLPdzkhV1WWM9" + - "ZWw7josT27BfBdMWk0ckQkClHAyqLtlKZ84WamxK2q3NtHR5gr7ohIjU8CZoKDjv1bA8ZI8wBesyOhqbmHf" + - "0Ltypq39WKZ63VTGSf5Dd9kuTEjlXJtxZD1DXH4FFplY45DH5WuQ61Ih5dGx0WFEEVb1L3aku3Ht8rKG7YU" + - "bOPeanGMBmeI9YRdiD4MmuTUkJfVLkA9rrpRtiEYw8RS3Jf9iqDkTpES9aLQODMip5xTsT4liIcUbLo0Z1d" + - "NhHk7YKubigNQIm1mmh2iU3Q0ZEm8TraDpKu2o27gIwSKbAnTllrOokprPxWQWDVrN9bIliwGHzgTKPI5z8" + - "gUybaqewxUYe12GvxnzqpfPFvvHricyZAC9i6Fkil5VmFdae75tLFWRBfE8Wfep0dSjL751m2yzvzZTc6uZ" + - "RTcUiipvl42DaY8Z5eG2b6xPVhvXshMORvHzwhJhPkHSbnwXX5K1"))) + assertTrue( + shouldTrimStatus( + SpannableStringBuilder( + "hIAXqY7DYynQGcr3zxcjCjNZFcdwAzwnWv" + + "NHONtT55rO3r2faeMRZLTG3JlOshq8M1mtLRn0Ca8M9w82nIjJDm1jspxhFc4uLFpOjb9Gm2BokgRftA8ih" + + "pv6wvMwF5Fg8V4qa8GcXcqt1q7S9g09S3PszCXG4wnrR6dp8GGc9TqVArgmoLSc9EVREIRcLPdzkhV1WWM9" + + "ZWw7josT27BfBdMWk0ckQkClHAyqLtlKZ84WamxK2q3NtHR5gr7ohIjU8CZoKDjv1bA8ZI8wBesyOhqbmHf" + + "0Ltypq39WKZ63VTGSf5Dd9kuTEjlXJtxZD1DXH4FFplY45DH5WuQ61Ih5dGx0WFEEVb1L3aku3Ht8rKG7YU" + + "bOPeanGMBmeI9YRdiD4MmuTUkJfVLkA9rrpRtiEYw8RS3Jf9iqDkTpES9aLQODMip5xTsT4liIcUbLo0Z1d" + + "NhHk7YKubigNQIm1mmh2iU3Q0ZEm8TraDpKu2o27gIwSKbAnTllrOokprPxWQWDVrN9bIliwGHzgTKPI5z8" + + "gUybaqewxUYe12GvxnzqpfPFvvHricyZAC9i6Fkil5VmFdae75tLFWRBfE8Wfep0dSjL751m2yzvzZTc6uZ" + + "RTcUiipvl42DaY8Z5eG2b6xPVhvXshMORvHzwhJhPkHSbnwXX5K1" + ) + ) + ) } @Test fun shouldTrimStatusWithLength1000() { - assertTrue(shouldTrimStatus(SpannableStringBuilder("u1Pc5TbDVYFnzIdqlQkb3xuZ2S61fFD1K4u" + - "cb3q40dnELjAsWxnSH59jqly249Spr0Vod029zfwFHYQ0PkBCNQ7tuk90h6aY661RFC7vhIKJna4yDYOBFj" + - "RR9u0CsUa6vlgEE5yUrk5LKn3bmnnzRCXmU6HyT2bFu256qoUWbmMQ6GFXUXjO28tru8Q3UiXKLgrotKdSH" + - "mmqPwQgtatbMykTW4RZdKTE46nzlbD3mXHdWQkf4uVPYhVT1CMvVbCPMaimfQ0xuU8CpxyVqA8a6lCL3YX9" + - "pNnZjD7DoCg2FCejANnjXsTF6vuqPSHjQZDjy696nSAFy95p9kBeJkc70fHzX5TcfUqSaNtvx3LUtpIkwh4" + - "q2EYmKISPsxlANaspEMPuX6r9fSACiEwmHsitZkp4RMKZq5NqRsGPCiAXcNIN3jj9fCYVGxUwVxVeCescDG" + - "5naEEszIR3FT1RO4MSn9c2ZZi0UdLizd8ciJAIuwwmcVyYyyM4"+ - "u1Pc5TbDVYFnzIdqlQkb3xuZ2S61fFD1K4u" + - "cb3q40dnELjAsWxnSH59jqly249Spr0Vod029zfwFHYQ0PkBCNQ7tuk90h6aY661RFC7vhIKJna4yDYOBFj" + - "RR9u0CsUa6vlgEE5yUrk5LKn3bmnnzRCXmU6HyT2bFu256qoUWbmMQ6GFXUXjO28tru8Q3UiXKLgrotKdSH" + - "mmqPwQgtatbMykTW4RZdKTE46nzlbD3mXHdWQkf4uVPYhVT1CMvVbCPMaimfQ0xuU8CpxyVqA8a6lCL3YX9" + - "pNnZjD7DoCg2FCejANnjXsTF6vuqPSHjQZDjy696nSAFy95p9kBeJkc70fHzX5TcfUqSaNtvx3LUtpIkwh4" + - "q2EYmKISPsxlANaspEMPuX6r9fSACiEwmHsitZkp4RMKZq5NqRsGPCiAXcNIN3jj9fCYVGxUwVxVeCescDG" + - "5naEEszIR3FT1RO4MSn9c2ZZi0UdLizd8ciJAIuwwmcVyYyyM4"))) + assertTrue( + shouldTrimStatus( + SpannableStringBuilder( + "u1Pc5TbDVYFnzIdqlQkb3xuZ2S61fFD1K4u" + + "cb3q40dnELjAsWxnSH59jqly249Spr0Vod029zfwFHYQ0PkBCNQ7tuk90h6aY661RFC7vhIKJna4yDYOBFj" + + "RR9u0CsUa6vlgEE5yUrk5LKn3bmnnzRCXmU6HyT2bFu256qoUWbmMQ6GFXUXjO28tru8Q3UiXKLgrotKdSH" + + "mmqPwQgtatbMykTW4RZdKTE46nzlbD3mXHdWQkf4uVPYhVT1CMvVbCPMaimfQ0xuU8CpxyVqA8a6lCL3YX9" + + "pNnZjD7DoCg2FCejANnjXsTF6vuqPSHjQZDjy696nSAFy95p9kBeJkc70fHzX5TcfUqSaNtvx3LUtpIkwh4" + + "q2EYmKISPsxlANaspEMPuX6r9fSACiEwmHsitZkp4RMKZq5NqRsGPCiAXcNIN3jj9fCYVGxUwVxVeCescDG" + + "5naEEszIR3FT1RO4MSn9c2ZZi0UdLizd8ciJAIuwwmcVyYyyM4" + + "u1Pc5TbDVYFnzIdqlQkb3xuZ2S61fFD1K4u" + + "cb3q40dnELjAsWxnSH59jqly249Spr0Vod029zfwFHYQ0PkBCNQ7tuk90h6aY661RFC7vhIKJna4yDYOBFj" + + "RR9u0CsUa6vlgEE5yUrk5LKn3bmnnzRCXmU6HyT2bFu256qoUWbmMQ6GFXUXjO28tru8Q3UiXKLgrotKdSH" + + "mmqPwQgtatbMykTW4RZdKTE46nzlbD3mXHdWQkf4uVPYhVT1CMvVbCPMaimfQ0xuU8CpxyVqA8a6lCL3YX9" + + "pNnZjD7DoCg2FCejANnjXsTF6vuqPSHjQZDjy696nSAFy95p9kBeJkc70fHzX5TcfUqSaNtvx3LUtpIkwh4" + + "q2EYmKISPsxlANaspEMPuX6r9fSACiEwmHsitZkp4RMKZq5NqRsGPCiAXcNIN3jj9fCYVGxUwVxVeCescDG" + + "5naEEszIR3FT1RO4MSn9c2ZZi0UdLizd8ciJAIuwwmcVyYyyM4" + ) + ) + ) } } diff --git a/app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt b/app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt index 03ab3d947..2731228a0 100644 --- a/app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt +++ b/app/src/test/java/com/keylesspalace/tusky/util/VersionUtilsTest.kt @@ -7,24 +7,24 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) class VersionUtilsTest( - private val versionString: String, - private val supportsScheduledToots: Boolean + private val versionString: String, + private val supportsScheduledToots: Boolean ) { companion object { @JvmStatic @Parameterized.Parameters fun data() = listOf( - arrayOf("2.0.0", false), - arrayOf("2a9a0", false), - arrayOf("1.0", false), - arrayOf("error", false), - arrayOf("", false), - arrayOf("2.6.9", false), - arrayOf("2.7.0", true), - arrayOf("2.00008.0", true), - arrayOf("2.7.2 (compatible; Pleroma 1.0.0-1168-ge18c7866-pleroma-dot-site)", true), - arrayOf("3.0.1", true) + arrayOf("2.0.0", false), + arrayOf("2a9a0", false), + arrayOf("1.0", false), + arrayOf("error", false), + arrayOf("", false), + arrayOf("2.6.9", false), + arrayOf("2.7.0", true), + arrayOf("2.00008.0", true), + arrayOf("2.7.2 (compatible; Pleroma 1.0.0-1168-ge18c7866-pleroma-dot-site)", true), + arrayOf("3.0.1", true) ) } @@ -32,5 +32,4 @@ class VersionUtilsTest( fun testVersionUtils() { assertEquals(VersionUtils(versionString).supportsScheduledToots(), supportsScheduledToots) } - -} \ No newline at end of file +} diff --git a/build.gradle b/build.gradle index 3bc072dcd..ccc0c7faa 100644 --- a/build.gradle +++ b/build.gradle @@ -3,14 +3,20 @@ buildscript { repositories { google() mavenCentral() + gradlePluginPortal() } dependencies { classpath 'com.android.tools.build:gradle:4.2.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jlleitschuh.gradle:ktlint-gradle:10.1.0" } } +plugins { + id "org.jlleitschuh.gradle.ktlint" version "10.1.0" +} allprojects { + apply plugin: "org.jlleitschuh.gradle.ktlint" repositories { google() mavenCentral()