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```.
|
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
|
### Translation
|
||||||
Translations are done through https://weblate.tusky.app/projects/tusky/tusky/ .
|
Translations are done through our [Weblate](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.
|
To add a new language, click on the 'Start a new translation' button on at the bottom of the page.
|
||||||
|
|
||||||
### Kotlin
|
### 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
|
### 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
|
### 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
|
### 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:
|
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.room.testing.MigrationTestHelper
|
||||||
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.keylesspalace.tusky.db.AppDatabase
|
import com.keylesspalace.tusky.db.AppDatabase
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
@ -33,12 +33,15 @@ class MigrationsTest {
|
||||||
val active = true
|
val active = true
|
||||||
val accountId = "accountId"
|
val accountId = "accountId"
|
||||||
val username = "username"
|
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,
|
"https://picture.url", true, true, true, true, true, true, true,
|
||||||
true, "1000", "[]", "[{\"shortcode\": \"emoji\", \"url\": \"yes\"}]", 0, false,
|
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`," +
|
"`accountId`,`username`,`displayName`,`profilePictureUrl`,`notificationsEnabled`," +
|
||||||
"`notificationsMentioned`,`notificationsFollowed`,`notificationsReblogged`," +
|
"`notificationsMentioned`,`notificationsFollowed`,`notificationsReblogged`," +
|
||||||
"`notificationsFavorited`,`notificationSound`,`notificationVibration`," +
|
"`notificationsFavorited`,`notificationSound`,`notificationVibration`," +
|
||||||
|
@ -46,7 +49,8 @@ class MigrationsTest {
|
||||||
"`defaultPostPrivacy`,`defaultMediaSensitivity`,`alwaysShowSensitiveMedia`," +
|
"`defaultPostPrivacy`,`defaultMediaSensitivity`,`alwaysShowSensitiveMedia`," +
|
||||||
"`mediaPreviewEnabled`) " +
|
"`mediaPreviewEnabled`) " +
|
||||||
"VALUES (nullif(?, 0),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (nullif(?, 0),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
values)
|
values
|
||||||
|
)
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,13 @@ package com.keylesspalace.tusky
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
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.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.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
|
@ -41,8 +45,10 @@ class TimelineDAOTest {
|
||||||
timelineDao.insertInTransaction(status, author, reblogger)
|
timelineDao.insertInTransaction(status, author, reblogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
val resultsFromDb = timelineDao.getStatusesForAccount(setOne.first.timelineUserId,
|
val resultsFromDb = timelineDao.getStatusesForAccount(
|
||||||
maxId = "21", sinceId = ignoredOne.first.serverId, limit = 10)
|
setOne.first.timelineUserId,
|
||||||
|
maxId = "21", sinceId = ignoredOne.first.serverId, limit = 10
|
||||||
|
)
|
||||||
.blockingGet()
|
.blockingGet()
|
||||||
|
|
||||||
assertEquals(2, resultsFromDb.size)
|
assertEquals(2, resultsFromDb.size)
|
||||||
|
@ -71,7 +77,6 @@ class TimelineDAOTest {
|
||||||
assertEquals(author, result.account)
|
assertEquals(author, result.account)
|
||||||
assertEquals(status, result.status)
|
assertEquals(status, result.status)
|
||||||
assertNull(result.reblogAccount)
|
assertNull(result.reblogAccount)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -185,7 +190,6 @@ class TimelineDAOTest {
|
||||||
)
|
)
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
|
|
||||||
val even = accountId % 2 == 0L
|
val even = accountId % 2 == 0L
|
||||||
val status = TimelineStatusEntity(
|
val status = TimelineStatusEntity(
|
||||||
serverId = statusId.toString(),
|
serverId = statusId.toString(),
|
||||||
|
|
|
@ -2,13 +2,13 @@ package com.keylesspalace.tusky
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import android.text.util.Linkify
|
import android.text.util.Linkify
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import com.keylesspalace.tusky.databinding.ActivityAboutBinding
|
import com.keylesspalace.tusky.databinding.ActivityAboutBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.util.NoUnderlineURLSpan
|
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.interfaces.ReselectableFragment
|
||||||
import com.keylesspalace.tusky.pager.AccountPagerAdapter
|
import com.keylesspalace.tusky.pager.AccountPagerAdapter
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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.view.showMuteAccountDialog
|
||||||
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
||||||
import dagger.android.DispatchingAndroidInjector
|
import dagger.android.DispatchingAndroidInjector
|
||||||
|
@ -233,7 +242,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
override fun onTabUnselected(tab: TabLayout.Tab?) {}
|
||||||
|
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {}
|
override fun onTabSelected(tab: TabLayout.Tab?) {}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +322,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0
|
binding.swipeToRefreshLayout.isEnabled = verticalOffset == 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeNotificationBarTransparent() {
|
private fun makeNotificationBarTransparent() {
|
||||||
|
@ -347,12 +354,14 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
.setAction(R.string.action_retry) { viewModel.refresh() }
|
.setAction(R.string.action_retry) { viewModel.refresh() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
viewModel.accountFieldData.observe(this, {
|
viewModel.accountFieldData.observe(
|
||||||
|
this,
|
||||||
|
{
|
||||||
accountFieldAdapter.fields = it
|
accountFieldAdapter.fields = it
|
||||||
accountFieldAdapter.notifyDataSetChanged()
|
accountFieldAdapter.notifyDataSetChanged()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
viewModel.noteSaved.observe(this) {
|
viewModel.noteSaved.observe(this) {
|
||||||
binding.saveNoteInfo.visible(it, View.INVISIBLE)
|
binding.saveNoteInfo.visible(it, View.INVISIBLE)
|
||||||
}
|
}
|
||||||
|
@ -366,9 +375,12 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
viewModel.refresh()
|
viewModel.refresh()
|
||||||
adapter.refreshContent()
|
adapter.refreshContent()
|
||||||
}
|
}
|
||||||
viewModel.isRefreshing.observe(this, { isRefreshing ->
|
viewModel.isRefreshing.observe(
|
||||||
|
this,
|
||||||
|
{ isRefreshing ->
|
||||||
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
binding.swipeToRefreshLayout.isRefreshing = isRefreshing == true
|
||||||
})
|
}
|
||||||
|
)
|
||||||
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
binding.swipeToRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +433,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(binding.accountHeaderImageView)
|
.into(binding.accountHeaderImageView)
|
||||||
|
|
||||||
|
|
||||||
binding.accountAvatarImageView.setOnClickListener { avatarView ->
|
binding.accountAvatarImageView.setOnClickListener { avatarView ->
|
||||||
val intent = ViewMediaActivity.newSingleImageIntent(avatarView.context, account.avatar)
|
val intent = ViewMediaActivity.newSingleImageIntent(avatarView.context, account.avatar)
|
||||||
|
|
||||||
|
@ -478,7 +489,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
|
|
||||||
binding.accountMovedText.setCompoundDrawablesRelativeWithIntrinsicBounds(movedIcon, null, null, null)
|
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
|
// 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
|
// it's also now supported in Mastodon 3.3.0rc but called notifying and use different API call
|
||||||
if(!viewModel.isSelf && followState == FollowState.FOLLOWING
|
if (!viewModel.isSelf && followState == FollowState.FOLLOWING &&
|
||||||
&& (relation.subscribing != null || relation.notifying != null)) {
|
(relation.subscribing != null || relation.notifying != null)
|
||||||
|
) {
|
||||||
binding.accountSubscribeButton.show()
|
binding.accountSubscribeButton.show()
|
||||||
binding.accountSubscribeButton.setOnClickListener {
|
binding.accountSubscribeButton.setOnClickListener {
|
||||||
viewModel.changeSubscribingState()
|
viewModel.changeSubscribingState()
|
||||||
|
@ -648,7 +659,6 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
binding.accountMuteButton.hide()
|
binding.accountMuteButton.hide()
|
||||||
updateMuteButton()
|
updateMuteButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
binding.accountFloatingActionButton.hide()
|
binding.accountFloatingActionButton.hide()
|
||||||
binding.accountFollowButton.hide()
|
binding.accountFollowButton.hide()
|
||||||
|
@ -698,11 +708,9 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.action_show_reblogs)
|
getString(R.string.action_show_reblogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
menu.removeItem(R.id.action_show_reblogs)
|
menu.removeItem(R.id.action_show_reblogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// It shouldn't be possible to block, mute or report yourself.
|
// It shouldn't be possible to block, mute or report yourself.
|
||||||
menu.removeItem(R.id.action_block)
|
menu.removeItem(R.id.action_block)
|
||||||
|
@ -772,8 +780,10 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
|
|
||||||
private fun mention() {
|
private fun mention() {
|
||||||
loadedAccount?.let {
|
loadedAccount?.let {
|
||||||
val intent = ComposeActivity.startIntent(this,
|
val intent = ComposeActivity.startIntent(
|
||||||
ComposeActivity.ComposeOptions(mentionedUsernames = setOf(it.username)))
|
this,
|
||||||
|
ComposeActivity.ComposeOptions(mentionedUsernames = setOf(it.username))
|
||||||
|
)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -849,5 +859,4 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,13 @@ import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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.AccountsInListViewModel
|
||||||
import com.keylesspalace.tusky.viewmodel.State
|
import com.keylesspalace.tusky.viewmodel.State
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
@ -146,11 +152,15 @@ class AccountsInListFragment : DialogFragment(), Injectable {
|
||||||
viewModel.load(listId)
|
viewModel.load(listId)
|
||||||
}
|
}
|
||||||
if (error is IOException) {
|
if (error is IOException) {
|
||||||
binding.messageView.setup(R.drawable.elephant_offline,
|
binding.messageView.setup(
|
||||||
R.string.error_network, retryAction)
|
R.drawable.elephant_offline,
|
||||||
|
R.string.error_network, retryAction
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
binding.messageView.setup(R.drawable.elephant_error,
|
binding.messageView.setup(
|
||||||
R.string.error_generic, retryAction)
|
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 {
|
override fun areContentsTheSame(oldItem: AccountInfo, newItem: AccountInfo): Boolean {
|
||||||
return oldItem.second == newItem.second
|
return oldItem.second == newItem.second &&
|
||||||
&& oldItem.first.deepEquals(newItem.first)
|
oldItem.first.deepEquals(newItem.first)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,6 @@ abstract class BottomSheetActivity : BaseActivity() {
|
||||||
|
|
||||||
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
|
override fun onSlide(bottomSheet: View, slideOffset: Float) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun viewUrl(url: String, lookupFallbackBehavior: PostLookupFallbackBehavior = PostLookupFallbackBehavior.OPEN_IN_BROWSER) {
|
open fun viewUrl(url: String, lookupFallbackBehavior: PostLookupFallbackBehavior = PostLookupFallbackBehavior.OPEN_IN_BROWSER) {
|
||||||
|
@ -74,7 +73,8 @@ abstract class BottomSheetActivity : BaseActivity() {
|
||||||
resolve = true
|
resolve = true
|
||||||
).observeOn(AndroidSchedulers.mainThread())
|
).observeOn(AndroidSchedulers.mainThread())
|
||||||
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
||||||
.subscribe({ (accounts, statuses) ->
|
.subscribe(
|
||||||
|
{ (accounts, statuses) ->
|
||||||
if (getCancelSearchRequested(url)) {
|
if (getCancelSearchRequested(url)) {
|
||||||
return@subscribe
|
return@subscribe
|
||||||
}
|
}
|
||||||
|
@ -90,12 +90,14 @@ abstract class BottomSheetActivity : BaseActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
performUrlFallbackAction(url, lookupFallbackBehavior)
|
performUrlFallbackAction(url, lookupFallbackBehavior)
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
if (!getCancelSearchRequested(url)) {
|
if (!getCancelSearchRequested(url)) {
|
||||||
onEndSearch(url)
|
onEndSearch(url)
|
||||||
performUrlFallbackAction(url, lookupFallbackBehavior)
|
performUrlFallbackAction(url, lookupFallbackBehavior)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
onBeginSearch(url)
|
onBeginSearch(url)
|
||||||
}
|
}
|
||||||
|
@ -187,7 +189,8 @@ fun looksLikeMastodonUrl(urlString: String): Boolean {
|
||||||
|
|
||||||
if (uri.query != null ||
|
if (uri.query != null ||
|
||||||
uri.fragment != null ||
|
uri.fragment != null ||
|
||||||
uri.path == null) {
|
uri.path == null
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,18 +36,24 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.resource.bitmap.FitCenter
|
import com.bumptech.glide.load.resource.bitmap.FitCenter
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||||
|
import com.canhub.cropper.CropImage
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
|
import com.keylesspalace.tusky.adapter.AccountFieldEditAdapter
|
||||||
import com.keylesspalace.tusky.databinding.ActivityEditProfileBinding
|
import com.keylesspalace.tusky.databinding.ActivityEditProfileBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
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.keylesspalace.tusky.viewmodel.EditProfileViewModel
|
||||||
import com.mikepenz.iconics.IconicsDrawable
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
import com.mikepenz.iconics.utils.sizeDp
|
import com.mikepenz.iconics.utils.sizeDp
|
||||||
import com.canhub.cropper.CropImage
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class EditProfileActivity : BaseActivity(), Injectable {
|
class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
|
@ -150,7 +156,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
.load(me.header)
|
.load(me.header)
|
||||||
.into(binding.headerPreview)
|
.into(binding.headerPreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Error -> {
|
is Error -> {
|
||||||
|
@ -159,7 +164,6 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
viewModel.obtainProfile()
|
viewModel.obtainProfile()
|
||||||
}
|
}
|
||||||
snackbar.show()
|
snackbar.show()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,7 +183,9 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
observeImage(viewModel.avatarData, binding.avatarPreview, binding.avatarProgressBar, true)
|
observeImage(viewModel.avatarData, binding.avatarPreview, binding.avatarProgressBar, true)
|
||||||
observeImage(viewModel.headerData, binding.headerPreview, binding.headerProgressBar, false)
|
observeImage(viewModel.headerData, binding.headerPreview, binding.headerProgressBar, false)
|
||||||
|
|
||||||
viewModel.saveData.observe(this, {
|
viewModel.saveData.observe(
|
||||||
|
this,
|
||||||
|
{
|
||||||
when (it) {
|
when (it) {
|
||||||
is Success -> {
|
is Success -> {
|
||||||
finish()
|
finish()
|
||||||
|
@ -191,8 +197,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
onSaveFailure(it.errorMessage)
|
onSaveFailure(it.errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
@ -203,18 +209,24 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
if (!isFinishing) {
|
if (!isFinishing) {
|
||||||
viewModel.updateProfile(binding.displayNameEditText.text.toString(),
|
viewModel.updateProfile(
|
||||||
|
binding.displayNameEditText.text.toString(),
|
||||||
binding.noteEditText.text.toString(),
|
binding.noteEditText.text.toString(),
|
||||||
binding.lockedCheckBox.isChecked,
|
binding.lockedCheckBox.isChecked,
|
||||||
accountFieldEditAdapter.getFieldData())
|
accountFieldEditAdapter.getFieldData()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun observeImage(liveData: LiveData<Resource<Bitmap>>,
|
private fun observeImage(
|
||||||
|
liveData: LiveData<Resource<Bitmap>>,
|
||||||
imageView: ImageView,
|
imageView: ImageView,
|
||||||
progressBar: View,
|
progressBar: View,
|
||||||
roundedCorners: Boolean) {
|
roundedCorners: Boolean
|
||||||
liveData.observe(this, {
|
) {
|
||||||
|
liveData.observe(
|
||||||
|
this,
|
||||||
|
{
|
||||||
|
|
||||||
when (it) {
|
when (it) {
|
||||||
is Success -> {
|
is Success -> {
|
||||||
|
@ -242,10 +254,10 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
onResizeFailure()
|
onResizeFailure()
|
||||||
it.consumed = true
|
it.consumed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onMediaPick(pickType: PickType) {
|
private fun onMediaPick(pickType: PickType) {
|
||||||
|
@ -261,8 +273,11 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
override fun onRequestPermissionsResult(
|
||||||
grantResults: IntArray) {
|
requestCode: Int,
|
||||||
|
permissions: Array<String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE -> {
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE -> {
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
@ -310,11 +325,13 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.save(binding.displayNameEditText.text.toString(),
|
viewModel.save(
|
||||||
|
binding.displayNameEditText.text.toString(),
|
||||||
binding.noteEditText.text.toString(),
|
binding.noteEditText.text.toString(),
|
||||||
binding.lockedCheckBox.isChecked,
|
binding.lockedCheckBox.isChecked,
|
||||||
accountFieldEditAdapter.getFieldData(),
|
accountFieldEditAdapter.getFieldData(),
|
||||||
this)
|
this
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onSaveFailure(msg: String?) {
|
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()
|
Snackbar.make(binding.avatarButton, R.string.error_media_upload_sending, Snackbar.LENGTH_LONG).show()
|
||||||
endMediaPicking()
|
endMediaPicking()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.lang.Exception
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class FiltersActivity : BaseActivity() {
|
class FiltersActivity : BaseActivity() {
|
||||||
|
@ -94,8 +93,10 @@ class FiltersActivity: BaseActivity() {
|
||||||
} else {
|
} else {
|
||||||
// Keep the filter, but remove it from this context
|
// Keep the filter, but remove it from this context
|
||||||
val oldFilter = filters[itemIndex]
|
val oldFilter = filters[itemIndex]
|
||||||
val newFilter = Filter(oldFilter.id, oldFilter.phrase, oldFilter.context.filter { c -> c != context },
|
val newFilter = Filter(
|
||||||
oldFilter.expiresAt, oldFilter.irreversible, oldFilter.wholeWord)
|
oldFilter.id, oldFilter.phrase, oldFilter.context.filter { c -> c != context },
|
||||||
|
oldFilter.expiresAt, oldFilter.irreversible, oldFilter.wholeWord
|
||||||
|
)
|
||||||
updateFilter(newFilter, itemIndex)
|
updateFilter(newFilter, itemIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,8 +144,10 @@ class FiltersActivity: BaseActivity() {
|
||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
|
.setPositiveButton(R.string.filter_dialog_update_button) { _, _ ->
|
||||||
val oldFilter = filters[itemIndex]
|
val oldFilter = filters[itemIndex]
|
||||||
val newFilter = Filter(oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context,
|
val newFilter = Filter(
|
||||||
oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked)
|
oldFilter.id, binding.phraseEditText.text.toString(), oldFilter.context,
|
||||||
|
oldFilter.expiresAt, oldFilter.irreversible, binding.phraseWholeWord.isChecked
|
||||||
|
)
|
||||||
updateFilter(newFilter, itemIndex)
|
updateFilter(newFilter, itemIndex)
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.filter_dialog_remove_button) { _, _ ->
|
.setNegativeButton(R.string.filter_dialog_remove_button) { _, _ ->
|
||||||
|
@ -173,11 +176,15 @@ class FiltersActivity: BaseActivity() {
|
||||||
binding.filterProgressBar.hide()
|
binding.filterProgressBar.hide()
|
||||||
binding.filterMessageView.show()
|
binding.filterMessageView.show()
|
||||||
if (t is IOException) {
|
if (t is IOException) {
|
||||||
binding.filterMessageView.setup(R.drawable.elephant_offline,
|
binding.filterMessageView.setup(
|
||||||
R.string.error_network) { loadFilters() }
|
R.drawable.elephant_offline,
|
||||||
|
R.string.error_network
|
||||||
|
) { loadFilters() }
|
||||||
} else {
|
} else {
|
||||||
binding.filterMessageView.setup(R.drawable.elephant_error,
|
binding.filterMessageView.setup(
|
||||||
R.string.error_generic) { loadFilters() }
|
R.drawable.elephant_error,
|
||||||
|
R.string.error_generic
|
||||||
|
) { loadFilters() }
|
||||||
}
|
}
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
package com.keylesspalace.tusky
|
package com.keylesspalace.tusky
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.RawRes
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.RawRes
|
||||||
import com.keylesspalace.tusky.databinding.ActivityLicenseBinding
|
import com.keylesspalace.tusky.databinding.ActivityLicenseBinding
|
||||||
import com.keylesspalace.tusky.util.IOUtils
|
import com.keylesspalace.tusky.util.IOUtils
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
@ -41,7 +41,6 @@ class LicenseActivity : BaseActivity() {
|
||||||
setTitle(R.string.title_licenses)
|
setTitle(R.string.title_licenses)
|
||||||
|
|
||||||
loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView)
|
loadFileIntoTextView(R.raw.apache, binding.licenseApacheTextView)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) {
|
private fun loadFileIntoTextView(@RawRes fileId: Int, textView: TextView) {
|
||||||
|
|
|
@ -23,25 +23,41 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.activity.viewModels
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
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.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import at.connyduck.sparkbutton.helpers.Utils
|
import at.connyduck.sparkbutton.helpers.Utils
|
||||||
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
|
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.from
|
||||||
import autodispose2.autoDispose
|
import autodispose2.autoDispose
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
||||||
import com.keylesspalace.tusky.databinding.ActivityListsBinding
|
import com.keylesspalace.tusky.databinding.ActivityListsBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.entity.MastoList
|
import com.keylesspalace.tusky.entity.MastoList
|
||||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
import com.keylesspalace.tusky.util.ThemeUtils
|
||||||
import com.keylesspalace.tusky.util.*
|
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
|
||||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event.*
|
import com.keylesspalace.tusky.viewmodel.ListsViewModel.Event
|
||||||
import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.*
|
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.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
|
@ -84,7 +100,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
binding.listsRecycler.adapter = adapter
|
binding.listsRecycler.adapter = adapter
|
||||||
binding.listsRecycler.layoutManager = LinearLayoutManager(this)
|
binding.listsRecycler.layoutManager = LinearLayoutManager(this)
|
||||||
binding.listsRecycler.addItemDecoration(
|
binding.listsRecycler.addItemDecoration(
|
||||||
DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
|
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
|
||||||
|
)
|
||||||
|
|
||||||
viewModel.state
|
viewModel.state
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
@ -101,9 +118,9 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
.subscribe { event ->
|
.subscribe { event ->
|
||||||
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
|
@Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA")
|
||||||
when (event) {
|
when (event) {
|
||||||
CREATE_ERROR -> showMessage(R.string.error_create_list)
|
Event.CREATE_ERROR -> showMessage(R.string.error_create_list)
|
||||||
RENAME_ERROR -> showMessage(R.string.error_rename_list)
|
Event.RENAME_ERROR -> showMessage(R.string.error_rename_list)
|
||||||
DELETE_ERROR -> showMessage(R.string.error_delete_list)
|
Event.DELETE_ERROR -> showMessage(R.string.error_delete_list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +138,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
.setView(layout)
|
.setView(layout)
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
if (list == null) R.string.action_create_list
|
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)
|
onPickedDialogName(editText.text, list?.id)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
@ -145,7 +163,6 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun update(state: ListsViewModel.State) {
|
private fun update(state: ListsViewModel.State) {
|
||||||
adapter.submitList(state.lists)
|
adapter.submitList(state.lists)
|
||||||
binding.progressBar.visible(state.loadingState == LOADING)
|
binding.progressBar.visible(state.loadingState == LOADING)
|
||||||
|
@ -166,8 +183,10 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
LOADED ->
|
LOADED ->
|
||||||
if (state.lists.isEmpty()) {
|
if (state.lists.isEmpty()) {
|
||||||
binding.messageView.show()
|
binding.messageView.show()
|
||||||
binding.messageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty,
|
binding.messageView.setup(
|
||||||
null)
|
R.drawable.elephant_friend_empty, R.string.message_empty,
|
||||||
|
null
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
binding.messageView.hide()
|
binding.messageView.hide()
|
||||||
}
|
}
|
||||||
|
@ -182,7 +201,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
|
|
||||||
private fun onListSelected(listId: String) {
|
private fun onListSelected(listId: String) {
|
||||||
startActivityWithSlideInAnimation(
|
startActivityWithSlideInAnimation(
|
||||||
ModalTimelineActivity.newIntent(this, TimelineViewModel.Kind.LIST, listId))
|
ModalTimelineActivity.newIntent(this, TimelineViewModel.Kind.LIST, listId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openListSettings(list: MastoList) {
|
private fun openListSettings(list: MastoList) {
|
||||||
|
@ -219,8 +239,8 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ListsAdapter
|
private inner class ListsAdapter :
|
||||||
: ListAdapter<MastoList, ListsAdapter.ListViewHolder>(ListsDiffer) {
|
ListAdapter<MastoList, ListsAdapter.ListViewHolder>(ListsDiffer) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
|
||||||
return LayoutInflater.from(parent.context).inflate(R.layout.item_list, parent, false)
|
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
|
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 {
|
View.OnClickListener {
|
||||||
val nameTextView: TextView = view.findViewById(R.id.list_name_textview)
|
val nameTextView: TextView = view.findViewById(R.id.list_name_textview)
|
||||||
val moreButton: ImageButton = view.findViewById(R.id.editListButton)
|
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.AccessToken
|
||||||
import com.keylesspalace.tusky.entity.AppCredentials
|
import com.keylesspalace.tusky.entity.AppCredentials
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
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 okhttp3.HttpUrl
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.Callback
|
import retrofit2.Callback
|
||||||
|
@ -75,7 +79,8 @@ class LoginActivity : BaseActivity(), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences = getSharedPreferences(
|
preferences = getSharedPreferences(
|
||||||
getString(R.string.preferences_file_key), Context.MODE_PRIVATE)
|
getString(R.string.preferences_file_key), Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
binding.loginButton.setOnClickListener { onButtonClick() }
|
binding.loginButton.setOnClickListener { onButtonClick() }
|
||||||
|
|
||||||
|
@ -95,7 +100,6 @@ class LoginActivity : BaseActivity(), Injectable {
|
||||||
} else {
|
} else {
|
||||||
binding.toolbar.visibility = View.GONE
|
binding.toolbar.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requiresLogin(): Boolean {
|
override fun requiresLogin(): Boolean {
|
||||||
|
@ -134,8 +138,10 @@ class LoginActivity : BaseActivity(), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
val callback = object : Callback<AppCredentials> {
|
val callback = object : Callback<AppCredentials> {
|
||||||
override fun onResponse(call: Call<AppCredentials>,
|
override fun onResponse(
|
||||||
response: Response<AppCredentials>) {
|
call: Call<AppCredentials>,
|
||||||
|
response: Response<AppCredentials>
|
||||||
|
) {
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
binding.loginButton.isEnabled = true
|
binding.loginButton.isEnabled = true
|
||||||
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
binding.domainTextInputLayout.error = getString(R.string.error_failed_app_registration)
|
||||||
|
@ -165,11 +171,12 @@ class LoginActivity : BaseActivity(), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
mastodonApi
|
mastodonApi
|
||||||
.authenticateApp(domain, getString(R.string.app_name), oauthRedirectUri,
|
.authenticateApp(
|
||||||
OAUTH_SCOPES, getString(R.string.tusky_website))
|
domain, getString(R.string.app_name), oauthRedirectUri,
|
||||||
|
OAUTH_SCOPES, getString(R.string.tusky_website)
|
||||||
|
)
|
||||||
.enqueue(callback)
|
.enqueue(callback)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) {
|
private fun redirectUserToAuthorizeAndLogin(domain: String, clientId: String) {
|
||||||
|
@ -224,31 +231,27 @@ class LoginActivity : BaseActivity(), Injectable {
|
||||||
} else {
|
} else {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
||||||
Log.e(TAG, String.format("%s %s",
|
Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), response.message()))
|
||||||
getString(R.string.error_retrieving_oauth_token),
|
|
||||||
response.message()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
|
override fun onFailure(call: Call<AccessToken>, t: Throwable) {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
binding.domainTextInputLayout.error = getString(R.string.error_retrieving_oauth_token)
|
||||||
Log.e(TAG, String.format("%s %s",
|
Log.e(TAG, "%s %s".format(getString(R.string.error_retrieving_oauth_token), t.message))
|
||||||
getString(R.string.error_retrieving_oauth_token),
|
|
||||||
t.message))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mastodonApi.fetchOAuthToken(domain, clientId, clientSecret, redirectUri, code,
|
mastodonApi.fetchOAuthToken(
|
||||||
"authorization_code").enqueue(callback)
|
domain, clientId, clientSecret, redirectUri, code,
|
||||||
|
"authorization_code"
|
||||||
|
).enqueue(callback)
|
||||||
} else if (error != null) {
|
} else if (error != null) {
|
||||||
/* Authorization failed. Put the error response where the user can read it and they
|
/* Authorization failed. Put the error response where the user can read it and they
|
||||||
* can try again. */
|
* can try again. */
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied)
|
binding.domainTextInputLayout.error = getString(R.string.error_authorization_denied)
|
||||||
Log.e(TAG, String.format("%s %s",
|
Log.e(TAG, "%s %s".format(getString(R.string.error_authorization_denied), error))
|
||||||
getString(R.string.error_authorization_denied),
|
|
||||||
error))
|
|
||||||
} else {
|
} else {
|
||||||
// This case means a junk response was received somehow.
|
// This case means a junk response was received somehow.
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
|
@ -4,9 +4,9 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
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.TimelineFragment
|
||||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
||||||
|
import com.keylesspalace.tusky.databinding.ActivityModalTimelineBinding
|
||||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
||||||
import dagger.android.DispatchingAndroidInjector
|
import dagger.android.DispatchingAndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
|
@ -48,13 +48,15 @@ class ModalTimelineActivity : BottomSheetActivity(), ActionButtonActivity, HasAn
|
||||||
private const val ARG_ARG = "arg"
|
private const val ARG_ARG = "arg"
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newIntent(context: Context, kind: TimelineViewModel.Kind,
|
fun newIntent(
|
||||||
argument: String?): Intent {
|
context: Context,
|
||||||
|
kind: TimelineViewModel.Kind,
|
||||||
|
argument: String?
|
||||||
|
): Intent {
|
||||||
val intent = Intent(context, ModalTimelineActivity::class.java)
|
val intent = Intent(context, ModalTimelineActivity::class.java)
|
||||||
intent.putExtra(ARG_KIND, kind)
|
intent.putExtra(ARG_KIND, kind)
|
||||||
intent.putExtra(ARG_ARG, argument)
|
intent.putExtra(ARG_ARG, argument)
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,9 @@ package com.keylesspalace.tusky
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.keylesspalace.tusky.components.notifications.NotificationHelper
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
|
|
||||||
import com.keylesspalace.tusky.components.notifications.NotificationHelper
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SplashActivity : AppCompatActivity(), Injectable {
|
class SplashActivity : AppCompatActivity(), Injectable {
|
||||||
|
@ -46,5 +45,4 @@ class SplashActivity : AppCompatActivity(), Injectable {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,12 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding
|
|
||||||
|
|
||||||
import com.keylesspalace.tusky.components.timeline.TimelineFragment
|
import com.keylesspalace.tusky.components.timeline.TimelineFragment
|
||||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel.Kind
|
import com.keylesspalace.tusky.components.timeline.TimelineViewModel.Kind
|
||||||
|
import com.keylesspalace.tusky.databinding.ActivityStatuslistBinding
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
import dagger.android.DispatchingAndroidInjector
|
import dagger.android.DispatchingAndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||||
|
|
||||||
|
@ -60,7 +57,6 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||||
val fragment = TimelineFragment.newInstance(kind)
|
val fragment = TimelineFragment.newInstance(kind)
|
||||||
replace(R.id.fragment_container, fragment)
|
replace(R.id.fragment_container, fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun androidInjector() = dispatchingAndroidInjector
|
override fun androidInjector() = dispatchingAndroidInjector
|
||||||
|
@ -81,5 +77,4 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||||
putExtra(EXTRA_KIND, Kind.BOOKMARKS.name)
|
putExtra(EXTRA_KIND, Kind.BOOKMARKS.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,9 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.keylesspalace.tusky.components.conversation.ConversationsFragment
|
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.TimelineFragment
|
||||||
import com.keylesspalace.tusky.components.timeline.TimelineViewModel
|
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 */
|
/** 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 HASHTAG = "Hashtag"
|
||||||
const val LIST = "List"
|
const val LIST = "List"
|
||||||
|
|
||||||
data class TabData(val id: String,
|
data class TabData(
|
||||||
|
val id: String,
|
||||||
@StringRes val text: Int,
|
@StringRes val text: Int,
|
||||||
@DrawableRes val icon: Int,
|
@DrawableRes val icon: Int,
|
||||||
val fragment: (List<String>) -> Fragment,
|
val fragment: (List<String>) -> Fragment,
|
||||||
|
|
|
@ -333,7 +333,6 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||||
.subscribe()
|
.subscribe()
|
||||||
|
|
||||||
}
|
}
|
||||||
tabsChanged = true
|
tabsChanged = true
|
||||||
}
|
}
|
||||||
|
@ -357,5 +356,4 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
||||||
private const val MIN_TAB_COUNT = 2
|
private const val MIN_TAB_COUNT = 2
|
||||||
private const val MAX_TAB_COUNT = 5
|
private const val MAX_TAB_COUNT = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
|
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
|
||||||
|
|
||||||
|
@ -102,7 +102,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
val realAttachs = attachments!!.map(AttachmentViewData::attachment)
|
val realAttachs = attachments!!.map(AttachmentViewData::attachment)
|
||||||
// Setup the view pager.
|
// Setup the view pager.
|
||||||
ImagePagerAdapter(this, realAttachs, initialPosition)
|
ImagePagerAdapter(this, realAttachs, initialPosition)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
imageUrl = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL)
|
imageUrl = intent.getStringExtra(EXTRA_SINGLE_IMAGE_URL)
|
||||||
?: throw IllegalArgumentException("attachment list or image url has to be set")
|
?: throw IllegalArgumentException("attachment list or image url has to be set")
|
||||||
|
@ -206,8 +205,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
|
|
||||||
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
val request = DownloadManager.Request(Uri.parse(url))
|
val request = DownloadManager.Request(Uri.parse(url))
|
||||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES,
|
request.setDestinationInExternalPublicDir(
|
||||||
getString(R.string.app_name) + "/" + filename)
|
Environment.DIRECTORY_PICTURES,
|
||||||
|
getString(R.string.app_name) + "/" + filename
|
||||||
|
)
|
||||||
downloadManager.enqueue(request)
|
downloadManager.enqueue(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +262,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to)))
|
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_media_to)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private var isCreating: Boolean = false
|
private var isCreating: Boolean = false
|
||||||
|
|
||||||
private fun shareImage(directory: File, url: String) {
|
private fun shareImage(directory: File, url: String) {
|
||||||
|
@ -284,7 +284,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
Log.e(TAG, "Error writing temporary media.")
|
Log.e(TAG, "Error writing temporary media.")
|
||||||
}
|
}
|
||||||
return@fromCallable false
|
return@fromCallable false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
@ -309,7 +308,6 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
Log.e(TAG, "Failed to download image", error)
|
Log.e(TAG, "Failed to download image", error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shareMediaFile(directory: File, url: String) {
|
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_ACCOUNT = 0
|
||||||
const val VIEW_TYPE_FOOTER = 1
|
const val VIEW_TYPE_FOOTER = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -16,16 +16,19 @@
|
||||||
package com.keylesspalace.tusky.adapter
|
package com.keylesspalace.tusky.adapter
|
||||||
|
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.databinding.ItemAccountFieldBinding
|
import com.keylesspalace.tusky.databinding.ItemAccountFieldBinding
|
||||||
import com.keylesspalace.tusky.entity.Emoji
|
import com.keylesspalace.tusky.entity.Emoji
|
||||||
import com.keylesspalace.tusky.entity.Field
|
import com.keylesspalace.tusky.entity.Field
|
||||||
import com.keylesspalace.tusky.entity.IdentityProof
|
import com.keylesspalace.tusky.entity.IdentityProof
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
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(
|
class AccountFieldAdapter(
|
||||||
private val linkListener: LinkListener,
|
private val linkListener: LinkListener,
|
||||||
|
@ -70,6 +73,5 @@ class AccountFieldAdapter(
|
||||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
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) {}
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MutableStringPair(var first: String, var second: String)
|
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.databinding.ItemAutocompleteAccountBinding
|
||||||
import com.keylesspalace.tusky.db.AccountEntity
|
import com.keylesspalace.tusky.db.AccountEntity
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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) {
|
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)
|
val animateAvatar = pm.getBoolean("animateGifAvatars", false)
|
||||||
|
|
||||||
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar)
|
loadAvatar(account.profilePictureUrl, binding.avatar, avatarRadius, animateAvatar)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
|
|
|
@ -22,7 +22,7 @@ import com.bumptech.glide.Glide
|
||||||
import com.keylesspalace.tusky.databinding.ItemEmojiButtonBinding
|
import com.keylesspalace.tusky.databinding.ItemEmojiButtonBinding
|
||||||
import com.keylesspalace.tusky.entity.Emoji
|
import com.keylesspalace.tusky.entity.Emoji
|
||||||
import com.keylesspalace.tusky.util.BindingHolder
|
import com.keylesspalace.tusky.util.BindingHolder
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
class EmojiAdapter(
|
class EmojiAdapter(
|
||||||
emojiList: List<Emoji>,
|
emojiList: List<Emoji>,
|
||||||
|
|
|
@ -16,7 +16,6 @@ package com.keylesspalace.tusky.adapter
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
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.databinding.ItemFollowRequestBinding
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
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(
|
class FollowRequestViewHolder(
|
||||||
private val binding: ItemFollowRequestBinding,
|
private val binding: ItemFollowRequestBinding,
|
||||||
|
|
|
@ -16,8 +16,6 @@ package com.keylesspalace.tusky.adapter
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.keylesspalace.tusky.R
|
|
||||||
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
|
import com.keylesspalace.tusky.databinding.ItemFollowRequestBinding
|
||||||
import com.keylesspalace.tusky.interfaces.AccountActionListener
|
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
|
override fun getItemCount() = if (accountLocked) 0 else 1
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class HeaderViewHolder(var textView: TextView) : RecyclerView.ViewHolder(textView)
|
class HeaderViewHolder(var textView: TextView) : RecyclerView.ViewHolder(textView)
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.adapter
|
package com.keylesspalace.tusky.adapter
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
class LoadingFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
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.interfaces.AccountActionListener
|
||||||
import com.keylesspalace.tusky.util.emojify
|
import com.keylesspalace.tusky.util.emojify
|
||||||
import com.keylesspalace.tusky.util.loadAvatar
|
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
|
* 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.databinding.ItemNetworkStateBinding
|
||||||
import com.keylesspalace.tusky.util.visible
|
import com.keylesspalace.tusky.util.visible
|
||||||
|
|
||||||
class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding,
|
class NetworkStateViewHolder(
|
||||||
private val retryCallback: () -> Unit)
|
private val binding: ItemNetworkStateBinding,
|
||||||
: RecyclerView.ViewHolder(binding.root) {
|
private val retryCallback: () -> Unit
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun setUpWithNetworkState(state: LoadState) {
|
fun setUpWithNetworkState(state: LoadState) {
|
||||||
binding.progressBar.visible(state == LoadState.Loading)
|
binding.progressBar.visible(state == LoadState.Loading)
|
||||||
|
@ -38,5 +39,4 @@ class NetworkStateViewHolder(private val binding: ItemNetworkStateBinding,
|
||||||
retryCallback()
|
retryCallback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -46,7 +46,8 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
emojis: List<Emoji>,
|
emojis: List<Emoji>,
|
||||||
mode: Int,
|
mode: Int,
|
||||||
resultClickListener: View.OnClickListener?,
|
resultClickListener: View.OnClickListener?,
|
||||||
animateEmojis: Boolean) {
|
animateEmojis: Boolean
|
||||||
|
) {
|
||||||
this.pollOptions = options
|
this.pollOptions = options
|
||||||
this.voteCount = voteCount
|
this.voteCount = voteCount
|
||||||
this.votersCount = votersCount
|
this.votersCount = votersCount
|
||||||
|
@ -62,7 +63,6 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
.map { pollOptions.indexOf(it) }
|
.map { pollOptions.indexOf(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemPollBinding> {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemPollBinding> {
|
||||||
val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = ItemPollBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return BindingHolder(binding)
|
return BindingHolder(binding)
|
||||||
|
@ -114,7 +114,6 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -60,7 +60,6 @@ class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
|
||||||
|
|
||||||
textView.setOnClickListener(clickListener)
|
textView.setOnClickListener(clickListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
class PreviewViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||||
|
|
|
@ -43,7 +43,8 @@ interface ItemInteractionListener {
|
||||||
fun onChipClicked(tab: TabData, tabPosition: Int, chipPosition: Int)
|
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 small: Boolean,
|
||||||
private val listener: ItemInteractionListener,
|
private val listener: ItemInteractionListener,
|
||||||
private var removeButtonEnabled: Boolean = false
|
private var removeButtonEnabled: Boolean = false
|
||||||
|
@ -77,7 +78,6 @@ class TabAdapter(private var data: List<TabData>,
|
||||||
binding.textView.setOnClickListener {
|
binding.textView.setOnClickListener {
|
||||||
listener.onTabAdded(tab)
|
listener.onTabAdded(tab)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
val binding = holder.binding as ItemTabPreferenceBinding
|
val binding = holder.binding as ItemTabPreferenceBinding
|
||||||
|
|
||||||
|
@ -143,7 +143,6 @@ class TabAdapter(private var data: List<TabData>,
|
||||||
binding.actionChip.setOnClickListener {
|
binding.actionChip.setOnClickListener {
|
||||||
listener.onActionChipClicked(tab, holder.bindingAdapterPosition)
|
listener.onActionChipClicked(tab, holder.bindingAdapterPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
binding.chipGroup.hide()
|
binding.chipGroup.hide()
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,8 +111,8 @@ class ThreadAdapter(
|
||||||
fun getItem(position: Int): StatusViewData.Concrete? = statuses.getOrNull(position)
|
fun getItem(position: Int): StatusViewData.Concrete? = statuses.getOrNull(position)
|
||||||
|
|
||||||
fun setDetailedStatusPosition(position: Int) {
|
fun setDetailedStatusPosition(position: Int) {
|
||||||
if (position != detailedStatusPosition
|
if (position != detailedStatusPosition &&
|
||||||
&& detailedStatusPosition != RecyclerView.NO_POSITION
|
detailedStatusPosition != RecyclerView.NO_POSITION
|
||||||
) {
|
) {
|
||||||
val prior = detailedStatusPosition
|
val prior = detailedStatusPosition
|
||||||
detailedStatusPosition = position
|
detailedStatusPosition = position
|
||||||
|
|
|
@ -67,12 +67,12 @@ class AnnouncementAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
item.reactions.forEachIndexed { i, reaction ->
|
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 {
|
?: Chip(ContextThemeWrapper(chips.context, R.style.Widget_MaterialComponents_Chip_Choice)).apply {
|
||||||
isCheckable = true
|
isCheckable = true
|
||||||
checkedIcon = null
|
checkedIcon = null
|
||||||
chips.addView(this, i)
|
chips.addView(this, i)
|
||||||
})
|
}
|
||||||
.apply {
|
.apply {
|
||||||
val emojiText = if (reaction.url == null) {
|
val emojiText = if (reaction.url == null) {
|
||||||
reaction.name
|
reaction.name
|
||||||
|
@ -81,12 +81,14 @@ class AnnouncementAdapter(
|
||||||
}
|
}
|
||||||
this.text = ("$emojiText ${reaction.count}")
|
this.text = ("$emojiText ${reaction.count}")
|
||||||
.emojify(
|
.emojify(
|
||||||
listOf(Emoji(
|
listOf(
|
||||||
|
Emoji(
|
||||||
reaction.name,
|
reaction.name,
|
||||||
reaction.url ?: "",
|
reaction.url ?: "",
|
||||||
reaction.staticUrl ?: "",
|
reaction.staticUrl ?: "",
|
||||||
null
|
null
|
||||||
)),
|
)
|
||||||
|
),
|
||||||
this,
|
this,
|
||||||
animateEmojis
|
animateEmojis
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,7 +34,12 @@ import com.keylesspalace.tusky.databinding.ActivityAnnouncementsBinding
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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 com.keylesspalace.tusky.view.EmojiPicker
|
||||||
import javax.inject.Inject
|
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.Emoji
|
||||||
import com.keylesspalace.tusky.entity.Instance
|
import com.keylesspalace.tusky.entity.Instance
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
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 io.reactivex.rxjava3.core.Single
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -45,7 +50,8 @@ class AnnouncementsViewModel @Inject constructor(
|
||||||
val emojis: LiveData<List<Emoji>> = emojisMutable
|
val emojis: LiveData<List<Emoji>> = emojisMutable
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Single.zip(mastodonApi.getCustomEmojis(),
|
Single.zip(
|
||||||
|
mastodonApi.getCustomEmojis(),
|
||||||
appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
|
appDatabase.instanceDao().loadMetadataForInstance(accountManager.activeAccount?.domain!!)
|
||||||
.map<Either<InstanceEntity, Instance>> { Either.Left(it) }
|
.map<Either<InstanceEntity, Instance>> { Either.Left(it) }
|
||||||
.onErrorResumeNext {
|
.onErrorResumeNext {
|
||||||
|
@ -62,22 +68,27 @@ class AnnouncementsViewModel @Inject constructor(
|
||||||
either.asRight().pollLimits?.maxOptionChars,
|
either.asRight().pollLimits?.maxOptionChars,
|
||||||
either.asRight().version
|
either.asRight().version
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.doOnSuccess {
|
.doOnSuccess {
|
||||||
appDatabase.instanceDao().insertOrReplace(it)
|
appDatabase.instanceDao().insertOrReplace(it)
|
||||||
}
|
}
|
||||||
.subscribe({
|
.subscribe(
|
||||||
|
{
|
||||||
emojisMutable.postValue(it.emojiList.orEmpty())
|
emojisMutable.postValue(it.emojiList.orEmpty())
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
Log.w(TAG, "Failed to get custom emojis.", it)
|
Log.w(TAG, "Failed to get custom emojis.", it)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.autoDispose()
|
.autoDispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
announcementsMutable.postValue(Loading())
|
announcementsMutable.postValue(Loading())
|
||||||
mastodonApi.listAnnouncements()
|
mastodonApi.listAnnouncements()
|
||||||
.subscribe({
|
.subscribe(
|
||||||
|
{
|
||||||
announcementsMutable.postValue(Success(it))
|
announcementsMutable.postValue(Success(it))
|
||||||
it.filter { announcement -> !announcement.read }
|
it.filter { announcement -> !announcement.read }
|
||||||
.forEach { announcement ->
|
.forEach { announcement ->
|
||||||
|
@ -92,15 +103,18 @@ class AnnouncementsViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
.autoDispose()
|
.autoDispose()
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
announcementsMutable.postValue(Error(cause = it))
|
announcementsMutable.postValue(Error(cause = it))
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.autoDispose()
|
.autoDispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addReaction(announcementId: String, name: String) {
|
fun addReaction(announcementId: String, name: String) {
|
||||||
mastodonApi.addAnnouncementReaction(announcementId, name)
|
mastodonApi.addAnnouncementReaction(announcementId, name)
|
||||||
.subscribe({
|
.subscribe(
|
||||||
|
{
|
||||||
announcementsMutable.postValue(
|
announcementsMutable.postValue(
|
||||||
Success(
|
Success(
|
||||||
announcements.value!!.data!!.map { announcement ->
|
announcements.value!!.data!!.map { announcement ->
|
||||||
|
@ -139,15 +153,18 @@ class AnnouncementsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
Log.w(TAG, "Failed to add reaction to the announcement.", it)
|
Log.w(TAG, "Failed to add reaction to the announcement.", it)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.autoDispose()
|
.autoDispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeReaction(announcementId: String, name: String) {
|
fun removeReaction(announcementId: String, name: String) {
|
||||||
mastodonApi.removeAnnouncementReaction(announcementId, name)
|
mastodonApi.removeAnnouncementReaction(announcementId, name)
|
||||||
.subscribe({
|
.subscribe(
|
||||||
|
{
|
||||||
announcementsMutable.postValue(
|
announcementsMutable.postValue(
|
||||||
Success(
|
Success(
|
||||||
announcements.value!!.data!!.map { announcement ->
|
announcements.value!!.data!!.map { announcement ->
|
||||||
|
@ -174,9 +191,11 @@ class AnnouncementsViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
Log.w(TAG, "Failed to remove reaction from the announcement.", it)
|
Log.w(TAG, "Failed to remove reaction from the announcement.", it)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.autoDispose()
|
.autoDispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,10 @@ import android.view.KeyEvent
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.ColorInt
|
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.NewPoll
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
import com.mikepenz.iconics.utils.colorInt
|
import com.mikepenz.iconics.utils.colorInt
|
||||||
|
@ -83,7 +99,8 @@ import javax.inject.Inject
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
class ComposeActivity : BaseActivity(),
|
class ComposeActivity :
|
||||||
|
BaseActivity(),
|
||||||
ComposeOptionsListener,
|
ComposeOptionsListener,
|
||||||
ComposeAutoCompleteAdapter.AutocompletionProvider,
|
ComposeAutoCompleteAdapter.AutocompletionProvider,
|
||||||
OnEmojiSelectedListener,
|
OnEmojiSelectedListener,
|
||||||
|
@ -288,8 +305,9 @@ class ComposeActivity : BaseActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// work around Android platform bug -> https://issuetracker.google.com/issues/67102093
|
// work around Android platform bug -> https://issuetracker.google.com/issues/67102093
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O ||
|
||||||
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) {
|
Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1
|
||||||
|
) {
|
||||||
binding.composeEditField.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
binding.composeEditField.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,9 +348,9 @@ class ComposeActivity : BaseActivity(),
|
||||||
updateScheduleButton()
|
updateScheduleButton()
|
||||||
}
|
}
|
||||||
combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
|
combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
|
||||||
val active = poll == null
|
val active = poll == null &&
|
||||||
&& media!!.size != 4
|
media!!.size != 4 &&
|
||||||
&& (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
(media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
||||||
enableButton(binding.composeAddMediaButton, active, active)
|
enableButton(binding.composeAddMediaButton, active, active)
|
||||||
enablePollButton(media.isNullOrEmpty())
|
enablePollButton(media.isNullOrEmpty())
|
||||||
}.subscribe()
|
}.subscribe()
|
||||||
|
@ -393,7 +411,6 @@ class ComposeActivity : BaseActivity(),
|
||||||
setDisplayShowHomeEnabled(true)
|
setDisplayShowHomeEnabled(true)
|
||||||
setHomeAsUpIndicator(R.drawable.ic_close_24dp)
|
setHomeAsUpIndicator(R.drawable.ic_close_24dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupAvatar(preferences: SharedPreferences, activeAccount: AccountEntity) {
|
private fun setupAvatar(preferences: SharedPreferences, activeAccount: AccountEntity) {
|
||||||
|
@ -409,8 +426,10 @@ class ComposeActivity : BaseActivity(),
|
||||||
avatarSize / 8,
|
avatarSize / 8,
|
||||||
animateAvatars
|
animateAvatars
|
||||||
)
|
)
|
||||||
binding.composeAvatar.contentDescription = getString(R.string.compose_active_account_description,
|
binding.composeAvatar.contentDescription = getString(
|
||||||
activeAccount.fullName)
|
R.string.compose_active_account_description,
|
||||||
|
activeAccount.fullName
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replaceTextAtCaret(text: CharSequence) {
|
private fun replaceTextAtCaret(text: CharSequence) {
|
||||||
|
@ -468,7 +487,6 @@ class ComposeActivity : BaseActivity(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun atButtonClicked() {
|
private fun atButtonClicked() {
|
||||||
prependSelectedWordsWith("@")
|
prependSelectedWordsWith("@")
|
||||||
}
|
}
|
||||||
|
@ -502,7 +520,6 @@ class ComposeActivity : BaseActivity(),
|
||||||
binding.composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp)
|
binding.composeHideMediaButton.setImageResource(R.drawable.ic_hide_media_24dp)
|
||||||
binding.composeHideMediaButton.isClickable = false
|
binding.composeHideMediaButton.isClickable = false
|
||||||
ContextCompat.getColor(this, R.color.transparent_tusky_blue)
|
ContextCompat.getColor(this, R.color.transparent_tusky_blue)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
binding.composeHideMediaButton.isClickable = true
|
binding.composeHideMediaButton.isClickable = true
|
||||||
if (markMediaSensitive) {
|
if (markMediaSensitive) {
|
||||||
|
@ -615,9 +632,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||||
addMediaBehavior.removeBottomSheetCallback(this)
|
addMediaBehavior.removeBottomSheetCallback(this)
|
||||||
if (ContextCompat.checkSelfPermission(this@ComposeActivity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
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),
|
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||||
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE)
|
PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
pickMediaFile.launch(true)
|
pickMediaFile.launch(true)
|
||||||
}
|
}
|
||||||
|
@ -633,8 +652,10 @@ class ComposeActivity : BaseActivity(),
|
||||||
private fun openPollDialog() {
|
private fun openPollDialog() {
|
||||||
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
addMediaBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||||
val instanceParams = viewModel.instanceParams.value!!
|
val instanceParams = viewModel.instanceParams.value!!
|
||||||
showAddPollDialog(this, viewModel.poll.value, instanceParams.pollMaxOptions,
|
showAddPollDialog(
|
||||||
instanceParams.pollMaxLength, viewModel::updatePoll)
|
this, viewModel.poll.value, instanceParams.pollMaxOptions,
|
||||||
|
instanceParams.pollMaxLength, viewModel::updatePoll
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupPollView() {
|
private fun setupPollView() {
|
||||||
|
@ -755,14 +776,17 @@ class ComposeActivity : BaseActivity(),
|
||||||
if (viewModel.media.value!!.isNotEmpty()) {
|
if (viewModel.media.value!!.isNotEmpty()) {
|
||||||
finishingUploadDialog = ProgressDialog.show(
|
finishingUploadDialog = ProgressDialog.show(
|
||||||
this, getString(R.string.dialog_title_finishing_media_upload),
|
this, getString(R.string.dialog_title_finishing_media_upload),
|
||||||
getString(R.string.dialog_message_uploading_media), true, true)
|
getString(R.string.dialog_message_uploading_media), true, true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.sendStatus(contentText, spoilerText).observe(this, {
|
viewModel.sendStatus(contentText, spoilerText).observe(
|
||||||
|
this,
|
||||||
|
{
|
||||||
finishingUploadDialog?.dismiss()
|
finishingUploadDialog?.dismiss()
|
||||||
deleteDraftAndFinish()
|
deleteDraftAndFinish()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
|
binding.composeEditField.error = getString(R.string.error_compose_character_limit)
|
||||||
enableButtons(true)
|
enableButtons(true)
|
||||||
|
@ -776,8 +800,10 @@ class ComposeActivity : BaseActivity(),
|
||||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
pickMediaFile.launch(true)
|
pickMediaFile.launch(true)
|
||||||
} else {
|
} else {
|
||||||
Snackbar.make(binding.activityCompose, R.string.error_media_upload_permission,
|
Snackbar.make(
|
||||||
Snackbar.LENGTH_SHORT).apply {
|
binding.activityCompose, R.string.error_media_upload_permission,
|
||||||
|
Snackbar.LENGTH_SHORT
|
||||||
|
).apply {
|
||||||
setAction(R.string.action_retry) { onMediaPick() }
|
setAction(R.string.action_retry) { onMediaPick() }
|
||||||
// necessary so snackbar is shown over everything
|
// necessary so snackbar is shown over everything
|
||||||
view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
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
|
// Continue only if the File was successfully created
|
||||||
photoUploadUri = FileProvider.getUriForFile(this,
|
photoUploadUri = FileProvider.getUriForFile(
|
||||||
|
this,
|
||||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||||
photoFile)
|
photoFile
|
||||||
|
)
|
||||||
takePicture.launch(photoUploadUri)
|
takePicture.launch(photoUploadUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) {
|
private fun enableButton(button: ImageButton, clickable: Boolean, colorActive: Boolean) {
|
||||||
button.isEnabled = clickable
|
button.isEnabled = clickable
|
||||||
ThemeUtils.setDrawableTint(this, button.drawable,
|
ThemeUtils.setDrawableTint(
|
||||||
|
this, button.drawable,
|
||||||
if (colorActive) android.R.attr.textColorTertiary
|
if (colorActive) android.R.attr.textColorTertiary
|
||||||
else R.attr.textColorDisabled)
|
else R.attr.textColorDisabled
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun enablePollButton(enable: Boolean) {
|
private fun enablePollButton(enable: Boolean) {
|
||||||
binding.addPollTextActionTextView.isEnabled = enable
|
binding.addPollTextActionTextView.isEnabled = enable
|
||||||
val textColor = ThemeUtils.getColor(this,
|
val textColor = ThemeUtils.getColor(
|
||||||
|
this,
|
||||||
if (enable) android.R.attr.textColorTertiary
|
if (enable) android.R.attr.textColorTertiary
|
||||||
else R.attr.textColorDisabled)
|
else R.attr.textColorDisabled
|
||||||
|
)
|
||||||
binding.addPollTextActionTextView.setTextColor(textColor)
|
binding.addPollTextActionTextView.setTextColor(textColor)
|
||||||
binding.addPollTextActionTextView.compoundDrawablesRelative[0].colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
binding.addPollTextActionTextView.compoundDrawablesRelative[0].colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
|
@ -847,7 +879,6 @@ class ComposeActivity : BaseActivity(),
|
||||||
}
|
}
|
||||||
displayTransientError(errorId)
|
displayTransientError(errorId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -881,7 +912,8 @@ class ComposeActivity : BaseActivity(),
|
||||||
if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
if (composeOptionsBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||||
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
addMediaBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||||
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
emojiBehavior.state == BottomSheetBehavior.STATE_EXPANDED ||
|
||||||
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
|
scheduleBehavior.state == BottomSheetBehavior.STATE_EXPANDED
|
||||||
|
) {
|
||||||
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
composeOptionsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
addMediaBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
emojiBehavior.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 {
|
override fun areItemsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
||||||
return oldItem.localId == newItem.localId
|
return oldItem.localId == newItem.localId
|
||||||
}
|
}
|
||||||
|
@ -89,10 +91,11 @@ class MediaPreviewAdapter(
|
||||||
override fun areContentsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
override fun areContentsTheSame(oldItem: ComposeActivity.QueuedMedia, newItem: ComposeActivity.QueuedMedia): Boolean {
|
||||||
return oldItem == newItem
|
return oldItem == newItem
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
inner class PreviewViewHolder(val progressImageView: ProgressImageView)
|
inner class PreviewViewHolder(val progressImageView: ProgressImageView) :
|
||||||
: RecyclerView.ViewHolder(progressImageView) {
|
RecyclerView.ViewHolder(progressImageView) {
|
||||||
init {
|
init {
|
||||||
val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize)
|
val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize)
|
||||||
val margin = itemView.context.resources
|
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.entity.Attachment
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import com.keylesspalace.tusky.network.ProgressRequestBody
|
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.Observable
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
@ -37,7 +40,7 @@ import okhttp3.MultipartBody
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
sealed class UploadEvent {
|
sealed class UploadEvent {
|
||||||
data class ProgressEvent(val percentage: Int) : UploadEvent()
|
data class ProgressEvent(val percentage: Int) : UploadEvent()
|
||||||
|
@ -101,12 +104,13 @@ class MediaUploaderImpl(
|
||||||
val file = File.createTempFile("randomTemp1", suffix, context.cacheDir)
|
val file = File.createTempFile("randomTemp1", suffix, context.cacheDir)
|
||||||
FileOutputStream(file.absoluteFile).use { out ->
|
FileOutputStream(file.absoluteFile).use { out ->
|
||||||
input.copyTo(out)
|
input.copyTo(out)
|
||||||
uri = FileProvider.getUriForFile(context,
|
uri = FileProvider.getUriForFile(
|
||||||
|
context,
|
||||||
BuildConfig.APPLICATION_ID + ".fileprovider",
|
BuildConfig.APPLICATION_ID + ".fileprovider",
|
||||||
file)
|
file
|
||||||
|
)
|
||||||
mediaSize = getMediaSize(contentResolver, uri)
|
mediaSize = getMediaSize(contentResolver, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.w(TAG, e)
|
Log.w(TAG, e)
|
||||||
|
@ -151,20 +155,22 @@ class MediaUploaderImpl(
|
||||||
var mimeType = contentResolver.getType(media.uri)
|
var mimeType = contentResolver.getType(media.uri)
|
||||||
val map = MimeTypeMap.getSingleton()
|
val map = MimeTypeMap.getSingleton()
|
||||||
val fileExtension = map.getExtensionFromMimeType(mimeType)
|
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),
|
context.getString(R.string.app_name),
|
||||||
Date().time.toString(),
|
Date().time.toString(),
|
||||||
randomAlphanumericString(10),
|
randomAlphanumericString(10),
|
||||||
fileExtension)
|
fileExtension
|
||||||
|
)
|
||||||
|
|
||||||
val stream = contentResolver.openInputStream(media.uri)
|
val stream = contentResolver.openInputStream(media.uri)
|
||||||
|
|
||||||
if (mimeType == null) mimeType = "multipart/form-data"
|
if (mimeType == null) mimeType = "multipart/form-data"
|
||||||
|
|
||||||
|
|
||||||
var lastProgress = -1
|
var lastProgress = -1
|
||||||
val fileBody = ProgressRequestBody(stream, media.mediaSize,
|
val fileBody = ProgressRequestBody(
|
||||||
mimeType.toMediaTypeOrNull()) { percentage ->
|
stream, media.mediaSize,
|
||||||
|
mimeType.toMediaTypeOrNull()
|
||||||
|
) { percentage ->
|
||||||
if (percentage != lastProgress) {
|
if (percentage != lastProgress) {
|
||||||
emitter.onNext(UploadEvent.ProgressEvent(percentage))
|
emitter.onNext(UploadEvent.ProgressEvent(percentage))
|
||||||
}
|
}
|
||||||
|
@ -180,12 +186,15 @@ class MediaUploaderImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
val uploadDisposable = mastodonApi.uploadMedia(body, description)
|
val uploadDisposable = mastodonApi.uploadMedia(body, description)
|
||||||
.subscribe({ attachment ->
|
.subscribe(
|
||||||
|
{ attachment ->
|
||||||
emitter.onNext(UploadEvent.FinishedEvent(attachment))
|
emitter.onNext(UploadEvent.FinishedEvent(attachment))
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
}, { e ->
|
},
|
||||||
|
{ e ->
|
||||||
emitter.onError(e)
|
emitter.onError(e)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Cancel the request when our observable is cancelled
|
// Cancel the request when our observable is cancelled
|
||||||
emitter.setDisposable(uploadDisposable)
|
emitter.setDisposable(uploadDisposable)
|
||||||
|
@ -194,15 +203,16 @@ class MediaUploaderImpl(
|
||||||
|
|
||||||
private fun downsize(media: QueuedMedia): QueuedMedia {
|
private fun downsize(media: QueuedMedia): QueuedMedia {
|
||||||
val file = createNewImageFile(context)
|
val file = createNewImageFile(context)
|
||||||
DownsizeImageTask.resize(arrayOf(media.uri),
|
DownsizeImageTask.resize(
|
||||||
STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file)
|
arrayOf(media.uri),
|
||||||
|
STATUS_IMAGE_SIZE_LIMIT, context.contentResolver, file
|
||||||
|
)
|
||||||
return media.copy(uri = file.toUri(), mediaSize = file.length())
|
return media.copy(uri = file.toUri(), mediaSize = file.length())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldResizeMedia(media: QueuedMedia): Boolean {
|
private fun shouldResizeMedia(media: QueuedMedia): Boolean {
|
||||||
return media.type == QueuedMedia.Type.IMAGE
|
return media.type == QueuedMedia.Type.IMAGE &&
|
||||||
&& (media.mediaSize > STATUS_IMAGE_SIZE_LIMIT
|
(media.mediaSize > STATUS_IMAGE_SIZE_LIMIT || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
|
||||||
|| getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
@ -211,6 +221,5 @@ class MediaUploaderImpl(
|
||||||
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
|
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
|
||||||
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
|
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
|
||||||
private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels
|
private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,11 +82,13 @@ fun showAddPollDialog(
|
||||||
val pollDuration = context.resources
|
val pollDuration = context.resources
|
||||||
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
.getIntArray(R.array.poll_duration_values)[selectedPollDurationId]
|
||||||
|
|
||||||
onUpdatePoll(NewPoll(
|
onUpdatePoll(
|
||||||
|
NewPoll(
|
||||||
options = adapter.pollOptions,
|
options = adapter.pollOptions,
|
||||||
expiresIn = pollDuration,
|
expiresIn = pollDuration,
|
||||||
multiple = binding.multipleChoicesCheckBox.isChecked
|
multiple = binding.multipleChoicesCheckBox.isChecked
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
dialog.dismiss()
|
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
|
// https://github.com/tootsuite/mastodon/blob/c6904c0d3766a2ea8a81ab025c127169ecb51373/app/models/media_attachment.rb#L32
|
||||||
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
|
private const val MEDIA_DESCRIPTION_CHARACTER_LIMIT = 1500
|
||||||
|
|
||||||
fun <T> T.makeCaptionDialog(existingDescription: String?,
|
fun <T> T.makeCaptionDialog(
|
||||||
|
existingDescription: String?,
|
||||||
previewUri: Uri,
|
previewUri: Uri,
|
||||||
onUpdateDescription: (String) -> LiveData<Boolean>
|
onUpdateDescription: (String) -> LiveData<Boolean>
|
||||||
) where T : Activity, T : LifecycleOwner {
|
) 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)
|
(imageView.layoutParams as LinearLayout.LayoutParams).setMargins(0, margin, 0, 0)
|
||||||
|
|
||||||
val input = EditText(this)
|
val input = EditText(this)
|
||||||
input.hint = resources.getQuantityString(R.plurals.hint_describe_for_visually_impaired,
|
input.hint = resources.getQuantityString(
|
||||||
MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT)
|
R.plurals.hint_describe_for_visually_impaired,
|
||||||
|
MEDIA_DESCRIPTION_CHARACTER_LIMIT, MEDIA_DESCRIPTION_CHARACTER_LIMIT
|
||||||
|
)
|
||||||
dialogLayout.addView(input)
|
dialogLayout.addView(input)
|
||||||
(input.layoutParams as LinearLayout.LayoutParams).setMargins(margin, margin, margin, margin)
|
(input.layoutParams as LinearLayout.LayoutParams).setMargins(margin, margin, margin, margin)
|
||||||
input.setLines(2)
|
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_MULTI_LINE
|
||||||
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
|
or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||||
|
)
|
||||||
input.setText(existingDescription)
|
input.setText(existingDescription)
|
||||||
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
|
input.filters = arrayOf(InputFilter.LengthFilter(MEDIA_DESCRIPTION_CHARACTER_LIMIT))
|
||||||
|
|
||||||
|
@ -76,7 +81,6 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||||
withLifecycleContext {
|
withLifecycleContext {
|
||||||
onUpdateDescription(input.text.toString())
|
onUpdateDescription(input.text.toString())
|
||||||
.observe { success -> if (!success) showFailedCaptionMessage() }
|
.observe { success -> if (!success) showFailedCaptionMessage() }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
|
@ -90,7 +94,8 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||||
|
|
||||||
val window = dialog.window
|
val window = dialog.window
|
||||||
window?.setSoftInputMode(
|
window?.setSoftInputMode(
|
||||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
||||||
|
)
|
||||||
|
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
|
@ -109,7 +114,6 @@ fun <T> T.makeCaptionDialog(existingDescription: String?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Activity.showFailedCaptionMessage() {
|
private fun Activity.showFailedCaptionMessage() {
|
||||||
Toast.makeText(this, R.string.error_failed_set_caption, Toast.LENGTH_SHORT).show()
|
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
|
R.id.directRadioButton
|
||||||
else ->
|
else ->
|
||||||
R.id.directRadioButton
|
R.id.directRadioButton
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
check(selectedButton)
|
check(selectedButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComposeOptionsListener {
|
interface ComposeOptionsListener {
|
||||||
|
|
|
@ -16,19 +16,21 @@
|
||||||
package com.keylesspalace.tusky.components.compose.view
|
package com.keylesspalace.tusky.components.compose.view
|
||||||
|
|
||||||
import android.content.Context
|
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.InputType
|
||||||
import android.text.method.KeyListener
|
import android.text.method.KeyListener
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputConnection
|
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,
|
class EditTextTyped @JvmOverloads constructor(
|
||||||
attributeSet: AttributeSet? = null)
|
context: Context,
|
||||||
: AppCompatMultiAutoCompleteTextView(context, attributeSet) {
|
attributeSet: AttributeSet? = null
|
||||||
|
) :
|
||||||
|
AppCompatMultiAutoCompleteTextView(context, attributeSet) {
|
||||||
|
|
||||||
private var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null
|
private var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null
|
||||||
private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this)
|
private val emojiEditTextHelper: EmojiEditTextHelper = EmojiEditTextHelper(this)
|
||||||
|
@ -52,8 +54,13 @@ class EditTextTyped @JvmOverloads constructor(context: Context,
|
||||||
val connection = super.onCreateInputConnection(editorInfo)
|
val connection = super.onCreateInputConnection(editorInfo)
|
||||||
return if (onCommitContentListener != null) {
|
return if (onCommitContentListener != null) {
|
||||||
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
|
EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*"))
|
||||||
getEmojiEditTextHelper().onCreateInputConnection(InputConnectionCompat.createWrapper(connection, editorInfo,
|
getEmojiEditTextHelper().onCreateInputConnection(
|
||||||
onCommitContentListener!!), editorInfo)!!
|
InputConnectionCompat.createWrapper(
|
||||||
|
connection, editorInfo,
|
||||||
|
onCommitContentListener!!
|
||||||
|
),
|
||||||
|
editorInfo
|
||||||
|
)!!
|
||||||
} else {
|
} else {
|
||||||
connection
|
connection
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,9 @@ import com.keylesspalace.tusky.entity.NewPoll
|
||||||
class PollPreviewView @JvmOverloads constructor(
|
class PollPreviewView @JvmOverloads constructor(
|
||||||
context: Context?,
|
context: Context?,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0)
|
defStyleAttr: Int = 0
|
||||||
: LinearLayout(context, attrs, defStyleAttr) {
|
) :
|
||||||
|
LinearLayout(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
private val adapter = PreviewPollOptionsAdapter()
|
private val adapter = PreviewPollOptionsAdapter()
|
||||||
|
|
||||||
|
|
|
@ -68,8 +68,5 @@ class TootButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,8 @@ data class ConversationStatusEntity(
|
||||||
pinned = false,
|
pinned = false,
|
||||||
muted = muted,
|
muted = muted,
|
||||||
poll = poll,
|
poll = poll,
|
||||||
card = null)
|
card = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,5 +37,4 @@ class ConversationLoadStateAdapter(
|
||||||
val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return NetworkStateViewHolder(binding, retryCallback)
|
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.ReselectableFragment
|
||||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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.CardViewMode
|
||||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
|
import com.keylesspalace.tusky.util.show
|
||||||
import com.keylesspalace.tusky.util.viewBinding
|
import com.keylesspalace.tusky.util.viewBinding
|
||||||
|
import com.keylesspalace.tusky.util.visible
|
||||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
|
class ConversationsFragment : SFragment(), StatusActionListener, Injectable, ReselectableFragment {
|
||||||
|
|
|
@ -34,5 +34,4 @@ class ConversationsRepository @Inject constructor(
|
||||||
}.subscribeOn(Schedulers.io())
|
}.subscribeOn(Schedulers.io())
|
||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -38,7 +38,6 @@ class DraftMediaAdapter(
|
||||||
override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
override fun areContentsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||||
return oldItem == newItem
|
return oldItem == newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -60,8 +59,8 @@ class DraftMediaAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class DraftMediaViewHolder(val imageView: ImageView)
|
inner class DraftMediaViewHolder(val imageView: ImageView) :
|
||||||
: RecyclerView.ViewHolder(imageView) {
|
RecyclerView.ViewHolder(imageView) {
|
||||||
init {
|
init {
|
||||||
val thumbnailViewSize =
|
val thumbnailViewSize =
|
||||||
imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size)
|
imageView.context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size)
|
||||||
|
|
|
@ -93,7 +93,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
||||||
viewModel.getToot(draft.inReplyToId)
|
viewModel.getToot(draft.inReplyToId)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.autoDispose(from(this))
|
.autoDispose(from(this))
|
||||||
.subscribe({ status ->
|
.subscribe(
|
||||||
|
{ status ->
|
||||||
val composeOptions = ComposeActivity.ComposeOptions(
|
val composeOptions = ComposeActivity.ComposeOptions(
|
||||||
draftId = draft.id,
|
draftId = draft.id,
|
||||||
tootText = draft.content,
|
tootText = draft.content,
|
||||||
|
@ -110,8 +111,8 @@ class DraftsActivity : BaseActivity(), DraftActionListener {
|
||||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
|
|
||||||
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
startActivity(ComposeActivity.startIntent(this, composeOptions))
|
||||||
|
},
|
||||||
}, { throwable ->
|
{ throwable ->
|
||||||
|
|
||||||
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
|
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)
|
Snackbar.make(binding.root, getString(R.string.drafts_failed_loading_reply), Snackbar.LENGTH_SHORT)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
openDraftWithoutReply(draft)
|
openDraftWithoutReply(draft)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,5 +33,4 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun androidInjector() = androidInjector
|
override fun androidInjector() = androidInjector
|
||||||
|
|
||||||
}
|
}
|
|
@ -114,7 +114,8 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
api.domainBlocks(id, bottomId)
|
api.domainBlocks(id, bottomId)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||||
.subscribe({ response ->
|
.subscribe(
|
||||||
|
{ response ->
|
||||||
val instances = response.body()
|
val instances = response.body()
|
||||||
|
|
||||||
if (response.isSuccessful && instances != null) {
|
if (response.isSuccessful && instances != null) {
|
||||||
|
@ -122,9 +123,11 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
} else {
|
} else {
|
||||||
onFetchInstancesFailure(Exception(response.message()))
|
onFetchInstancesFailure(Exception(response.message()))
|
||||||
}
|
}
|
||||||
}, {throwable ->
|
},
|
||||||
|
{ throwable ->
|
||||||
onFetchInstancesFailure(throwable)
|
onFetchInstancesFailure(throwable)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onFetchInstancesSuccess(instances: List<String>, linkHeader: String?) {
|
private fun onFetchInstancesSuccess(instances: List<String>, linkHeader: String?) {
|
||||||
|
|
|
@ -22,7 +22,11 @@ import android.util.Log
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.google.android.material.snackbar.Snackbar
|
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.EventHub
|
||||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||||
import com.keylesspalace.tusky.components.instancemute.InstanceListActivity
|
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.Filter
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
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.keylesspalace.tusky.util.ThemeUtils
|
||||||
import com.mikepenz.iconics.IconicsDrawable
|
import com.mikepenz.iconics.IconicsDrawable
|
||||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||||
|
@ -75,8 +84,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
val intent = Intent(context, TabPreferenceActivity::class.java)
|
val intent = Intent(context, TabPreferenceActivity::class.java)
|
||||||
activity?.startActivity(intent)
|
activity?.startActivity(intent)
|
||||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
activity?.overridePendingTransition(
|
||||||
R.anim.slide_to_left)
|
R.anim.slide_from_right,
|
||||||
|
R.anim.slide_to_left
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,8 +99,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
val intent = Intent(context, AccountListActivity::class.java)
|
val intent = Intent(context, AccountListActivity::class.java)
|
||||||
intent.putExtra("type", AccountListActivity.Type.MUTES)
|
intent.putExtra("type", AccountListActivity.Type.MUTES)
|
||||||
activity?.startActivity(intent)
|
activity?.startActivity(intent)
|
||||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
activity?.overridePendingTransition(
|
||||||
R.anim.slide_to_left)
|
R.anim.slide_from_right,
|
||||||
|
R.anim.slide_to_left
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,8 +117,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
val intent = Intent(context, AccountListActivity::class.java)
|
val intent = Intent(context, AccountListActivity::class.java)
|
||||||
intent.putExtra("type", AccountListActivity.Type.BLOCKS)
|
intent.putExtra("type", AccountListActivity.Type.BLOCKS)
|
||||||
activity?.startActivity(intent)
|
activity?.startActivity(intent)
|
||||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
activity?.overridePendingTransition(
|
||||||
R.anim.slide_to_left)
|
R.anim.slide_from_right,
|
||||||
|
R.anim.slide_to_left
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -116,8 +131,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
val intent = Intent(context, InstanceListActivity::class.java)
|
val intent = Intent(context, InstanceListActivity::class.java)
|
||||||
activity?.startActivity(intent)
|
activity?.startActivity(intent)
|
||||||
activity?.overridePendingTransition(R.anim.slide_from_right,
|
activity?.overridePendingTransition(
|
||||||
R.anim.slide_to_left)
|
R.anim.slide_from_right,
|
||||||
|
R.anim.slide_to_left
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,8 +218,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
preference {
|
preference {
|
||||||
setTitle(R.string.pref_title_public_filter_keywords)
|
setTitle(R.string.pref_title_public_filter_keywords)
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
launchFilterActivity(Filter.PUBLIC,
|
launchFilterActivity(
|
||||||
R.string.pref_title_public_filter_keywords)
|
Filter.PUBLIC,
|
||||||
|
R.string.pref_title_public_filter_keywords
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,8 +245,10 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
preference {
|
preference {
|
||||||
setTitle(R.string.pref_title_thread_filter_keywords)
|
setTitle(R.string.pref_title_thread_filter_keywords)
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
launchFilterActivity(Filter.THREAD,
|
launchFilterActivity(
|
||||||
R.string.pref_title_thread_filter_keywords)
|
Filter.THREAD,
|
||||||
|
R.string.pref_title_thread_filter_keywords
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,7 +276,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
it.startActivity(intent)
|
it.startActivity(intent)
|
||||||
it.overridePendingTransition(R.anim.slide_from_right, R.anim.slide_to_left)
|
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)
|
Log.e("AccountPreferences", "failed updating settings on server", t)
|
||||||
showErrorSnackbar(visibility, sensitive)
|
showErrorSnackbar(visibility, sensitive)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,8 +124,6 @@ class EmojiPreference(
|
||||||
finishDownload(font, binding)
|
finishDownload(font, binding)
|
||||||
}
|
}
|
||||||
).also { downloadDisposables[font.id] = it }
|
).also { downloadDisposables[font.id] = it }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
private fun cancelDownload(font: EmojiCompatFont, binding: ItemEmojiPrefBinding) {
|
||||||
|
@ -222,12 +220,14 @@ class EmojiPreference(
|
||||||
context,
|
context,
|
||||||
0x1f973, // This is the codepoint of the party face emoji :D
|
0x1f973, // This is the codepoint of the party face emoji :D
|
||||||
launchIntent,
|
launchIntent,
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT)
|
PendingIntent.FLAG_CANCEL_CURRENT
|
||||||
|
)
|
||||||
val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
mgr.set(
|
mgr.set(
|
||||||
AlarmManager.RTC,
|
AlarmManager.RTC,
|
||||||
System.currentTimeMillis() + 100,
|
System.currentTimeMillis() + 100,
|
||||||
mPendingIntent)
|
mPendingIntent
|
||||||
|
)
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,5 +176,4 @@ class NotificationPreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
return NotificationPreferencesFragment()
|
return NotificationPreferencesFragment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,9 @@ import dagger.android.DispatchingAndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreferenceChangeListener,
|
class PreferencesActivity :
|
||||||
|
BaseActivity(),
|
||||||
|
SharedPreferences.OnSharedPreferenceChangeListener,
|
||||||
HasAndroidInjector {
|
HasAndroidInjector {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -91,7 +93,6 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
||||||
}
|
}
|
||||||
|
|
||||||
restartActivitiesOnExit = intent.getBooleanExtra("restart", false)
|
restartActivitiesOnExit = intent.getBooleanExtra("restart", false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -122,7 +123,6 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
||||||
|
|
||||||
restartActivitiesOnExit = true
|
restartActivitiesOnExit = true
|
||||||
this.restartCurrentActivity()
|
this.restartCurrentActivity()
|
||||||
|
|
||||||
}
|
}
|
||||||
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars",
|
"statusTextSize", "absoluteTimeView", "showBotOverlay", "animateGifAvatars",
|
||||||
"useBlurhash", "showCardsInTimelines", "confirmReblogs", "enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> {
|
"useBlurhash", "showCardsInTimelines", "confirmReblogs", "enableSwipeForTabs", "mainNavPosition", PrefKeys.HIDE_TOP_TOOLBAR -> {
|
||||||
|
@ -179,5 +179,4 @@ class PreferencesActivity : BaseActivity(), SharedPreferences.OnSharedPreference
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,14 @@ import com.keylesspalace.tusky.R
|
||||||
import com.keylesspalace.tusky.db.AccountManager
|
import com.keylesspalace.tusky.db.AccountManager
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.entity.Notification
|
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.ThemeUtils
|
||||||
import com.keylesspalace.tusky.util.deserialize
|
import com.keylesspalace.tusky.util.deserialize
|
||||||
import com.keylesspalace.tusky.util.getNonNullString
|
import com.keylesspalace.tusky.util.getNonNullString
|
||||||
|
@ -122,7 +129,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
setTitle(R.string.pref_title_bot_overlay)
|
setTitle(R.string.pref_title_bot_overlay)
|
||||||
isSingleLineTitle = false
|
isSingleLineTitle = false
|
||||||
setIcon(R.drawable.ic_bot_24dp)
|
setIcon(R.drawable.ic_bot_24dp)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switchPreference {
|
switchPreference {
|
||||||
|
@ -259,7 +265,6 @@ class PreferencesFragment : PreferenceFragmentCompat(), Injectable {
|
||||||
sizePx = iconSize
|
sizePx = iconSize
|
||||||
colorInt = ThemeUtils.getColor(context, R.attr.iconColor)
|
colorInt = ThemeUtils.getColor(context, R.attr.iconColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|
|
@ -50,7 +50,6 @@ class ProxyPreferencesFragment : PreferenceFragmentCompat() {
|
||||||
setSummaryProvider { text }
|
setSummaryProvider { text }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
|
|
@ -126,7 +126,6 @@ class ReportViewModel @Inject constructor(
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ data ->
|
{ data ->
|
||||||
updateRelationship(data.getOrNull(0))
|
updateRelationship(data.getOrNull(0))
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
updateRelationship(null)
|
updateRelationship(null)
|
||||||
|
@ -210,7 +209,6 @@ class ReportViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.autoDispose()
|
.autoDispose()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkClickedUrl(url: String?) {
|
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.Emoji
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
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.COLLAPSE_INPUT_FILTER
|
||||||
import com.keylesspalace.tusky.util.StatusViewHelper.Companion.NO_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 com.keylesspalace.tusky.viewdata.toViewData
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
class StatusViewHolder(
|
class StatusViewHolder(
|
||||||
private val binding: ItemReportStatusBinding,
|
private val binding: ItemReportStatusBinding,
|
||||||
|
@ -71,9 +78,11 @@ class StatusViewHolder(
|
||||||
|
|
||||||
val sensitive = status.sensitive
|
val sensitive = status.sensitive
|
||||||
|
|
||||||
statusViewHelper.setMediasPreview(statusDisplayOptions, status.attachments,
|
statusViewHelper.setMediasPreview(
|
||||||
|
statusDisplayOptions, status.attachments,
|
||||||
sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive),
|
sensitive, previewListener, viewState.isMediaShow(status.id, status.sensitive),
|
||||||
mediaViewHeight)
|
mediaViewHeight
|
||||||
|
)
|
||||||
|
|
||||||
statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions)
|
statusViewHelper.setupPollReadonly(status.poll.toViewData(), status.emojis, statusDisplayOptions)
|
||||||
setCreatedAt(status.createdAt)
|
setCreatedAt(status.createdAt)
|
||||||
|
@ -81,8 +90,10 @@ class StatusViewHolder(
|
||||||
|
|
||||||
private fun updateTextView() {
|
private fun updateTextView() {
|
||||||
status()?.let { status ->
|
status()?.let { status ->
|
||||||
setupCollapsedState(shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true),
|
setupCollapsedState(
|
||||||
viewState.isContentShow(status.id, status.sensitive), status.spoilerText)
|
shouldTrimStatus(status.content), viewState.isCollapsed(status.id, true),
|
||||||
|
viewState.isContentShow(status.id, status.sensitive), status.spoilerText
|
||||||
|
)
|
||||||
|
|
||||||
if (status.spoilerText.isBlank()) {
|
if (status.spoilerText.isBlank()) {
|
||||||
setTextVisible(true, status.content, status.mentions, status.emojis, adapterHandler)
|
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,
|
content: Spanned,
|
||||||
mentions: List<Status.Mention>?,
|
mentions: List<Status.Mention>?,
|
||||||
emojis: List<Emoji>,
|
emojis: List<Emoji>,
|
||||||
listener: LinkListener) {
|
listener: LinkListener
|
||||||
|
) {
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
val emojifiedText = content.emojify(emojis, binding.statusContent, statusDisplayOptions.animateEmojis)
|
val emojifiedText = content.emojify(emojis, binding.statusContent, statusDisplayOptions.animateEmojis)
|
||||||
LinkHelper.setClickableText(binding.statusContent, emojifiedText, mentions, listener)
|
LinkHelper.setClickableText(binding.statusContent, emojifiedText, mentions, listener)
|
||||||
|
|
|
@ -37,8 +37,10 @@ class StatusesAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatusViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatusViewHolder {
|
||||||
val binding = ItemReportStatusBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = ItemReportStatusBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return StatusViewHolder(binding, statusDisplayOptions, statusViewState, adapterHandler,
|
return StatusViewHolder(
|
||||||
statusForPosition)
|
binding, statusDisplayOptions, statusViewState, adapterHandler,
|
||||||
|
statusForPosition
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: StatusViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: StatusViewHolder, position: Int) {
|
||||||
|
|
|
@ -65,7 +65,6 @@ class StatusesPagingSource(
|
||||||
prevKey = result.firstOrNull()?.id,
|
prevKey = result.firstOrNull()?.id,
|
||||||
nextKey = result.lastOrNull()?.id
|
nextKey = result.lastOrNull()?.id
|
||||||
)
|
)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w("StatusesPagingSource", "failed to load statuses", e)
|
Log.w("StatusesPagingSource", "failed to load statuses", e)
|
||||||
return LoadResult.Error(e)
|
return LoadResult.Error(e)
|
||||||
|
|
|
@ -56,27 +56,29 @@ class ReportDoneFragment : Fragment(R.layout.fragment_report_done), Injectable {
|
||||||
binding.progressMute.hide()
|
binding.progressMute.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.buttonMute.setText(when (it.data) {
|
binding.buttonMute.setText(
|
||||||
|
when (it.data) {
|
||||||
true -> R.string.action_unmute
|
true -> R.string.action_unmute
|
||||||
else -> R.string.action_mute
|
else -> R.string.action_mute
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.blockState.observe(viewLifecycleOwner) {
|
viewModel.blockState.observe(viewLifecycleOwner) {
|
||||||
if (it !is Loading) {
|
if (it !is Loading) {
|
||||||
binding.buttonBlock.show()
|
binding.buttonBlock.show()
|
||||||
binding.progressBlock.show()
|
binding.progressBlock.show()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
binding.buttonBlock.hide()
|
binding.buttonBlock.hide()
|
||||||
binding.progressBlock.hide()
|
binding.progressBlock.hide()
|
||||||
}
|
}
|
||||||
binding.buttonBlock.setText(when (it.data) {
|
binding.buttonBlock.setText(
|
||||||
|
when (it.data) {
|
||||||
true -> R.string.action_unblock
|
true -> R.string.action_unblock
|
||||||
else -> R.string.action_block
|
else -> R.string.action_block
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleClicks() {
|
private fun handleClicks() {
|
||||||
|
|
|
@ -67,8 +67,7 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
if (viewModel.isRemoteAccount) {
|
if (viewModel.isRemoteAccount) {
|
||||||
binding.checkIsNotifyRemote.show()
|
binding.checkIsNotifyRemote.show()
|
||||||
binding.reportDescriptionRemoteInstance.show()
|
binding.reportDescriptionRemoteInstance.show()
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
binding.checkIsNotifyRemote.hide()
|
binding.checkIsNotifyRemote.hide()
|
||||||
binding.reportDescriptionRemoteInstance.hide()
|
binding.reportDescriptionRemoteInstance.hide()
|
||||||
}
|
}
|
||||||
|
@ -84,7 +83,6 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
is Success -> viewModel.navigateTo(Screen.Done)
|
is Success -> viewModel.navigateTo(Screen.Done)
|
||||||
is Loading -> showLoading()
|
is Loading -> showLoading()
|
||||||
is Error -> showError(it.cause)
|
is Error -> showError(it.cause)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,9 +132,10 @@ class ReportStatusesFragment : Fragment(R.layout.fragment_report_statuses), Inje
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.addLoadStateListener { loadState ->
|
adapter.addLoadStateListener { loadState ->
|
||||||
if (loadState.refresh is LoadState.Error
|
if (loadState.refresh is LoadState.Error ||
|
||||||
|| loadState.append is LoadState.Error
|
loadState.append is LoadState.Error ||
|
||||||
|| loadState.prepend is LoadState.Error) {
|
loadState.prepend is LoadState.Error
|
||||||
|
) {
|
||||||
showError()
|
showError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,12 +95,16 @@ class SearchViewModel @Inject constructor(
|
||||||
|
|
||||||
fun removeItem(status: Pair<Status, StatusViewData.Concrete>) {
|
fun removeItem(status: Pair<Status, StatusViewData.Concrete>) {
|
||||||
timelineCases.delete(status.first.id)
|
timelineCases.delete(status.first.id)
|
||||||
.subscribe({
|
.subscribe(
|
||||||
|
{
|
||||||
if (loadedStatuses.remove(status))
|
if (loadedStatuses.remove(status))
|
||||||
statusesPagingSourceFactory.invalidate()
|
statusesPagingSourceFactory.invalidate()
|
||||||
}, {
|
},
|
||||||
err -> Log.d(TAG, "Failed to delete status", err)
|
{
|
||||||
})
|
err ->
|
||||||
|
Log.d(TAG, "Failed to delete status", err)
|
||||||
|
}
|
||||||
|
)
|
||||||
.autoDispose()
|
.autoDispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ import com.keylesspalace.tusky.adapter.AccountViewHolder
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||||
|
|
||||||
class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean)
|
class SearchAccountsAdapter(private val linkListener: LinkListener, private val animateAvatars: Boolean, private val animateEmojis: Boolean) :
|
||||||
: PagingDataAdapter<Account, AccountViewHolder>(ACCOUNT_COMPARATOR) {
|
PagingDataAdapter<Account, AccountViewHolder>(ACCOUNT_COMPARATOR) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder {
|
||||||
val view = LayoutInflater.from(parent.context)
|
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.interfaces.LinkListener
|
||||||
import com.keylesspalace.tusky.util.BindingHolder
|
import com.keylesspalace.tusky.util.BindingHolder
|
||||||
|
|
||||||
class SearchHashtagsAdapter(private val linkListener: LinkListener)
|
class SearchHashtagsAdapter(private val linkListener: LinkListener) :
|
||||||
: PagingDataAdapter<HashTag, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
|
PagingDataAdapter<HashTag, BindingHolder<ItemHashtagBinding>>(HASHTAG_COMPARATOR) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemHashtagBinding> {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder<ItemHashtagBinding> {
|
||||||
val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
val binding = ItemHashtagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
|
|
@ -34,5 +34,4 @@ class SearchPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(acti
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = 3
|
override fun getItemCount() = 3
|
||||||
|
|
||||||
}
|
}
|
|
@ -27,7 +27,8 @@ class SearchPagingSource<T: Any>(
|
||||||
private val searchType: SearchType,
|
private val searchType: SearchType,
|
||||||
private val searchRequest: String,
|
private val searchRequest: String,
|
||||||
private val initialItems: List<T>?,
|
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? {
|
override fun getRefreshKey(state: PagingState<Int, T>): Int? {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -29,8 +29,11 @@ import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
abstract class SearchFragment<T: Any> : Fragment(R.layout.fragment_search),
|
abstract class SearchFragment<T : Any> :
|
||||||
LinkListener, Injectable, SwipeRefreshLayout.OnRefreshListener {
|
Fragment(R.layout.fragment_search),
|
||||||
|
LinkListener,
|
||||||
|
Injectable,
|
||||||
|
SwipeRefreshLayout.OnRefreshListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
|
|
@ -125,13 +125,17 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
when (actionable.attachments[attachmentIndex].type) {
|
when (actionable.attachments[attachmentIndex].type) {
|
||||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
||||||
val attachments = AttachmentViewData.list(actionable)
|
val attachments = AttachmentViewData.list(actionable)
|
||||||
val intent = ViewMediaActivity.newIntent(context, attachments,
|
val intent = ViewMediaActivity.newIntent(
|
||||||
attachmentIndex)
|
context, attachments,
|
||||||
|
attachmentIndex
|
||||||
|
)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val url = actionable.attachments[attachmentIndex].url
|
val url = actionable.attachments[attachmentIndex].url
|
||||||
ViewCompat.setTransitionName(view, url)
|
ViewCompat.setTransitionName(view, url)
|
||||||
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(),
|
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
|
||||||
view, url)
|
requireActivity(),
|
||||||
|
view, url
|
||||||
|
)
|
||||||
startActivity(intent, options.toBundle())
|
startActivity(intent, options.toBundle())
|
||||||
} else {
|
} else {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
|
@ -204,14 +208,17 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
remove(viewModel.activeAccount?.username)
|
remove(viewModel.activeAccount?.username)
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
val intent = ComposeActivity.startIntent(
|
||||||
|
requireContext(),
|
||||||
|
ComposeOptions(
|
||||||
inReplyToId = status.actionableId,
|
inReplyToId = status.actionableId,
|
||||||
replyVisibility = actionableStatus.visibility,
|
replyVisibility = actionableStatus.visibility,
|
||||||
contentWarning = actionableStatus.spoilerText,
|
contentWarning = actionableStatus.spoilerText,
|
||||||
mentionedUsernames = mentionedUsernames,
|
mentionedUsernames = mentionedUsernames,
|
||||||
replyingStatusAuthor = actionableStatus.account.localUsername,
|
replyingStatusAuthor = actionableStatus.account.localUsername,
|
||||||
replyingStatusContent = actionableStatus.content.toString()
|
replyingStatusContent = actionableStatus.content.toString()
|
||||||
))
|
)
|
||||||
|
)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +282,8 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
R.string.action_unmute_conversation
|
R.string.action_unmute_conversation
|
||||||
} else {
|
} else {
|
||||||
R.string.action_mute_conversation
|
R.string.action_mute_conversation
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
popup.setOnMenuItemClickListener { item ->
|
popup.setOnMenuItemClickListener { item ->
|
||||||
|
@ -383,11 +391,14 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence) {
|
private fun showOpenAsDialog(statusUrl: String, dialogTitle: CharSequence) {
|
||||||
bottomSheetActivity?.showAccountChooserDialog(dialogTitle, false, object : AccountSelectionListener {
|
bottomSheetActivity?.showAccountChooserDialog(
|
||||||
|
dialogTitle, false,
|
||||||
|
object : AccountSelectionListener {
|
||||||
override fun onAccountSelected(account: AccountEntity) {
|
override fun onAccountSelected(account: AccountEntity) {
|
||||||
openAsAccount(statusUrl, account)
|
openAsAccount(statusUrl, account)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openAsAccount(statusUrl: String, account: AccountEntity) {
|
private fun openAsAccount(statusUrl: String, account: AccountEntity) {
|
||||||
|
@ -448,7 +459,8 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
viewModel.deleteStatus(id)
|
viewModel.deleteStatus(id)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||||
.subscribe({ deletedStatus ->
|
.subscribe(
|
||||||
|
{ deletedStatus ->
|
||||||
removeItem(position)
|
removeItem(position)
|
||||||
|
|
||||||
val redraftStatus = if (deletedStatus.isEmpty()) {
|
val redraftStatus = if (deletedStatus.isEmpty()) {
|
||||||
|
@ -457,7 +469,9 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
deletedStatus
|
deletedStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
val intent = ComposeActivity.startIntent(
|
||||||
|
requireContext(),
|
||||||
|
ComposeOptions(
|
||||||
tootText = redraftStatus.text ?: "",
|
tootText = redraftStatus.text ?: "",
|
||||||
inReplyToId = redraftStatus.inReplyToId,
|
inReplyToId = redraftStatus.inReplyToId,
|
||||||
visibility = redraftStatus.visibility,
|
visibility = redraftStatus.visibility,
|
||||||
|
@ -465,13 +479,15 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
mediaAttachments = redraftStatus.attachments,
|
mediaAttachments = redraftStatus.attachments,
|
||||||
sensitive = redraftStatus.sensitive,
|
sensitive = redraftStatus.sensitive,
|
||||||
poll = redraftStatus.poll?.toNewPoll(status.createdAt)
|
poll = redraftStatus.poll?.toNewPoll(status.createdAt)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}, { error ->
|
},
|
||||||
|
{ error ->
|
||||||
Log.w("SearchStatusesFragment", "error deleting status", error)
|
Log.w("SearchStatusesFragment", "error deleting status", error)
|
||||||
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.error_generic, Toast.LENGTH_SHORT).show()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show()
|
.show()
|
||||||
|
|
|
@ -25,10 +25,16 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.preference.PreferenceManager
|
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 androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
||||||
import at.connyduck.sparkbutton.helpers.Utils
|
import at.connyduck.sparkbutton.helpers.Utils
|
||||||
import autodispose2.androidx.lifecycle.AndroidLifecycleScopeProvider.*
|
|
||||||
import autodispose2.androidx.lifecycle.autoDispose
|
import autodispose2.androidx.lifecycle.autoDispose
|
||||||
import com.keylesspalace.tusky.AccountListActivity
|
import com.keylesspalace.tusky.AccountListActivity
|
||||||
import com.keylesspalace.tusky.AccountListActivity.Companion.newIntent
|
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.ReselectableFragment
|
||||||
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
import com.keylesspalace.tusky.interfaces.StatusActionListener
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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.view.EndlessOnScrollListener
|
||||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||||
|
@ -56,8 +68,13 @@ import io.reactivex.rxjava3.core.Observable
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, Injectable,
|
class TimelineFragment :
|
||||||
ReselectableFragment, RefreshableFragment {
|
SFragment(),
|
||||||
|
OnRefreshListener,
|
||||||
|
StatusActionListener,
|
||||||
|
Injectable,
|
||||||
|
ReselectableFragment,
|
||||||
|
RefreshableFragment {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var viewModelFactory: ViewModelFactory
|
lateinit var viewModelFactory: ViewModelFactory
|
||||||
|
@ -161,8 +178,7 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
binding.recyclerView.setAccessibilityDelegateCompat(
|
binding.recyclerView.setAccessibilityDelegateCompat(
|
||||||
ListStatusAccessibilityDelegate(binding.recyclerView, this)
|
ListStatusAccessibilityDelegate(binding.recyclerView, this) { pos -> viewModel.statuses.getOrNull(pos) }
|
||||||
{ pos -> viewModel.statuses.getOrNull(pos) }
|
|
||||||
)
|
)
|
||||||
binding.recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
layoutManager = LinearLayoutManager(context)
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
@ -330,8 +346,10 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewAccount(id: String) {
|
override fun onViewAccount(id: String) {
|
||||||
if ((viewModel.kind == TimelineViewModel.Kind.USER ||
|
if ((
|
||||||
viewModel.kind == TimelineViewModel.Kind.USER_WITH_REPLIES) &&
|
viewModel.kind == TimelineViewModel.Kind.USER ||
|
||||||
|
viewModel.kind == TimelineViewModel.Kind.USER_WITH_REPLIES
|
||||||
|
) &&
|
||||||
viewModel.id == id
|
viewModel.id == id
|
||||||
) {
|
) {
|
||||||
/* If already viewing an account page, then any requests to view that account page
|
/* 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 HASHTAGS_ARG = "hashtags"
|
||||||
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "enableSwipeToRefresh"
|
private const val ARG_ENABLE_SWIPE_TO_REFRESH = "enableSwipeToRefresh"
|
||||||
|
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
kind: TimelineViewModel.Kind,
|
kind: TimelineViewModel.Kind,
|
||||||
hashtagOrId: String? = null,
|
hashtagOrId: String? = null,
|
||||||
|
@ -531,7 +548,6 @@ class TimelineFragment : SFragment(), OnRefreshListener, StatusActionListener, I
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val diffCallback: DiffUtil.ItemCallback<StatusViewData> =
|
private val diffCallback: DiffUtil.ItemCallback<StatusViewData> =
|
||||||
object : DiffUtil.ItemCallback<StatusViewData>() {
|
object : DiffUtil.ItemCallback<StatusViewData>() {
|
||||||
override fun areItemsTheSame(
|
override fun areItemsTheSame(
|
||||||
|
|
|
@ -5,11 +5,19 @@ import androidx.core.text.parseAsHtml
|
||||||
import androidx.core.text.toHtml
|
import androidx.core.text.toHtml
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.keylesspalace.tusky.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.DISK
|
||||||
import com.keylesspalace.tusky.components.timeline.TimelineRequestMode.NETWORK
|
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.Either
|
||||||
import com.keylesspalace.tusky.util.dec
|
import com.keylesspalace.tusky.util.dec
|
||||||
import com.keylesspalace.tusky.util.inc
|
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.core.Single
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
|
|
||||||
data class Placeholder(val id: String)
|
data class Placeholder(val id: String)
|
||||||
|
|
||||||
|
@ -31,7 +38,10 @@ enum class TimelineRequestMode {
|
||||||
|
|
||||||
interface TimelineRepository {
|
interface TimelineRepository {
|
||||||
fun getStatuses(
|
fun getStatuses(
|
||||||
maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int,
|
maxId: String?,
|
||||||
|
sinceId: String?,
|
||||||
|
sincedIdMinusOne: String?,
|
||||||
|
limit: Int,
|
||||||
requestMode: TimelineRequestMode
|
requestMode: TimelineRequestMode
|
||||||
): Single<out List<TimelineStatus>>
|
): Single<out List<TimelineStatus>>
|
||||||
|
|
||||||
|
@ -52,8 +62,11 @@ class TimelineRepositoryImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatuses(
|
override fun getStatuses(
|
||||||
maxId: String?, sinceId: String?, sincedIdMinusOne: String?,
|
maxId: String?,
|
||||||
limit: Int, requestMode: TimelineRequestMode
|
sinceId: String?,
|
||||||
|
sincedIdMinusOne: String?,
|
||||||
|
limit: Int,
|
||||||
|
requestMode: TimelineRequestMode
|
||||||
): Single<out List<TimelineStatus>> {
|
): Single<out List<TimelineStatus>> {
|
||||||
val acc = accountManager.activeAccount ?: throw IllegalStateException()
|
val acc = accountManager.activeAccount ?: throw IllegalStateException()
|
||||||
val accountId = acc.id
|
val accountId = acc.id
|
||||||
|
@ -66,9 +79,12 @@ class TimelineRepositoryImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStatusesFromNetwork(
|
private fun getStatusesFromNetwork(
|
||||||
maxId: String?, sinceId: String?,
|
maxId: String?,
|
||||||
sinceIdMinusOne: String?, limit: Int,
|
sinceId: String?,
|
||||||
accountId: Long, requestMode: TimelineRequestMode
|
sinceIdMinusOne: String?,
|
||||||
|
limit: Int,
|
||||||
|
accountId: Long,
|
||||||
|
requestMode: TimelineRequestMode
|
||||||
): Single<out List<TimelineStatus>> {
|
): Single<out List<TimelineStatus>> {
|
||||||
return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)
|
return mastodonApi.homeTimeline(maxId, sinceIdMinusOne, limit + 1)
|
||||||
.map { response ->
|
.map { response ->
|
||||||
|
@ -87,8 +103,11 @@ class TimelineRepositoryImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addFromDbIfNeeded(
|
private fun addFromDbIfNeeded(
|
||||||
accountId: Long, statuses: List<Either<Placeholder, Status>>,
|
accountId: Long,
|
||||||
maxId: String?, sinceId: String?, limit: Int,
|
statuses: List<Either<Placeholder, Status>>,
|
||||||
|
maxId: String?,
|
||||||
|
sinceId: String?,
|
||||||
|
limit: Int,
|
||||||
requestMode: TimelineRequestMode
|
requestMode: TimelineRequestMode
|
||||||
): Single<List<TimelineStatus>> {
|
): Single<List<TimelineStatus>> {
|
||||||
return if (requestMode != NETWORK && statuses.size < 2) {
|
return if (requestMode != NETWORK && statuses.size < 2) {
|
||||||
|
@ -113,7 +132,9 @@ class TimelineRepositoryImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getStatusesFromDb(
|
private fun getStatusesFromDb(
|
||||||
accountId: Long, maxId: String?, sinceId: String?,
|
accountId: Long,
|
||||||
|
maxId: String?,
|
||||||
|
sinceId: String?,
|
||||||
limit: Int
|
limit: Int
|
||||||
): Single<out List<TimelineStatus>> {
|
): Single<out List<TimelineStatus>> {
|
||||||
return timelineDao.getStatusesForAccount(accountId, maxId, sinceId, limit)
|
return timelineDao.getStatusesForAccount(accountId, maxId, sinceId, limit)
|
||||||
|
@ -124,8 +145,10 @@ class TimelineRepositoryImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveStatusesToDb(
|
private fun saveStatusesToDb(
|
||||||
accountId: Long, statuses: List<Status>,
|
accountId: Long,
|
||||||
maxId: String?, sinceId: String?
|
statuses: List<Status>,
|
||||||
|
maxId: String?,
|
||||||
|
sinceId: String?
|
||||||
): List<Either<Placeholder, Status>> {
|
): List<Either<Placeholder, Status>> {
|
||||||
var placeholderToInsert: Placeholder? = null
|
var placeholderToInsert: Placeholder? = null
|
||||||
|
|
||||||
|
@ -347,7 +370,6 @@ fun TimelineAccountEntity.toAccount(gson: Gson): Account {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity {
|
fun Placeholder.toEntity(timelineUserId: Long): TimelineStatusEntity {
|
||||||
return TimelineStatusEntity(
|
return TimelineStatusEntity(
|
||||||
serverId = this.id,
|
serverId = this.id,
|
||||||
|
|
|
@ -3,7 +3,20 @@ package com.keylesspalace.tusky.components.timeline
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.viewModelScope
|
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.db.AccountManager
|
||||||
import com.keylesspalace.tusky.entity.Filter
|
import com.keylesspalace.tusky.entity.Filter
|
||||||
import com.keylesspalace.tusky.entity.Poll
|
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.MastodonApi
|
||||||
import com.keylesspalace.tusky.network.TimelineCases
|
import com.keylesspalace.tusky.network.TimelineCases
|
||||||
import com.keylesspalace.tusky.settings.PrefKeys
|
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 com.keylesspalace.tusky.viewdata.StatusViewData
|
||||||
import io.reactivex.rxjava3.core.Observable
|
import io.reactivex.rxjava3.core.Observable
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
@ -238,8 +259,8 @@ class TimelineViewModel @Inject constructor(
|
||||||
private fun addStatusesBelow(statuses: MutableList<Either<Placeholder, Status>>) {
|
private fun addStatusesBelow(statuses: MutableList<Either<Placeholder, Status>>) {
|
||||||
val fullFetch = isFullFetch(statuses)
|
val fullFetch = isFullFetch(statuses)
|
||||||
// Remove placeholder in the bottom if it's there
|
// Remove placeholder in the bottom if it's there
|
||||||
if (this.statuses.isNotEmpty()
|
if (this.statuses.isNotEmpty() &&
|
||||||
&& this.statuses.last() !is StatusViewData.Concrete
|
this.statuses.last() !is StatusViewData.Concrete
|
||||||
) {
|
) {
|
||||||
this.statuses.removeAt(this.statuses.lastIndex)
|
this.statuses.removeAt(this.statuses.lastIndex)
|
||||||
}
|
}
|
||||||
|
@ -318,7 +339,6 @@ class TimelineViewModel @Inject constructor(
|
||||||
} catch (t: Exception) {
|
} catch (t: Exception) {
|
||||||
ifExpected(t) {
|
ifExpected(t) {
|
||||||
Log.d(TAG, "Failed to reblog status " + status.id, 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 {
|
private fun shouldFilterStatus(status: Status): Boolean {
|
||||||
return status.inReplyToId != null && filterRemoveReplies
|
return status.inReplyToId != null && filterRemoveReplies ||
|
||||||
|| status.reblog != null && filterRemoveReblogs
|
status.reblog != null && filterRemoveReblogs ||
|
||||||
|| filterModel.shouldFilterStatus(status.actionableStatus)
|
filterModel.shouldFilterStatus(status.actionableStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractNextId(response: Response<*>): String? {
|
private fun extractNextId(response: Response<*>): String? {
|
||||||
|
@ -644,7 +664,8 @@ class TimelineViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun replacePlaceholderWithStatuses(
|
private fun replacePlaceholderWithStatuses(
|
||||||
newStatuses: MutableList<Either<Placeholder, Status>>,
|
newStatuses: MutableList<Either<Placeholder, Status>>,
|
||||||
fullFetch: Boolean, pos: Int
|
fullFetch: Boolean,
|
||||||
|
pos: Int
|
||||||
) {
|
) {
|
||||||
val placeholder = statuses[pos]
|
val placeholder = statuses[pos]
|
||||||
if (placeholder is StatusViewData.Placeholder) {
|
if (placeholder is StatusViewData.Placeholder) {
|
||||||
|
@ -873,9 +894,11 @@ class TimelineViewModel @Inject constructor(
|
||||||
Log.e(TAG, "Failed to fetch filters", t)
|
Log.e(TAG, "Failed to fetch filters", t)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
filterModel.initWithFilters(filters.filter {
|
filterModel.initWithFilters(
|
||||||
|
filters.filter {
|
||||||
filterContextMatchesKind(kind, it.context)
|
filterContextMatchesKind(kind, it.context)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
filterViewData(this@TimelineViewModel.statuses)
|
filterViewData(this@TimelineViewModel.statuses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -891,7 +914,6 @@ class TimelineViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "TimelineVM"
|
private const val TAG = "TimelineVM"
|
||||||
internal const val LOAD_AT_ONCE = 30
|
internal const val LOAD_AT_ONCE = 30
|
||||||
|
|
|
@ -15,7 +15,11 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.db
|
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
|
@Dao
|
||||||
interface AccountDao {
|
interface AccountDao {
|
||||||
|
@ -27,5 +31,4 @@ interface AccountDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM AccountEntity ORDER BY id ASC")
|
@Query("SELECT * FROM AccountEntity ORDER BY id ASC")
|
||||||
fun loadAll(): List<AccountEntity>
|
fun loadAll(): List<AccountEntity>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,20 @@ import androidx.room.PrimaryKey
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import com.keylesspalace.tusky.TabData
|
import com.keylesspalace.tusky.TabData
|
||||||
import com.keylesspalace.tusky.defaultTabs
|
import com.keylesspalace.tusky.defaultTabs
|
||||||
|
|
||||||
import com.keylesspalace.tusky.entity.Emoji
|
import com.keylesspalace.tusky.entity.Emoji
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
|
|
||||||
@Entity(indices = [Index(value = ["domain", "accountId"],
|
@Entity(
|
||||||
unique = true)])
|
indices = [
|
||||||
|
Index(
|
||||||
|
value = ["domain", "accountId"],
|
||||||
|
unique = true
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
@TypeConverters(Converters::class)
|
@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,
|
val domain: String,
|
||||||
var accessToken: String,
|
var accessToken: String,
|
||||||
var isActive: Boolean,
|
var isActive: Boolean,
|
||||||
|
@ -56,7 +62,8 @@ data class AccountEntity(@field:PrimaryKey(autoGenerate = true) var id: Long,
|
||||||
var activeNotifications: String = "[]",
|
var activeNotifications: String = "[]",
|
||||||
var emojis: List<Emoji> = emptyList(),
|
var emojis: List<Emoji> = emptyList(),
|
||||||
var tabPreferences: List<TabData> = defaultTabs(),
|
var tabPreferences: List<TabData> = defaultTabs(),
|
||||||
var notificationsFilter: String = "[\"follow_request\"]") {
|
var notificationsFilter: String = "[\"follow_request\"]"
|
||||||
|
) {
|
||||||
|
|
||||||
val identifier: String
|
val identifier: String
|
||||||
get() = "$domain:$accountId"
|
get() = "$domain:$accountId"
|
||||||
|
|
|
@ -18,7 +18,7 @@ package com.keylesspalace.tusky.db
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.keylesspalace.tusky.entity.Account
|
import com.keylesspalace.tusky.entity.Account
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@ -66,7 +66,6 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
||||||
val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0
|
val maxAccountId = accounts.maxByOrNull { it.id }?.id ?: 0
|
||||||
val newAccountId = maxAccountId + 1
|
val newAccountId = maxAccountId + 1
|
||||||
activeAccount = AccountEntity(id = newAccountId, domain = domain.lowercase(Locale.ROOT), accessToken = accessToken, isActive = true)
|
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)
|
Log.d(TAG, "saveAccount: saving account with id " + account.id)
|
||||||
accountDao.insertOrReplace(account)
|
accountDao.insertOrReplace(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,9 +101,7 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
||||||
activeAccount = null
|
activeAccount = null
|
||||||
}
|
}
|
||||||
return activeAccount
|
return activeAccount
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,7 +131,6 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
||||||
} else {
|
} else {
|
||||||
accounts.add(it)
|
accounts.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,5 +189,4 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
||||||
id == accountId
|
id == accountId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -39,5 +39,4 @@ interface ConversationsDao {
|
||||||
|
|
||||||
@Query("DELETE FROM ConversationEntity WHERE accountId = :accountId")
|
@Query("DELETE FROM ConversationEntity WHERE accountId = :accountId")
|
||||||
fun deleteForAccount(accountId: Long)
|
fun deleteForAccount(accountId: Long)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,16 @@ import com.google.gson.reflect.TypeToken
|
||||||
import com.keylesspalace.tusky.TabData
|
import com.keylesspalace.tusky.TabData
|
||||||
import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity
|
import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity
|
||||||
import com.keylesspalace.tusky.createTabDataFromId
|
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 com.keylesspalace.tusky.util.trimTrailingWhitespace
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.util.*
|
import java.util.ArrayList
|
||||||
|
import java.util.Date
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
|
@ -38,5 +38,4 @@ interface DraftDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM DraftEntity WHERE id = :id")
|
@Query("SELECT * FROM DraftEntity WHERE id = :id")
|
||||||
suspend fun find(id: Int): DraftEntity?
|
suspend fun find(id: Int): DraftEntity?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,11 +17,11 @@ abstract class TimelineDao {
|
||||||
@Insert(onConflict = REPLACE)
|
@Insert(onConflict = REPLACE)
|
||||||
abstract fun insertStatus(timelineAccountEntity: TimelineStatusEntity): Long
|
abstract fun insertStatus(timelineAccountEntity: TimelineStatusEntity): Long
|
||||||
|
|
||||||
|
|
||||||
@Insert(onConflict = IGNORE)
|
@Insert(onConflict = IGNORE)
|
||||||
abstract fun insertStatusIfNotThere(timelineAccountEntity: TimelineStatusEntity): Long
|
abstract fun insertStatusIfNotThere(timelineAccountEntity: TimelineStatusEntity): Long
|
||||||
|
|
||||||
@Query("""
|
@Query(
|
||||||
|
"""
|
||||||
SELECT s.serverId, s.url, s.timelineUserId,
|
SELECT s.serverId, s.url, s.timelineUserId,
|
||||||
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt,
|
s.authorServerId, s.inReplyToId, s.inReplyToAccountId, s.createdAt,
|
||||||
s.emojis, s.reblogsCount, s.favouritesCount, s.reblogged, s.favourited, s.bookmarked, s.sensitive,
|
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)
|
(LENGTH(s.serverId) > LENGTH(:sinceId) OR LENGTH(s.serverId) == LENGTH(:sinceId) AND s.serverId > :sinceId)
|
||||||
ELSE 1 END)
|
ELSE 1 END)
|
||||||
ORDER BY LENGTH(s.serverId) DESC, s.serverId DESC
|
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>>
|
abstract fun getStatusesForAccount(account: Long, maxId: String?, sinceId: String?, limit: Int): Single<List<TimelineStatusWithAccount>>
|
||||||
|
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
open fun insertInTransaction(status: TimelineStatusEntity, account: TimelineAccountEntity,
|
open fun insertInTransaction(
|
||||||
reblogAccount: TimelineAccountEntity?) {
|
status: TimelineStatusEntity,
|
||||||
|
account: TimelineAccountEntity,
|
||||||
|
reblogAccount: TimelineAccountEntity?
|
||||||
|
) {
|
||||||
insertAccount(account)
|
insertAccount(account)
|
||||||
reblogAccount?.let(this::insertAccount)
|
reblogAccount?.let(this::insertAccount)
|
||||||
insertStatus(status)
|
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)
|
(LENGTH(serverId) < LENGTH(:maxId) OR LENGTH(serverId) == LENGTH(:maxId) AND serverId < :maxId)
|
||||||
AND
|
AND
|
||||||
(LENGTH(serverId) > LENGTH(:minId) OR LENGTH(serverId) == LENGTH(:minId) AND serverId > :minId)
|
(LENGTH(serverId) > LENGTH(:minId) OR LENGTH(serverId) == LENGTH(:minId) AND serverId > :minId)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
abstract fun deleteRange(accountId: Long, minId: String, maxId: String)
|
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
|
AND timelineUserId = :account AND
|
||||||
(LENGTH(serverId) < LENGTH(:maxId) OR LENGTH(serverId) == LENGTH(:maxId) AND serverId < :maxId)
|
(LENGTH(serverId) < LENGTH(:maxId) OR LENGTH(serverId) == LENGTH(:maxId) AND serverId < :maxId)
|
||||||
AND
|
AND
|
||||||
(LENGTH(serverId) > LENGTH(:sinceId) OR LENGTH(serverId) == LENGTH(:sinceId) AND serverId > :sinceId)
|
(LENGTH(serverId) > LENGTH(:sinceId) OR LENGTH(serverId) == LENGTH(:sinceId) AND serverId > :sinceId)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
abstract fun removeAllPlaceholdersBetween(account: Long, maxId: String, sinceId: String)
|
abstract fun removeAllPlaceholdersBetween(account: Long, maxId: String, sinceId: String)
|
||||||
|
|
||||||
@Query("""UPDATE TimelineStatusEntity SET favourited = :favourited
|
@Query(
|
||||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""")
|
"""UPDATE TimelineStatusEntity SET favourited = :favourited
|
||||||
|
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||||
|
)
|
||||||
abstract fun setFavourited(accountId: Long, statusId: String, favourited: Boolean)
|
abstract fun setFavourited(accountId: Long, statusId: String, favourited: Boolean)
|
||||||
|
|
||||||
@Query("""UPDATE TimelineStatusEntity SET bookmarked = :bookmarked
|
@Query(
|
||||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""")
|
"""UPDATE TimelineStatusEntity SET bookmarked = :bookmarked
|
||||||
|
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||||
|
)
|
||||||
abstract fun setBookmarked(accountId: Long, statusId: String, bookmarked: Boolean)
|
abstract fun setBookmarked(accountId: Long, statusId: String, bookmarked: Boolean)
|
||||||
|
|
||||||
@Query("""UPDATE TimelineStatusEntity SET reblogged = :reblogged
|
@Query(
|
||||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""")
|
"""UPDATE TimelineStatusEntity SET reblogged = :reblogged
|
||||||
|
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||||
|
)
|
||||||
abstract fun setReblogged(accountId: Long, statusId: String, reblogged: Boolean)
|
abstract fun setReblogged(accountId: Long, statusId: String, reblogged: Boolean)
|
||||||
|
|
||||||
@Query("""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND
|
@Query(
|
||||||
(authorServerId = :userId OR reblogAccountId = :userId)""")
|
"""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId AND
|
||||||
|
(authorServerId = :userId OR reblogAccountId = :userId)"""
|
||||||
|
)
|
||||||
abstract fun removeAllByUser(accountId: Long, userId: String)
|
abstract fun removeAllByUser(accountId: Long, userId: String)
|
||||||
|
|
||||||
@Query("DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId")
|
@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")
|
@Query("DELETE FROM TimelineAccountEntity WHERE timelineUserId = :accountId")
|
||||||
abstract fun removeAllUsersForAccount(accountId: Long)
|
abstract fun removeAllUsersForAccount(accountId: Long)
|
||||||
|
|
||||||
@Query("""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId
|
@Query(
|
||||||
AND serverId = :statusId""")
|
"""DELETE FROM TimelineStatusEntity WHERE timelineUserId = :accountId
|
||||||
|
AND serverId = :statusId"""
|
||||||
|
)
|
||||||
abstract fun delete(accountId: Long, statusId: String)
|
abstract fun delete(accountId: Long, statusId: String)
|
||||||
|
|
||||||
@Query("""DELETE FROM TimelineStatusEntity WHERE createdAt < :olderThan""")
|
@Query("""DELETE FROM TimelineStatusEntity WHERE createdAt < :olderThan""")
|
||||||
abstract fun cleanup(olderThan: Long)
|
abstract fun cleanup(olderThan: Long)
|
||||||
|
|
||||||
@Query("""UPDATE TimelineStatusEntity SET poll = :poll
|
@Query(
|
||||||
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)""")
|
"""UPDATE TimelineStatusEntity SET poll = :poll
|
||||||
|
WHERE timelineUserId = :accountId AND (serverId = :statusId OR reblogServerId = :statusId)"""
|
||||||
|
)
|
||||||
abstract fun setVoted(accountId: Long, statusId: String, poll: String)
|
abstract fun setVoted(accountId: Long, statusId: String, poll: String)
|
||||||
}
|
}
|
|
@ -1,6 +1,10 @@
|
||||||
package com.keylesspalace.tusky.db
|
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
|
import com.keylesspalace.tusky.entity.Status
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,13 +20,15 @@ import com.keylesspalace.tusky.entity.Status
|
||||||
*/
|
*/
|
||||||
@Entity(
|
@Entity(
|
||||||
primaryKeys = ["serverId", "timelineUserId"],
|
primaryKeys = ["serverId", "timelineUserId"],
|
||||||
foreignKeys = ([
|
foreignKeys = (
|
||||||
|
[
|
||||||
ForeignKey(
|
ForeignKey(
|
||||||
entity = TimelineAccountEntity::class,
|
entity = TimelineAccountEntity::class,
|
||||||
parentColumns = ["serverId", "timelineUserId"],
|
parentColumns = ["serverId", "timelineUserId"],
|
||||||
childColumns = ["authorServerId", "timelineUserId"]
|
childColumns = ["authorServerId", "timelineUserId"]
|
||||||
)
|
)
|
||||||
]),
|
]
|
||||||
|
),
|
||||||
// Avoiding rescanning status table when accounts table changes. Recommended by Room(c).
|
// Avoiding rescanning status table when accounts table changes. Recommended by Room(c).
|
||||||
indices = [Index("authorServerId", "timelineUserId")]
|
indices = [Index("authorServerId", "timelineUserId")]
|
||||||
)
|
)
|
||||||
|
@ -70,7 +76,6 @@ data class TimelineAccountEntity(
|
||||||
val bot: Boolean
|
val bot: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TimelineStatusWithAccount {
|
class TimelineStatusWithAccount {
|
||||||
@Embedded
|
@Embedded
|
||||||
lateinit var status: TimelineStatusEntity
|
lateinit var status: TimelineStatusEntity
|
||||||
|
|
|
@ -15,7 +15,23 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.di
|
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.announcements.AnnouncementsActivity
|
||||||
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
import com.keylesspalace.tusky.components.compose.ComposeActivity
|
||||||
import com.keylesspalace.tusky.components.drafts.DraftsActivity
|
import com.keylesspalace.tusky.components.drafts.DraftsActivity
|
||||||
|
|
|
@ -21,13 +21,13 @@ import dagger.Component
|
||||||
import dagger.android.support.AndroidSupportInjectionModule
|
import dagger.android.support.AndroidSupportInjectionModule
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by charlag on 3/21/18.
|
* Created by charlag on 3/21/18.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = [
|
@Component(
|
||||||
|
modules = [
|
||||||
AppModule::class,
|
AppModule::class,
|
||||||
NetworkModule::class,
|
NetworkModule::class,
|
||||||
AndroidSupportInjectionModule::class,
|
AndroidSupportInjectionModule::class,
|
||||||
|
@ -37,7 +37,8 @@ import javax.inject.Singleton
|
||||||
ViewModelModule::class,
|
ViewModelModule::class,
|
||||||
RepositoryModule::class,
|
RepositoryModule::class,
|
||||||
MediaUploaderModule::class
|
MediaUploaderModule::class
|
||||||
])
|
]
|
||||||
|
)
|
||||||
interface AppComponent {
|
interface AppComponent {
|
||||||
@Component.Builder
|
@Component.Builder
|
||||||
interface Builder {
|
interface Builder {
|
||||||
|
|
|
@ -58,7 +58,6 @@ object AppInjector {
|
||||||
|
|
||||||
override fun onActivityStopped(activity: Activity) {
|
override fun onActivityStopped(activity: Activity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +73,9 @@ object AppInjector {
|
||||||
AndroidSupportInjection.inject(f)
|
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,
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
* see <http://www.gnu.org/licenses>. */
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
@ -60,8 +59,10 @@ class AppModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun providesTimelineUseCases(api: MastodonApi,
|
fun providesTimelineUseCases(
|
||||||
eventHub: EventHub): TimelineCases {
|
api: MastodonApi,
|
||||||
|
eventHub: EventHub
|
||||||
|
): TimelineCases {
|
||||||
return TimelineCasesImpl(api, eventHub)
|
return TimelineCasesImpl(api, eventHub)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +76,8 @@ class AppModule {
|
||||||
return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB")
|
return Room.databaseBuilder(appContext, AppDatabase::class.java, "tuskyDB")
|
||||||
.addTypeConverter(converters)
|
.addTypeConverter(converters)
|
||||||
.allowMainThreadQueries()
|
.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_5_6, AppDatabase.MIGRATION_6_7, AppDatabase.MIGRATION_7_8,
|
||||||
AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11,
|
AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11,
|
||||||
AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13,
|
AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13, AppDatabase.MIGRATION_10_13,
|
||||||
|
@ -92,5 +94,4 @@ class AppModule {
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun notifier(context: Context): Notifier = SystemNotifier(context)
|
fun notifier(context: Context): Notifier = SystemNotifier(context)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver
|
|
||||||
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver
|
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver
|
||||||
|
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.android.ContributesAndroidInjector
|
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,
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
* see <http://www.gnu.org/licenses>. */
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
import com.keylesspalace.tusky.AccountsInListFragment
|
import com.keylesspalace.tusky.AccountsInListFragment
|
||||||
import com.keylesspalace.tusky.components.conversation.ConversationsFragment
|
import com.keylesspalace.tusky.components.conversation.ConversationsFragment
|
||||||
import com.keylesspalace.tusky.components.instancemute.fragment.InstanceListFragment
|
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.AccountPreferencesFragment
|
||||||
import com.keylesspalace.tusky.components.preference.NotificationPreferencesFragment
|
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.ReportDoneFragment
|
||||||
import com.keylesspalace.tusky.components.report.fragments.ReportNoteFragment
|
import com.keylesspalace.tusky.components.report.fragments.ReportNoteFragment
|
||||||
import com.keylesspalace.tusky.components.report.fragments.ReportStatusesFragment
|
import com.keylesspalace.tusky.components.report.fragments.ReportStatusesFragment
|
||||||
import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragment
|
import com.keylesspalace.tusky.components.search.fragments.SearchAccountsFragment
|
||||||
import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment
|
import com.keylesspalace.tusky.components.search.fragments.SearchHashtagsFragment
|
||||||
import com.keylesspalace.tusky.components.search.fragments.SearchStatusesFragment
|
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.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.Module
|
||||||
import dagger.android.ContributesAndroidInjector
|
import dagger.android.ContributesAndroidInjector
|
||||||
|
|
||||||
|
@ -89,5 +91,4 @@ abstract class FragmentBuildersModule {
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun preferencesFragment(): PreferencesFragment
|
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,
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||||
* see <http://www.gnu.org/licenses>. */
|
* see <http://www.gnu.org/licenses>. */
|
||||||
|
|
||||||
|
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -112,7 +112,6 @@ class NetworkModule {
|
||||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package com.keylesspalace.tusky.di
|
package com.keylesspalace.tusky.di
|
||||||
|
|
||||||
import com.google.gson.Gson
|
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.AccountManager
|
||||||
import com.keylesspalace.tusky.db.AppDatabase
|
import com.keylesspalace.tusky.db.AppDatabase
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
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.Module
|
||||||
import dagger.Provides
|
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.report.ReportViewModel
|
||||||
import com.keylesspalace.tusky.components.scheduled.ScheduledTootViewModel
|
import com.keylesspalace.tusky.components.scheduled.ScheduledTootViewModel
|
||||||
import com.keylesspalace.tusky.components.search.SearchViewModel
|
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.components.timeline.TimelineViewModel
|
||||||
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
import com.keylesspalace.tusky.viewmodel.AccountViewModel
|
||||||
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
|
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
|
||||||
|
@ -63,7 +62,6 @@ abstract class ViewModelModule {
|
||||||
@ViewModelKey(ListsViewModel::class)
|
@ViewModelKey(ListsViewModel::class)
|
||||||
internal abstract fun listsViewModel(viewModel: ListsViewModel): ViewModel
|
internal abstract fun listsViewModel(viewModel: ListsViewModel): ViewModel
|
||||||
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(AccountsInListViewModel::class)
|
@ViewModelKey(AccountsInListViewModel::class)
|
||||||
|
|
|
@ -57,22 +57,22 @@ data class Account(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deepEquals(other: Account): Boolean {
|
fun deepEquals(other: Account): Boolean {
|
||||||
return id == other.id
|
return id == other.id &&
|
||||||
&& localUsername == other.localUsername
|
localUsername == other.localUsername &&
|
||||||
&& displayName == other.displayName
|
displayName == other.displayName &&
|
||||||
&& note == other.note
|
note == other.note &&
|
||||||
&& url == other.url
|
url == other.url &&
|
||||||
&& avatar == other.avatar
|
avatar == other.avatar &&
|
||||||
&& header == other.header
|
header == other.header &&
|
||||||
&& locked == other.locked
|
locked == other.locked &&
|
||||||
&& followersCount == other.followersCount
|
followersCount == other.followersCount &&
|
||||||
&& followingCount == other.followingCount
|
followingCount == other.followingCount &&
|
||||||
&& statusesCount == other.statusesCount
|
statusesCount == other.statusesCount &&
|
||||||
&& source == other.source
|
source == other.source &&
|
||||||
&& bot == other.bot
|
bot == other.bot &&
|
||||||
&& emojis == other.emojis
|
emojis == other.emojis &&
|
||||||
&& fields == other.fields
|
fields == other.fields &&
|
||||||
&& moved == other.moved
|
moved == other.moved
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isRemote(): Boolean = this.username != this.localUsername
|
fun isRemote(): Boolean = this.username != this.localUsername
|
||||||
|
|
|
@ -17,7 +17,7 @@ package com.keylesspalace.tusky.entity
|
||||||
|
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
data class Announcement(
|
data class Announcement(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
package com.keylesspalace.tusky.entity
|
package com.keylesspalace.tusky.entity
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import java.util.*
|
import java.util.ArrayList
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
data class DeletedStatus(
|
data class DeletedStatus(
|
||||||
var text: String?,
|
var text: String?,
|
||||||
|
|
|
@ -45,4 +45,3 @@ data class Filter (
|
||||||
return filter?.id.equals(id)
|
return filter?.id.equals(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package com.keylesspalace.tusky.entity
|
package com.keylesspalace.tusky.entity
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API type for saving the scroll position of a timeline.
|
* 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