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
|
||||||
|
@ -143,7 +148,7 @@ class TimelineDAOTest {
|
||||||
timelineDao.insertInTransaction(status, author, reblogAuthor)
|
timelineDao.insertInTransaction(status, author, reblogAuthor)
|
||||||
}
|
}
|
||||||
|
|
||||||
//make sure status 2 is no longer in db
|
// make sure status 2 is no longer in db
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
newStatuses,
|
newStatuses,
|
||||||
|
@ -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
|
||||||
|
@ -32,7 +32,7 @@ class AboutActivity : BottomSheetActivity(), Injectable {
|
||||||
|
|
||||||
binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME)
|
binding.versionTextView.text = getString(R.string.about_app_version, getString(R.string.app_name), BuildConfig.VERSION_NAME)
|
||||||
|
|
||||||
if(BuildConfig.CUSTOM_INSTANCE.isBlank()) {
|
if (BuildConfig.CUSTOM_INSTANCE.isBlank()) {
|
||||||
binding.aboutPoweredByTusky.hide()
|
binding.aboutPoweredByTusky.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -82,7 +91,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
|
|
||||||
private val binding: ActivityAccountBinding by viewBinding(ActivityAccountBinding::inflate)
|
private val binding: ActivityAccountBinding by viewBinding(ActivityAccountBinding::inflate)
|
||||||
|
|
||||||
private lateinit var accountFieldAdapter : AccountFieldAdapter
|
private lateinit var accountFieldAdapter: AccountFieldAdapter
|
||||||
|
|
||||||
private var followState: FollowState = FollowState.NOT_FOLLOWING
|
private var followState: FollowState = FollowState.NOT_FOLLOWING
|
||||||
private var blocking: Boolean = false
|
private var blocking: Boolean = false
|
||||||
|
@ -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,15 +564,16 @@ 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()
|
||||||
}
|
}
|
||||||
if(relation.notifying != null)
|
if (relation.notifying != null)
|
||||||
subscribing = relation.notifying
|
subscribing = relation.notifying
|
||||||
else if(relation.subscribing != null)
|
else if (relation.subscribing != null)
|
||||||
subscribing = relation.subscribing
|
subscribing = relation.subscribing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -577,7 +588,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
updateButtons()
|
updateButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val noteWatcher = object: DefaultTextWatcher() {
|
private val noteWatcher = object : DefaultTextWatcher() {
|
||||||
override fun afterTextChanged(s: Editable) {
|
override fun afterTextChanged(s: Editable) {
|
||||||
viewModel.noteChanged(s.toString())
|
viewModel.noteChanged(s.toString())
|
||||||
}
|
}
|
||||||
|
@ -615,11 +626,11 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSubscribeButton() {
|
private fun updateSubscribeButton() {
|
||||||
if(followState != FollowState.FOLLOWING) {
|
if (followState != FollowState.FOLLOWING) {
|
||||||
binding.accountSubscribeButton.hide()
|
binding.accountSubscribeButton.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
if(subscribing) {
|
if (subscribing) {
|
||||||
binding.accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp)
|
binding.accountSubscribeButton.setIconResource(R.drawable.ic_notifications_active_24dp)
|
||||||
binding.accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account)
|
binding.accountSubscribeButton.contentDescription = getString(R.string.action_unsubscribe_account)
|
||||||
} else {
|
} else {
|
||||||
|
@ -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)
|
||||||
|
@ -732,7 +740,7 @@ class AccountActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidI
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleBlockDomain(instance: String) {
|
private fun toggleBlockDomain(instance: String) {
|
||||||
if(blockingDomain) {
|
if (blockingDomain) {
|
||||||
viewModel.unblockDomain(instance)
|
viewModel.unblockDomain(instance)
|
||||||
} else {
|
} else {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
|
@ -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 {
|
||||||
|
@ -110,11 +116,11 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
|
|
||||||
binding.addFieldButton.setOnClickListener {
|
binding.addFieldButton.setOnClickListener {
|
||||||
accountFieldEditAdapter.addField()
|
accountFieldEditAdapter.addField()
|
||||||
if(accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) {
|
if (accountFieldEditAdapter.itemCount >= MAX_ACCOUNT_FIELDS) {
|
||||||
it.isVisible = false
|
it.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.scrollView.post{
|
binding.scrollView.post {
|
||||||
binding.scrollView.smoothScrollTo(0, it.bottom)
|
binding.scrollView.smoothScrollTo(0, it.bottom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +140,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList())
|
accountFieldEditAdapter.setFields(me.source?.fields ?: emptyList())
|
||||||
binding.addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
|
binding.addFieldButton.isEnabled = me.source?.fields?.size ?: 0 < MAX_ACCOUNT_FIELDS
|
||||||
|
|
||||||
if(viewModel.avatarData.value == null) {
|
if (viewModel.avatarData.value == null) {
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.load(me.avatar)
|
.load(me.avatar)
|
||||||
.placeholder(R.drawable.avatar_default)
|
.placeholder(R.drawable.avatar_default)
|
||||||
|
@ -145,12 +151,11 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
.into(binding.avatarPreview)
|
.into(binding.avatarPreview)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(viewModel.headerData.value == null) {
|
if (viewModel.headerData.value == null) {
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.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,8 +183,10 @@ 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(
|
||||||
when(it) {
|
this,
|
||||||
|
{
|
||||||
|
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) {
|
||||||
|
@ -202,19 +208,25 @@ 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 -> {
|
||||||
|
@ -238,14 +250,14 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
}
|
}
|
||||||
is Error -> {
|
is Error -> {
|
||||||
progressBar.hide()
|
progressBar.hide()
|
||||||
if(!it.consumed) {
|
if (!it.consumed) {
|
||||||
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?) {
|
||||||
|
@ -383,7 +400,7 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun beginResize(uri: Uri?) {
|
private fun beginResize(uri: Uri?) {
|
||||||
if(uri == null) {
|
if (uri == null) {
|
||||||
currentlyPicking = PickType.NOTHING
|
currentlyPicking = PickType.NOTHING
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -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,10 +22,9 @@ 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() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
|
||||||
|
@ -34,7 +33,7 @@ class FiltersActivity: BaseActivity() {
|
||||||
|
|
||||||
private val binding by viewBinding(ActivityFiltersBinding::inflate)
|
private val binding by viewBinding(ActivityFiltersBinding::inflate)
|
||||||
|
|
||||||
private lateinit var context : String
|
private lateinit var context: String
|
||||||
private lateinit var filters: MutableList<Filter>
|
private lateinit var filters: MutableList<Filter>
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -58,7 +57,7 @@ class FiltersActivity: BaseActivity() {
|
||||||
|
|
||||||
private fun updateFilter(filter: Filter, itemIndex: Int) {
|
private fun updateFilter(filter: Filter, itemIndex: Int) {
|
||||||
api.updateFilter(filter.id, filter.phrase, filter.context, filter.irreversible, filter.wholeWord, filter.expiresAt)
|
api.updateFilter(filter.id, filter.phrase, filter.context, filter.irreversible, filter.wholeWord, filter.expiresAt)
|
||||||
.enqueue(object: Callback<Filter>{
|
.enqueue(object : Callback<Filter> {
|
||||||
override fun onFailure(call: Call<Filter>, t: Throwable) {
|
override fun onFailure(call: Call<Filter>, t: Throwable) {
|
||||||
Toast.makeText(this@FiltersActivity, "Error updating filter '${filter.phrase}'", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@FiltersActivity, "Error updating filter '${filter.phrase}'", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
@ -80,7 +79,7 @@ class FiltersActivity: BaseActivity() {
|
||||||
val filter = filters[itemIndex]
|
val filter = filters[itemIndex]
|
||||||
if (filter.context.size == 1) {
|
if (filter.context.size == 1) {
|
||||||
// This is the only context for this filter; delete it
|
// This is the only context for this filter; delete it
|
||||||
api.deleteFilter(filters[itemIndex].id).enqueue(object: Callback<ResponseBody> {
|
api.deleteFilter(filters[itemIndex].id).enqueue(object : Callback<ResponseBody> {
|
||||||
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
|
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
|
||||||
Toast.makeText(this@FiltersActivity, "Error updating filter '${filters[itemIndex].phrase}'", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@FiltersActivity, "Error updating filter '${filters[itemIndex].phrase}'", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
@ -94,17 +93,19 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createFilter(phrase: String, wholeWord: Boolean) {
|
private fun createFilter(phrase: String, wholeWord: Boolean) {
|
||||||
api.createFilter(phrase, listOf(context), false, wholeWord, "").enqueue(object: Callback<Filter> {
|
api.createFilter(phrase, listOf(context), false, wholeWord, "").enqueue(object : Callback<Filter> {
|
||||||
override fun onResponse(call: Call<Filter>, response: Response<Filter>) {
|
override fun onResponse(call: Call<Filter>, response: Response<Filter>) {
|
||||||
val filterResponse = response.body()
|
val filterResponse = response.body()
|
||||||
if(response.isSuccessful && filterResponse != null) {
|
if (response.isSuccessful && filterResponse != null) {
|
||||||
filters.add(filterResponse)
|
filters.add(filterResponse)
|
||||||
refreshFilterDisplay()
|
refreshFilterDisplay()
|
||||||
eventHub.dispatch(PreferenceChangedEvent(context))
|
eventHub.dispatch(PreferenceChangedEvent(context))
|
||||||
|
@ -125,7 +126,7 @@ class FiltersActivity: BaseActivity() {
|
||||||
AlertDialog.Builder(this@FiltersActivity)
|
AlertDialog.Builder(this@FiltersActivity)
|
||||||
.setTitle(R.string.filter_addition_dialog_title)
|
.setTitle(R.string.filter_addition_dialog_title)
|
||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
.setPositiveButton(android.R.string.ok){ _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked)
|
createFilter(binding.phraseEditText.text.toString(), binding.phraseWholeWord.isChecked)
|
||||||
}
|
}
|
||||||
.setNeutralButton(android.R.string.cancel, null)
|
.setNeutralButton(android.R.string.cancel, null)
|
||||||
|
@ -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)
|
||||||
|
@ -138,14 +156,13 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
|
||||||
private fun showListDeleteDialog(list: MastoList) {
|
private fun showListDeleteDialog(list: MastoList) {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setMessage(getString(R.string.dialog_delete_list_warning, list.title))
|
.setMessage(getString(R.string.dialog_delete_list_warning, list.title))
|
||||||
.setPositiveButton(R.string.action_delete){ _, _ ->
|
.setPositiveButton(R.string.action_delete) { _, _ ->
|
||||||
viewModel.deleteList(list.id)
|
viewModel.deleteList(list.id)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.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
|
||||||
|
@ -62,12 +66,12 @@ class LoginActivity : BaseActivity(), Injectable {
|
||||||
|
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
if(savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
|
if (savedInstanceState == null && BuildConfig.CUSTOM_INSTANCE.isNotBlank() && !isAdditionalLogin()) {
|
||||||
binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
|
binding.domainEditText.setText(BuildConfig.CUSTOM_INSTANCE)
|
||||||
binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
|
binding.domainEditText.setSelection(BuildConfig.CUSTOM_INSTANCE.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
|
if (BuildConfig.CUSTOM_LOGO_URL.isNotBlank()) {
|
||||||
Glide.with(binding.loginLogo)
|
Glide.with(binding.loginLogo)
|
||||||
.load(BuildConfig.CUSTOM_LOGO_URL)
|
.load(BuildConfig.CUSTOM_LOGO_URL)
|
||||||
.placeholder(null)
|
.placeholder(null)
|
||||||
|
@ -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 {
|
||||||
|
@ -104,7 +108,7 @@ class LoginActivity : BaseActivity(), Injectable {
|
||||||
|
|
||||||
override fun finish() {
|
override fun finish() {
|
||||||
super.finish()
|
super.finish()
|
||||||
if(isAdditionalLogin()) {
|
if (isAdditionalLogin()) {
|
||||||
overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right)
|
overridePendingTransition(R.anim.slide_from_left, R.anim.slide_to_right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -44,7 +41,7 @@ class StatusListActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||||
|
|
||||||
setSupportActionBar(binding.includedToolbar.toolbar)
|
setSupportActionBar(binding.includedToolbar.toolbar)
|
||||||
|
|
||||||
val title = if(kind == Kind.FAVOURITES) {
|
val title = if (kind == Kind.FAVOURITES) {
|
||||||
R.string.title_favourites
|
R.string.title_favourites
|
||||||
} else {
|
} else {
|
||||||
R.string.title_bookmarks
|
R.string.title_bookmarks
|
||||||
|
@ -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,13 +34,14 @@ 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,
|
||||||
val arguments: List<String> = emptyList(),
|
val arguments: List<String> = emptyList(),
|
||||||
val title: (Context) -> String = { context -> context.getString(text)}
|
val title: (Context) -> String = { context -> context.getString(text) }
|
||||||
)
|
)
|
||||||
|
|
||||||
fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabData {
|
fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabData {
|
||||||
return when (id) {
|
return when (id) {
|
||||||
|
@ -80,7 +81,7 @@ fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabD
|
||||||
R.drawable.ic_hashtag,
|
R.drawable.ic_hashtag,
|
||||||
{ args -> TimelineFragment.newHashtagInstance(args) },
|
{ args -> TimelineFragment.newHashtagInstance(args) },
|
||||||
arguments,
|
arguments,
|
||||||
{ context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) }}
|
{ context -> arguments.joinToString(separator = " ") { context.getString(R.string.title_tag, it) } }
|
||||||
)
|
)
|
||||||
LIST -> TabData(
|
LIST -> TabData(
|
||||||
LIST,
|
LIST,
|
||||||
|
|
|
@ -256,7 +256,7 @@ class TabPreferenceActivity : BaseActivity(), Injectable, ItemInteractionListene
|
||||||
mastodonApi.getLists()
|
mastodonApi.getLists()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
.autoDispose(from(this, Lifecycle.Event.ON_DESTROY))
|
||||||
.subscribe (
|
.subscribe(
|
||||||
{ lists ->
|
{ lists ->
|
||||||
adapter.addAll(lists)
|
adapter.addAll(lists)
|
||||||
},
|
},
|
||||||
|
@ -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")
|
||||||
|
@ -112,7 +111,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
|
|
||||||
binding.viewPager.adapter = adapter
|
binding.viewPager.adapter = adapter
|
||||||
binding.viewPager.setCurrentItem(initialPosition, false)
|
binding.viewPager.setCurrentItem(initialPosition, false)
|
||||||
binding.viewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() {
|
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
binding.toolbar.title = getPageTitle(position)
|
binding.toolbar.title = getPageTitle(position)
|
||||||
}
|
}
|
||||||
|
@ -193,7 +192,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPageTitle(position: Int): CharSequence {
|
private fun getPageTitle(position: Int): CharSequence {
|
||||||
if(attachments == null) {
|
if (attachments == null) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size)
|
return String.format(Locale.getDefault(), "%d/%d", position + 1, attachments?.size)
|
||||||
|
@ -206,8 +205,10 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
|
|
||||||
val downloadManager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val 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) {
|
||||||
|
@ -352,7 +350,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ViewMediaAdapter(activity: FragmentActivity): FragmentStateAdapter(activity) {
|
abstract class ViewMediaAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
|
||||||
abstract fun onTransitionEnd(position: Int)
|
abstract fun onTransitionEnd(position: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -47,7 +50,7 @@ class AccountFieldAdapter(
|
||||||
val nameTextView = holder.binding.accountFieldName
|
val nameTextView = holder.binding.accountFieldName
|
||||||
val valueTextView = holder.binding.accountFieldValue
|
val valueTextView = holder.binding.accountFieldValue
|
||||||
|
|
||||||
if(proofOrField.isLeft()) {
|
if (proofOrField.isLeft()) {
|
||||||
val identityProof = proofOrField.asLeft()
|
val identityProof = proofOrField.asLeft()
|
||||||
|
|
||||||
nameTextView.text = identityProof.provider
|
nameTextView.text = identityProof.provider
|
||||||
|
@ -64,12 +67,11 @@ class AccountFieldAdapter(
|
||||||
val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
|
val emojifiedValue = field.value.emojify(emojis, valueTextView, animateEmojis)
|
||||||
LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener)
|
LinkHelper.setClickableText(valueTextView, emojifiedValue, null, linkListener)
|
||||||
|
|
||||||
if(field.verifiedAt != null) {
|
if (field.verifiedAt != null) {
|
||||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_check_circle, 0)
|
||||||
} else {
|
} else {
|
||||||
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0 )
|
valueTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
||||||
fields.forEach { field ->
|
fields.forEach { field ->
|
||||||
fieldData.add(MutableStringPair(field.name, field.value))
|
fieldData.add(MutableStringPair(field.name, field.value))
|
||||||
}
|
}
|
||||||
if(fieldData.isEmpty()) {
|
if (fieldData.isEmpty()) {
|
||||||
fieldData.add(MutableStringPair("", ""))
|
fieldData.add(MutableStringPair("", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ class AccountFieldEditAdapter : RecyclerView.Adapter<BindingHolder<ItemEditField
|
||||||
holder.binding.accountFieldName.setText(fieldData[position].first)
|
holder.binding.accountFieldName.setText(fieldData[position].first)
|
||||||
holder.binding.accountFieldValue.setText(fieldData[position].second)
|
holder.binding.accountFieldValue.setText(fieldData[position].second)
|
||||||
|
|
||||||
holder.binding.accountFieldName.addTextChangedListener(object: TextWatcher {
|
holder.binding.accountFieldName.addTextChangedListener(object : TextWatcher {
|
||||||
override fun afterTextChanged(newText: Editable) {
|
override fun afterTextChanged(newText: Editable) {
|
||||||
fieldData[holder.bindingAdapterPosition].first = newText.toString()
|
fieldData[holder.bindingAdapterPosition].first = newText.toString()
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,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) {}
|
||||||
})
|
})
|
||||||
|
|
||||||
holder.binding.accountFieldValue.addTextChangedListener(object: TextWatcher {
|
holder.binding.accountFieldValue.addTextChangedListener(object : TextWatcher {
|
||||||
override fun afterTextChanged(newText: Editable) {
|
override fun afterTextChanged(newText: Editable) {
|
||||||
fieldData[holder.bindingAdapterPosition].second = newText.toString()
|
fieldData[holder.bindingAdapterPosition].second = newText.toString()
|
||||||
}
|
}
|
||||||
|
@ -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,14 +22,14 @@ 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>,
|
||||||
private val onEmojiSelectedListener: OnEmojiSelectedListener
|
private val onEmojiSelectedListener: OnEmojiSelectedListener
|
||||||
) : RecyclerView.Adapter<BindingHolder<ItemEmojiButtonBinding>>() {
|
) : RecyclerView.Adapter<BindingHolder<ItemEmojiButtonBinding>>() {
|
||||||
|
|
||||||
private val emojiList : List<Emoji> = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
private val emojiList: List<Emoji> = emojiList.filter { emoji -> emoji.visibleInPicker == null || emoji.visibleInPicker }
|
||||||
.sortedBy { it.shortcode.lowercase(Locale.ROOT) }
|
.sortedBy { it.shortcode.lowercase(Locale.ROOT) }
|
||||||
|
|
||||||
override fun getItemCount() = emojiList.size
|
override fun getItemCount() = emojiList.size
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -29,7 +29,7 @@ import com.keylesspalace.tusky.viewdata.PollOptionViewData
|
||||||
import com.keylesspalace.tusky.viewdata.buildDescription
|
import com.keylesspalace.tusky.viewdata.buildDescription
|
||||||
import com.keylesspalace.tusky.viewdata.calculatePercent
|
import com.keylesspalace.tusky.viewdata.calculatePercent
|
||||||
|
|
||||||
class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
class PollAdapter : RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
|
|
||||||
private var pollOptions: List<PollOptionViewData> = emptyList()
|
private var pollOptions: List<PollOptionViewData> = emptyList()
|
||||||
private var voteCount: Int = 0
|
private var voteCount: Int = 0
|
||||||
|
@ -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
|
||||||
|
@ -57,12 +58,11 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelected() : List<Int> {
|
fun getSelected(): List<Int> {
|
||||||
return pollOptions.filter { it.selected }
|
return pollOptions.filter { it.selected }
|
||||||
.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)
|
||||||
|
@ -82,7 +82,7 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
radioButton.visible(mode == SINGLE)
|
radioButton.visible(mode == SINGLE)
|
||||||
checkBox.visible(mode == MULTIPLE)
|
checkBox.visible(mode == MULTIPLE)
|
||||||
|
|
||||||
when(mode) {
|
when (mode) {
|
||||||
RESULT -> {
|
RESULT -> {
|
||||||
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
|
val percent = calculatePercent(option.votesCount, votersCount, voteCount)
|
||||||
val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context)
|
val emojifiedPollOptionText = buildDescription(option.title, percent, resultTextView.context)
|
||||||
|
@ -114,7 +114,6 @@ class PollAdapter: RecyclerView.Adapter<BindingHolder<ItemPollBinding>>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import androidx.core.widget.TextViewCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.keylesspalace.tusky.R
|
import com.keylesspalace.tusky.R
|
||||||
|
|
||||||
class PreviewPollOptionsAdapter: RecyclerView.Adapter<PreviewViewHolder>() {
|
class PreviewPollOptionsAdapter : RecyclerView.Adapter<PreviewViewHolder>() {
|
||||||
|
|
||||||
private var options: List<String> = emptyList()
|
private var options: List<String> = emptyList()
|
||||||
private var multiple: Boolean = false
|
private var multiple: Boolean = false
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class TabAdapter(private var data: List<TabData>,
|
||||||
|
|
||||||
chip.text = arg
|
chip.text = arg
|
||||||
|
|
||||||
if(tab.arguments.size <= 1) {
|
if (tab.arguments.size <= 1) {
|
||||||
chip.chipIcon = null
|
chip.chipIcon = null
|
||||||
chip.setOnClickListener(null)
|
chip.setOnClickListener(null)
|
||||||
} else {
|
} else {
|
||||||
|
@ -136,14 +136,13 @@ class TabAdapter(private var data: List<TabData>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while(binding.chipGroup.size - 1 > tab.arguments.size) {
|
while (binding.chipGroup.size - 1 > tab.arguments.size) {
|
||||||
binding.chipGroup.removeViewAt(tab.arguments.size)
|
binding.chipGroup.removeViewAt(tab.arguments.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -27,7 +27,7 @@ class CacheUpdater @Inject constructor(
|
||||||
is ReblogEvent ->
|
is ReblogEvent ->
|
||||||
timelineDao.setReblogged(accountId, event.statusId, event.reblog)
|
timelineDao.setReblogged(accountId, event.statusId, event.reblog)
|
||||||
is BookmarkEvent ->
|
is BookmarkEvent ->
|
||||||
timelineDao.setBookmarked(accountId, event.statusId, event.bookmark )
|
timelineDao.setBookmarked(accountId, event.statusId, event.bookmark)
|
||||||
is UnfollowEvent ->
|
is UnfollowEvent ->
|
||||||
timelineDao.removeAllByUser(accountId, event.accountId)
|
timelineDao.removeAllByUser(accountId, event.accountId)
|
||||||
is StatusDeletedEvent ->
|
is StatusDeletedEvent ->
|
||||||
|
|
|
@ -19,6 +19,6 @@ data class ProfileEditedEvent(val newProfileData: Account) : Dispatchable
|
||||||
data class PreferenceChangedEvent(val preferenceKey: String) : Dispatchable
|
data class PreferenceChangedEvent(val preferenceKey: String) : Dispatchable
|
||||||
data class MainTabsChangedEvent(val newTabs: List<TabData>) : Dispatchable
|
data class MainTabsChangedEvent(val newTabs: List<TabData>) : Dispatchable
|
||||||
data class PollVoteEvent(val statusId: String, val poll: Poll) : Dispatchable
|
data class PollVoteEvent(val statusId: String, val poll: Poll) : Dispatchable
|
||||||
data class DomainMuteEvent(val instance: String): Dispatchable
|
data class DomainMuteEvent(val instance: String) : Dispatchable
|
||||||
data class AnnouncementReadEvent(val announcementId: String): Dispatchable
|
data class AnnouncementReadEvent(val announcementId: String) : Dispatchable
|
||||||
data class PinEvent(val statusId: String, val pinned: Boolean): Dispatchable
|
data class PinEvent(val statusId: String, val pinned: Boolean) : Dispatchable
|
||||||
|
|
|
@ -31,7 +31,7 @@ import com.keylesspalace.tusky.util.BindingHolder
|
||||||
import com.keylesspalace.tusky.util.LinkHelper
|
import com.keylesspalace.tusky.util.LinkHelper
|
||||||
import com.keylesspalace.tusky.util.emojify
|
import com.keylesspalace.tusky.util.emojify
|
||||||
|
|
||||||
interface AnnouncementActionListener: LinkListener {
|
interface AnnouncementActionListener : LinkListener {
|
||||||
fun openReactionPicker(announcementId: String, target: View)
|
fun openReactionPicker(announcementId: String, target: View)
|
||||||
fun addReaction(announcementId: String, name: String)
|
fun addReaction(announcementId: String, name: String)
|
||||||
fun removeReaction(announcementId: String, name: String)
|
fun removeReaction(announcementId: String, name: String)
|
||||||
|
@ -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("@")
|
||||||
}
|
}
|
||||||
|
@ -484,7 +502,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
|
|
||||||
private fun displayTransientError(@StringRes stringId: Int) {
|
private fun displayTransientError(@StringRes stringId: Int) {
|
||||||
val bar = Snackbar.make(binding.activityCompose, stringId, Snackbar.LENGTH_LONG)
|
val bar = Snackbar.make(binding.activityCompose, stringId, Snackbar.LENGTH_LONG)
|
||||||
//necessary so snackbar is shown over everything
|
// necessary so snackbar is shown over everything
|
||||||
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
bar.view.elevation = resources.getDimension(R.dimen.compose_activity_snackbar_elevation)
|
||||||
bar.show()
|
bar.show()
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -611,13 +628,15 @@ class ComposeActivity : BaseActivity(),
|
||||||
private fun onMediaPick() {
|
private fun onMediaPick() {
|
||||||
addMediaBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
addMediaBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
||||||
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
||||||
//Wait until bottom sheet is not collapsed and show next screen after
|
// Wait until bottom sheet is not collapsed and show next screen after
|
||||||
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
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,10 +800,12 @@ 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)
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class AddPollOptionsAdapter(
|
||||||
private val maxOptionLength: Int,
|
private val maxOptionLength: Int,
|
||||||
private val onOptionRemoved: (Boolean) -> Unit,
|
private val onOptionRemoved: (Boolean) -> Unit,
|
||||||
private val onOptionChanged: (Boolean) -> Unit
|
private val onOptionChanged: (Boolean) -> Unit
|
||||||
): RecyclerView.Adapter<BindingHolder<ItemAddPollOptionBinding>>() {
|
) : RecyclerView.Adapter<BindingHolder<ItemAddPollOptionBinding>>() {
|
||||||
|
|
||||||
val pollOptions: List<String>
|
val pollOptions: List<String>
|
||||||
get() = options.toList()
|
get() = options.toList()
|
||||||
|
@ -48,7 +48,7 @@ class AddPollOptionsAdapter(
|
||||||
|
|
||||||
binding.optionEditText.onTextChanged { s, _, _, _ ->
|
binding.optionEditText.onTextChanged { s, _, _, _ ->
|
||||||
val pos = holder.bindingAdapterPosition
|
val pos = holder.bindingAdapterPosition
|
||||||
if(pos != RecyclerView.NO_POSITION) {
|
if (pos != RecyclerView.NO_POSITION) {
|
||||||
options[pos] = s.toString()
|
options[pos] = s.toString()
|
||||||
onOptionChanged(validateInput())
|
onOptionChanged(validateInput())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,25 +16,27 @@
|
||||||
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)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//fix a bug with autocomplete and some keyboards
|
// fix a bug with autocomplete and some keyboards
|
||||||
val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)
|
val newInputType = inputType and (inputType xor InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)
|
||||||
inputType = newInputType
|
inputType = newInputType
|
||||||
super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener))
|
super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener))
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ class PollPreviewView @JvmOverloads constructor(
|
||||||
binding.pollPreviewOptions.adapter = adapter
|
binding.pollPreviewOptions.adapter = adapter
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPoll(poll: NewPoll){
|
fun setPoll(poll: NewPoll) {
|
||||||
adapter.update(poll.options, poll.multiple)
|
adapter.update(poll.options, poll.multiple)
|
||||||
|
|
||||||
val pollDurationId = resources.getIntArray(R.array.poll_duration_values).indexOfLast {
|
val pollDurationId = resources.getIntArray(R.array.poll_duration_values).indexOfLast {
|
||||||
|
|
|
@ -36,7 +36,7 @@ class TootButton
|
||||||
private val smallStyle: Boolean = context.resources.getBoolean(R.bool.show_small_toot_button)
|
private val smallStyle: Boolean = context.resources.getBoolean(R.bool.show_small_toot_button)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if(smallStyle) {
|
if (smallStyle) {
|
||||||
setIconResource(R.drawable.ic_send_24dp)
|
setIconResource(R.drawable.ic_send_24dp)
|
||||||
} else {
|
} else {
|
||||||
setText(R.string.action_send)
|
setText(R.string.action_send)
|
||||||
|
@ -47,7 +47,7 @@ class TootButton
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setStatusVisibility(visibility: Status.Visibility) {
|
fun setStatusVisibility(visibility: Status.Visibility) {
|
||||||
if(!smallStyle) {
|
if (!smallStyle) {
|
||||||
|
|
||||||
icon = when (visibility) {
|
icon = when (visibility) {
|
||||||
Status.Visibility.PUBLIC -> {
|
Status.Visibility.PUBLIC -> {
|
||||||
|
@ -68,8 +68,5 @@ class TootButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.util.shouldTrimStatus
|
import com.keylesspalace.tusky.util.shouldTrimStatus
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
@Entity(primaryKeys = ["id","accountId"])
|
@Entity(primaryKeys = ["id", "accountId"])
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
data class ConversationEntity(
|
data class ConversationEntity(
|
||||||
val accountId: Long,
|
val accountId: Long,
|
||||||
|
@ -98,7 +98,7 @@ data class ConversationStatusEntity(
|
||||||
if (inReplyToId != other.inReplyToId) return false
|
if (inReplyToId != other.inReplyToId) return false
|
||||||
if (inReplyToAccountId != other.inReplyToAccountId) return false
|
if (inReplyToAccountId != other.inReplyToAccountId) return false
|
||||||
if (account != other.account) return false
|
if (account != other.account) return false
|
||||||
if (content.toString() != other.content.toString()) return false //TODO find a better method to compare two spanned strings
|
if (content.toString() != other.content.toString()) return false // TODO find a better method to compare two spanned strings
|
||||||
if (createdAt != other.createdAt) return false
|
if (createdAt != other.createdAt) return false
|
||||||
if (emojis != other.emojis) return false
|
if (emojis != other.emojis) return false
|
||||||
if (favouritesCount != other.favouritesCount) return false
|
if (favouritesCount != other.favouritesCount) return false
|
||||||
|
@ -157,7 +157,7 @@ data class ConversationStatusEntity(
|
||||||
reblogged = false,
|
reblogged = false,
|
||||||
favourited = favourited,
|
favourited = favourited,
|
||||||
bookmarked = bookmarked,
|
bookmarked = bookmarked,
|
||||||
sensitive= sensitive,
|
sensitive = sensitive,
|
||||||
spoilerText = spoilerText,
|
spoilerText = spoilerText,
|
||||||
visibility = Status.Visibility.DIRECT,
|
visibility = Status.Visibility.DIRECT,
|
||||||
attachments = attachments,
|
attachments = attachments,
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@ import com.keylesspalace.tusky.db.DraftAttachment
|
||||||
class DraftMediaAdapter(
|
class DraftMediaAdapter(
|
||||||
private val attachmentClick: () -> Unit
|
private val attachmentClick: () -> Unit
|
||||||
) : ListAdapter<DraftAttachment, DraftMediaAdapter.DraftMediaViewHolder>(
|
) : ListAdapter<DraftAttachment, DraftMediaAdapter.DraftMediaViewHolder>(
|
||||||
object: DiffUtil.ItemCallback<DraftAttachment>() {
|
object : DiffUtil.ItemCallback<DraftAttachment>() {
|
||||||
override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
override fun areItemsTheSame(oldItem: DraftAttachment, newItem: DraftAttachment): Boolean {
|
||||||
return oldItem == newItem
|
return oldItem == newItem
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import dagger.android.DispatchingAndroidInjector
|
||||||
import dagger.android.HasAndroidInjector
|
import dagger.android.HasAndroidInjector
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
class InstanceListActivity : BaseActivity(), HasAndroidInjector {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||||
|
@ -33,5 +33,4 @@ class InstanceListActivity: BaseActivity(), HasAndroidInjector {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun androidInjector() = androidInjector
|
override fun androidInjector() = androidInjector
|
||||||
|
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ import com.keylesspalace.tusky.util.BindingHolder
|
||||||
|
|
||||||
class DomainMutesAdapter(
|
class DomainMutesAdapter(
|
||||||
private val actionListener: InstanceActionListener
|
private val actionListener: InstanceActionListener
|
||||||
): RecyclerView.Adapter<BindingHolder<ItemMutedDomainBinding>>() {
|
) : RecyclerView.Adapter<BindingHolder<ItemMutedDomainBinding>>() {
|
||||||
|
|
||||||
var instances: MutableList<String> = mutableListOf()
|
var instances: MutableList<String> = mutableListOf()
|
||||||
var bottomLoading: Boolean = false
|
var bottomLoading: Boolean = false
|
||||||
|
|
|
@ -29,7 +29,7 @@ import retrofit2.Response
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener {
|
class InstanceListFragment : Fragment(R.layout.fragment_instance_list), Injectable, InstanceActionListener {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var api: MastodonApi
|
lateinit var api: MastodonApi
|
||||||
|
@ -65,7 +65,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
|
|
||||||
override fun mute(mute: Boolean, instance: String, position: Int) {
|
override fun mute(mute: Boolean, instance: String, position: Int) {
|
||||||
if (mute) {
|
if (mute) {
|
||||||
api.blockDomain(instance).enqueue(object: Callback<Any> {
|
api.blockDomain(instance).enqueue(object : Callback<Any> {
|
||||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
override fun onFailure(call: Call<Any>, t: Throwable) {
|
||||||
Log.e(TAG, "Error muting domain $instance")
|
Log.e(TAG, "Error muting domain $instance")
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ class InstanceListFragment: Fragment(R.layout.fragment_instance_list), Injectabl
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
api.unblockDomain(instance).enqueue(object: Callback<Any> {
|
api.unblockDomain(instance).enqueue(object : Callback<Any> {
|
||||||
override fun onFailure(call: Call<Any>, t: Throwable) {
|
override fun onFailure(call: Call<Any>, t: Throwable) {
|
||||||
Log.e(TAG, "Error unmuting domain $instance")
|
Log.e(TAG, "Error unmuting domain $instance")
|
||||||
}
|
}
|
||||||
|
@ -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?) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import android.view.View
|
||||||
import com.keylesspalace.tusky.entity.Status
|
import com.keylesspalace.tusky.entity.Status
|
||||||
import com.keylesspalace.tusky.interfaces.LinkListener
|
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||||
|
|
||||||
interface AdapterHandler: LinkListener {
|
interface AdapterHandler : LinkListener {
|
||||||
fun showMedia(v: View?, status: Status?, idx: Int)
|
fun showMedia(v: View?, status: Status?, idx: Int)
|
||||||
fun setStatusChecked(status: Status, isChecked: Boolean)
|
fun setStatusChecked(status: Status, isChecked: Boolean)
|
||||||
fun isStatusChecked(id: String): Boolean
|
fun isStatusChecked(id: String): Boolean
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -109,18 +120,20 @@ class StatusViewHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setContentWarningButtonText(contentShown: Boolean) {
|
private fun setContentWarningButtonText(contentShown: Boolean) {
|
||||||
if(contentShown) {
|
if (contentShown) {
|
||||||
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_less)
|
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_less)
|
||||||
} else {
|
} else {
|
||||||
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_more)
|
binding.statusContentWarningButton.setText(R.string.status_content_warning_show_more)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -152,7 +165,7 @@ class StatusViewHolder(
|
||||||
private fun setupCollapsedState(collapsible: Boolean, collapsed: Boolean, expanded: Boolean, spoilerText: String) {
|
private fun setupCollapsedState(collapsible: Boolean, collapsed: Boolean, expanded: Boolean, spoilerText: String) {
|
||||||
/* input filter for TextViews have to be set before text */
|
/* input filter for TextViews have to be set before text */
|
||||||
if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
|
if (collapsible && (expanded || TextUtils.isEmpty(spoilerText))) {
|
||||||
binding.buttonToggleContent.setOnClickListener{
|
binding.buttonToggleContent.setOnClickListener {
|
||||||
status()?.let { status ->
|
status()?.let { status ->
|
||||||
viewState.setCollapsed(status.id, !collapsed)
|
viewState.setCollapsed(status.id, !collapsed)
|
||||||
updateTextView()
|
updateTextView()
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -64,11 +64,10 @@ class ReportNoteFragment : Fragment(R.layout.fragment_report_note), Injectable {
|
||||||
private fun fillViews() {
|
private fun fillViews() {
|
||||||
binding.editNote.setText(viewModel.reportNote)
|
binding.editNote.setText(viewModel.reportNote)
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import javax.inject.Inject
|
||||||
class ScheduledTootViewModel @Inject constructor(
|
class ScheduledTootViewModel @Inject constructor(
|
||||||
val mastodonApi: MastodonApi,
|
val mastodonApi: MastodonApi,
|
||||||
val eventHub: EventHub
|
val eventHub: EventHub
|
||||||
): ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val pagingSourceFactory = ScheduledTootPagingSourceFactory(mastodonApi)
|
private val pagingSourceFactory = ScheduledTootPagingSourceFactory(mastodonApi)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
|
@ -22,12 +22,13 @@ import com.keylesspalace.tusky.entity.SearchResult
|
||||||
import com.keylesspalace.tusky.network.MastodonApi
|
import com.keylesspalace.tusky.network.MastodonApi
|
||||||
import kotlinx.coroutines.rx3.await
|
import kotlinx.coroutines.rx3.await
|
||||||
|
|
||||||
class SearchPagingSource<T: Any>(
|
class SearchPagingSource<T : Any>(
|
||||||
private val mastodonApi: MastodonApi,
|
private val mastodonApi: MastodonApi,
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +251,7 @@ class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concre
|
||||||
menu.findItem(R.id.status_unreblog_private).isVisible = reblogged
|
menu.findItem(R.id.status_unreblog_private).isVisible = reblogged
|
||||||
}
|
}
|
||||||
Status.Visibility.UNKNOWN, Status.Visibility.DIRECT -> {
|
Status.Visibility.UNKNOWN, Status.Visibility.DIRECT -> {
|
||||||
} //Ignore
|
} // Ignore
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
popup.inflate(R.menu.status_more)
|
popup.inflate(R.menu.status_more)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -264,7 +285,7 @@ class TimelineViewModel @Inject constructor(
|
||||||
|
|
||||||
fun loadGap(position: Int): Job {
|
fun loadGap(position: Int): Job {
|
||||||
return viewModelScope.launch {
|
return viewModelScope.launch {
|
||||||
//check bounds before accessing list,
|
// check bounds before accessing list,
|
||||||
if (statuses.size < position || position <= 0) {
|
if (statuses.size < position || position <= 0) {
|
||||||
Log.e(TAG, "Wrong gap position: $position")
|
Log.e(TAG, "Wrong gap position: $position")
|
||||||
return@launch
|
return@launch
|
||||||
|
@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -129,13 +125,12 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
||||||
val accountIndex = accounts.indexOf(it)
|
val accountIndex = accounts.indexOf(it)
|
||||||
|
|
||||||
if (accountIndex != -1) {
|
if (accountIndex != -1) {
|
||||||
//in case the user was already logged in with this account, remove the old information
|
// in case the user was already logged in with this account, remove the old information
|
||||||
accounts.removeAt(accountIndex)
|
accounts.removeAt(accountIndex)
|
||||||
accounts.add(accountIndex, it)
|
accounts.add(accountIndex, it)
|
||||||
} else {
|
} else {
|
||||||
accounts.add(it)
|
accounts.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,5 +189,4 @@ class AccountManager @Inject constructor(db: AppDatabase) {
|
||||||
id == accountId
|
id == accountId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -35,9 +35,8 @@ interface ConversationsDao {
|
||||||
suspend fun delete(conversation: ConversationEntity): Int
|
suspend fun delete(conversation: ConversationEntity): Int
|
||||||
|
|
||||||
@Query("SELECT * FROM ConversationEntity WHERE accountId = :accountId ORDER BY s_createdAt DESC")
|
@Query("SELECT * FROM ConversationEntity WHERE accountId = :accountId ORDER BY s_createdAt DESC")
|
||||||
fun conversationsForAccount(accountId: Long) : PagingSource<Int, ConversationEntity>
|
fun conversationsForAccount(accountId: Long): PagingSource<Int, ConversationEntity>
|
||||||
|
|
||||||
@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
|
||||||
|
|
||||||
|
@ -126,7 +131,7 @@ class Converters @Inject constructor (
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun spannedToString(spanned: Spanned?): String? {
|
fun spannedToString(spanned: Spanned?): String? {
|
||||||
if(spanned == null) {
|
if (spanned == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return spanned.toHtml()
|
return spanned.toHtml()
|
||||||
|
@ -134,7 +139,7 @@ class Converters @Inject constructor (
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun stringToSpanned(spannedString: String?): Spanned? {
|
fun stringToSpanned(spannedString: String?): Spanned? {
|
||||||
if(spannedString == null) {
|
if (spannedString == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return spannedString.parseAsHtml().trimTrailingWhitespace()
|
return spannedString.parseAsHtml().trimTrailingWhitespace()
|
||||||
|
|
|
@ -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?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ data class DraftAttachment(
|
||||||
val uriString: String,
|
val uriString: String,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val type: Type
|
val type: Type
|
||||||
): Parcelable {
|
) : Parcelable {
|
||||||
val uri: Uri
|
val uri: Uri
|
||||||
get() = uriString.toUri()
|
get() = uriString.toUri()
|
||||||
|
|
||||||
|
|
|
@ -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,16 +16,16 @@
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
abstract class BroadcastReceiverModule {
|
abstract class BroadcastReceiverModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun contributeSendStatusBroadcastReceiver() : SendStatusBroadcastReceiver
|
abstract fun contributeSendStatusBroadcastReceiver(): SendStatusBroadcastReceiver
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract fun contributeNotificationClearBroadcastReceiver() : NotificationClearBroadcastReceiver
|
abstract fun contributeNotificationClearBroadcastReceiver(): NotificationClearBroadcastReceiver
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue