Merge remote-tracking branch 'tuskyapp/develop'
This commit is contained in:
commit
cc31f7af70
|
@ -89,6 +89,13 @@
|
|||
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="audio/*" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.service.chooser.chooser_target_service"
|
||||
|
|
|
@ -186,8 +186,14 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
for (listener in toolbarVisibilityListeners) {
|
||||
listener(isToolbarVisible)
|
||||
}
|
||||
|
||||
val visibility = if (isToolbarVisible) View.VISIBLE else View.INVISIBLE
|
||||
val alpha = if (isToolbarVisible) 1.0f else 0.0f
|
||||
if (isToolbarVisible) {
|
||||
// If to be visible, need to make visible immediately and animate alpha
|
||||
toolbar.alpha = 0.0f
|
||||
toolbar.visibility = visibility
|
||||
}
|
||||
|
||||
toolbar.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
|
@ -248,8 +254,9 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
val attachment = attachments!![viewPager.currentItem].attachment
|
||||
when (attachment.type) {
|
||||
Attachment.Type.IMAGE -> shareImage(directory, attachment.url)
|
||||
Attachment.Type.AUDIO,
|
||||
Attachment.Type.VIDEO,
|
||||
Attachment.Type.GIFV -> shareVideo(directory, attachment.url)
|
||||
Attachment.Type.GIFV -> shareMediaFile(directory, attachment.url)
|
||||
else -> Log.e(TAG, "Unknown media format for sharing.")
|
||||
}
|
||||
}
|
||||
|
@ -313,7 +320,7 @@ class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener
|
|||
|
||||
}
|
||||
|
||||
private fun shareVideo(directory: File, url: String) {
|
||||
private fun shareMediaFile(directory: File, url: String) {
|
||||
val uri = Uri.parse(url)
|
||||
val mimeTypeMap = MimeTypeMap.getSingleton()
|
||||
val extension = MimeTypeMap.getFileExtensionFromUrl(url)
|
||||
|
|
|
@ -203,7 +203,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
} else {
|
||||
if (payloadForHolder instanceof List)
|
||||
for (Object item : (List) payloadForHolder) {
|
||||
if (StatusBaseViewHolder.Key.KEY_CREATED.equals(item)) {
|
||||
if (StatusBaseViewHolder.Key.KEY_CREATED.equals(item) && statusViewData != null) {
|
||||
holder.setCreatedAt(statusViewData.getCreatedAt());
|
||||
}
|
||||
}
|
||||
|
@ -486,6 +486,11 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
|
|||
boolean hasSpoiler = !TextUtils.isEmpty(statusViewData.getSpoilerText());
|
||||
contentWarningDescriptionTextView.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
|
||||
contentWarningButton.setVisibility(hasSpoiler ? View.VISIBLE : View.GONE);
|
||||
if (statusViewData.isExpanded()) {
|
||||
contentWarningButton.setText(R.string.status_content_warning_show_less);
|
||||
} else {
|
||||
contentWarningButton.setText(R.string.status_content_warning_show_more);
|
||||
}
|
||||
|
||||
contentWarningButton.setOnClickListener(view -> {
|
||||
if (getAdapterPosition() != RecyclerView.NO_POSITION) {
|
||||
|
|
|
@ -181,8 +181,7 @@ class ComposeActivity : BaseActivity(),
|
|||
setupContentWarningField(composeOptions?.contentWarning)
|
||||
setupPollView()
|
||||
applyShareIntent(intent, savedInstanceState)
|
||||
|
||||
composeEditField.requestFocus()
|
||||
viewModel.setupComplete.value = true
|
||||
|
||||
if (composeOptions?.tootRightNow == true && calculateTextLength() > 0) {
|
||||
onSendClicked()
|
||||
|
@ -222,7 +221,7 @@ class ComposeActivity : BaseActivity(),
|
|||
* instance state will be re-queued. */
|
||||
val type = intent.type
|
||||
if (type != null) {
|
||||
if (type.startsWith("image/") || type.startsWith("video/")) {
|
||||
if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) {
|
||||
val uriList = ArrayList<Uri>()
|
||||
if (intent.action != null) {
|
||||
when (intent.action) {
|
||||
|
@ -376,13 +375,17 @@ class ComposeActivity : BaseActivity(),
|
|||
combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
|
||||
val active = poll == null
|
||||
&& media!!.size != 4
|
||||
&& media.firstOrNull()?.type != QueuedMedia.Type.VIDEO
|
||||
&& (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
|
||||
enableButton(composeAddMediaButton, active, active)
|
||||
enablePollButton(media.isNullOrEmpty())
|
||||
}.subscribe()
|
||||
viewModel.uploadError.observe {
|
||||
displayTransientError(R.string.error_media_upload_sending)
|
||||
}
|
||||
viewModel.setupComplete.observe {
|
||||
// Focus may have changed during view model setup, ensure initial focus is on the edit field
|
||||
composeEditField.requestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -478,18 +481,63 @@ class ComposeActivity : BaseActivity(),
|
|||
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
|
||||
val start = composeEditField.selectionStart.coerceAtMost(composeEditField.selectionEnd)
|
||||
val end = composeEditField.selectionStart.coerceAtLeast(composeEditField.selectionEnd)
|
||||
composeEditField.text.replace(start, end, text)
|
||||
val textToInsert = if (
|
||||
composeEditField.text.isNotEmpty()
|
||||
&& !composeEditField.text[start - 1].isWhitespace()
|
||||
) " $text" else text
|
||||
composeEditField.text.replace(start, end, textToInsert)
|
||||
|
||||
// Set the cursor after the inserted text
|
||||
composeEditField.setSelection(start + text.length)
|
||||
}
|
||||
|
||||
fun prependSelectedWordsWith(text: CharSequence) {
|
||||
// If you select "backward" in an editable, you get SelectionStart > SelectionEnd
|
||||
val start = composeEditField.selectionStart.coerceAtMost(composeEditField.selectionEnd)
|
||||
val end = composeEditField.selectionStart.coerceAtLeast(composeEditField.selectionEnd)
|
||||
val editorText = composeEditField.text
|
||||
|
||||
if (start == end) {
|
||||
// No selection, just insert text at caret
|
||||
editorText.insert(start, text)
|
||||
// Set the cursor after the inserted text
|
||||
composeEditField.setSelection(start + text.length)
|
||||
} else {
|
||||
var wasWord: Boolean
|
||||
var isWord = end < editorText.length && !Character.isWhitespace(editorText[end])
|
||||
var newEnd = end
|
||||
|
||||
// Iterate the selection backward so we don't have to juggle indices on insertion
|
||||
var index = end - 1
|
||||
while (index >= start - 1 && index >= 0) {
|
||||
wasWord = isWord
|
||||
isWord = !Character.isWhitespace(editorText[index])
|
||||
if (wasWord && !isWord) {
|
||||
// We've reached the beginning of a word, perform insert
|
||||
editorText.insert(index + 1, text)
|
||||
newEnd += text.length
|
||||
}
|
||||
--index
|
||||
}
|
||||
|
||||
if (start == 0 && isWord) {
|
||||
// Special case when the selection includes the start of the text
|
||||
editorText.insert(0, text)
|
||||
newEnd += text.length
|
||||
}
|
||||
|
||||
// Keep the same text (including insertions) selected
|
||||
composeEditField.setSelection(start, newEnd)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun atButtonClicked() {
|
||||
replaceTextAtCaret("@")
|
||||
prependSelectedWordsWith("@")
|
||||
}
|
||||
|
||||
private fun hashButtonClicked() {
|
||||
replaceTextAtCaret("#")
|
||||
prependSelectedWordsWith("#")
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
|
@ -843,7 +891,7 @@ class ComposeActivity : BaseActivity(),
|
|||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
|
||||
val mimeTypes = arrayOf("image/*", "video/*")
|
||||
val mimeTypes = arrayOf("image/*", "video/*", "audio/*")
|
||||
intent.type = "*/*"
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
|
||||
startActivityForResult(intent, MEDIA_PICK_RESULT)
|
||||
|
@ -886,6 +934,9 @@ class ComposeActivity : BaseActivity(),
|
|||
is VideoSizeException -> {
|
||||
R.string.error_video_upload_size
|
||||
}
|
||||
is AudioSizeException -> {
|
||||
R.string.error_audio_upload_size
|
||||
}
|
||||
is VideoOrImageException -> {
|
||||
R.string.error_media_upload_image_or_video
|
||||
}
|
||||
|
@ -1010,7 +1061,7 @@ class ComposeActivity : BaseActivity(),
|
|||
val description: String? = null
|
||||
) {
|
||||
enum class Type {
|
||||
IMAGE, VIDEO;
|
||||
IMAGE, VIDEO, AUDIO;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1075,7 +1126,7 @@ class ComposeActivity : BaseActivity(),
|
|||
|
||||
@JvmStatic
|
||||
fun canHandleMimeType(mimeType: String?): Boolean {
|
||||
return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType == "text/plain")
|
||||
return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType == "text/plain")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ class ComposeViewModel
|
|||
|
||||
val statusVisibility = mutableLiveData(Status.Visibility.UNKNOWN)
|
||||
val showContentWarning = mutableLiveData(false)
|
||||
val setupComplete = mutableLiveData(false)
|
||||
val poll: MutableLiveData<NewPoll?> = mutableLiveData(null)
|
||||
val scheduledAt: MutableLiveData<String?> = mutableLiveData(null)
|
||||
|
||||
|
@ -142,7 +143,7 @@ class ComposeViewModel
|
|||
mediaUploader.prepareMedia(uri)
|
||||
.map { (type, uri, size) ->
|
||||
val mediaItems = media.value!!
|
||||
if (type == QueuedMedia.Type.VIDEO
|
||||
if (type != QueuedMedia.Type.IMAGE
|
||||
&& mediaItems.isNotEmpty()
|
||||
&& mediaItems[0].type == QueuedMedia.Type.IMAGE) {
|
||||
throw VideoOrImageException()
|
||||
|
@ -392,6 +393,7 @@ class ComposeViewModel
|
|||
if (contentWarning != null) {
|
||||
startingContentWarning = contentWarning
|
||||
}
|
||||
showContentWarning.value = !contentWarning.isNullOrBlank()
|
||||
|
||||
// recreate media list
|
||||
// when coming from SavedTootActivity
|
||||
|
@ -411,6 +413,7 @@ class ComposeViewModel
|
|||
val mediaType = when (a.type) {
|
||||
Attachment.Type.VIDEO, Attachment.Type.GIFV -> QueuedMedia.Type.VIDEO
|
||||
Attachment.Type.UNKNOWN, Attachment.Type.IMAGE -> QueuedMedia.Type.IMAGE
|
||||
Attachment.Type.AUDIO -> QueuedMedia.Type.AUDIO
|
||||
else -> QueuedMedia.Type.IMAGE
|
||||
}
|
||||
addUploadedMedia(a.id, mediaType, a.url.toUri(), a.description)
|
||||
|
|
|
@ -69,11 +69,16 @@ class MediaPreviewAdapter(
|
|||
val item = differ.currentList[position]
|
||||
holder.progressImageView.setChecked(!item.description.isNullOrEmpty())
|
||||
holder.progressImageView.setProgress(item.uploadPercent)
|
||||
Glide.with(holder.itemView.context)
|
||||
.load(item.uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(holder.progressImageView)
|
||||
if (item.type == ComposeActivity.QueuedMedia.Type.AUDIO) {
|
||||
// TODO: Fancy waveform display?
|
||||
holder.progressImageView.setImageResource(R.drawable.ic_music_box_preview_24dp)
|
||||
} else {
|
||||
Glide.with(holder.itemView.context)
|
||||
.load(item.uri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(holder.progressImageView)
|
||||
}
|
||||
}
|
||||
|
||||
private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() {
|
||||
|
|
|
@ -63,6 +63,7 @@ interface MediaUploader {
|
|||
fun uploadMedia(media: QueuedMedia): Observable<UploadEvent>
|
||||
}
|
||||
|
||||
class AudioSizeException : Exception()
|
||||
class VideoSizeException : Exception()
|
||||
class MediaTypeException : Exception()
|
||||
class CouldNotOpenFileException : Exception()
|
||||
|
@ -128,6 +129,12 @@ class MediaUploaderImpl(
|
|||
"image" -> {
|
||||
PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
|
||||
}
|
||||
"audio" -> {
|
||||
if (mediaSize > STATUS_AUDIO_SIZE_LIMIT) {
|
||||
throw AudioSizeException()
|
||||
}
|
||||
PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
|
||||
}
|
||||
else -> {
|
||||
throw MediaTypeException()
|
||||
}
|
||||
|
@ -196,6 +203,7 @@ class MediaUploaderImpl(
|
|||
private companion object {
|
||||
private const val TAG = "MediaUploaderImpl"
|
||||
private const val STATUS_VIDEO_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_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels
|
||||
|
||||
|
|
|
@ -78,7 +78,8 @@ class ReportActivity : BottomSheetActivity(), HasAndroidInjector {
|
|||
}
|
||||
|
||||
private fun initViewPager() {
|
||||
wizard.adapter = ReportPagerAdapter(supportFragmentManager)
|
||||
wizard.isUserInputEnabled = false
|
||||
wizard.adapter = ReportPagerAdapter(this)
|
||||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
package com.keylesspalace.tusky.components.report.adapter
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.keylesspalace.tusky.components.report.fragments.ReportDoneFragment
|
||||
import com.keylesspalace.tusky.components.report.fragments.ReportNoteFragment
|
||||
import com.keylesspalace.tusky.components.report.fragments.ReportStatusesFragment
|
||||
|
||||
class ReportPagerAdapter(manager: FragmentManager) : FragmentPagerAdapter(manager) {
|
||||
override fun getItem(position: Int): Fragment {
|
||||
class ReportPagerAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
0 -> ReportStatusesFragment.newInstance()
|
||||
1 -> ReportNoteFragment.newInstance()
|
||||
|
@ -32,5 +32,5 @@ class ReportPagerAdapter(manager: FragmentManager) : FragmentPagerAdapter(manage
|
|||
}
|
||||
}
|
||||
|
||||
override fun getCount(): Int = 3
|
||||
override fun getItemCount() = 3
|
||||
}
|
|
@ -28,13 +28,12 @@ import com.keylesspalace.tusky.BottomSheetActivity
|
|||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.components.search.adapter.SearchPagerAdapter
|
||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||
import dagger.android.AndroidInjector
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import kotlinx.android.synthetic.main.activity_search.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, HasAndroidInjector {
|
||||
class SearchActivity : BottomSheetActivity(), HasAndroidInjector {
|
||||
@Inject
|
||||
lateinit var androidInjector: DispatchingAndroidInjector<Any>
|
||||
|
||||
|
@ -95,14 +94,6 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha
|
|||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getPageTitle(position: Int): CharSequence? {
|
||||
return when (position) {
|
||||
0 -> getString(R.string.title_statuses)
|
||||
|
@ -125,15 +116,12 @@ class SearchActivity : BottomSheetActivity(), SearchView.OnQueryTextListener, Ha
|
|||
|
||||
searchView.setSearchableInfo((getSystemService(Context.SEARCH_SERVICE) as? SearchManager)?.getSearchableInfo(componentName))
|
||||
|
||||
searchView.setOnQueryTextListener(this)
|
||||
searchView.requestFocus()
|
||||
|
||||
searchView.maxWidth = Integer.MAX_VALUE
|
||||
}
|
||||
|
||||
override fun androidInjector(): AndroidInjector<Any>? {
|
||||
return androidInjector
|
||||
}
|
||||
override fun androidInjector() = androidInjector
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
|
|
@ -3,20 +3,17 @@ package com.keylesspalace.tusky.components.search
|
|||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.paging.PagedList
|
||||
import com.keylesspalace.tusky.components.search.adapter.SearchNotestockRepository
|
||||
import com.keylesspalace.tusky.components.search.adapter.SearchRepository
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.entity.*
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.network.NotestockApi
|
||||
import com.keylesspalace.tusky.network.TimelineCases
|
||||
import com.keylesspalace.tusky.util.Listing
|
||||
import com.keylesspalace.tusky.util.NetworkState
|
||||
import com.keylesspalace.tusky.util.RxAwareViewModel
|
||||
import com.keylesspalace.tusky.util.ViewDataUtils
|
||||
import com.keylesspalace.tusky.util.*
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
@ -26,7 +23,8 @@ class SearchViewModel @Inject constructor(
|
|||
mastodonApi: MastodonApi,
|
||||
notestockApi: NotestockApi,
|
||||
private val timelineCases: TimelineCases,
|
||||
private val accountManager: AccountManager) : RxAwareViewModel() {
|
||||
private val accountManager: AccountManager
|
||||
) : RxAwareViewModel() {
|
||||
|
||||
var currentQuery: String = ""
|
||||
|
||||
|
@ -36,56 +34,53 @@ class SearchViewModel @Inject constructor(
|
|||
accountManager.activeAccount = value
|
||||
}
|
||||
|
||||
val mediaPreviewEnabled: Boolean
|
||||
get() = activeAccount?.mediaPreviewEnabled ?: false
|
||||
val mediaPreviewEnabled = activeAccount?.mediaPreviewEnabled ?: false
|
||||
val alwaysShowSensitiveMedia = activeAccount?.alwaysShowSensitiveMedia ?: false
|
||||
val alwaysOpenSpoiler = activeAccount?.alwaysOpenSpoiler ?: false
|
||||
|
||||
private val statusesRepository = SearchRepository<Pair<Status, StatusViewData.Concrete>>(mastodonApi)
|
||||
private val accountsRepository = SearchRepository<Account>(mastodonApi)
|
||||
private val hashtagsRepository = SearchRepository<HashTag>(mastodonApi)
|
||||
private val notestockRepository = SearchNotestockRepository(notestockApi)
|
||||
val alwaysShowSensitiveMedia: Boolean = activeAccount?.alwaysShowSensitiveMedia
|
||||
?: false
|
||||
val alwaysOpenSpoiler: Boolean = activeAccount?.alwaysOpenSpoiler
|
||||
?: false
|
||||
|
||||
private val repoResultStatus = MutableLiveData<Listing<Pair<Status, StatusViewData.Concrete>>>()
|
||||
val statuses: LiveData<PagedList<Pair<Status, StatusViewData.Concrete>>> = Transformations.switchMap(repoResultStatus) { it.pagedList }
|
||||
val networkStateStatus: LiveData<NetworkState> = Transformations.switchMap(repoResultStatus) { it.networkState }
|
||||
val networkStateStatusRefresh: LiveData<NetworkState> = Transformations.switchMap(repoResultStatus) { it.refreshState }
|
||||
val statuses: LiveData<PagedList<Pair<Status, StatusViewData.Concrete>>> = repoResultStatus.switchMap { it.pagedList }
|
||||
val networkStateStatus: LiveData<NetworkState> = repoResultStatus.switchMap { it.networkState }
|
||||
val networkStateStatusRefresh: LiveData<NetworkState> = repoResultStatus.switchMap { it.refreshState }
|
||||
|
||||
private val repoResultAccount = MutableLiveData<Listing<Account>>()
|
||||
val accounts: LiveData<PagedList<Account>> = Transformations.switchMap(repoResultAccount) { it.pagedList }
|
||||
val networkStateAccount: LiveData<NetworkState> = Transformations.switchMap(repoResultAccount) { it.networkState }
|
||||
val networkStateAccountRefresh: LiveData<NetworkState> = Transformations.switchMap(repoResultAccount) { it.refreshState }
|
||||
val accounts: LiveData<PagedList<Account>> = repoResultAccount.switchMap { it.pagedList }
|
||||
val networkStateAccount: LiveData<NetworkState> = repoResultAccount.switchMap { it.networkState }
|
||||
val networkStateAccountRefresh: LiveData<NetworkState> = repoResultAccount.switchMap { it.refreshState }
|
||||
|
||||
private val repoResultHashTag = MutableLiveData<Listing<HashTag>>()
|
||||
val hashtags: LiveData<PagedList<HashTag>> = Transformations.switchMap(repoResultHashTag) { it.pagedList }
|
||||
val networkStateHashTag: LiveData<NetworkState> = Transformations.switchMap(repoResultHashTag) { it.networkState }
|
||||
val networkStateHashTagRefresh: LiveData<NetworkState> = Transformations.switchMap(repoResultHashTag) { it.refreshState }
|
||||
val hashtags: LiveData<PagedList<HashTag>> = repoResultHashTag.switchMap { it.pagedList }
|
||||
val networkStateHashTag: LiveData<NetworkState> = repoResultHashTag.switchMap { it.networkState }
|
||||
val networkStateHashTagRefresh: LiveData<NetworkState> = repoResultHashTag.switchMap { it.refreshState }
|
||||
|
||||
private val repoResultNotestock = MutableLiveData<Listing<Pair<Status, StatusViewData.Concrete>>>()
|
||||
val notestockStatuses: LiveData<PagedList<Pair<Status, StatusViewData.Concrete>>> = Transformations.switchMap(repoResultNotestock) { it.pagedList }
|
||||
val networkStateNotestock: LiveData<NetworkState> = Transformations.switchMap(repoResultNotestock) { it.networkState }
|
||||
val networkStateNotestockRefresh: LiveData<NetworkState> = Transformations.switchMap(repoResultNotestock) { it.networkState }
|
||||
val notestockStatuses: LiveData<PagedList<Pair<Status, StatusViewData.Concrete>>> = repoResultNotestock.switchMap { it.pagedList }
|
||||
val networkStateNotestock: LiveData<NetworkState> = repoResultNotestock.switchMap { it.networkState }
|
||||
val networkStateNotestockRefresh: LiveData<NetworkState> = repoResultNotestock.switchMap { it.networkState }
|
||||
|
||||
private val loadedStatuses = ArrayList<Pair<Status, StatusViewData.Concrete>>()
|
||||
private val loadedNotestockStatuses = ArrayList<Pair<Status, StatusViewData.Concrete>>()
|
||||
fun search(query: String) {
|
||||
loadedStatuses.clear()
|
||||
repoResultStatus.value = statusesRepository.getSearchData(SearchType.Status, query, disposables, initialItems = loadedStatuses) {
|
||||
(it?.statuses?.map { status -> Pair(status, ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia, alwaysOpenSpoiler)!!) }
|
||||
?: emptyList())
|
||||
it?.statuses?.map { status -> Pair(status, ViewDataUtils.statusToViewData(status, alwaysShowSensitiveMedia, alwaysOpenSpoiler)!!) }
|
||||
.orEmpty()
|
||||
.apply {
|
||||
loadedStatuses.addAll(this)
|
||||
}
|
||||
}
|
||||
repoResultAccount.value = accountsRepository.getSearchData(SearchType.Account, query, disposables) {
|
||||
it?.accounts ?: emptyList()
|
||||
it?.accounts.orEmpty()
|
||||
}
|
||||
val hashtagQuery = if (query.startsWith("#")) query else "#$query"
|
||||
repoResultHashTag.value =
|
||||
hashtagsRepository.getSearchData(SearchType.Hashtag, hashtagQuery, disposables) {
|
||||
it?.hashtags ?: emptyList()
|
||||
it?.hashtags.orEmpty()
|
||||
}
|
||||
loadedNotestockStatuses.clear()
|
||||
repoResultNotestock.value = notestockRepository.getSearchData(query, disposables) {
|
||||
|
@ -234,11 +229,11 @@ class SearchViewModel @Inject constructor(
|
|||
fun bookmark(status: Pair<Status, StatusViewData.Concrete>, isBookmarked: Boolean) {
|
||||
val idx = loadedStatuses.indexOf(status)
|
||||
if (idx >= 0) {
|
||||
val newPair = Pair(status.first, StatusViewData.Builder(status.second).setFavourited(isBookmarked).createStatusViewData())
|
||||
val newPair = Pair(status.first, StatusViewData.Builder(status.second).setBookmarked(isBookmarked).createStatusViewData())
|
||||
loadedStatuses[idx] = newPair
|
||||
repoResultStatus.value?.refresh?.invoke()
|
||||
}
|
||||
timelineCases.favourite(status.first, isBookmarked)
|
||||
timelineCases.bookmark(status.first, isBookmarked)
|
||||
.onErrorReturnItem(status.first)
|
||||
.subscribe()
|
||||
.autoDispose()
|
||||
|
|
|
@ -26,8 +26,7 @@ import com.keylesspalace.tusky.entity.Account
|
|||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
|
||||
class SearchAccountsAdapter(private val linkListener: LinkListener)
|
||||
: PagedListAdapter<Account, RecyclerView.ViewHolder>(STATUS_COMPARATOR) {
|
||||
|
||||
: PagedListAdapter<Account, RecyclerView.ViewHolder>(ACCOUNT_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
|
@ -37,21 +36,16 @@ class SearchAccountsAdapter(private val linkListener: LinkListener)
|
|||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
getItem(position)?.let { item ->
|
||||
(holder as? AccountViewHolder)?.apply {
|
||||
(holder as AccountViewHolder).apply {
|
||||
setupWithAccount(item)
|
||||
setupLinkListener(linkListener)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override fun getItem(position: Int): Account? {
|
||||
return super.getItem(position)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val STATUS_COMPARATOR = object : DiffUtil.ItemCallback<Account>() {
|
||||
val ACCOUNT_COMPARATOR = object : DiffUtil.ItemCallback<Account>() {
|
||||
override fun areContentsTheSame(oldItem: Account, newItem: Account): Boolean =
|
||||
oldItem.deepEquals(newItem)
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
package com.keylesspalace.tusky.components.search.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.PositionalDataSource
|
||||
import com.keylesspalace.tusky.components.search.SearchType
|
||||
|
@ -23,16 +22,18 @@ import com.keylesspalace.tusky.entity.SearchResult
|
|||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.NetworkState
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.rxkotlin.addTo
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
class SearchDataSource<T>(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val searchType: SearchType,
|
||||
private val searchRequest: String?,
|
||||
private val searchRequest: String,
|
||||
private val disposables: CompositeDisposable,
|
||||
private val retryExecutor: Executor,
|
||||
private val initialItems: List<T>? = null,
|
||||
private val parser: (SearchResult?) -> List<T>) : PositionalDataSource<T>() {
|
||||
private val parser: (SearchResult?) -> List<T>,
|
||||
private val source: SearchDataSourceFactory<T>) : PositionalDataSource<T>() {
|
||||
|
||||
val networkState = MutableLiveData<NetworkState>()
|
||||
|
||||
|
@ -48,24 +49,20 @@ class SearchDataSource<T>(
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
|
||||
if (!initialItems.isNullOrEmpty()) {
|
||||
callback.onResult(initialItems, 0)
|
||||
callback.onResult(initialItems.toList(), 0)
|
||||
} else {
|
||||
networkState.postValue(NetworkState.LOADED)
|
||||
retry = null
|
||||
initialLoad.postValue(NetworkState.LOADING)
|
||||
mastodonApi.searchObservable(
|
||||
query = searchRequest ?: "",
|
||||
query = searchRequest,
|
||||
type = searchType.apiParameter,
|
||||
resolve = true,
|
||||
limit = params.requestedLoadSize,
|
||||
offset = 0,
|
||||
following =false)
|
||||
.doOnSubscribe {
|
||||
disposables.add(it)
|
||||
}
|
||||
.subscribe(
|
||||
{ data ->
|
||||
val res = parser(data)
|
||||
|
@ -79,19 +76,18 @@ class SearchDataSource<T>(
|
|||
}
|
||||
initialLoad.postValue(NetworkState.error(error.message))
|
||||
}
|
||||
)
|
||||
).addTo(disposables)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
|
||||
networkState.postValue(NetworkState.LOADING)
|
||||
retry = null
|
||||
if(source.exhausted) {
|
||||
return callback.onResult(emptyList())
|
||||
}
|
||||
mastodonApi.searchObservable(searchType.apiParameter, searchRequest, true, params.loadSize, params.startPosition, false)
|
||||
.doOnSubscribe {
|
||||
disposables.add(it)
|
||||
}
|
||||
.subscribe(
|
||||
{ data ->
|
||||
// Working around Mastodon bug where exact match is returned no matter
|
||||
|
@ -105,9 +101,11 @@ class SearchDataSource<T>(
|
|||
} else {
|
||||
parser(data)
|
||||
}
|
||||
if(res.isEmpty()) {
|
||||
source.exhausted = true
|
||||
}
|
||||
callback.onResult(res)
|
||||
networkState.postValue(NetworkState.LOADED)
|
||||
|
||||
},
|
||||
{ error ->
|
||||
retry = {
|
||||
|
@ -115,7 +113,7 @@ class SearchDataSource<T>(
|
|||
}
|
||||
networkState.postValue(NetworkState.error(error.message))
|
||||
}
|
||||
)
|
||||
).addTo(disposables)
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -26,14 +26,18 @@ import java.util.concurrent.Executor
|
|||
class SearchDataSourceFactory<T>(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val searchType: SearchType,
|
||||
private val searchRequest: String?,
|
||||
private val searchRequest: String,
|
||||
private val disposables: CompositeDisposable,
|
||||
private val retryExecutor: Executor,
|
||||
private val cacheData: List<T>? = null,
|
||||
private val parser: (SearchResult?) -> List<T>) : DataSource.Factory<Int, T>() {
|
||||
|
||||
val sourceLiveData = MutableLiveData<SearchDataSource<T>>()
|
||||
|
||||
var exhausted = false
|
||||
|
||||
override fun create(): DataSource<Int, T> {
|
||||
val source = SearchDataSource(mastodonApi, searchType, searchRequest, disposables, retryExecutor, cacheData, parser)
|
||||
val source = SearchDataSource(mastodonApi, searchType, searchRequest, disposables, retryExecutor, cacheData, parser, this)
|
||||
sourceLiveData.postValue(source)
|
||||
return source
|
||||
}
|
||||
|
|
|
@ -26,8 +26,7 @@ import com.keylesspalace.tusky.entity.HashTag
|
|||
import com.keylesspalace.tusky.interfaces.LinkListener
|
||||
|
||||
class SearchHashtagsAdapter(private val linkListener: LinkListener)
|
||||
: PagedListAdapter<HashTag, RecyclerView.ViewHolder>(STATUS_COMPARATOR) {
|
||||
|
||||
: PagedListAdapter<HashTag, RecyclerView.ViewHolder>(HASHTAG_COMPARATOR) {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
|
@ -36,17 +35,14 @@ class SearchHashtagsAdapter(private val linkListener: LinkListener)
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
getItem(position)?.let { item ->
|
||||
(holder as? HashtagViewHolder)?.apply {
|
||||
setup(item.name, linkListener)
|
||||
}
|
||||
getItem(position)?.let { (name) ->
|
||||
(holder as HashtagViewHolder).setup(name, linkListener)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val STATUS_COMPARATOR = object : DiffUtil.ItemCallback<HashTag>() {
|
||||
val HASHTAG_COMPARATOR = object : DiffUtil.ItemCallback<HashTag>() {
|
||||
override fun areContentsTheSame(oldItem: HashTag, newItem: HashTag): Boolean =
|
||||
oldItem.name == newItem.name
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class SearchRepository<T>(private val mastodonApi: MastodonApi) {
|
|||
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
fun getSearchData(searchType: SearchType, searchRequest: String?, disposables: CompositeDisposable, pageSize: Int = 20,
|
||||
fun getSearchData(searchType: SearchType, searchRequest: String, disposables: CompositeDisposable, pageSize: Int = 20,
|
||||
initialItems: List<T>? = null, parser: (SearchResult?) -> List<T>): Listing<T> {
|
||||
val sourceFactory = SearchDataSourceFactory(mastodonApi, searchType, searchRequest, disposables, executor, initialItems, parser)
|
||||
val livePagedList = sourceFactory.toLiveData(
|
||||
|
|
|
@ -32,7 +32,6 @@ class SearchStatusesAdapter(
|
|||
private val statusListener: StatusActionListener
|
||||
) : PagedListAdapter<Pair<Status, StatusViewData.Concrete>, RecyclerView.ViewHolder>(STATUS_COMPARATOR) {
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_status, parent, false)
|
||||
|
@ -41,8 +40,7 @@ class SearchStatusesAdapter(
|
|||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
getItem(position)?.let { item ->
|
||||
(holder as? StatusViewHolder)?.setupWithStatus(item.second, statusListener,
|
||||
statusDisplayOptions)
|
||||
(holder as StatusViewHolder).setupWithStatus(item.second, statusListener, statusDisplayOptions)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import androidx.paging.PagedList
|
|||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.keylesspalace.tusky.AccountActivity
|
||||
|
@ -62,8 +63,8 @@ abstract class SearchFragment<T> : Fragment(),
|
|||
swipeRefreshLayout.setOnRefreshListener(this)
|
||||
swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
|
||||
swipeRefreshLayout.setProgressBackgroundColorSchemeColor(
|
||||
ThemeUtils.getColor(swipeRefreshLayout.context, android.R.attr.colorBackground))
|
||||
|
||||
ThemeUtils.getColor(swipeRefreshLayout.context, android.R.attr.colorBackground)
|
||||
)
|
||||
}
|
||||
|
||||
private fun subscribeObservables() {
|
||||
|
@ -75,8 +76,9 @@ abstract class SearchFragment<T> : Fragment(),
|
|||
|
||||
searchProgressBar.visible(it == NetworkState.LOADING)
|
||||
|
||||
if (it.status == Status.FAILED)
|
||||
showError(it.msg)
|
||||
if (it.status == Status.FAILED) {
|
||||
showError()
|
||||
}
|
||||
checkNoData()
|
||||
|
||||
})
|
||||
|
@ -85,8 +87,9 @@ abstract class SearchFragment<T> : Fragment(),
|
|||
|
||||
progressBarBottom.visible(it == NetworkState.LOADING)
|
||||
|
||||
if (it.status == Status.FAILED)
|
||||
showError(it.msg)
|
||||
if (it.status == Status.FAILED) {
|
||||
showError()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -99,7 +102,8 @@ abstract class SearchFragment<T> : Fragment(),
|
|||
searchRecyclerView.layoutManager = LinearLayoutManager(searchRecyclerView.context)
|
||||
adapter = createAdapter()
|
||||
searchRecyclerView.adapter = adapter
|
||||
|
||||
searchRecyclerView.setHasFixedSize(true)
|
||||
(searchRecyclerView.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
|
||||
}
|
||||
|
||||
private fun showNoData(isEmpty: Boolean) {
|
||||
|
@ -109,7 +113,7 @@ abstract class SearchFragment<T> : Fragment(),
|
|||
searchNoResultsText.hide()
|
||||
}
|
||||
|
||||
private fun showError(@Suppress("UNUSED_PARAMETER") msg: String?) {
|
||||
private fun showError() {
|
||||
if (snackbarErrorRetry?.isShown != true) {
|
||||
snackbarErrorRetry = Snackbar.make(layoutRoot, R.string.failed_search, Snackbar.LENGTH_INDEFINITE)
|
||||
snackbarErrorRetry?.setAction(R.string.action_retry) {
|
||||
|
@ -129,13 +133,12 @@ abstract class SearchFragment<T> : Fragment(),
|
|||
}
|
||||
|
||||
protected val bottomSheetActivity
|
||||
get() = (activity as? BottomSheetActivity)
|
||||
get() = (activity as? BottomSheetActivity)
|
||||
|
||||
override fun onRefresh() {
|
||||
|
||||
// Dismissed here because the RecyclerView bottomProgressBar is shown as soon as the retry begins.
|
||||
swipeRefreshLayout.post {
|
||||
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
viewModel.retryAllSearches()
|
||||
|
|
|
@ -59,7 +59,6 @@ import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from
|
|||
import com.uber.autodispose.autoDispose
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import kotlinx.android.synthetic.main.fragment_search.*
|
||||
import java.util.*
|
||||
|
||||
open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.Concrete>>(), StatusActionListener {
|
||||
|
||||
|
@ -70,6 +69,9 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
override val data: LiveData<PagedList<Pair<Status, StatusViewData.Concrete>>>
|
||||
get() = viewModel.statuses
|
||||
|
||||
private val searchAdapter
|
||||
get() = super.adapter as SearchStatusesAdapter
|
||||
|
||||
override fun createAdapter(): PagedListAdapter<Pair<Status, StatusViewData.Concrete>, *> {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(searchRecyclerView.context)
|
||||
val statusDisplayOptions = StatusDisplayOptions(
|
||||
|
@ -87,43 +89,43 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
|
||||
|
||||
override fun onContentHiddenChange(isShowing: Boolean, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let {
|
||||
searchAdapter.getItem(position)?.let {
|
||||
viewModel.contentHiddenChange(it, isShowing)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReply(position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.let { status ->
|
||||
searchAdapter.getItem(position)?.first?.let { status ->
|
||||
reply(status)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFavourite(favourite: Boolean, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let { status ->
|
||||
searchAdapter.getItem(position)?.let { status ->
|
||||
viewModel.favorite(status, favourite)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQuote(position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.let { status ->
|
||||
searchAdapter.getItem(position)?.first?.let { status ->
|
||||
quote(status)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBookmark(bookmark: Boolean, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let { status ->
|
||||
searchAdapter.getItem(position)?.let { status ->
|
||||
viewModel.bookmark(status, bookmark)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMore(view: View, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.let {
|
||||
searchAdapter.getItem(position)?.first?.let {
|
||||
more(it, view, position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewMedia(position: Int, attachmentIndex: Int, view: View?) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.actionableStatus?.let { actionable ->
|
||||
searchAdapter.getItem(position)?.first?.actionableStatus?.let { actionable ->
|
||||
when (actionable.attachments[attachmentIndex].type) {
|
||||
Attachment.Type.GIFV, Attachment.Type.VIDEO, Attachment.Type.IMAGE, Attachment.Type.AUDIO -> {
|
||||
val attachments = AttachmentViewData.list(actionable)
|
||||
|
@ -148,48 +150,48 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
}
|
||||
|
||||
override fun onViewThread(position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.let { status ->
|
||||
searchAdapter.getItem(position)?.first?.let { status ->
|
||||
val actionableStatus = status.actionableStatus
|
||||
bottomSheetActivity?.viewThread(actionableStatus.id, actionableStatus.url)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOpenReblog(position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.first?.let { status ->
|
||||
searchAdapter.getItem(position)?.first?.let { status ->
|
||||
bottomSheetActivity?.viewAccount(status.account.id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onExpandedChange(expanded: Boolean, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let {
|
||||
searchAdapter.getItem(position)?.let {
|
||||
viewModel.expandedChange(it, expanded)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadMore(position: Int) {
|
||||
//Ignore
|
||||
// Not possible here
|
||||
}
|
||||
|
||||
override fun onContentCollapsedChange(isCollapsed: Boolean, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let {
|
||||
searchAdapter.getItem(position)?.let {
|
||||
viewModel.collapsedChange(it, isCollapsed)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onVoteInPoll(position: Int, choices: MutableList<Int>) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let {
|
||||
searchAdapter.getItem(position)?.let {
|
||||
viewModel.voteInPoll(it, choices)
|
||||
}
|
||||
}
|
||||
|
||||
open fun removeItem(position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let {
|
||||
searchAdapter.getItem(position)?.let {
|
||||
viewModel.removeItem(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReblog(reblog: Boolean, position: Int) {
|
||||
(adapter as? SearchStatusesAdapter)?.getItem(position)?.let { status ->
|
||||
searchAdapter.getItem(position)?.let { status ->
|
||||
viewModel.reblog(status, reblog)
|
||||
}
|
||||
}
|
||||
|
@ -199,46 +201,39 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
}
|
||||
|
||||
private fun reply(status: Status) {
|
||||
val inReplyToId = status.actionableId
|
||||
val actionableStatus = status.actionableStatus
|
||||
val replyVisibility = actionableStatus.visibility
|
||||
val contentWarning = actionableStatus.spoilerText
|
||||
val mentions = actionableStatus.mentions
|
||||
val mentionedUsernames = LinkedHashSet<String>()
|
||||
mentionedUsernames.add(actionableStatus.account.username)
|
||||
val loggedInUsername = viewModel.activeAccount?.username
|
||||
for ((_, _, username) in mentions) {
|
||||
mentionedUsernames.add(username)
|
||||
}
|
||||
mentionedUsernames.remove(loggedInUsername)
|
||||
val intent = ComposeActivity.startIntent(context!!, ComposeOptions(
|
||||
inReplyToId = inReplyToId,
|
||||
replyVisibility = replyVisibility,
|
||||
contentWarning = contentWarning,
|
||||
val mentionedUsernames = actionableStatus.mentions.map { it.username }
|
||||
.toMutableSet()
|
||||
.apply {
|
||||
add(actionableStatus.account.username)
|
||||
remove(viewModel.activeAccount?.username)
|
||||
}
|
||||
|
||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
||||
inReplyToId = status.actionableId,
|
||||
replyVisibility = actionableStatus.visibility,
|
||||
contentWarning = actionableStatus.spoilerText,
|
||||
mentionedUsernames = mentionedUsernames,
|
||||
replyingStatusAuthor = actionableStatus.account.localUsername,
|
||||
replyingStatusContent = actionableStatus.content.toString()
|
||||
))
|
||||
requireActivity().startActivity(intent)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun quote(status: Status) {
|
||||
val id = status.actionableId
|
||||
val actionableStatus = status.actionableStatus
|
||||
val visibility = actionableStatus.visibility
|
||||
val url = actionableStatus.url
|
||||
val mentions = actionableStatus.mentions
|
||||
val mentionedUsernames = LinkedHashSet<String>()
|
||||
mentionedUsernames.add(actionableStatus.account.username)
|
||||
val loggedInUsername = viewModel.activeAccount?.username
|
||||
for ((_, _, username) in mentions) {
|
||||
mentionedUsernames.add(username)
|
||||
}
|
||||
mentionedUsernames.remove(loggedInUsername)
|
||||
val intent = ComposeActivity.startIntent(context!!, ComposeOptions(
|
||||
quoteId = id,
|
||||
quoteUrl = url,
|
||||
replyVisibility = visibility,
|
||||
val mentionedUsernames = actionableStatus.mentions.map { it.username }
|
||||
.toMutableSet()
|
||||
.apply {
|
||||
add(actionableStatus.account.name)
|
||||
remove(viewModel.activeAccount?.username)
|
||||
}
|
||||
|
||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
||||
quoteId = status.actionableId,
|
||||
quoteUrl = actionableStatus.url,
|
||||
replyVisibility = actionableStatus.visibility,
|
||||
contentWarning = actionableStatus.spoilerText,
|
||||
mentionedUsernames = mentionedUsernames
|
||||
))
|
||||
startActivity(intent)
|
||||
|
@ -280,8 +275,7 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
}
|
||||
}
|
||||
|
||||
val menu = popup.menu
|
||||
val openAsItem = menu.findItem(R.id.status_open_as)
|
||||
val openAsItem = popup.menu.findItem(R.id.status_open_as)
|
||||
when (accounts.size) {
|
||||
0, 1 -> openAsItem.isVisible = false
|
||||
2 -> for (account in accounts) {
|
||||
|
@ -297,13 +291,12 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
popup.setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
R.id.status_share_content -> {
|
||||
var statusToShare: Status? = status
|
||||
if (statusToShare!!.reblog != null) statusToShare = statusToShare.reblog
|
||||
val statusToShare: Status = status.actionableStatus
|
||||
|
||||
val sendIntent = Intent()
|
||||
sendIntent.action = Intent.ACTION_SEND
|
||||
|
||||
val stringToShare = statusToShare!!.account.username +
|
||||
val stringToShare = statusToShare.account.username +
|
||||
" - " +
|
||||
statusToShare.content.toString()
|
||||
sendIntent.putExtra(Intent.EXTRA_TEXT, stringToShare)
|
||||
|
@ -320,7 +313,7 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
R.id.status_copy_link -> {
|
||||
val clipboard = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipboard = requireActivity().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText(null, statusUrl))
|
||||
return@setOnMenuItemClickListener true
|
||||
}
|
||||
|
@ -393,7 +386,7 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
val uri = Uri.parse(url)
|
||||
val filename = uri.lastPathSegment
|
||||
|
||||
val downloadManager = activity!!.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val downloadManager = requireActivity().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(uri)
|
||||
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, filename)
|
||||
downloadManager.enqueue(request)
|
||||
|
@ -445,7 +438,7 @@ open class SearchStatusesFragment : SearchFragment<Pair<Status, StatusViewData.C
|
|||
deletedStatus
|
||||
}
|
||||
|
||||
val intent = ComposeActivity.startIntent(context!!, ComposeOptions(
|
||||
val intent = ComposeActivity.startIntent(requireContext(), ComposeOptions(
|
||||
tootText = redraftStatus.text ?: "",
|
||||
inReplyToId = redraftStatus.inReplyToId,
|
||||
visibility = redraftStatus.visibility,
|
||||
|
|
|
@ -1029,6 +1029,14 @@ public class NotificationsFragment extends SFragment implements
|
|||
}
|
||||
}
|
||||
Log.e(TAG, "Fetch failure: " + exception.getMessage());
|
||||
|
||||
if (fetchEnd == FetchEnd.TOP) {
|
||||
topLoading = false;
|
||||
}
|
||||
if (fetchEnd == FetchEnd.BOTTOM) {
|
||||
bottomLoading = false;
|
||||
}
|
||||
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.annotation.SuppressLint
|
|||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -31,6 +32,7 @@ import com.keylesspalace.tusky.ViewMediaActivity
|
|||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.visible
|
||||
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
||||
import kotlinx.android.synthetic.main.activity_view_media.*
|
||||
import kotlinx.android.synthetic.main.fragment_view_video.*
|
||||
|
||||
|
@ -41,11 +43,13 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
// Hoist toolbar hiding to activity so it can track state across different fragments
|
||||
// This is explicitly stored as runnable so that we pass it to the handler later for cancellation
|
||||
mediaActivity.onPhotoTap()
|
||||
mediaController.hide()
|
||||
}
|
||||
private lateinit var mediaActivity: ViewMediaActivity
|
||||
private val TOOLBAR_HIDE_DELAY_MS = 3000L
|
||||
override lateinit var descriptionView : TextView
|
||||
private lateinit var mediaController : MediaController
|
||||
private var isAudio = false
|
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
// Start/pause/resume video playback as fragment is shown/hidden
|
||||
|
@ -72,14 +76,43 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
|
||||
videoView.transitionName = url
|
||||
videoView.setVideoPath(url)
|
||||
mediaController = MediaController(mediaActivity)
|
||||
mediaController = object : MediaController(mediaActivity) {
|
||||
override fun show(timeout: Int) {
|
||||
// We're doing manual auto-close management.
|
||||
// Also, take focus back from the pause button so we can use the back button.
|
||||
super.show(0)
|
||||
mediaController.requestFocus()
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||
if (event?.keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (event.action == KeyEvent.ACTION_UP) {
|
||||
hide()
|
||||
activity?.supportFinishAfterTransition()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
mediaController.setMediaPlayer(videoView)
|
||||
videoView.setMediaController(mediaController)
|
||||
videoView.requestFocus()
|
||||
videoView.setOnTouchListener { _, _ ->
|
||||
mediaActivity.onPhotoTap()
|
||||
false
|
||||
}
|
||||
videoView.setPlayPauseListener(object: ExposedPlayPauseVideoView.PlayPauseListener {
|
||||
override fun onPause() {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
}
|
||||
override fun onPlay() {
|
||||
// Audio doesn't cause the controller to show automatically,
|
||||
// and we only want to hide the toolbar if it's a video.
|
||||
if (isAudio) {
|
||||
mediaController.show()
|
||||
} else {
|
||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
||||
}
|
||||
}
|
||||
})
|
||||
videoView.setOnPreparedListener { mp ->
|
||||
val containerWidth = videoContainer.measuredWidth.toFloat()
|
||||
val containerHeight = videoContainer.measuredHeight.toFloat()
|
||||
|
@ -94,10 +127,16 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
videoView.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
|
||||
// Wait until the media is loaded before accepting taps as we don't want toolbar to
|
||||
// be hidden until then.
|
||||
videoView.setOnTouchListener { _, _ ->
|
||||
mediaActivity.onPhotoTap()
|
||||
false
|
||||
}
|
||||
|
||||
progressBar.hide()
|
||||
mp.isLooping = true
|
||||
if (arguments!!.getBoolean(ARG_START_POSTPONED_TRANSITION)) {
|
||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
||||
videoView.start()
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +165,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
throw IllegalArgumentException("attachment has to be set")
|
||||
}
|
||||
url = attachment.url
|
||||
isAudio = attachment.type == Attachment.Type.AUDIO
|
||||
finalizeViewSetup(url, attachment.previewUrl, attachment.description)
|
||||
}
|
||||
|
||||
|
@ -136,6 +176,12 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
|
||||
isDescriptionVisible = showingDescription && visible
|
||||
val alpha = if (isDescriptionVisible) 1.0f else 0.0f
|
||||
if (isDescriptionVisible) {
|
||||
// If to be visible, need to make visible immediately and animate alpha
|
||||
descriptionView.alpha = 0.0f
|
||||
descriptionView.visible(isDescriptionVisible)
|
||||
}
|
||||
|
||||
descriptionView.animate().alpha(alpha)
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
|
@ -145,7 +191,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
|||
})
|
||||
.start()
|
||||
|
||||
if (visible) {
|
||||
if (visible && videoView.isPlaying && !isAudio) {
|
||||
hideToolbarAfterDelay(TOOLBAR_HIDE_DELAY_MS)
|
||||
} else {
|
||||
handler.removeCallbacks(hideToolbar)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.keylesspalace.tusky.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.VideoView
|
||||
|
||||
class ExposedPlayPauseVideoView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0)
|
||||
: VideoView(context, attrs, defStyleAttr) {
|
||||
|
||||
private var listener: PlayPauseListener? = null
|
||||
|
||||
fun setPlayPauseListener(listener: PlayPauseListener) {
|
||||
this.listener = listener
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
super.start()
|
||||
listener?.onPlay()
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
super.pause()
|
||||
listener?.onPause()
|
||||
}
|
||||
|
||||
interface PlayPauseListener {
|
||||
fun onPlay()
|
||||
fun onPause()
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/* Copyright 2019 Joel Pyska
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
|
||||
class NoSwipeViewPager @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : ViewPager(context, attrs) {
|
||||
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="?android:textColorTertiary" android:pathData="M16,9H13V14.5A2.5,2.5 0 0,1 10.5,17A2.5,2.5 0 0,1 8,14.5A2.5,2.5 0 0,1 10.5,12C11.07,12 11.58,12.19 12,12.5V7H16M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />
|
||||
</vector>
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<include layout="@layout/toolbar_basic" />
|
||||
|
||||
<com.keylesspalace.tusky.view.NoSwipeViewPager
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/wizard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Some media description" />
|
||||
|
||||
<VideoView
|
||||
<com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
||||
android:id="@+id/videoView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -235,7 +235,6 @@
|
|||
<string name="status_media_images">صور</string>
|
||||
<string name="status_media_video">فيديو</string>
|
||||
<string name="state_follow_requested">طلب متابعة</string>
|
||||
<string name="no_content">ليس هناك محتوى</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">في %dy</string>
|
||||
<string name="abbreviated_in_days">في %dd</string>
|
||||
|
@ -498,4 +497,7 @@
|
|||
<string name="list">القائمة</string>
|
||||
<string name="gradient_for_media">اظهر ألوانا متدرّجة للوسائط المخفية</string>
|
||||
|
||||
</resources>
|
||||
<string name="no_saved_status">ليس لديك أية مسودات.</string>
|
||||
<string name="no_scheduled_status">ليس لديك أية منشورات مُبرمَجة للنشر.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -282,8 +282,6 @@
|
|||
|
||||
<string name="state_follow_requested">অনুরোধ অনুসরণ করুন</string>
|
||||
|
||||
<string name="no_content">কোন উপাদান নেই</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%dy এ</string>
|
||||
<string name="abbreviated_in_days">%dd এ</string>
|
||||
|
|
|
@ -203,8 +203,6 @@
|
|||
<string name="status_media_images">Imatges</string>
|
||||
<string name="status_media_video">Vídeo</string>
|
||||
|
||||
<string name="no_content">no hi ha cap contingut</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">en %d anys</string>
|
||||
<string name="abbreviated_in_days">en %dd</string>
|
||||
|
|
|
@ -245,7 +245,6 @@
|
|||
<string name="status_media_images">Obrázky</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">Vyžádáno sledování</string>
|
||||
<string name="no_content">žádný obsah</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">za %d let</string>
|
||||
<string name="abbreviated_in_days">za %d d</string>
|
||||
|
|
|
@ -214,7 +214,6 @@
|
|||
<string name="status_media_images">Delweddau</string>
|
||||
<string name="status_media_video">Fideo</string>
|
||||
<string name="state_follow_requested">Gofyn i ddilyn</string>
|
||||
<string name="no_content">dim cynnwys</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%dy </string>
|
||||
<string name="abbreviated_in_days"> %dd </string>
|
||||
|
|
|
@ -241,7 +241,6 @@
|
|||
<string name="status_media_images">Bilder</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">Folgeanfrage gesendet</string>
|
||||
<string name="no_content">leer</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="follows_you">Folgt dir</string>
|
||||
<string name="pref_title_alway_show_sensitive_media">Heikle Inhalte immer anzeigen</string>
|
||||
|
|
|
@ -241,7 +241,6 @@
|
|||
<string name="status_media_images">Bildoj</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">Sekvado petita</string>
|
||||
<string name="no_content">neniu enhavo</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">en %dj</string>
|
||||
<string name="abbreviated_in_days">en %dt</string>
|
||||
|
|
|
@ -228,7 +228,6 @@
|
|||
<string name="status_media_images">Imágenes</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">Solicitud enviada</string>
|
||||
<string name="no_content">No hay nada aquí</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">en %dy</string>
|
||||
<string name="abbreviated_in_days">en %dd</string>
|
||||
|
|
|
@ -213,7 +213,6 @@
|
|||
<string name="status_media_images">Irudiak</string>
|
||||
<string name="status_media_video">Bideoak</string>
|
||||
<string name="state_follow_requested">Eskaera bidalita</string>
|
||||
<string name="no_content">Hutsik dago hau</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%du-an</string>
|
||||
<string name="abbreviated_in_days">%de-an</string>
|
||||
|
|
|
@ -213,7 +213,6 @@
|
|||
<string name="status_media_images">تصویرها</string>
|
||||
<string name="status_media_video">فیلم</string>
|
||||
<string name="state_follow_requested">تقاضای پیگیری شد</string>
|
||||
<string name="no_content">بدون هیچ محتوا</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">در %dسال</string>
|
||||
<string name="abbreviated_in_days">در %dر</string>
|
||||
|
|
|
@ -245,7 +245,6 @@
|
|||
<string name="status_media_images">Images</string>
|
||||
<string name="status_media_video">Vidéo</string>
|
||||
<string name="state_follow_requested">Demande de suivi effectuée</string>
|
||||
<string name="no_content">aucun contenu</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">en %da</string>
|
||||
<string name="abbreviated_in_days">en %dj</string>
|
||||
|
|
|
@ -226,7 +226,6 @@
|
|||
<string name="status_media_images">Képek</string>
|
||||
<string name="status_media_video">Videók</string>
|
||||
<string name="state_follow_requested">Követés kérelmezve</string>
|
||||
<string name="no_content">nincs tartalom</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="follows_you">Követ téged</string>
|
||||
<string name="pref_title_alway_show_sensitive_media">Mindig mutassa a szenzitív tartalmat</string>
|
||||
|
|
|
@ -239,7 +239,6 @@
|
|||
<string name="status_media_images">Immagini</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">In attesa di approvazione</string>
|
||||
<string name="no_content">nessun contenuto</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">in %da</string>
|
||||
<string name="abbreviated_in_days">in %dg</string>
|
||||
|
|
|
@ -243,7 +243,6 @@
|
|||
<string name="status_media_images">画像</string>
|
||||
<string name="status_media_video">動画</string>
|
||||
<string name="state_follow_requested">フォローリクエスト中</string>
|
||||
<string name="no_content">下書きはありません</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%d年後</string>
|
||||
<string name="abbreviated_in_days">%d日後</string>
|
||||
|
|
|
@ -293,8 +293,6 @@
|
|||
|
||||
<string name="state_follow_requested">팔로우 요청함</string>
|
||||
|
||||
<string name="no_content">임시 저장된 게시물이 없습니다</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%d년 후</string>
|
||||
<string name="abbreviated_in_days">%d일 후</string>
|
||||
|
|
|
@ -236,7 +236,6 @@
|
|||
<string name="status_media_images">Afbeeldingen</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">Volgverzoek verzonden</string>
|
||||
<string name="no_content">geen inhoud</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">over %dj</string>
|
||||
<string name="abbreviated_in_days">over %dd</string>
|
||||
|
|
|
@ -247,8 +247,6 @@
|
|||
|
||||
<string name="state_follow_requested">Forespørsel sendt</string>
|
||||
|
||||
<string name="no_content">ikke noe innhold</string>
|
||||
|
||||
<string name="abbreviated_in_days">om %dd</string>
|
||||
<string name="abbreviated_in_hours">om %dh</string>
|
||||
<string name="abbreviated_in_minutes">om %dm</string>
|
||||
|
@ -520,4 +518,5 @@
|
|||
|
||||
<string name="no_scheduled_status">Du har ingen planlagte statuser.</string>
|
||||
|
||||
</resources>
|
||||
<string name="no_saved_status">Du har ikke lagret noen kladder.</string>
|
||||
</resources>
|
||||
|
|
|
@ -209,7 +209,6 @@
|
|||
<string name="status_media_images">Imatges</string>
|
||||
<string name="status_media_video">Vidèo</string>
|
||||
<string name="state_follow_requested">Demanda d’abonament</string>
|
||||
<string name="no_content">I a pas cap de contengut</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">en %d ans</string>
|
||||
<string name="abbreviated_in_days">en %dd</string>
|
||||
|
|
|
@ -206,7 +206,6 @@
|
|||
<string name="status_media_images">Obrazy</string>
|
||||
<string name="status_media_video">Wideo</string>
|
||||
<string name="state_follow_requested">Wysłano prośbę o możliwość śledzenia</string>
|
||||
<string name="no_content">brak zawartości</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">w %d lata</string>
|
||||
<string name="abbreviated_in_days">w %d dni</string>
|
||||
|
|
|
@ -226,7 +226,6 @@
|
|||
<string name="status_media_images">Imagens</string>
|
||||
<string name="status_media_video">Vídeo</string>
|
||||
<string name="state_follow_requested">Solicitação enviada</string>
|
||||
<string name="no_content">sem conteúdo</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">em %dy</string>
|
||||
<string name="abbreviated_in_days">em %dd</string>
|
||||
|
@ -463,7 +462,7 @@
|
|||
<string name="poll_duration_3_days">3 dias</string>
|
||||
<string name="poll_duration_7_days">7 dias</string>
|
||||
<string name="add_poll_choice">Adicionar opção</string>
|
||||
<string name="poll_allow_multiple_choices">Múltiplas opções</string>
|
||||
<string name="poll_allow_multiple_choices">Múltiplos votos</string>
|
||||
<string name="poll_new_choice_hint">Opção %d</string>
|
||||
<string name="edit_poll">Editar</string>
|
||||
|
||||
|
@ -481,4 +480,8 @@
|
|||
<string name="description_status_bookmarked">Salvo</string>
|
||||
<string name="select_list_title">Selecionar lista</string>
|
||||
<string name="list">Lista</string>
|
||||
</resources>
|
||||
<string name="gradient_for_media">Mostrar blur em mídias ocultas</string>
|
||||
|
||||
<string name="no_scheduled_status">Sem toots agendados.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -275,8 +275,6 @@
|
|||
|
||||
<string name="state_follow_requested">Запрошенные подписки</string>
|
||||
|
||||
<string name="no_content">ничего нет</string>
|
||||
|
||||
<!--Отметки времени у постов: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">через %dг</string>
|
||||
<string name="abbreviated_in_days">через %dд</string>
|
||||
|
|
|
@ -253,8 +253,6 @@
|
|||
|
||||
<string name="state_follow_requested">Prošnja za sledenje</string>
|
||||
|
||||
<string name="no_content">brez vsebine</string>
|
||||
|
||||
<string name="abbreviated_in_years">v %dy</string>
|
||||
<string name="abbreviated_in_days">v %dd</string>
|
||||
<string name="abbreviated_in_hours">v %dh</string>
|
||||
|
|
|
@ -14,11 +14,11 @@
|
|||
<string name="error_video_upload_size">Videofiler måste vara mindre än 40MB.</string>
|
||||
<string name="error_media_upload_type">Den typen av fil kan inte laddas upp.</string>
|
||||
<string name="error_media_upload_opening">Den filen kunde inte öppnas.</string>
|
||||
<string name="error_media_upload_permission">Tillstånd att läsa media krävs.</string>
|
||||
<string name="error_media_download_permission">Tillstånd att lagra media krävs.</string>
|
||||
<string name="error_media_upload_permission">Behörighet att läsa media krävs.</string>
|
||||
<string name="error_media_download_permission">Behörighet att spara media krävs.</string>
|
||||
<string name="error_media_upload_image_or_video">Bilder och videoklipp kan inte båda bifogas i samma status.</string>
|
||||
<string name="error_media_upload_sending">Uppladdningen misslyckades.</string>
|
||||
<string name="error_sender_account_gone">Misslyckades med att få ett inloggningsnamn.</string>
|
||||
<string name="error_sender_account_gone">Kunde inte skicka toot.</string>
|
||||
<string name="title_home">Hem</string>
|
||||
<string name="title_notifications">Notifikationer</string>
|
||||
<string name="title_public_local">Lokalt</string>
|
||||
|
@ -109,7 +109,7 @@
|
|||
<string name="action_links">Länkar</string>
|
||||
<string name="action_mentions">Omnämnanden</string>
|
||||
<string name="action_hashtags">Hashtaggar</string>
|
||||
<string name="action_open_reblogger">Öppna knuffa författare</string>
|
||||
<string name="action_open_reblogger">Öppna knuff författare</string>
|
||||
<string name="action_open_reblogged_by">Visa knuffar</string>
|
||||
<string name="action_open_faved_by">Visa favoriter</string>
|
||||
<string name="title_hashtags_dialog">Hashtaggar</string>
|
||||
|
@ -117,7 +117,7 @@
|
|||
<string name="title_links_dialog">Länkar</string>
|
||||
<string name="action_open_media_n">Öppna media #%d</string>
|
||||
<string name="download_image">Laddar ned %1$s</string>
|
||||
<string name="action_copy_link">Kopiera länken</string>
|
||||
<string name="action_copy_link">Kopiera länk</string>
|
||||
<string name="action_open_as">Öppen som %s</string>
|
||||
<string name="action_share_as">Dela som …</string>
|
||||
<string name="send_status_link_to">Dela toot-URL till…</string>
|
||||
|
@ -158,8 +158,8 @@
|
|||
<string name="visibility_unlisted">Olistad: Visa inte i offentliga tidslinjer</string>
|
||||
<string name="visibility_private">Enbart-följare: Ses enbart av följare</string>
|
||||
<string name="visibility_direct">Direkt: Skicka endast till nämnda användare</string>
|
||||
<string name="pref_title_edit_notification_settings">Redigera notifieringar</string>
|
||||
<string name="pref_title_notifications_enabled">Notifieringar</string>
|
||||
<string name="pref_title_edit_notification_settings">Notifikationer</string>
|
||||
<string name="pref_title_notifications_enabled">Notifikationer</string>
|
||||
<string name="pref_title_notification_alerts">Alarm</string>
|
||||
<string name="pref_title_notification_alert_sound">Meddela med ljud</string>
|
||||
<string name="pref_title_notification_alert_vibrate">Meddela med vibration</string>
|
||||
|
@ -173,14 +173,14 @@
|
|||
<string name="pref_title_app_theme">Applikationstema</string>
|
||||
<string name="pref_title_timelines">Tidslinjer</string>
|
||||
<string name="pref_title_timeline_filters">Filter</string>
|
||||
<string name="app_them_dark">Mörk</string>
|
||||
<string name="app_theme_light">Ljus</string>
|
||||
<string name="app_them_dark">Mörkt</string>
|
||||
<string name="app_theme_light">Ljust</string>
|
||||
<string name="app_theme_black">Svart</string>
|
||||
<string name="app_theme_auto">Automatiskt vid solnedgång</string>
|
||||
<string name="app_theme_system">Använd systemdesign</string>
|
||||
<string name="pref_title_browser_settings">Webbläsare</string>
|
||||
<string name="pref_title_custom_tabs">Använd Chrome-anpassade flikar</string>
|
||||
<string name="pref_title_hide_follow_button">Dölj skriv-knappen medan du scrollar</string>
|
||||
<string name="pref_title_hide_follow_button">Dölj skriv-knappen vid skrollning</string>
|
||||
<string name="pref_title_language">Språk</string>
|
||||
<string name="pref_title_status_filter">Filtrering av tidslinje</string>
|
||||
<string name="pref_title_status_tabs">Flikar</string>
|
||||
|
@ -208,11 +208,11 @@
|
|||
<string name="notification_mention_name">Nya omnämnanden</string>
|
||||
<string name="notification_mention_descriptions">Notifieringar om nya omnämnanden</string>
|
||||
<string name="notification_follow_name">Nya följare</string>
|
||||
<string name="notification_follow_description">Notifieringar angående nya följare</string>
|
||||
<string name="notification_follow_description">Notifieringar om nya följare</string>
|
||||
<string name="notification_boost_name">Knuffar</string>
|
||||
<string name="notification_boost_description">Notifieringar när dina toot blir knuffade</string>
|
||||
<string name="notification_boost_description">Notifieringar när dina toots blir knuffade</string>
|
||||
<string name="notification_favourite_name">Favoriter</string>
|
||||
<string name="notification_favourite_description">Notifieringar när dina toot blir markerade som favoriter</string>
|
||||
<string name="notification_favourite_description">Notifieringar när dina toots blir markerade som favoriter</string>
|
||||
<string name="notification_mention_format">%s omnämnde dig</string>
|
||||
<string name="notification_summary_large">%1$s, %2$s, %3$s och %4$d andra</string>
|
||||
<string name="notification_summary_medium">%1$s, %2$s, och %3$s</string>
|
||||
|
@ -239,8 +239,7 @@
|
|||
<string name="status_share_link">Dela länk till toot</string>
|
||||
<string name="status_media_images">Bilder</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">Följarförfrågning</string>
|
||||
<string name="no_content">inget innehåll</string>
|
||||
<string name="state_follow_requested">Följarförfrågad</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">om %dy</string>
|
||||
<string name="abbreviated_in_days">om %dd</string>
|
||||
|
@ -273,11 +272,11 @@
|
|||
<string name="error_rename_list">Kunde inte byta namn på lista</string>
|
||||
<string name="error_delete_list">Kunde inte radera lista</string>
|
||||
<string name="action_create_list">Skapa en lista</string>
|
||||
<string name="action_rename_list">Byt namn på lista</string>
|
||||
<string name="action_delete_list">Ta bort denna lista</string>
|
||||
<string name="action_edit_list">Redigera listan</string>
|
||||
<string name="action_rename_list">Byt namn</string>
|
||||
<string name="action_delete_list">Ta bort</string>
|
||||
<string name="action_edit_list">Ändra</string>
|
||||
<string name="hint_search_people_list">Sök efter personer du följer</string>
|
||||
<string name="action_add_to_list">Lägga till kontot i listan</string>
|
||||
<string name="action_add_to_list">Lägg till konto i listan</string>
|
||||
<string name="action_remove_from_list">Ta bort kontot från listan</string>
|
||||
<string name="compose_active_account_description">Inlägg med kontot %1$s</string>
|
||||
<string name="error_failed_set_caption">Misslyckades med att ange bildtext</string>
|
||||
|
@ -302,7 +301,7 @@
|
|||
<string name="expand_collapse_all_statuses">Expandera/Dölj alla status</string>
|
||||
<string name="action_open_toot">Öppna toot</string>
|
||||
<string name="restart_required">Omstart av appen krävs</string>
|
||||
<string name="restart_emoji">Du måste starta om Yuito för att kunna tillämpa ändringarna</string>
|
||||
<string name="restart_emoji">Du måste starta om Yuito för att tillämpa ändringarna</string>
|
||||
<string name="later">Senare</string>
|
||||
<string name="restart">Starta om</string>
|
||||
<string name="caption_systememoji">Standard-emojis för din enhet</string>
|
||||
|
@ -330,9 +329,9 @@
|
|||
<item quantity="other"><b>%1$s</b> Favoriter</item>
|
||||
</plurals>
|
||||
<plurals name="reblogs">
|
||||
<item quantity="one"><b>%s</b> Knuffa</item>
|
||||
<item quantity="other"><b>%s</b> Knuffar</item>
|
||||
</plurals>
|
||||
<item quantity="one"><b>%s</b> Knuff</item>
|
||||
<item quantity="other"><b>%s</b> Knuffar</item>
|
||||
</plurals>
|
||||
<string name="title_reblogged_by">Knuffad av</string>
|
||||
<string name="title_favourited_by">Favoriserad av</string>
|
||||
<string name="conversation_1_recipients">%1$s</string>
|
||||
|
@ -377,7 +376,7 @@
|
|||
<string name="notification_clear_text">Är du säker på att du vill rensa dina notifieringar permanent\?</string>
|
||||
|
||||
<string name="action_delete_and_redraft">Radera och skriv nytt</string>
|
||||
<string name="dialog_redraft_toot_warning">Radera och skriva ny toot\?</string>
|
||||
<string name="dialog_redraft_toot_warning">Radera och skriv ny toot\?</string>
|
||||
|
||||
<string name="poll_info_format"> <!-- 15 röster • 1 timme kvar --> %1$s • %2$s</string>
|
||||
<plurals name="poll_info_votes">
|
||||
|
@ -391,7 +390,7 @@
|
|||
<string name="poll_vote">Rösta</string>
|
||||
|
||||
|
||||
<string name="pref_title_notification_filter_poll">omröstningen är avslutad</string>
|
||||
<string name="pref_title_notification_filter_poll">omröstning är avslutad</string>
|
||||
<string name="notification_poll_name">Omröstningar</string>
|
||||
<string name="notification_poll_description">Notifieringar när omröstningar har avslutats</string>
|
||||
|
||||
|
@ -428,7 +427,7 @@
|
|||
<string name="confirmation_domain_unmuted">%s inte tystnad längre</string>
|
||||
|
||||
<string name="mute_domain_warning">Är du säker på att du vill blockera allt från %s\? Du kommer inte kunna se något innehåll från denna domän i publika tidslinje eller i dina notifieringar. Dina följare på domänen kommer inte att bli borttagna.</string>
|
||||
<string name="mute_domain_warning_dialog_ok">Dölj hela domän</string>
|
||||
<string name="mute_domain_warning_dialog_ok">Dölj hela domänen</string>
|
||||
|
||||
<string name="caption_notoemoji">Google\'s nuvarande emojis</string>
|
||||
<string name="button_continue">Fortsätt</string>
|
||||
|
@ -440,7 +439,7 @@
|
|||
<string name="failed_report">Misslyckades att anmäla</string>
|
||||
<string name="failed_fetch_statuses">Misslyckades att hämta status</string>
|
||||
<string name="report_description_1">Anmälan kommer att skickas till din serveradminstratör. Du kan beskriva varför du anmäler kontot nedan:</string>
|
||||
<string name="report_description_remote_instance">Kontot är från en annan server. Skicka en avidentifierad kopia av anmälan dit också\?</string>
|
||||
<string name="report_description_remote_instance">Kontot är från en annan server. Skicka en anonym kopia av anmälan dit också\?</string>
|
||||
|
||||
<string name="pref_title_show_notifications_filter">Visa notifikationsfilter</string>
|
||||
<string name="filter_dialog_whole_word">Helt ord</string>
|
||||
|
@ -477,4 +476,8 @@
|
|||
<string name="description_status_bookmarked">Bokmärkt</string>
|
||||
<string name="select_list_title">Välj lista</string>
|
||||
<string name="list">Lista</string>
|
||||
</resources>
|
||||
<string name="gradient_for_media">Visa färgglada gradienter för gömd media</string>
|
||||
|
||||
<string name="no_scheduled_status">Du har inga schemalagda statusar.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -194,7 +194,6 @@
|
|||
<string name="status_media_images">படங்கள்</string>
|
||||
<string name="status_media_video">காணொளி</string>
|
||||
<string name="state_follow_requested">கோரிக்கையைப் பின்பற்றவும்</string>
|
||||
<string name="no_content">எந்த உள்ளடக்கமும் இல்லை</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%dஆ-முன்</string>
|
||||
<string name="abbreviated_in_days">%dநா-முன்</string>
|
||||
|
|
|
@ -4,17 +4,17 @@
|
|||
<string name="error_network">Bir ağ hatası oluştu! Lütfen bağlantınızı kontrol edin ve tekrar deneyin!</string>
|
||||
<string name="error_empty">Bu alan boş bırakılamaz.</string>
|
||||
<string name="error_invalid_domain">Girilen alan adı geçersiz</string>
|
||||
<string name="error_failed_app_registration">Bu sunucuda kimlik doğrulama başarısız oldu.</string>
|
||||
<string name="error_no_web_browser_found">Kullanılabilir tarayıcı bulunmadı.</string>
|
||||
<string name="error_failed_app_registration">Kimlik doğrulama başarısız oldu.</string>
|
||||
<string name="error_no_web_browser_found">Kullanılabilir bir web tarayıcı bulunmadı.</string>
|
||||
<string name="error_authorization_unknown">Tanımlanamayan bir yetkilendirme hatası oluştu.</string>
|
||||
<string name="error_authorization_denied">Kimlik doğrulama reddedildi.</string>
|
||||
<string name="error_retrieving_oauth_token">Giriş jetonu alınamadı.</string>
|
||||
<string name="error_retrieving_oauth_token">Giriş tokenı alınamadı.</string>
|
||||
<string name="error_compose_character_limit">İleti çok uzun!</string>
|
||||
<string name="error_image_upload_size">Dosya 8MB\'ten küçük olmalı.</string>
|
||||
<string name="error_video_upload_size">Video dosyaları 40 MB’den az olmalıdır.</string>
|
||||
<string name="error_image_upload_size">Dosya 8MB\'den küçük olmalı.</string>
|
||||
<string name="error_video_upload_size">Video dosyaları 40MB’den küçük olmalıdır.</string>
|
||||
<string name="error_media_upload_type">Bu tür bir dosya yüklenemiyor.</string>
|
||||
<string name="error_media_upload_opening">Dosya açılamadı.</string>
|
||||
<string name="error_media_upload_permission">Medya okuma izni gerekiyor.</string>
|
||||
<string name="error_media_upload_permission">Medya erişim izni gerekiyor.</string>
|
||||
<string name="error_media_download_permission">Medya yazma izni gerekiyor.</string>
|
||||
<string name="error_media_upload_image_or_video">Aynı iletiye hem video hem resim eklenemez.</string>
|
||||
<string name="error_media_upload_sending">Yükleme başarsız.</string>
|
||||
|
@ -34,7 +34,7 @@
|
|||
<string name="title_favourites">Favoriler</string>
|
||||
<string name="title_mutes">Sesize alınmış kullanıcılar</string>
|
||||
<string name="title_blocks">Engellenmiş kullanıcılar</string>
|
||||
<string name="title_follow_requests">Takip Etme İstekleri</string>
|
||||
<string name="title_follow_requests">Takip İstekleri</string>
|
||||
<string name="title_edit_profile">Profil düzenle</string>
|
||||
<string name="title_saved_toot">Taslaklar</string>
|
||||
<string name="title_licenses">Lisanslar</string>
|
||||
|
@ -42,27 +42,27 @@
|
|||
<string name="status_boosted_format">%s yineledi</string>
|
||||
<string name="status_sensitive_media_title">Hasas içerik</string>
|
||||
<string name="status_media_hidden_title">Gizlenmiş medya</string>
|
||||
<string name="status_sensitive_media_directions">Görüntülemek için tıklayın</string>
|
||||
<string name="status_sensitive_media_directions">Görüntülemek için dokunun</string>
|
||||
<string name="status_content_warning_show_more">Daha Fazla</string>
|
||||
<string name="status_content_warning_show_less">Daha Az</string>
|
||||
<string name="status_content_warning_show_less">Daha az</string>
|
||||
<string name="status_content_show_more">Genişlet</string>
|
||||
<string name="status_content_show_less">Daralt</string>
|
||||
<string name="message_empty">Burada hiçbir şey yok.</string>
|
||||
<string name="footer_empty">Burada henüz hiçbir şey yok. Yenilemek için aşağıya çekin!</string>
|
||||
<string name="notification_reblog_format">%s iletini yineledi</string>
|
||||
<string name="notification_favourite_format">%s ileti favorilerine ekledi</string>
|
||||
<string name="notification_favourite_format">%s iletini favorilerine ekledi</string>
|
||||
<string name="notification_follow_format">%s seni takip etti</string>
|
||||
<string name="report_username_format">\@%s bildir</string>
|
||||
<string name="report_comment_hint">Daha fazla yorum?</string>
|
||||
<string name="action_quick_reply">Hızlı Cevapla</string>
|
||||
<string name="action_reply">Yanıtla</string>
|
||||
<string name="action_reblog">Yinele</string>
|
||||
<string name="action_favourite">Favori</string>
|
||||
<string name="action_favourite">Favorile</string>
|
||||
<string name="action_more">Daha fazla</string>
|
||||
<string name="action_compose">Oluştur</string>
|
||||
<string name="action_login">Mastodon ile giriş yap</string>
|
||||
<string name="action_logout">Çıkış Yap</string>
|
||||
<string name="action_logout_confirm">%1$s hesabından çıkmak istediğinize emin misiniz\?</string>
|
||||
<string name="action_logout_confirm">%1$s hesabından çıkmak istediğine emin misin\?</string>
|
||||
<string name="action_follow">Takip et</string>
|
||||
<string name="action_unfollow">Takibi bırak</string>
|
||||
<string name="action_block">Engelle</string>
|
||||
|
@ -107,7 +107,7 @@
|
|||
<string name="download_image">%1$s indiriliyor</string>
|
||||
<string name="action_copy_link">Bağlantıyı kopyala</string>
|
||||
<string name="action_open_as">Farklı aç %s</string>
|
||||
<string name="action_share_as">... olarak paylaş</string>
|
||||
<string name="action_share_as">Farklı paylaş…</string>
|
||||
<string name="send_status_link_to">İletinin adresini paylaş…</string>
|
||||
<string name="send_status_content_to">İletiyi paylaş…</string>
|
||||
<string name="send_media_to">Medyayı paylaş…</string>
|
||||
|
@ -119,7 +119,7 @@
|
|||
<string name="hint_domain">Hangi sunucu?</string>
|
||||
<string name="hint_compose">Neler oluyor?</string>
|
||||
<string name="hint_content_warning">İçerik uyarısı</string>
|
||||
<string name="hint_display_name">Görünen ad</string>
|
||||
<string name="hint_display_name">Görüntülenecek isim</string>
|
||||
<string name="hint_note">Biyo</string>
|
||||
<string name="hint_search">Ara…</string>
|
||||
<string name="search_no_results">Sonuç bulunamadı</string>
|
||||
|
@ -128,33 +128,33 @@
|
|||
<string name="label_header">Başlık</string>
|
||||
<string name="link_whats_an_instance">Sunucu nedir?</string>
|
||||
<string name="login_connection">Bağlantı kuruluyor…</string>
|
||||
<string name="dialog_whats_an_instance">Burada her hangi bir Mastodon sunucusunun adresi (mastodon.social, icosahedron.website, social.tchncs.de, ve <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md">daha fazla!</a>) girilebiliri.
|
||||
<string name="dialog_whats_an_instance">Burada her hangi bir Mastodon sunucusunun adresi (mastodon.social, icosahedron.website, social.tchncs.de, ve <a href="https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md">daha fazlasını</a>) girebilirsin!
|
||||
\n
|
||||
\nEğer hesabınız henüz yok ise katılmak istediğiniz sunucunun adresini girerek hesap oluşturabilirsiniz
|
||||
\nHenüz hesabın yok ise katılmak istediğin sunucunun adresini girerek hesap oluşturabilirsin
|
||||
\n
|
||||
\nHer bir sunucu kendi hesap kayıtlarını tutar ancak diğer sunucularda bulunan insanlarla aynı sitedeymişçesine iletişime geçip takip edebilirsiniz.
|
||||
\nHer bir sunucu kendi hesap kayıtlarını tutar ancak diğer sunucularda bulunan insanlarla aynı sitedeymişçesine iletişime geçip takip edebilirsin.
|
||||
\n
|
||||
\nDaha fazla bilgi için <a href="https://mastodon.social/about">mastodon.social</a>. </string>
|
||||
<string name="dialog_title_finishing_media_upload">Medya Yükleme Bitiriliyor</string>
|
||||
<string name="dialog_title_finishing_media_upload">Medya Yükleme Tamamlanıyor</string>
|
||||
<string name="dialog_message_uploading_media">Yükleniyor…</string>
|
||||
<string name="dialog_download_image">İndir</string>
|
||||
<string name="dialog_message_cancel_follow_request">Takip isteğini iptal et?</string>
|
||||
<string name="dialog_unfollow_warning">Takibi bırak?</string>
|
||||
<string name="dialog_delete_toot_warning">Bu iletiyi silmek istiyor musunuz\?</string>
|
||||
<string name="visibility_public">Genel: Genel zaman çizelgelerine gönder</string>
|
||||
<string name="visibility_unlisted">Listelenmemiş: Genel zaman çizelgelerinde gösterme</string>
|
||||
<string name="visibility_private">Özel: Sadece takipçiler gönder</string>
|
||||
<string name="visibility_direct">Doğrudan: Sadece bahsedilen kullanıcılara gönder</string>
|
||||
<string name="dialog_message_cancel_follow_request">Takip isteği iptal edilsin mi\?</string>
|
||||
<string name="dialog_unfollow_warning">Takibi bırakmak istiyor musun\?</string>
|
||||
<string name="dialog_delete_toot_warning">Bu iletiyi silmek istiyor musun\?</string>
|
||||
<string name="visibility_public">Genel: Herkese açık zaman çizelgesinde göster</string>
|
||||
<string name="visibility_unlisted">Liste dışı: Herkese açık zaman çizelgelerinde gösterme</string>
|
||||
<string name="visibility_private">Takipçiler: Sadece takipçilere göster</string>
|
||||
<string name="visibility_direct">Doğrudan: Sadece bahsedilen kullanıcılara göster</string>
|
||||
<string name="pref_title_edit_notification_settings">Bildirimler</string>
|
||||
<string name="pref_title_notifications_enabled">Bildirimler</string>
|
||||
<string name="pref_title_notification_alerts">Uyarılar</string>
|
||||
<string name="pref_title_notification_alert_sound">Sesli uyarı</string>
|
||||
<string name="pref_title_notification_alert_vibrate">Titreşim ile uyarı</string>
|
||||
<string name="pref_title_notification_alert_sound">Sesle bildir</string>
|
||||
<string name="pref_title_notification_alert_vibrate">Titreşimle bildir</string>
|
||||
<string name="pref_title_notification_alert_light">Bildirim ışığıyla bildir</string>
|
||||
<string name="pref_title_notification_filters">Beni bildir</string>
|
||||
<string name="pref_title_notification_filter_mentions">Bahsedilince</string>
|
||||
<string name="pref_title_notification_filter_follows">Takip edilince</string>
|
||||
<string name="pref_title_notification_filter_reblogs">İletilerim yinelenince</string>
|
||||
<string name="pref_title_notification_filters">Bildirim ayarları</string>
|
||||
<string name="pref_title_notification_filter_mentions">bahsedilince</string>
|
||||
<string name="pref_title_notification_filter_follows">takip edilince</string>
|
||||
<string name="pref_title_notification_filter_reblogs">iletilerim yinelenince</string>
|
||||
<string name="pref_title_notification_filter_favourites">iletilerim favorilenince</string>
|
||||
<string name="pref_title_appearance_settings">Görünüm</string>
|
||||
<string name="pref_title_app_theme">Uygulama Teması</string>
|
||||
|
@ -164,7 +164,7 @@
|
|||
<string name="app_theme_black">Siyah</string>
|
||||
<string name="app_theme_auto">Gün batımında otomatik</string>
|
||||
<string name="pref_title_browser_settings">Tarayıcı</string>
|
||||
<string name="pref_title_custom_tabs">Chrome Özel Sekmelerini Kullan</string>
|
||||
<string name="pref_title_custom_tabs">Tarayıcı Özel Sekmelerini Kullan</string>
|
||||
<string name="pref_title_hide_follow_button">Kaydırırken yeni ileti düğmesi gizlensin</string>
|
||||
<string name="pref_title_status_filter">Zaman çizelgesi filtreleme</string>
|
||||
<string name="pref_title_status_tabs">Sekmeler</string>
|
||||
|
@ -177,26 +177,26 @@
|
|||
<string name="pref_title_http_proxy_server">HTTP ağ vekili sunucusu</string>
|
||||
<string name="pref_title_http_proxy_port">HTTP ağ vekili bağlantı noktası</string>
|
||||
<string name="pref_default_post_privacy">Varsayılan ileti gizliliği</string>
|
||||
<string name="pref_default_media_sensitivity">Her zaman hassas olarak işaretle</string>
|
||||
<string name="pref_default_media_sensitivity">Medyaları her zaman hassas olarak işaretle</string>
|
||||
<string name="pref_publishing">Yayınlama (sunucuyla eşitlenir)</string>
|
||||
<string name="pref_failed_to_sync">Ayarlar senkronize edilemedi</string>
|
||||
<string name="post_privacy_public">Herkese açık</string>
|
||||
<string name="post_privacy_unlisted">Liste dışı</string>
|
||||
<string name="post_privacy_followers_only">Sadece takip edenler</string>
|
||||
<string name="post_privacy_followers_only">Sadece takipçiler</string>
|
||||
<string name="pref_status_text_size">İleti metin boyutu</string>
|
||||
<string name="status_text_size_smallest">En küçük</string>
|
||||
<string name="status_text_size_smallest">Çok küçük</string>
|
||||
<string name="status_text_size_small">Küçük</string>
|
||||
<string name="status_text_size_medium">Orta</string>
|
||||
<string name="status_text_size_large">Büyük</string>
|
||||
<string name="status_text_size_largest">En büyük</string>
|
||||
<string name="notification_mention_name">Yeni Bahsedilenler</string>
|
||||
<string name="notification_mention_descriptions">Yeni bahsedilenler hakkında bildirim</string>
|
||||
<string name="notification_follow_name">Yeni Takipçiler</string>
|
||||
<string name="notification_follow_description">Yeni takipçiler hakkında bildirim</string>
|
||||
<string name="status_text_size_largest">Çok büyük</string>
|
||||
<string name="notification_mention_name">Senden bahsedildi</string>
|
||||
<string name="notification_mention_descriptions">Senden bahsedenler hakkında bildirim</string>
|
||||
<string name="notification_follow_name">Yeni Takipçi</string>
|
||||
<string name="notification_follow_description">Yeni takipçi hakkında bildirim</string>
|
||||
<string name="notification_boost_name">Yinelemeler</string>
|
||||
<string name="notification_boost_description">İletilerin yinelendiğinde</string>
|
||||
<string name="notification_favourite_name">Favoriler</string>
|
||||
<string name="notification_favourite_description">İletilerin favori olarak işaretlendiğinde</string>
|
||||
<string name="notification_favourite_description">İletilerim favori olarak işaretlendiğinde</string>
|
||||
<string name="notification_mention_format">%s senden bahsetti</string>
|
||||
<string name="notification_summary_large">%1$s, %2$s, %3$s ve %4$d diğerleri</string>
|
||||
<string name="notification_summary_medium">%1$s, %2$s ve %3$s</string>
|
||||
|
@ -212,9 +212,8 @@
|
|||
to show we do not mean the software is gratis. Source: https://www.gnu.org/philosophy/free-sw.html
|
||||
* the url can be changed to link to the localized version of the license.
|
||||
-->
|
||||
<string name="about_project_site"> Proje Web sitesi:\n
|
||||
https://accelf.net/yuito
|
||||
</string>
|
||||
<string name="about_project_site">Projenin internet sitesi:
|
||||
\n https://accelf.net/yuito</string>
|
||||
<string name="about_bug_feature_request_site">Hata raporları & özellik istekleri:
|
||||
\n https://github.com/accelforce/Yuito/issues</string>
|
||||
<string name="about_tusky_account">Yuito\'in Profili</string>
|
||||
|
@ -223,19 +222,18 @@
|
|||
<string name="status_media_images">Görseller</string>
|
||||
<string name="status_media_video">Video</string>
|
||||
<string name="state_follow_requested">Takip istekleri</string>
|
||||
<string name="no_content">içerik yok</string>
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%dy</string>
|
||||
<string name="abbreviated_in_days">%dd</string>
|
||||
<string name="abbreviated_in_hours">%dh</string>
|
||||
<string name="abbreviated_in_minutes">%dm</string>
|
||||
<string name="abbreviated_in_seconds">%ds</string>
|
||||
<string name="abbreviated_years_ago">%dy yıl önce</string>
|
||||
<string name="abbreviated_days_ago">%dd gün önce</string>
|
||||
<string name="abbreviated_hours_ago">%dh saat önce</string>
|
||||
<string name="abbreviated_minutes_ago">%dm dk önce</string>
|
||||
<string name="abbreviated_seconds_ago">%ds sn önce</string>
|
||||
<string name="follows_you">Sizi takip ediyor</string>
|
||||
<string name="abbreviated_years_ago">%dy</string>
|
||||
<string name="abbreviated_days_ago">%dd</string>
|
||||
<string name="abbreviated_hours_ago">%dh</string>
|
||||
<string name="abbreviated_minutes_ago">%dm</string>
|
||||
<string name="abbreviated_seconds_ago">%ds</string>
|
||||
<string name="follows_you">Seni takip ediyor</string>
|
||||
<string name="pref_title_alway_show_sensitive_media">Her zaman hassas içerikleri göster</string>
|
||||
<string name="title_media">Medya</string>
|
||||
|
||||
|
@ -247,10 +245,10 @@
|
|||
<string name="title_list_timeline">Zaman çizelgesini listele</string>
|
||||
<string name="compose_active_account_description">%1$s hesabıyla gönderiliyor</string>
|
||||
<string name="hint_describe_for_visually_impaired">Görme engelliler için açıklama
|
||||
\n(%d karakter limiti)</string>
|
||||
\n(%d karakter sınırı)</string>
|
||||
<string name="action_set_caption">Başlık belirle</string>
|
||||
<string name="action_remove">Kaldır</string>
|
||||
<string name="lock_account_label">Hesabı Gizle</string>
|
||||
<string name="lock_account_label">Hesabı Kilitle</string>
|
||||
<string name="lock_account_label_description">Aktif edilirse takipçileri elle onaylamanız gerekir</string>
|
||||
<string name="compose_save_draft">Taslaklara kaydedilsin mi\?</string>
|
||||
<string name="send_toot_notification_title">İleti gönderiliyor…</string>
|
||||
|
@ -261,17 +259,17 @@
|
|||
<string name="action_compose_shortcut">Oluştur</string>
|
||||
<string name="error_no_custom_emojis">%s sunucunuzun özel emoji seti yok</string>
|
||||
<string name="copy_to_clipboard_success">Panoya kopyalandı</string>
|
||||
<string name="emoji_style">Emoji Stili</string>
|
||||
<string name="emoji_style">Emoji tipi</string>
|
||||
<string name="system_default">Sistem varsayılanı</string>
|
||||
<string name="download_fonts">Emoji setini kullanabilmek için indirmeniz gerekli</string>
|
||||
<string name="performing_lookup_title">Araştırılıyor…</string>
|
||||
<string name="expand_collapse_all_statuses">Tüm durumları Genişlet/Daralt</string>
|
||||
<string name="action_open_toot">İleti aç</string>
|
||||
<string name="restart_required">Uygulamayı yeniden başlatmanız gerekiyor</string>
|
||||
<string name="expand_collapse_all_statuses">Tüm iletileri genişlet/daralt</string>
|
||||
<string name="action_open_toot">İletiyi aç</string>
|
||||
<string name="restart_required">Uygulamayı yeniden başlatman gerekiyor</string>
|
||||
<string name="restart_emoji">Değişikliklerin uygulanabilmesi için uygulama yeniden başlatılmalı</string>
|
||||
<string name="later">Sonra</string>
|
||||
<string name="restart">Yeniden başlat</string>
|
||||
<string name="caption_systememoji">Cihazınızın varsayılan emoji seti</string>
|
||||
<string name="caption_systememoji">Cihaz varsayılan emoji seti</string>
|
||||
<string name="caption_blobmoji">Android 4.4 — 7.1\'den bilinen baloncuk emojisi</string>
|
||||
<string name="caption_twemoji">Mastodon\'un standart emoji seti</string>
|
||||
<string name="download_failed">İndirme başarısız</string>
|
||||
|
@ -281,7 +279,7 @@
|
|||
<string name="license_apache_2">Apache Lisansı altında lisanslanmıştır (kopya aşağıda)</string>
|
||||
<string name="license_cc_by_4">CC-BY 4.0</string>
|
||||
<string name="license_cc_by_sa_4">CC-BY-SA 4.0</string>
|
||||
<string name="profile_metadata_label">Profil Meta verisi</string>
|
||||
<string name="profile_metadata_label">Profil meta verisi</string>
|
||||
<string name="profile_metadata_add">veri ekle</string>
|
||||
<string name="profile_metadata_label_label">Etiket</string>
|
||||
<string name="profile_metadata_content_label">İçerik</string>
|
||||
|
@ -290,23 +288,23 @@
|
|||
<string name="unpin_action">Sabitlemeyi kaldır</string>
|
||||
<string name="pin_action">Sabitle</string>
|
||||
<plurals name="favs">
|
||||
<item quantity="one"><b>%1$s</b> Favori</item>
|
||||
<item quantity="other"><b>%1$s</b> Favoriler</item>
|
||||
</plurals>
|
||||
<item quantity="one"><b>%1$s</b> Favori</item>
|
||||
<item quantity="other"><b>%1$s</b> Favori</item>
|
||||
</plurals>
|
||||
<plurals name="reblogs">
|
||||
<item quantity="one"><b>%s</b> Yinelenen</item>
|
||||
<item quantity="other"><b>%s</b> Yinelenenler</item>
|
||||
<item quantity="one"><b>%s</b> Yineleme</item>
|
||||
<item quantity="other"><b>%s</b> Yineleme</item>
|
||||
</plurals>
|
||||
<string name="title_reblogged_by">tarafından yinelendi</string>
|
||||
<string name="title_favourited_by">Tarafından favorilendi</string>
|
||||
<string name="title_favourited_by">favoriledi</string>
|
||||
<string name="conversation_1_recipients">%1$s</string>
|
||||
<string name="conversation_2_recipients">%1$s ve %2$s</string>
|
||||
<string name="conversation_more_recipients">%1$s, %2$s ve %3$d daha fazlası</string>
|
||||
<string name="max_tab_number_reached">%1$d maksimum sekme sayısına ulaşıldı</string>
|
||||
<string name="title_domain_mutes">Gizli alanadları</string>
|
||||
<string name="title_domain_mutes">Gizlenmiş alan adları</string>
|
||||
<string name="action_unreblog">Yinelemeyi kaldır</string>
|
||||
<string name="action_unfavourite">Favoriyi kaldır</string>
|
||||
<string name="action_view_domain_mutes">Gizli alan adları</string>
|
||||
<string name="action_view_domain_mutes">Gizlenmiş alan adları</string>
|
||||
<string name="action_mute_domain">%s alan adını sessize al</string>
|
||||
<string name="action_links">Bağlantılar</string>
|
||||
<string name="action_hashtags">Hashtag\'ler</string>
|
||||
|
@ -318,7 +316,7 @@
|
|||
<string name="downloading_media">Medya indiriliyor</string>
|
||||
|
||||
<string name="mute_domain_warning">%s alan adınından gelen her şeyi engellemek istediğinizden emin misiniz\? Bu alan adından gelen içeriği herhangi bir genel zaman çizelgesinde veya bildirimlerinizde göremezsiniz. Bu alan adındaki takipçileriniz de kaldırılacak.</string>
|
||||
<string name="pref_title_notification_filter_poll">Anket sona erince</string>
|
||||
<string name="pref_title_notification_filter_poll">anket sona erince</string>
|
||||
<string name="pref_title_timeline_filters">Filtreler</string>
|
||||
|
||||
<string name="app_theme_system">Sistem tasarımını kullan</string>
|
||||
|
@ -336,7 +334,7 @@
|
|||
<string name="filter_edit_dialog_title">Filtreyi düzenle</string>
|
||||
<string name="filter_dialog_remove_button">Kaldır</string>
|
||||
<string name="filter_dialog_update_button">Güncelle</string>
|
||||
<string name="filter_dialog_whole_word">Tüm dünya</string>
|
||||
<string name="filter_dialog_whole_word">Tüm kelime</string>
|
||||
<string name="filter_dialog_whole_word_description">Anahtar kelime veya kelime öbeği yalnızca alfasayısal olduğunda, yalnızca tüm sözcükle eşleşirse uygulanır</string>
|
||||
<string name="filter_add_description">Filtrelenecek ifade</string>
|
||||
|
||||
|
@ -347,7 +345,7 @@
|
|||
<string name="action_rename_list">Listeyi yeniden adlandır</string>
|
||||
<string name="action_delete_list">Listeyi sil</string>
|
||||
<string name="action_edit_list">Listeyi düzenle</string>
|
||||
<string name="hint_search_people_list">Takip ettiğiniz kişileri ara</string>
|
||||
<string name="hint_search_people_list">Takip ettiğim kişilerde ara</string>
|
||||
<string name="action_add_to_list">Listeye hesap ekle</string>
|
||||
<string name="action_remove_from_list">Hesabı listeden kaldır</string>
|
||||
|
||||
|
@ -376,7 +374,7 @@
|
|||
<string name="compose_shortcut_long_label">İleti Oluştur</string>
|
||||
<string name="compose_shortcut_short_label">Oluştur</string>
|
||||
|
||||
<string name="notification_clear_text">Tüm bildirimlerinizi kalıcı olarak silmek istediğinizden emin misiniz\?</string>
|
||||
<string name="notification_clear_text">Tüm bildirimleri kalıcı olarak silmek istediğinden emin misin\?</string>
|
||||
<string name="compose_preview_image_description">%s görüntüsü için eylemler</string>
|
||||
|
||||
<string name="poll_info_format"> <!-- 15 oy • 1 saat kaldı --> %1$s • %2$s</string>
|
||||
|
@ -388,8 +386,8 @@
|
|||
<string name="poll_info_closed">kapandı</string>
|
||||
<string name="poll_vote">Oy</string>
|
||||
|
||||
<string name="poll_ended_voted">Oy verdiğiniz bir anket sona erdi</string>
|
||||
<string name="poll_ended_created">Oluşturduğunuz bir anket sona erdi</string>
|
||||
<string name="poll_ended_voted">Oy verdiğin bir anket sona erdi</string>
|
||||
<string name="poll_ended_created">Oluşturduğun bir anket sona erdi</string>
|
||||
|
||||
<plurals name="poll_timespan_days">
|
||||
<item quantity="one">%d gün</item>
|
||||
|
@ -414,7 +412,7 @@
|
|||
<string name="report_sent_success">\@%s bildirildi</string>
|
||||
<string name="hint_additional_info">Ek yorumlar</string>
|
||||
<string name="report_remote_instance">%s adresine ilet</string>
|
||||
<string name="failed_fetch_statuses">Durumlar getirilemedi</string>
|
||||
<string name="failed_fetch_statuses">İletiler alınamadı</string>
|
||||
<string name="report_description_1">"Bildirim sunucu yöneticinize gönderilecektir. Bu hesabı neden bildirdiğinizle ilgili açıklama yapabilirsiniz:"</string>
|
||||
<string name="report_description_remote_instance">Hesap başka bir sunucuda. Raporun anonim bir kopyasını da oraya gönderilsin mi\?</string>
|
||||
|
||||
|
@ -430,7 +428,7 @@
|
|||
<string name="action_bookmark">Yerimi</string>
|
||||
<string name="action_edit">Düzenle</string>
|
||||
<string name="action_delete_and_redraft">Sil ve düzenle</string>
|
||||
<string name="action_view_bookmarks">Yer imleri</string>
|
||||
<string name="action_view_bookmarks">Yerimleri</string>
|
||||
<string name="action_add_poll">Anket ekle</string>
|
||||
<string name="action_access_scheduled_toot">Zamanlanmış iletiler</string>
|
||||
<string name="action_schedule_toot">İleti zamanla</string>
|
||||
|
@ -455,6 +453,21 @@
|
|||
<string name="add_poll_choice">Seçenek ekle</string>
|
||||
<string name="poll_allow_multiple_choices">Çoklu seçim</string>
|
||||
<string name="edit_poll">Düzenle</string>
|
||||
<string name="replying_to">\@%s olarak yanıtla</string>
|
||||
<string name="replying_to">Yanıtla @%s</string>
|
||||
<string name="profile_badge_bot_text">Bot</string>
|
||||
</resources>
|
||||
<string name="confirmation_domain_unmuted">%s gizleme</string>
|
||||
|
||||
<string name="mute_domain_warning_dialog_ok">Alan adından herşeyi gizle</string>
|
||||
|
||||
<string name="gradient_for_media">Gizli medya için renkli sansür uygula</string>
|
||||
|
||||
<string name="pref_title_alway_open_spoiler">Hassas içerikleri göster</string>
|
||||
<string name="poll_info_time_absolute">bitiş %s</string>
|
||||
<string name="failed_report">Bildirilemedi</string>
|
||||
<string name="poll_new_choice_hint">Seçenek %d</string>
|
||||
<string name="post_lookup_error_format">%s ileti aranırken hata oluştu</string>
|
||||
|
||||
<string name="no_saved_status">Hiç taslağınız yok.</string>
|
||||
<string name="no_scheduled_status">Hiç planlanmış durumun yok.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -286,8 +286,6 @@
|
|||
|
||||
<string name="state_follow_requested">已发送关注请求</string>
|
||||
|
||||
<string name="no_content">没有内容</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%d 年内</string>
|
||||
<string name="abbreviated_in_days">%d 天内</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="error_generic">應用程式出現異常</string>
|
||||
<string name="error_network">網絡請求出錯,請檢查互聯網連接並重試</string>
|
||||
|
@ -281,8 +281,6 @@
|
|||
|
||||
<string name="state_follow_requested">已請求關注</string>
|
||||
|
||||
<string name="no_content">沒有內容</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%d 年內</string>
|
||||
<string name="abbreviated_in_days">%d 天內</string>
|
||||
|
@ -385,11 +383,11 @@
|
|||
<string name="pin_action">置頂</string>
|
||||
|
||||
<plurals name="favs">
|
||||
<item quantity="other"><b>%1$s</b> 次收藏</item>
|
||||
<item quantity="other"><b>%1$s</b> 次收藏</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="reblogs">
|
||||
<item quantity="other"><b>%s</b> 次轉嘟</item>
|
||||
<item quantity="other"><b>%s</b> 次轉嘟</item>
|
||||
</plurals>
|
||||
|
||||
<string name="title_reblogged_by">轉嘟</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="error_generic">應用程式出現異常</string>
|
||||
<string name="error_network">網絡請求出錯,請檢查互聯網連接並重試</string>
|
||||
|
@ -281,8 +281,6 @@
|
|||
|
||||
<string name="state_follow_requested">已請求關注</string>
|
||||
|
||||
<string name="no_content">沒有內容</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%d 年內</string>
|
||||
<string name="abbreviated_in_days">%d 天內</string>
|
||||
|
@ -385,11 +383,11 @@
|
|||
<string name="pin_action">置頂</string>
|
||||
|
||||
<plurals name="favs">
|
||||
<item quantity="other"><b>%1$s</b> 次收藏</item>
|
||||
<item quantity="other"><b>%1$s</b> 次收藏</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="reblogs">
|
||||
<item quantity="other"><b>%s</b> 次轉嘟</item>
|
||||
<item quantity="other"><b>%s</b> 次轉嘟</item>
|
||||
</plurals>
|
||||
|
||||
<string name="title_reblogged_by">轉嘟</string>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<resources>
|
||||
<string name="error_generic">应用程序出现异常</string>
|
||||
<string name="error_network">网络请求出错,请检查互联网连接并重试</string>
|
||||
|
@ -286,8 +286,6 @@
|
|||
|
||||
<string name="state_follow_requested">已发送关注请求</string>
|
||||
|
||||
<string name="no_content">没有内容</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%d 年内</string>
|
||||
<string name="abbreviated_in_days">%d 天内</string>
|
||||
|
|
|
@ -280,8 +280,6 @@
|
|||
|
||||
<string name="state_follow_requested">已請求關注</string>
|
||||
|
||||
<string name="no_content">沒有內容</string>
|
||||
|
||||
<!--These are for timestamps on statuses. For example: "16s" or "2d"-->
|
||||
<string name="abbreviated_in_years">%d 年內</string>
|
||||
<string name="abbreviated_in_days">%d 天內</string>
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<string name="error_compose_character_limit">The status is too long!</string>
|
||||
<string name="error_image_upload_size">The file must be less than 8MB.</string>
|
||||
<string name="error_video_upload_size">Video files must be less than 40MB.</string>
|
||||
<string name="error_audio_upload_size">Audio files must be less than 40MB.</string>
|
||||
<string name="error_media_upload_type">That type of file cannot be uploaded.</string>
|
||||
<string name="error_media_upload_opening">That file could not be opened.</string>
|
||||
<string name="error_media_upload_permission">Permission to read media is required.</string>
|
||||
|
|
|
@ -198,6 +198,152 @@ class ComposeActivityTest {
|
|||
assertEquals(activity.calculateTextLength(), additionalContent.length + (ComposeActivity.MAXIMUM_URL_LENGTH * 2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionIsEmpty_specialTextIsInsertedAtCaret() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
editor.setText("Some text")
|
||||
|
||||
for (caretIndex in listOf(9, 1, 0)) {
|
||||
editor.setSelection(caretIndex)
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
// Text should be inserted at caret
|
||||
assertEquals("Unexpected value at ${caretIndex}", insertText, editor.text.substring(caretIndex, caretIndex + insertText.length))
|
||||
|
||||
// Caret should be placed after inserted text
|
||||
assertEquals(caretIndex + insertText.length, editor.selectionStart)
|
||||
assertEquals(caretIndex + insertText.length, editor.selectionEnd)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionDoesNotIncludeWordBreak_noSpecialTextIsInserted() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
val originalText = "Some text"
|
||||
val selectionStart = 1
|
||||
val selectionEnd = 4
|
||||
editor.setText(originalText)
|
||||
editor.setSelection(selectionStart, selectionEnd) // "ome"
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
|
||||
// Text and selection should be unmodified
|
||||
assertEquals(originalText, editor.text.toString())
|
||||
assertEquals(selectionStart, editor.selectionStart)
|
||||
assertEquals(selectionEnd, editor.selectionEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionIncludesWordBreaks_startsOfAllWordsArePrepended() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
val originalText = "one two three four"
|
||||
val selectionStart = 2
|
||||
val originalSelectionEnd = 15
|
||||
val modifiedSelectionEnd = 18
|
||||
editor.setText(originalText)
|
||||
editor.setSelection(selectionStart, originalSelectionEnd) // "e two three f"
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
|
||||
// text should be inserted at word starts inside selection
|
||||
assertEquals("one #two #three #four", editor.text.toString())
|
||||
|
||||
// selection should be expanded accordingly
|
||||
assertEquals(selectionStart, editor.selectionStart)
|
||||
assertEquals(modifiedSelectionEnd, editor.selectionEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionIncludesEnd_textIsNotAppended() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
val originalText = "Some text"
|
||||
val selectionStart = 7
|
||||
val selectionEnd = 9
|
||||
editor.setText(originalText)
|
||||
editor.setSelection(selectionStart, selectionEnd) // "xt"
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
|
||||
// Text and selection should be unmodified
|
||||
assertEquals(originalText, editor.text.toString())
|
||||
assertEquals(selectionStart, editor.selectionStart)
|
||||
assertEquals(selectionEnd, editor.selectionEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionIncludesStartAndStartIsAWord_textIsPrepended() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
val originalText = "Some text"
|
||||
val selectionStart = 0
|
||||
val selectionEnd = 3
|
||||
editor.setText(originalText)
|
||||
editor.setSelection(selectionStart, selectionEnd) // "Som"
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
|
||||
// Text should be inserted at beginning
|
||||
assert(editor.text.startsWith(insertText))
|
||||
|
||||
// selection should be expanded accordingly
|
||||
assertEquals(selectionStart, editor.selectionStart)
|
||||
assertEquals(selectionEnd + insertText.length, editor.selectionEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionIncludesStartAndStartIsNotAWord_textIsNotPrepended() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
val originalText = " Some text"
|
||||
val selectionStart = 0
|
||||
val selectionEnd = 1
|
||||
editor.setText(originalText)
|
||||
editor.setSelection(selectionStart, selectionEnd) // " "
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
|
||||
// Text and selection should be unmodified
|
||||
assertEquals(originalText, editor.text.toString())
|
||||
assertEquals(selectionStart, editor.selectionStart)
|
||||
assertEquals(selectionEnd, editor.selectionEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionBeginsAtWordStart_textIsPrepended() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
val originalText = "Some text"
|
||||
val selectionStart = 5
|
||||
val selectionEnd = 9
|
||||
editor.setText(originalText)
|
||||
editor.setSelection(selectionStart, selectionEnd) // "text"
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
|
||||
// Text is prepended
|
||||
assertEquals("Some #text", editor.text.toString())
|
||||
|
||||
// Selection is expanded accordingly
|
||||
assertEquals(selectionStart, editor.selectionStart)
|
||||
assertEquals(selectionEnd + insertText.length, editor.selectionEnd)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun whenSelectionEndsAtWordStart_textIsAppended() {
|
||||
val editor = activity.findViewById<EditText>(R.id.composeEditField)
|
||||
val insertText = "#"
|
||||
val originalText = "Some text"
|
||||
val selectionStart = 1
|
||||
val selectionEnd = 5
|
||||
editor.setText(originalText)
|
||||
editor.setSelection(selectionStart, selectionEnd) // "ome "
|
||||
activity.prependSelectedWordsWith(insertText)
|
||||
|
||||
// Text is prepended
|
||||
assertEquals("Some #text", editor.text.toString())
|
||||
|
||||
// Selection is expanded accordingly
|
||||
assertEquals(selectionStart, editor.selectionStart)
|
||||
assertEquals(selectionEnd + insertText.length, editor.selectionEnd)
|
||||
}
|
||||
|
||||
private fun clickUp() {
|
||||
val menuItem = RoboMenuItem(android.R.id.home)
|
||||
activity.onOptionsItemSelected(menuItem)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
Tusky v9.0
|
||||
|
||||
- Vous pouvez désormais créer des sondages depuis Tusky
|
||||
- Vous pouvez créer des sondages depuis Tusky
|
||||
- Recherche améliorée
|
||||
- Nouvelle option dans les préférences du compte pour toujours étendre les avertissements de contenu
|
||||
- Les avatars dans le menu de navigation ont désormais une forme de
|
||||
- Il est désormais possible de signaler des utilisateurs même si ils n'ont jamais posté de status
|
||||
- Tusky refuse désormais de se connecté via les connections cleartext sur Android 6+
|
||||
- Plein de petite corrections de bugs et d'améliorations
|
||||
- Les avatars dans le menu de navigation ont désormais une forme arrondie
|
||||
- Il est désormais possible de signaler des utilisateurs même si ils n’ont jamais publié de status
|
||||
- Tusky refusera désormais de se connecter en texte clair sur Android 6+
|
||||
- Plein de petite corrections de bugs et d’améliorations
|
||||
|
|
|
@ -1 +1 @@
|
|||
En klient med stöd för flera konton på det sociala nätverket Mastodon
|
||||
En klient med stöd för flera konton för det sociala nätverket Mastodon
|
||||
|
|
Loading…
Reference in New Issue