add ktlint plugin to project and apply default code style (#2209)
* add ktlint plugin to project and apply default code style * some manual adjustments, fix wildcard imports * update CONTRIBUTING.md * fix formatting
This commit is contained in:
parent
955267199e
commit
16ffcca748
227 changed files with 3933 additions and 3371 deletions
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
@ -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",
|
||||
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)
|
||||
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()
|
||||
|
||||
|
|
|
@ -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,8 +45,10 @@ class TimelineDAOTest {
|
|||
timelineDao.insertInTransaction(status, author, reblogger)
|
||||
}
|
||||
|
||||
val resultsFromDb = timelineDao.getStatusesForAccount(setOne.first.timelineUserId,
|
||||
maxId = "21", sinceId = ignoredOne.first.serverId, limit = 10)
|
||||
val resultsFromDb = timelineDao.getStatusesForAccount(
|
||||
setOne.first.timelineUserId,
|
||||
maxId = "21", sinceId = ignoredOne.first.serverId, limit = 10
|
||||
)
|
||||
.blockingGet()
|
||||
|
||||
assertEquals(2, resultsFromDb.size)
|
||||
|
@ -71,7 +77,6 @@ class TimelineDAOTest {
|
|||
assertEquals(author, result.account)
|
||||
assertEquals(status, result.status)
|
||||
assertNull(result.reblogAccount)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -185,7 +190,6 @@ class TimelineDAOTest {
|
|||
)
|
||||
} else null
|
||||
|
||||
|
||||
val even = accountId % 2 == 0L
|
||||
val status = TimelineStatusEntity(
|
||||
serverId = statusId.toString(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -233,7 +242,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||
|
||||
override fun onTabSelected(tab: TabLayout.Tab?) {}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -314,7 +322,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun makeNotificationBarTransparent() {
|
||||
|
@ -347,12 +354,14 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
viewModel.accountFieldData.observe(this, {
|
||||
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 ->
|
||||
viewModel.isRefreshing.observe(
|
||||
this,
|
||||
{ isRefreshing ->
|
||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||
})
|
||||
}
|
||||
)
|
||||
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
}
|
||||
|
||||
|
@ -421,7 +433,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
|||
.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,8 +564,9 @@ 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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
@ -74,7 +73,8 @@ abstract class BottomSheetActivity : BaseActivity() {
|
|||
resolve = true
|
||||
).observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe({ (accounts, statuses) ->
|
||||
.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)
|
||||
}
|
||||
|
@ -187,7 +189,8 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
|
|||
|
||||
if (uri.query != null ||
|
||||
uri.fragment != null ||
|
||||
uri.path == null) {
|
||||
uri.path == null
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
@ -150,7 +156,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
.load(me.header)
|
||||
.into(binding.headerPreview)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
is Error -> {
|
||||
|
@ -159,7 +164,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
viewModel.obtainProfile()
|
||||
}
|
||||
snackbar.show()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +183,9 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
observeImage(viewModel.avatarData, binding.avatarPreview, binding.avatarProgressBar, true)
|
||||
observeImage(viewModel.headerData, binding.headerPreview, binding.headerProgressBar, false)
|
||||
|
||||
viewModel.saveData.observe(this, {
|
||||
viewModel.saveData.observe(
|
||||
this,
|
||||
{
|
||||
when (it) {
|
||||
is Success -> {
|
||||
finish()
|
||||
|
@ -191,8 +197,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
onSaveFailure(it.errorMessage)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -203,18 +209,24 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (!isFinishing) {
|
||||
viewModel.updateProfile(binding.displayNameEditText.text.toString(),
|
||||
viewModel.updateProfile(
|
||||
binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData())
|
||||
accountFieldEditAdapter.getFieldData()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeImage(liveData: LiveData<Resource<Bitmap>>,
|
||||
private fun observeImage(
|
||||
liveData: LiveData<Resource<Bitmap>>,
|
||||
imageView: ImageView,
|
||||
progressBar: View,
|
||||
roundedCorners: Boolean) {
|
||||
liveData.observe(this, {
|
||||
roundedCorners: Boolean
|
||||
) {
|
||||
liveData.observe(
|
||||
this,
|
||||
{
|
||||
|
||||
when (it) {
|
||||
is Success -> {
|
||||
|
@ -242,10 +254,10 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
onResizeFailure()
|
||||
it.consumed = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun onMediaPick(pickType: PickType) {
|
||||
|
@ -261,8 +273,11 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE -> {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
|
@ -310,11 +325,13 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
return
|
||||
}
|
||||
|
||||
viewModel.save(binding.displayNameEditText.text.toString(),
|
||||
viewModel.save(
|
||||
binding.displayNameEditText.text.toString(),
|
||||
binding.noteEditText.text.toString(),
|
||||
binding.lockedCheckBox.isChecked,
|
||||
accountFieldEditAdapter.getFieldData(),
|
||||
this)
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSaveFailure(msg: String?) {
|
||||
|
@ -409,5 +426,4 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
Snackbar.make(binding.avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
|
||||
endMediaPicking()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import retrofit2.Call
|
|||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.IOException
|
||||
import java.lang.Exception
|
||||
import javax.inject.Inject
|
||||
|
||||
class FiltersActivity : BaseActivity() {
|
||||
|
@ -94,8 +93,10 @@ 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)
|
||||
}
|
||||
}
|
||||
|
@ -143,8 +144,10 @@ class FiltersActivity: BaseActivity() {
|
|||
.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)
|
||||
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) { _, _ ->
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,7 +100,8 @@ 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())
|
||||
|
@ -101,9 +118,9 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
.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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,7 +138,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
.setView(layout)
|
||||
.setPositiveButton(
|
||||
if (list == null) R.string.action_create_list
|
||||
else R.string.action_rename_list) { _, _ ->
|
||||
else R.string.action_rename_list
|
||||
) { _, _ ->
|
||||
onPickedDialogName(editText.text, list?.id)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
@ -145,7 +163,6 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
.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()
|
||||
}
|
||||
|
@ -182,7 +201,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
|
||||
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,8 +239,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
}
|
||||
}
|
||||
|
||||
private inner class ListsAdapter
|
||||
: ListAdapter<MastoList, ListsAdapter.ListViewHolder>(ListsDiffer) {
|
||||
private inner class ListsAdapter :
|
||||
ListAdapter<MastoList, ListsAdapter.ListViewHolder>(ListsDiffer) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
|
||||
return LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
|
||||
|
@ -238,7 +258,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
|||
holder.nameTextView.text = getItem(position).title
|
||||
}
|
||||
|
||||
private inner class ListViewHolder(view: View) : RecyclerView.ViewHolder(view),
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
|
@ -75,7 +79,8 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
preferences = getSharedPreferences(
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE)
|
||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
binding.loginButton.setOnClickListener { onButtonClick() }
|
||||
|
||||
|
@ -95,7 +100,6 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
} else {
|
||||
binding.toolbar.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun requiresLogin(): Boolean {
|
||||
|
@ -134,8 +138,10 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
val callback = object : Callback<AppCredentials> {
|
||||
override fun onResponse(call: Call<AppCredentials>,
|
||||
response: Response<AppCredentials>) {
|
||||
override fun onResponse(
|
||||
call: Call<AppCredentials>,
|
||||
response: Response<AppCredentials>
|
||||
) {
|
||||
if (!response.isSuccessful) {
|
||||
binding.loginButton.isEnabled = true
|
||||
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
||||
|
@ -165,11 +171,12 @@ class LoginActivity : BaseActivity(), Injectable {
|
|||
}
|
||||
|
||||
mastodonApi
|
||||
.authenticateApp(domain, getString(R.string.app_name), oauthRedirectUri,
|
||||
OAUTH_SCOPES, getString(R.string.tusky_website))
|
||||
.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) {
|
||||
|
@ -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<AccessToken>, 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)
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
@ -60,7 +57,6 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
val fragment = TimelineFragment.newInstance(kind)
|
||||
replace(R.id.fragment_container, fragment)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun androidInjector() = dispatchingAndroidInjector
|
||||
|
@ -81,5 +77,4 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
putExtra(EXTRA_KIND, Kind.BOOKMARKS.name)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,7 +34,8 @@ const val DIRECT = "Direct"
|
|||
const val HASHTAG = "Hashtag"
|
||||
const val LIST = "List"
|
||||
|
||||
data class TabData(val id: String,
|
||||
data class TabData(
|
||||
val id: String,
|
||||
@StringRes val text: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
val fragment: (List<String>) -> Fragment,
|
||||
|
|
|
@ -333,7 +333,6 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
|||
.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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,7 +102,6 @@ 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")
|
||||
|
@ -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) {
|
||||
|
@ -284,7 +284,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
Log.e(TAG, "Error writing temporary media.")
|
||||
}
|
||||
return@fromCallable false
|
||||
|
||||
}
|
||||
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
@ -309,7 +308,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
Log.e(TAG, "Failed to download image", error)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private fun shareMediaFile(directory: File, url: String) {
|
||||
|
|
|
@ -121,5 +121,4 @@ abstract class AccountAdapter<AVH : RecyclerView.ViewHolder> internal constructo
|
|||
const val VIEW_TYPE_ACCOUNT = 0
|
||||
const val VIEW_TYPE_FOOTER = 1
|
||||
}
|
||||
|
||||
}
|
|
@ -16,16 +16,19 @@
|
|||
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,
|
||||
|
@ -70,6 +73,5 @@ class AccountFieldAdapter(
|
|||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,9 +82,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
|||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
class MutableStringPair(var first: String, var second: String)
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ import com.keylesspalace.tusky.R
|
|||
import com.keylesspalace.tusky.databinding.ItemAutocompleteAccountBinding
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.util.emojify
|
||||
import com.keylesspalace.tusky.util.loadAvatar
|
||||
|
||||
class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(context, R.layout.item_autocomplete_account) {
|
||||
|
||||
|
@ -48,7 +49,6 @@ class AccountSelectionAdapter(context: Context) : ArrayAdapter<AccountEntity>(co
|
|||
val animateAvatar = pm.getBoolean("animateGifAvatars", false)
|
||||
|
||||
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar)
|
||||
|
||||
}
|
||||
|
||||
return binding.root
|
||||
|
|
|
@ -22,7 +22,7 @@ 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<Emoji>,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -24,7 +24,10 @@ 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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -46,7 +46,8 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
|||
emojis: List<Emoji>,
|
||||
mode: Int,
|
||||
resultClickListener: View.OnClickListener?,
|
||||
animateEmojis: Boolean) {
|
||||
animateEmojis: Boolean
|
||||
) {
|
||||
this.pollOptions = options
|
||||
this.voteCount = voteCount
|
||||
this.votersCount = votersCount
|
||||
|
@ -62,7 +63,6 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
|||
.map { pollOptions.indexOf(it) }
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemPollBinding> {
|
||||
val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return BindingHolder(binding)
|
||||
|
@ -114,7 +114,6 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -60,7 +60,6 @@ class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
|
|||
|
||||
textView.setOnClickListener(clickListener)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PreviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
|
|
@ -43,7 +43,8 @@ interface ItemInteractionListener {
|
|||
fun onChipClicked(tab: TabData, tabPosition: Int, chipPosition: Int)
|
||||
}
|
||||
|
||||
class TabAdapter(private var data: List<TabData>,
|
||||
class TabAdapter(
|
||||
private var data: List<TabData>,
|
||||
private val small: Boolean,
|
||||
private val listener: ItemInteractionListener,
|
||||
private var removeButtonEnabled: Boolean = false
|
||||
|
@ -77,7 +78,6 @@ class TabAdapter(private var data: List<TabData>,
|
|||
binding.textView.setOnClickListener {
|
||||
listener.onTabAdded(tab)
|
||||
}
|
||||
|
||||
} else {
|
||||
val binding = holder.binding as ItemTabPreferenceBinding
|
||||
|
||||
|
@ -143,7 +143,6 @@ class TabAdapter(private var data: List<TabData>,
|
|||
binding.actionChip.setOnClickListener {
|
||||
listener.onActionChipClicked(tab, holder.bindingAdapterPosition)
|
||||
}
|
||||
|
||||
} else {
|
||||
binding.chipGroup.hide()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -67,12 +67,12 @@ class AnnouncementAdapter(
|
|||
}
|
||||
|
||||
item.reactions.forEachIndexed { i, reaction ->
|
||||
(chips.getChildAt(i)?.takeUnless { it.id == R.id.addReactionChip } as Chip?
|
||||
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
|
||||
|
@ -81,12 +81,14 @@ class AnnouncementAdapter(
|
|||
}
|
||||
this.text = ("$emojiText ${reaction.count}")
|
||||
.emojify(
|
||||
listOf(Emoji(
|
||||
listOf(
|
||||
Emoji(
|
||||
reaction.name,
|
||||
reaction.url ?: "",
|
||||
reaction.staticUrl ?: "",
|
||||
null
|
||||
)),
|
||||
)
|
||||
),
|
||||
this,
|
||||
animateEmojis
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -27,7 +27,12 @@ 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
|
||||
|
||||
|
@ -45,7 +50,8 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
val emojis: LiveData<List<Emoji>> = emojisMutable
|
||||
|
||||
init {
|
||||
Single.zip(mastodonApi.getCustomEmojis(),
|
||||
Single.zip(
|
||||
mastodonApi.getCustomEmojis(),
|
||||
appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
|
||||
.map<Either<InstanceEntity, Instance>> { Either.Left(it) }
|
||||
.onErrorResumeNext {
|
||||
|
@ -62,22 +68,27 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
either.asRight().pollLimits?.maxOptionChars,
|
||||
either.asRight().version
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
.doOnSuccess {
|
||||
appDatabase.instanceDao().insertOrReplace(it)
|
||||
}
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
emojisMutable.postValue(it.emojiList.orEmpty())
|
||||
}, {
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to get custom emojis.", it)
|
||||
})
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun load() {
|
||||
announcementsMutable.postValue(Loading())
|
||||
mastodonApi.listAnnouncements()
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
announcementsMutable.postValue(Success(it))
|
||||
it.filter { announcement -> !announcement.read }
|
||||
.forEach { announcement ->
|
||||
|
@ -92,15 +103,18 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
)
|
||||
.autoDispose()
|
||||
}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
announcementsMutable.postValue(Error(cause = it))
|
||||
})
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun addReaction(announcementId: String, name: String) {
|
||||
mastodonApi.addAnnouncementReaction(announcementId, name)
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
announcementsMutable.postValue(
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
|
@ -139,15 +153,18 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
}
|
||||
)
|
||||
)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to add reaction to the announcement.", it)
|
||||
})
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
fun removeReaction(announcementId: String, name: String) {
|
||||
mastodonApi.removeAnnouncementReaction(announcementId, name)
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
announcementsMutable.postValue(
|
||||
Success(
|
||||
announcements.value!!.data!!.map { announcement ->
|
||||
|
@ -174,9 +191,11 @@ class AnnouncementsViewModel @Inject constructor(
|
|||
}
|
||||
)
|
||||
)
|
||||
}, {
|
||||
},
|
||||
{
|
||||
Log.w(TAG, "Failed to remove reaction from the announcement.", it)
|
||||
})
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
|
|
|
@ -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("@")
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -615,9 +632,11 @@ class ComposeActivity : BaseActivity(),
|
|||
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, {
|
||||
viewModel.sendStatus(contentText, spoilerText).observe(
|
||||
this,
|
||||
{
|
||||
finishingUploadDialog?.dismiss()
|
||||
deleteDraftAndFinish()
|
||||
})
|
||||
|
||||
}
|
||||
)
|
||||
} else {
|
||||
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
|
||||
enableButtons(true)
|
||||
|
@ -776,8 +800,10 @@ 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
|
||||
view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||
|
@ -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
|
||||
|
|
|
@ -81,7 +81,9 @@ class MediaPreviewAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() {
|
||||
private val differ = AsyncListDiffer(
|
||||
this,
|
||||
object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() {
|
||||
override fun areItemsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
||||
return oldItem.localId == newItem.localId
|
||||
}
|
||||
|
@ -89,10 +91,11 @@ class MediaPreviewAdapter(
|
|||
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
|
||||
|
|
|
@ -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()
|
||||
|
@ -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,
|
||||
uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||
file)
|
||||
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",
|
||||
val filename = "%s_%s_%s.%s".format(
|
||||
context.getString(R.string.app_name),
|
||||
Date().time.toString(),
|
||||
randomAlphanumericString(10),
|
||||
fileExtension)
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,11 +82,13 @@ fun showAddPollDialog(
|
|||
val pollDuration = context.resources
|
||||
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
||||
|
||||
onUpdatePoll(NewPoll(
|
||||
onUpdatePoll(
|
||||
NewPoll(
|
||||
options = adapter.pollOptions,
|
||||
expiresIn = pollDuration,
|
||||
multiple = binding.multipleChoicesCheckBox.isChecked
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
dialog.dismiss()
|
||||
}
|
||||
|
|
|
@ -40,7 +40,8 @@ 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> T.makeCaptionDialog(existingDescription: String?,
|
||||
fun <T> T.makeCaptionDialog(
|
||||
existingDescription: String?,
|
||||
previewUri: Uri,
|
||||
onUpdateDescription: (String) -> LiveData<Boolean>
|
||||
) where T : Activity, T : LifecycleOwner {
|
||||
|
@ -60,14 +61,18 @@ fun <T> 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
|
||||
input.inputType = (
|
||||
InputType.TYPE_CLASS_TEXT
|
||||
or InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
|
||||
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
)
|
||||
input.setText(existingDescription)
|
||||
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
|
||||
|
||||
|
@ -76,7 +81,6 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
|||
withLifecycleContext {
|
||||
onUpdateDescription(input.text.toString())
|
||||
.observe { success -> if (!success) showFailedCaptionMessage() }
|
||||
|
||||
}
|
||||
|
||||
dialog.dismiss()
|
||||
|
@ -90,7 +94,8 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
|||
|
||||
val window = dialog.window
|
||||
window?.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||
)
|
||||
|
||||
dialog.show()
|
||||
|
||||
|
@ -109,7 +114,6 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
private fun Activity.showFailedCaptionMessage() {
|
||||
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
|
|
@ -57,12 +57,10 @@ class ComposeOptionsView @JvmOverloads constructor(context: Context, attrs: Attr
|
|||
R.id.directRadioButton
|
||||
else ->
|
||||
R.id.directRadioButton
|
||||
|
||||
}
|
||||
|
||||
check(selectedButton)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface ComposeOptionsListener {
|
||||
|
|
|
@ -16,19 +16,21 @@
|
|||
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)
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -27,8 +27,9 @@ import com.keylesspalace.tusky.entity.NewPoll
|
|||
class PollPreviewView @JvmOverloads constructor(
|
||||
context: Context?,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0)
|
||||
: LinearLayout(context, attrs, defStyleAttr) {
|
||||
defStyleAttr: Int = 0
|
||||
) :
|
||||
LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val adapter = PreviewPollOptionsAdapter()
|
||||
|
||||
|
|
|
@ -68,8 +68,5 @@ class TootButton
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -166,7 +166,8 @@ data class ConversationStatusEntity(
|
|||
pinned = false,
|
||||
muted = muted,
|
||||
poll = poll,
|
||||
card = null)
|
||||
card = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,5 +37,4 @@ class ConversationLoadStateAdapter(
|
|||
val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return NetworkStateViewHolder(binding, retryCallback)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -34,5 +34,4 @@ class ConversationsRepository @Inject constructor(
|
|||
}.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
}
|
|
@ -38,7 +38,6 @@ class DraftMediaAdapter(
|
|||
override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
|
||||
}
|
||||
) {
|
||||
|
||||
|
@ -60,8 +59,8 @@ class DraftMediaAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -93,7 +93,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
viewModel.getToot(draft.inReplyToId)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this))
|
||||
.subscribe({ status ->
|
||||
.subscribe(
|
||||
{ status ->
|
||||
val composeOptions = ComposeActivity.ComposeOptions(
|
||||
draftId = draft.id,
|
||||
tootText = draft.content,
|
||||
|
@ -110,8 +111,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
||||
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
||||
|
||||
}, { throwable ->
|
||||
},
|
||||
{ throwable ->
|
||||
|
||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
|
||||
|
@ -126,7 +127,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
|||
Snackbar.make(binding.root, getString(R.string.drafts_failed_loading_reply), Snackbar.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
openDraftWithoutReply(draft)
|
||||
}
|
||||
|
|
|
@ -33,5 +33,4 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
override fun androidInjector() = androidInjector
|
||||
|
||||
}
|
|
@ -114,7 +114,8 @@ 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 ->
|
||||
.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<String>, linkHeader: String?) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,7 +309,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
Log.e("AccountPreferences", "failed updating settings on server", t)
|
||||
showErrorSnackbar(visibility, sensitive)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -124,8 +124,6 @@ class EmojiPreference(
|
|||
finishDownload(font, binding)
|
||||
}
|
||||
).also { downloadDisposables[font.id] = it }
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||
|
@ -222,12 +220,14 @@ class EmojiPreference(
|
|||
context,
|
||||
0x1f973, // This is the codepoint of the party face emoji :D
|
||||
launchIntent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT)
|
||||
PendingIntent.FLAG_CANCEL_CURRENT
|
||||
)
|
||||
val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||
mgr.set(
|
||||
AlarmManager.RTC,
|
||||
System.currentTimeMillis() + 100,
|
||||
mPendingIntent)
|
||||
mPendingIntent
|
||||
)
|
||||
exitProcess(0)
|
||||
}.show()
|
||||
}
|
||||
|
|
|
@ -176,5 +176,4 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
|||
return NotificationPreferencesFragment()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,7 +36,9 @@ import dagger.android.DispatchingAndroidInjector
|
|||
import dagger.android.HasAndroidInjector
|
||||
import javax.inject.Inject
|
||||
|
||||
class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
class PreferencesActivity :
|
||||
BaseActivity(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
HasAndroidInjector {
|
||||
|
||||
@Inject
|
||||
|
@ -91,7 +93,6 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -50,7 +50,6 @@ class ProxyPreferencesFragment : PreferenceFragmentCompat() {
|
|||
setSummaryProvider { text }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
|
@ -126,7 +126,6 @@ class ReportViewModel @Inject constructor(
|
|||
.subscribe(
|
||||
{ data ->
|
||||
updateRelationship(data.getOrNull(0))
|
||||
|
||||
},
|
||||
{
|
||||
updateRelationship(null)
|
||||
|
@ -210,7 +209,6 @@ class ReportViewModel @Inject constructor(
|
|||
}
|
||||
)
|
||||
.autoDispose()
|
||||
|
||||
}
|
||||
|
||||
fun checkClickedUrl(url: String?) {
|
||||
|
|
|
@ -25,11 +25,18 @@ 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,
|
||||
|
@ -71,9 +78,11 @@ class StatusViewHolder(
|
|||
|
||||
val sensitive = status.sensitive
|
||||
|
||||
statusViewHelper.setMediasPreview(statusDisplayOptions, status.attachments,
|
||||
statusViewHelper.setMediasPreview(
|
||||
statusDisplayOptions, status.attachments,
|
||||
sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive),
|
||||
mediaViewHeight)
|
||||
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)
|
||||
|
@ -116,11 +127,13 @@ class StatusViewHolder(
|
|||
}
|
||||
}
|
||||
|
||||
private fun setTextVisible(expanded: Boolean,
|
||||
private fun setTextVisible(
|
||||
expanded: Boolean,
|
||||
content: Spanned,
|
||||
mentions: List<Status.Mention>?,
|
||||
emojis: List<Emoji>,
|
||||
listener: LinkListener) {
|
||||
listener: LinkListener
|
||||
) {
|
||||
if (expanded) {
|
||||
val emojifiedText = content.emojify(emojis, binding.statusContent, statusDisplayOptions.animateEmojis)
|
||||
LinkHelper.setClickableText(binding.statusContent, emojifiedText, mentions, listener)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -56,27 +56,29 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
|||
binding.progressMute.hide()
|
||||
}
|
||||
|
||||
binding.buttonMute.setText(when (it.data) {
|
||||
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) {
|
||||
binding.buttonBlock.setText(
|
||||
when (it.data) {
|
||||
true -> R.string.action_unblock
|
||||
else -> R.string.action_block
|
||||
})
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleClicks() {
|
||||
|
|
|
@ -67,8 +67,7 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
|||
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)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -95,12 +95,16 @@ class SearchViewModel @Inject constructor(
|
|||
|
||||
fun removeItem(status: Pair<Status, StatusViewData.Concrete>) {
|
||||
timelineCases.delete(status.first.id)
|
||||
.subscribe({
|
||||
.subscribe(
|
||||
{
|
||||
if (loadedStatuses.remove(status))
|
||||
statusesPagingSourceFactory.invalidate()
|
||||
}, {
|
||||
err -> Log.d(TAG, "Failed to delete status", err)
|
||||
})
|
||||
},
|
||||
{
|
||||
err ->
|
||||
Log.d(TAG, "Failed to delete status", err)
|
||||
}
|
||||
)
|
||||
.autoDispose()
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ 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, AccountViewHolder>(ACCOUNT_COMPARATOR) {
|
||||
class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) :
|
||||
PagingDataAdapter<Account, AccountViewHolder>(ACCOUNT_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
|
|
|
@ -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, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
|
||||
class SearchHashtagsAdapter(private val linkListener: LinkListener) :
|
||||
PagingDataAdapter<HashTag, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemHashtagBinding> {
|
||||
val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
|
|
|
@ -34,5 +34,4 @@ class SearchPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(acti
|
|||
}
|
||||
|
||||
override fun getItemCount() = 3
|
||||
|
||||
}
|
|
@ -27,7 +27,8 @@ class SearchPagingSource<T: Any>(
|
|||
private val searchType: SearchType,
|
||||
private val searchRequest: String,
|
||||
private val initialItems: List<T>?,
|
||||
private val parser: (SearchResult) -> List<T>) : PagingSource<Int, T>() {
|
||||
private val parser: (SearchResult) -> List<T>
|
||||
) : PagingSource<Int, T>() {
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
|
||||
return null
|
||||
|
|
|
@ -29,8 +29,11 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class SearchFragment<T: Any> : Fragment(R.layout.fragment_search),
|
||||
LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener {
|
||||
abstract class SearchFragment<T : Any> :
|
||||
Fragment(R.layout.fragment_search),
|
||||
LinkListener,
|
||||
Injectable,
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
|
|
@ -125,13 +125,17 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
when (actionable.attachments[attachmentIndex].type) {
|
||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
||||
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)
|
||||
|
@ -204,14 +208,17 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
remove(viewModel.activeAccount?.username)
|
||||
}
|
||||
|
||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
||||
val intent = ComposeActivity.startIntent(
|
||||
requireContext(),
|
||||
ComposeOptions(
|
||||
inReplyToId = status.actionableId,
|
||||
replyVisibility = actionableStatus.visibility,
|
||||
contentWarning = actionableStatus.spoilerText,
|
||||
mentionedUsernames = mentionedUsernames,
|
||||
replyingStatusAuthor = actionableStatus.account.localUsername,
|
||||
replyingStatusContent = actionableStatus.content.toString()
|
||||
))
|
||||
)
|
||||
)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
@ -275,7 +282,8 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
R.string.action_unmute_conversation
|
||||
} else {
|
||||
R.string.action_mute_conversation
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
|
@ -383,11 +391,14 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
}
|
||||
|
||||
private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence) {
|
||||
bottomSheetActivity?.showAccountChooserDialog(dialogTitle, false, object : AccountSelectionListener {
|
||||
bottomSheetActivity?.showAccountChooserDialog(
|
||||
dialogTitle, false,
|
||||
object : AccountSelectionListener {
|
||||
override fun onAccountSelected(account: AccountEntity) {
|
||||
openAsAccount(statusUrl, account)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun openAsAccount(statusUrl: String, account: AccountEntity) {
|
||||
|
@ -448,7 +459,8 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
viewModel.deleteStatus(id)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe({ deletedStatus ->
|
||||
.subscribe(
|
||||
{ deletedStatus ->
|
||||
removeItem(position)
|
||||
|
||||
val redraftStatus = if (deletedStatus.isEmpty()) {
|
||||
|
@ -457,7 +469,9 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
deletedStatus
|
||||
}
|
||||
|
||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
||||
val intent = ComposeActivity.startIntent(
|
||||
requireContext(),
|
||||
ComposeOptions(
|
||||
tootText = redraftStatus.text ?: "",
|
||||
inReplyToId = redraftStatus.inReplyToId,
|
||||
visibility = redraftStatus.visibility,
|
||||
|
@ -465,13 +479,15 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
|||
mediaAttachments = redraftStatus.attachments,
|
||||
sensitive = redraftStatus.sensitive,
|
||||
poll = redraftStatus.poll?.toNewPoll(status.createdAt)
|
||||
))
|
||||
)
|
||||
)
|
||||
startActivity(intent)
|
||||
}, { error ->
|
||||
},
|
||||
{ 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()
|
||||
|
|
|
@ -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
|
||||
|
@ -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<StatusViewData> =
|
||||
object : DiffUtil.ItemCallback<StatusViewData>() {
|
||||
override fun areItemsTheSame(
|
||||
|
|
|
@ -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<out List<TimelineStatus>>
|
||||
|
||||
|
@ -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<out List<TimelineStatus>> {
|
||||
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<out List<TimelineStatus>> {
|
||||
return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)
|
||||
.map { response ->
|
||||
|
@ -87,8 +103,11 @@ class TimelineRepositoryImpl(
|
|||
}
|
||||
|
||||
private fun addFromDbIfNeeded(
|
||||
accountId: Long, statuses: List<Either<Placeholder, Status>>,
|
||||
maxId: String?, sinceId: String?, limit: Int,
|
||||
accountId: Long,
|
||||
statuses: List<Either<Placeholder, Status>>,
|
||||
maxId: String?,
|
||||
sinceId: String?,
|
||||
limit: Int,
|
||||
requestMode: TimelineRequestMode
|
||||
): Single<List<TimelineStatus>> {
|
||||
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<out List<TimelineStatus>> {
|
||||
return timelineDao.getStatusesForAccount(accountId, maxId, sinceId, limit)
|
||||
|
@ -124,8 +145,10 @@ class TimelineRepositoryImpl(
|
|||
}
|
||||
|
||||
private fun saveStatusesToDb(
|
||||
accountId: Long, statuses: List<Status>,
|
||||
maxId: String?, sinceId: String?
|
||||
accountId: Long,
|
||||
statuses: List<Status>,
|
||||
maxId: String?,
|
||||
sinceId: String?
|
||||
): List<Either<Placeholder, Status>> {
|
||||
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,
|
||||
|
|
|
@ -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<Either<Placeholder, Status>>) {
|
||||
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)
|
||||
}
|
||||
|
@ -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<Either<Placeholder, Status>>,
|
||||
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 {
|
||||
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
|
||||
|
|
|
@ -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<AccountEntity>
|
||||
|
||||
}
|
||||
|
|
|
@ -21,14 +21,20 @@ 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,
|
||||
data class AccountEntity(
|
||||
@field:PrimaryKey(autoGenerate = true) var id: Long,
|
||||
val domain: String,
|
||||
var accessToken: String,
|
||||
var isActive: Boolean,
|
||||
|
@ -56,7 +62,8 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long,
|
|||
var activeNotifications: String = "[]",
|
||||
var emojis: List<Emoji> = emptyList(),
|
||||
var tabPreferences: List<TabData> = defaultTabs(),
|
||||
var notificationsFilter: String = "[\"follow_request\"]") {
|
||||
var notificationsFilter: String = "[\"follow_request\"]"
|
||||
) {
|
||||
|
||||
val identifier: String
|
||||
get() = "$domain:$accountId"
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,7 +131,6 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
} else {
|
||||
accounts.add(it)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -194,5 +189,4 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
|||
id == accountId
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -39,5 +39,4 @@ interface ConversationsDao {
|
|||
|
||||
@Query("DELETE FROM ConversationEntity WHERE accountId = :accountId")
|
||||
fun deleteForAccount(accountId: Long)
|
||||
|
||||
}
|
||||
|
|
|
@ -25,11 +25,16 @@ 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
|
||||
|
||||
|
|
|
@ -38,5 +38,4 @@ interface DraftDao {
|
|||
|
||||
@Query("SELECT * FROM DraftEntity WHERE id = :id")
|
||||
suspend fun find(id: Int): DraftEntity?
|
||||
|
||||
}
|
||||
|
|
|
@ -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<List<TimelineStatusWithAccount>>
|
||||
|
||||
|
||||
@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)
|
||||
}
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -16,13 +20,15 @@ import com.keylesspalace.tusky.entity.Status
|
|||
*/
|
||||
@Entity(
|
||||
primaryKeys = ["serverId", "timelineUserId"],
|
||||
foreignKeys = ([
|
||||
foreignKeys = (
|
||||
[
|
||||
ForeignKey(
|
||||
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")]
|
||||
)
|
||||
|
@ -70,7 +76,6 @@ data class TimelineAccountEntity(
|
|||
val bot: Boolean
|
||||
)
|
||||
|
||||
|
||||
class TimelineStatusWithAccount {
|
||||
@Embedded
|
||||
lateinit var status: TimelineStatusEntity
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -21,13 +21,13 @@ import dagger.Component
|
|||
import dagger.android.support.AndroidSupportInjectionModule
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
/**
|
||||
* Created by charlag on 3/21/18.
|
||||
*/
|
||||
|
||||
@Singleton
|
||||
@Component(modules = [
|
||||
@Component(
|
||||
modules = [
|
||||
AppModule::class,
|
||||
NetworkModule::class,
|
||||
AndroidSupportInjectionModule::class,
|
||||
|
@ -37,7 +37,8 @@ import javax.inject.Singleton
|
|||
ViewModelModule::class,
|
||||
RepositoryModule::class,
|
||||
MediaUploaderModule::class
|
||||
])
|
||||
]
|
||||
)
|
||||
interface AppComponent {
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
|
|
|
@ -58,7 +58,6 @@ object AppInjector {
|
|||
|
||||
override fun onActivityStopped(activity: Activity) {
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -74,7 +73,9 @@ object AppInjector {
|
|||
AndroidSupportInjection.inject(f)
|
||||
}
|
||||
}
|
||||
}, true)
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,6 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -75,7 +76,8 @@ class AppModule {
|
|||
return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB")
|
||||
.addTypeConverter(converters)
|
||||
.allowMainThreadQueries()
|
||||
.addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5,
|
||||
.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,
|
||||
|
@ -92,5 +94,4 @@ class AppModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun notifier(context: Context): Notifier = SystemNotifier(context)
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
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
|
||||
|
||||
|
|
|
@ -13,23 +13,25 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
|
||||
package com.keylesspalace.tusky.di
|
||||
|
||||
/**
|
||||
|
|
|
@ -112,7 +112,6 @@ class NetworkModule {
|
|||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||
.build()
|
||||
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -57,22 +57,22 @@ 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
|
||||
|
|
|
@ -17,7 +17,7 @@ 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,
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
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?,
|
||||
|
|
|
@ -45,4 +45,3 @@ data class Filter (
|
|||
return filter?.id.equals(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue