1
0
mirror of https://github.com/TwidereProject/Twidere-Android synced 2025-02-17 04:00:48 +01:00

supports save media for ExoPlayerPageFragment

This commit is contained in:
Mariotaku Lee 2017-03-03 22:56:29 +08:00
parent 993e95b08c
commit 9e615969f5
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
63 changed files with 422 additions and 310 deletions

View File

@ -802,13 +802,6 @@ public interface TwidereDataStore {
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
}
interface ByFriends extends Activities {
String CONTENT_PATH = "activities_by_friends";
String TABLE_NAME = "activities_by_friends";
Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, CONTENT_PATH);
}
}
interface Tabs extends BaseColumns {

View File

@ -31,4 +31,14 @@ fun String?.toDouble(def: Double): Double {
fun Int.coerceInOr(range: ClosedRange<Int>, or: Int): Int {
if (range.isEmpty()) return or
return coerceIn(range)
}
}
/**
* Convenience method checking int flags
*/
operator fun Int.contains(i: Int): Boolean = (this and i) == i
/**
* Convenience method checking long flags
*/
operator fun Long.contains(i: Long): Boolean = (this and i) == i

View File

@ -68,6 +68,7 @@ import org.mariotaku.kpreferences.get
import org.mariotaku.kpreferences.set
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.coerceInOr
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
import org.mariotaku.twidere.Constants.*
import org.mariotaku.twidere.R
@ -221,7 +222,7 @@ class HomeActivity : BaseActivity(), OnClickListener, OnPageChangeListener, Supp
tabDisplayOptionInt = TabPagerIndicator.DisplayOption.ICON
}
mainTabs.setTabDisplayOption(tabDisplayOptionInt)
mainTabs.setTabExpandEnabled(tabDisplayOptionInt and TabPagerIndicator.DisplayOption.LABEL == 0)
mainTabs.setTabExpandEnabled(TabPagerIndicator.DisplayOption.LABEL !in tabDisplayOptionInt)
mainTabs.setDisplayBadge(preferences[unreadCountKey])
mainTabs.updateAppearance()

View File

@ -24,10 +24,10 @@ import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
import android.support.v4.app.DialogFragment
import android.support.v4.app.Fragment
import android.support.v4.app.hasRunningLoadersSafe
import android.support.v4.content.ContextCompat
import android.support.v4.graphics.ColorUtils
import android.support.v4.view.ViewPager
@ -429,18 +429,10 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
if (shareMediaPosition == -1) return
val viewPager = findViewPager()
val adapter = viewPager.adapter
val f = adapter.instantiateItem(viewPager, shareMediaPosition) as? CacheDownloadMediaViewerFragment ?: return
val cacheUri = f.downloadResult?.cacheUri ?: return
val f = adapter.instantiateItem(viewPager, shareMediaPosition) as? MediaViewerFragment ?: return
val fileInfo = f.cacheFileInfo() ?: return
val destination = ShareProvider.getFilesDir(this) ?: return
val type: String
when (f) {
is VideoPageFragment -> type = CacheFileType.VIDEO
is ImagePageFragment -> type = CacheFileType.IMAGE
is GifPageFragment -> type = CacheFileType.IMAGE
else -> throw UnsupportedOperationException("Unsupported fragment $f")
}
val task = object : SaveFileTask(this@MediaViewerActivity, cacheUri, destination,
CacheProvider.CacheFileTypeCallback(this@MediaViewerActivity, type)) {
val task = object : SaveFileTask(this@MediaViewerActivity, destination, fileInfo) {
private val PROGRESS_FRAGMENT_TAG = "progress"
override fun dismissProgress() {
@ -491,17 +483,44 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos
if (saveToStoragePosition == -1) return
val viewPager = findViewPager()
val adapter = viewPager.adapter
val f = adapter.instantiateItem(viewPager, saveToStoragePosition) as? CacheDownloadMediaViewerFragment ?: return
val cacheUri = f.downloadResult?.cacheUri ?: return
val task: SaveFileTask = when (f) {
is ImagePageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.IMAGE)
is VideoPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.VIDEO)
is GifPageFragment -> SaveMediaToGalleryTask.create(this, cacheUri, CacheFileType.IMAGE)
else -> throw UnsupportedOperationException()
val f = adapter.instantiateItem(viewPager, saveToStoragePosition) as? MediaViewerFragment ?: return
val fileInfo = f.cacheFileInfo() ?: return
val type = (fileInfo as? CacheProvider.CacheFileTypeSupport)?.cacheFileType
val pubDir = when (type) {
CacheFileType.VIDEO -> {
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
}
CacheFileType.IMAGE -> {
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
}
else -> {
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
}
}
val saveDir = File(pubDir, "Twidere")
val task = SaveMediaToGalleryTask(this, fileInfo, saveDir)
AsyncTaskUtils.executeTask(task)
}
private fun MediaViewerFragment.cacheFileInfo(): SaveFileTask.FileInfo? {
return when (this) {
is CacheDownloadMediaViewerFragment -> {
val cacheUri = downloadResult?.cacheUri ?: return null
val type = when (this) {
is ImagePageFragment -> CacheFileType.IMAGE
is VideoPageFragment -> CacheFileType.VIDEO
is GifPageFragment -> CacheFileType.IMAGE
else -> return null
}
CacheProvider.ContentUriFileInfo(activity, cacheUri, type)
}
is ExoPlayerPageFragment -> {
return getRequestFileInfo()
}
else -> return null
}
}
companion object {
private val REQUEST_SHARE_MEDIA = 201

View File

@ -31,6 +31,7 @@ import android.widget.TextView
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.layout_list_with_empty_view.*
import org.mariotaku.ktextension.Bundle
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.set
import org.mariotaku.twidere.R
import org.mariotaku.twidere.TwidereConstants.REQUEST_SELECT_USER
@ -175,7 +176,7 @@ class UserListSelectorActivity : BaseActivity(),
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
val accountKey = this.accountKey ?: return
val userKey = this.userKey ?: return
if (refreshing || position and adapter.loadMoreSupportedPosition == 0L) {
if (refreshing || position !in adapter.loadMoreSupportedPosition) {
return
}
adapter.loadMoreIndicatorPosition = position

View File

@ -28,6 +28,7 @@ import android.view.ViewGroup
import android.widget.TextView
import com.bumptech.glide.RequestManager
import org.apache.commons.lang3.ArrayUtils
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.ktextension.safeMoveToPosition
import org.mariotaku.library.objectcursor.ObjectCursor
@ -243,7 +244,7 @@ class ParcelableActivitiesAdapter(
}
override fun getItemViewType(position: Int): Int {
if (loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START != 0L && position == 0) {
if (position == 0 && ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) {
return ITEM_VIEW_TYPE_LOAD_INDICATOR
} else if (position == activityCount) {
return ITEM_VIEW_TYPE_LOAD_INDICATOR

View File

@ -26,6 +26,7 @@ import android.view.View
import android.view.ViewGroup
import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.IGroupsAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
@ -119,7 +120,7 @@ class ParcelableGroupsAdapter(
}
override fun getItemViewType(position: Int): Int {
if (loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START != 0L && position == 0) {
if (position == 0 && ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) {
return ITEM_VIEW_TYPE_LOAD_INDICATOR
}
if (position == groupsCount) {

View File

@ -27,6 +27,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.findPositionByItemId
import org.mariotaku.ktextension.rangeOfSize
import org.mariotaku.ktextension.safeMoveToPosition
@ -319,7 +320,7 @@ abstract class ParcelableStatusesAdapter(
}
override fun getItemViewType(position: Int): Int {
if (loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START != 0L && position == 0) {
if (position == 0 && ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) {
return ITEM_VIEW_TYPE_LOAD_INDICATOR
}
when (getItemCountIndex(position)) {

View File

@ -25,6 +25,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import com.bumptech.glide.RequestManager
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.Companion.ITEM_VIEW_TYPE_LOAD_INDICATOR
@ -110,7 +111,7 @@ class ParcelableUserListsAdapter(
}
override fun getItemViewType(position: Int): Int {
if (loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START != 0L && position == 0) {
if (position == 0 && ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) {
return ITEM_VIEW_TYPE_LOAD_INDICATOR
}
if (position == userListsCount) {

View File

@ -24,6 +24,7 @@ import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import com.bumptech.glide.RequestManager
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.R
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.Companion.ITEM_VIEW_TYPE_LOAD_INDICATOR
@ -150,7 +151,7 @@ class ParcelableUsersAdapter(
}
override fun getItemViewType(position: Int): Int {
if (loadMoreIndicatorPosition and ILoadMoreSupportAdapter.START != 0L && position == 0) {
if (position == 0 && ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition) {
return ITEM_VIEW_TYPE_LOAD_INDICATOR
}
if (position == userCount) {

View File

@ -26,11 +26,11 @@ import android.support.annotation.IntDef
*/
interface ILoadMoreSupportAdapter {
@IndicatorPosition
var loadMoreIndicatorPosition: Long
@IndicatorPosition get @IndicatorPosition set
@IndicatorPosition
var loadMoreSupportedPosition: Long
@IndicatorPosition get @IndicatorPosition set
@IntDef(flag = true, value = *longArrayOf(NONE, START, END, BOTH))
annotation class IndicatorPosition
@ -48,9 +48,5 @@ interface ILoadMoreSupportAdapter {
return orig and supported
}
@IndicatorPosition
fun has(@IndicatorPosition flags: Long, @IndicatorPosition compare: Long): Boolean {
return flags and compare != 0L
}
}
}

View File

@ -165,13 +165,13 @@ internal object AccountDataQueue {
fun getUserData(manager: AccountManager, account: Account, key: String): String? {
val future = FutureTask<String?> { manager.getUserData(account, key) }
if (Thread.currentThread() == Looper.getMainLooper().thread) {
if (Thread.currentThread() === Looper.getMainLooper().thread) {
future.run()
} else handler.post {
future.run()
}
try {
return future.get(5, TimeUnit.SECONDS)
return future.get(1, TimeUnit.SECONDS)
} catch (e: TimeoutException) {
return manager.getUserData(account, key)
}
@ -179,13 +179,13 @@ internal object AccountDataQueue {
fun peekAuthToken(manager: AccountManager, account: Account, authTokenType: String): String? {
val future = FutureTask<String?> { manager.peekAuthToken(account, authTokenType) }
if (Thread.currentThread() == Looper.getMainLooper().thread) {
if (Thread.currentThread() === Looper.getMainLooper().thread) {
future.run()
} else handler.post {
future.run()
}
try {
return future.get(5, TimeUnit.SECONDS)
return future.get(1, TimeUnit.SECONDS)
} catch (e: TimeoutException) {
return manager.peekAuthToken(account, authTokenType)
}

View File

@ -23,6 +23,7 @@ import android.content.Context
import android.support.v7.widget.FixedLinearLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.adapter.LoadMoreSupportAdapter
import org.mariotaku.twidere.adapter.decorator.DividerItemDecoration
@ -36,17 +37,16 @@ import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter.IndicatorPosi
abstract class AbsContentListRecyclerViewFragment<A : LoadMoreSupportAdapter<RecyclerView.ViewHolder>>
: AbsContentRecyclerViewFragment<A, LinearLayoutManager>() {
override fun createItemDecoration(context: Context,
recyclerView: RecyclerView,
layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
override fun createItemDecoration(context: Context, recyclerView: RecyclerView,
layoutManager: LinearLayoutManager): RecyclerView.ItemDecoration? {
return DividerItemDecoration(context, layoutManager.orientation)
}
override fun setLoadMoreIndicatorPosition(@IndicatorPosition position: Long) {
val decor = itemDecoration
if (decor is DividerItemDecoration) {
decor.setDecorationStart(if (position and ILoadMoreSupportAdapter.START != 0L) 1 else 0)
decor.setDecorationEndOffset(if (position and ILoadMoreSupportAdapter.END != 0L) 1 else 0)
decor.setDecorationStart(if (ILoadMoreSupportAdapter.START in position) 1 else 0)
decor.setDecorationEndOffset(if (ILoadMoreSupportAdapter.END in position) 1 else 0)
}
super.setLoadMoreIndicatorPosition(position)
}

View File

@ -31,6 +31,7 @@ import android.support.v4.content.Loader
import android.widget.Toast
import com.squareup.otto.Subscribe
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.toNulls
import org.mariotaku.library.objectcursor.ObjectCursor
@ -140,7 +141,7 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
// Only supports load from end, skip START flag
if (position and ILoadMoreSupportAdapter.START != 0L || refreshing) return
if (ILoadMoreSupportAdapter.START in position || refreshing) return
super.onLoadMoreContents(position)
if (position == 0L) return
getActivities(object : SimpleRefreshTaskParam() {
@ -337,8 +338,8 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
}
class CursorActivitiesLoader(context: Context, uri: Uri, projection: Array<String>,
selection: String, selectionArgs: Array<String>,
sortOrder: String, fromUser: Boolean) : ExtendedObjectCursorLoader<ParcelableActivity>(context, ParcelableActivityCursorIndices::class.java, uri, projection, selection, selectionArgs, sortOrder, fromUser) {
selection: String, selectionArgs: Array<String>,
sortOrder: String, fromUser: Boolean) : ExtendedObjectCursorLoader<ParcelableActivity>(context, ParcelableActivityCursorIndices::class.java, uri, projection, selection, selectionArgs, sortOrder, fromUser) {
override fun createObjectCursor(cursor: Cursor, indices: ObjectCursor.CursorIndices<ParcelableActivity>): ObjectCursor<ParcelableActivity> {
val filteredUserIds = DataStoreUtils.getFilteredUserIds(context)
@ -346,6 +347,6 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() {
}
class ActivityCursor(cursor: Cursor, indies: ObjectCursor.CursorIndices<ParcelableActivity>,
val filteredUserIds: Array<UserKey>) : ObjectCursor<ParcelableActivity>(cursor, indies)
val filteredUserIds: Array<UserKey>) : ObjectCursor<ParcelableActivity>(cursor, indies)
}
}

View File

@ -32,6 +32,7 @@ import com.bumptech.glide.Glide
import com.squareup.otto.Subscribe
import kotlinx.android.synthetic.main.fragment_content_recyclerview.*
import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe
import org.mariotaku.ktextension.toNulls
import org.mariotaku.sqliteqb.library.Columns.Column
@ -174,7 +175,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
// Only supports load from end, skip START flag
if (position and ILoadMoreSupportAdapter.START != 0L) return
if (ILoadMoreSupportAdapter.START in position) return
super.onLoadMoreContents(position)
if (position == 0L) return
getStatuses(object : SimpleRefreshTaskParam() {

View File

@ -45,6 +45,7 @@ import kotlinx.android.synthetic.main.layout_draggable_list_with_empty_view.*
import kotlinx.android.synthetic.main.list_item_section_header.view.*
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.ktextension.Bundle
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.set
import org.mariotaku.sqliteqb.library.Columns.Column
import org.mariotaku.sqliteqb.library.Expression
@ -353,8 +354,13 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks<Cursor?>, MultiChoice
positiveButton.setOnClickListener {
tab.name = tabName.text.toString()
tab.icon = (iconSpinner.selectedItem as DrawableHolder).persistentKey
tab.arguments = CustomTabUtils.newTabArguments(tabType)
if (hasAccount) {
if (tab.arguments == null) {
tab.arguments = CustomTabUtils.newTabArguments(tabType)
}
if (tab.extras == null) {
tab.extras = CustomTabUtils.newTabExtras(tabType)
}
if (hasAccount && (!editMode || TabConfiguration.FLAG_ACCOUNT_MUTABLE in conf.accountFlags)) {
val account = accountSpinner.selectedItem as? AccountDetails ?: return@setOnClickListener
if (!account.dummy) {
tab.arguments?.accountKeys = arrayOf(account.key)
@ -362,7 +368,6 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks<Cursor?>, MultiChoice
tab.arguments?.accountKeys = null
}
}
tab.extras = CustomTabUtils.newTabExtras(tabType)
extraConfigurations.forEach { extraConf ->
// Make sure immutable configuration skipped in edit mode
if (editMode && !extraConf.isMutable) return@forEach
@ -524,3 +529,4 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks<Cursor?>, MultiChoice
}
}

View File

@ -70,6 +70,7 @@ import kotlinx.android.synthetic.main.header_status_common.view.*
import kotlinx.android.synthetic.main.layout_content_fragment_common.*
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.applyFontFamily
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.findPositionByItemId
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.microblog.library.twitter.model.Paging
@ -1891,7 +1892,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
}
var isConversationsLoading: Boolean
get() = ILoadMoreSupportAdapter.has(loadMoreIndicatorPosition, ILoadMoreSupportAdapter.START)
get() = ILoadMoreSupportAdapter.START in loadMoreIndicatorPosition
set(loading) {
if (loading) {
loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.START
@ -1902,7 +1903,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
}
var isRepliesLoading: Boolean
get() = ILoadMoreSupportAdapter.has(loadMoreIndicatorPosition, ILoadMoreSupportAdapter.END)
get() = ILoadMoreSupportAdapter.END in loadMoreIndicatorPosition
set(loading) {
if (loading) {
loadMoreIndicatorPosition = loadMoreIndicatorPosition or ILoadMoreSupportAdapter.END
@ -1913,10 +1914,9 @@ class StatusFragment : BaseFragment(), LoaderCallbacks<SingleResponse<Parcelable
}
class StatusErrorItemViewHolder(itemView: View) : ViewHolder(itemView) {
private val textView: TextView
private val textView = itemView.findViewById(android.R.id.text1) as TextView
init {
textView = itemView.findViewById(android.R.id.text1) as TextView
textView.movementMethod = LinkMovementMethod.getInstance()
textView.linksClickable = true
}

View File

@ -9,6 +9,7 @@ import android.support.v7.widget.RecyclerView
import android.support.v7.widget.StaggeredGridLayoutManager
import android.text.TextUtils
import com.bumptech.glide.Glide
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.adapter.StaggeredGridParcelableStatusesAdapter
import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter
import org.mariotaku.twidere.constant.IntentConstants.*
@ -119,7 +120,7 @@ class UserMediaTimelineFragment : AbsContentRecyclerViewFragment<StaggeredGridPa
override fun onLoadMoreContents(position: Long) {
// Only supports load from end, skip START flag
if (position and ILoadMoreSupportAdapter.START != 0L) return
if (ILoadMoreSupportAdapter.START in position) return
super.onLoadMoreContents(position)
if (position == 0L) return
val maxId = adapter.getStatusId(adapter.statusCount - 1)

View File

@ -156,7 +156,7 @@ abstract class BaseFiltersImportFragment : AbsContentListRecyclerViewFragment<Se
override fun onLoadMoreContents(@IndicatorPosition position: Long) {
// Only supports load from end, skip START flag
if (position and ILoadMoreSupportAdapter.START != 0L) return
if (ILoadMoreSupportAdapter.START in position) return
super.onLoadMoreContents(position)
if (position == 0L) return
val loaderArgs = Bundle(arguments)

View File

@ -44,6 +44,9 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter
import com.google.android.exoplayer2.upstream.HttpDataSource
import kotlinx.android.synthetic.main.layout_media_viewer_exo_player_view.*
import kotlinx.android.synthetic.main.layout_media_viewer_video_overlay.*
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.mariotaku.mediaviewer.library.MediaViewerFragment
import org.mariotaku.mediaviewer.library.subsampleimageview.SubsampleImageViewerFragment
import org.mariotaku.restfu.RestRequest
@ -52,6 +55,7 @@ import org.mariotaku.restfu.http.MultiValueMap
import org.mariotaku.restfu.oauth.OAuthAuthorization
import org.mariotaku.restfu.oauth.OAuthEndpoint
import org.mariotaku.twidere.R
import org.mariotaku.twidere.annotation.CacheFileType
import org.mariotaku.twidere.constant.IntentConstants.EXTRA_POSITION
import org.mariotaku.twidere.extension.model.getAuthorization
import org.mariotaku.twidere.fragment.iface.IBaseFragment
@ -63,11 +67,14 @@ import org.mariotaku.twidere.fragment.media.VideoPageFragment.Companion.isContro
import org.mariotaku.twidere.fragment.media.VideoPageFragment.Companion.isLoopEnabled
import org.mariotaku.twidere.fragment.media.VideoPageFragment.Companion.isMutedByDefault
import org.mariotaku.twidere.fragment.media.VideoPageFragment.Companion.media
import org.mariotaku.twidere.model.AccountDetails
import org.mariotaku.twidere.model.ParcelableMedia
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.AccountUtils
import org.mariotaku.twidere.provider.CacheProvider
import org.mariotaku.twidere.task.SaveFileTask
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import org.mariotaku.twidere.util.media.TwidereMediaDownloader
import java.io.InputStream
import javax.inject.Inject
@ -84,12 +91,20 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
@Inject
internal lateinit var extractorsFactory: ExtractorsFactory
@Inject
internal lateinit var okHttpClient: OkHttpClient
private lateinit var mainHandler: Handler
private var playAudio: Boolean = false
private var pausedByUser: Boolean = false
private var playbackCompleted: Boolean = false
private var positionBackup: Long = -1L
private var playerHasError: Boolean = false
private val account by lazy {
AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true)
}
private val playerListener = object : ExoPlayer.EventListener {
override fun onLoadingChanged(isLoading: Boolean) {
@ -97,7 +112,8 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
}
override fun onPlayerError(error: ExoPlaybackException) {
playerHasError = true
hideProgress()
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
@ -119,6 +135,7 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
}
ExoPlayer.STATE_READY -> {
playbackCompleted = playWhenReady
playerHasError = false
hideProgress()
}
ExoPlayer.STATE_IDLE -> {
@ -235,13 +252,11 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
}
override fun isMediaLoaded(): Boolean {
val player = playerView.player ?: return false
return player.playbackState != ExoPlayer.STATE_IDLE
return !playerHasError
}
override fun isMediaLoading(): Boolean {
val player = playerView.player ?: return false
return player.isLoading
return false
}
private fun releasePlayer() {
@ -264,13 +279,13 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
player.seekTo(positionBackup)
}
player.playWhenReady = !pausedByUser
playerHasError = false
player.addListener(playerListener)
return@run player
}
val uri = media?.getDownloadUri() ?: return
val am = AccountManager.get(context)
val factory = AuthDelegatingDataSourceFactory(uri, accountKey, am, dataSourceFactory)
val factory = AuthDelegatingDataSourceFactory(uri, account, dataSourceFactory)
val uriSource = ExtractorMediaSource(uri, factory, extractorsFactory, null, null)
if (isLoopEnabled) {
playerView.player.prepare(LoopingMediaSource(uriSource))
@ -290,7 +305,7 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
}
}
fun ParcelableMedia.getDownloadUri(): Uri? {
private fun ParcelableMedia.getDownloadUri(): Uri? {
val bestVideoUrlAndType = VideoPageFragment.getBestVideoUrlAndType(this, SUPPORTED_VIDEO_TYPES)
if (bestVideoUrlAndType != null && bestVideoUrlAndType.first != null) {
return Uri.parse(bestVideoUrlAndType.first)
@ -298,12 +313,18 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
return arguments.getParcelable<Uri>(SubsampleImageViewerFragment.EXTRA_MEDIA_URI)
}
fun getRequestFileInfo(): RequestFileInfo? {
val uri = media?.getDownloadUri() ?: return null
return RequestFileInfo(uri, account, okHttpClient)
}
class AuthDelegatingDataSourceFactory(
val uri: Uri,
val accountKey: UserKey,
val am: AccountManager,
val account: AccountDetails?,
val delegate: DataSource.Factory
) : DataSource.Factory {
override fun createDataSource(): DataSource {
val source = delegate.createDataSource()
if (source is HttpDataSource) {
@ -313,28 +334,75 @@ class ExoPlayerPageFragment : MediaViewerFragment(), IBaseFragment<ExoPlayerPage
}
private fun setAuthorizationHeader(dataSource: HttpDataSource) {
val account = AccountUtils.getAccountDetails(am, accountKey, true) ?: return
val modifiedUri = TwidereMediaDownloader.getReplacedUri(uri, account.credentials.api_url_format) ?: uri
if (TwidereMediaDownloader.isAuthRequired(account, uri)) {
val auth = account.credentials.getAuthorization()
val endpoint: Endpoint
if (auth is OAuthAuthorization) {
endpoint = OAuthEndpoint(TwidereMediaDownloader.getEndpoint(modifiedUri),
TwidereMediaDownloader.getEndpoint(uri))
} else {
endpoint = Endpoint(TwidereMediaDownloader.getEndpoint(modifiedUri))
}
val queries = MultiValueMap<String>()
for (name in uri.queryParameterNames) {
for (value in uri.getQueryParameters(name)) {
queries.add(name, value)
}
}
val info = RestRequest("GET", false, uri.path, null, queries, null, null, null, null)
dataSource.setRequestProperty("Authorization", auth.getHeader(endpoint, info))
}
val authorizationHeader = account?.authorizationHeader(uri) ?: return
dataSource.setRequestProperty("Authorization", authorizationHeader)
}
}
class RequestFileInfo(
val uri: Uri,
val account: AccountDetails?,
val okHttpClient: OkHttpClient
) : SaveFileTask.FileInfo, CacheProvider.CacheFileTypeSupport {
private var response: Response? = null
override val cacheFileType: String? = CacheFileType.VIDEO
override val fileName: String? = uri.lastPathSegment
override val mimeType: String?
get() = request().body()?.contentType()?.toString()
override val specialCharacter: Char = '_'
override fun inputStream(): InputStream {
return request().body().byteStream()
}
override fun close() {
response?.close()
}
private fun request(): Response {
if (response != null) return response!!
val builder = Request.Builder()
builder.url(uri.toString())
val authHeader = account?.authorizationHeader(uri)
if (authHeader != null) {
builder.addHeader("Authorization", authHeader)
}
response = okHttpClient.newCall(builder.build()).execute()
return response!!
}
}
companion object {
internal fun AccountDetails.authorizationHeader(uri: Uri): String? {
val modifiedUri = TwidereMediaDownloader.getReplacedUri(uri, credentials.api_url_format) ?: uri
if (!TwidereMediaDownloader.isAuthRequired(this, uri)) {
return null
}
val auth = credentials.getAuthorization()
val endpoint: Endpoint
if (auth is OAuthAuthorization) {
endpoint = OAuthEndpoint(TwidereMediaDownloader.getEndpoint(modifiedUri),
TwidereMediaDownloader.getEndpoint(uri))
} else {
endpoint = Endpoint(TwidereMediaDownloader.getEndpoint(modifiedUri))
}
val queries = MultiValueMap<String>()
for (name in uri.queryParameterNames) {
for (value in uri.getQueryParameters(name)) {
queries.add(name, value)
}
}
val info = RestRequest("GET", false, uri.path, null, queries, null, null, null, null)
return auth.getHeader(endpoint, info)
}
}
}

View File

@ -48,6 +48,7 @@ import org.mariotaku.abstask.library.TaskStarter
import org.mariotaku.chameleon.Chameleon
import org.mariotaku.chameleon.ChameleonUtils
import org.mariotaku.kpreferences.get
import org.mariotaku.ktextension.contains
import org.mariotaku.ktextension.empty
import org.mariotaku.ktextension.set
import org.mariotaku.pickncrop.library.MediaPickerActivity
@ -283,7 +284,7 @@ class MessagesConversationFragment : AbsContentListRecyclerViewFragment<Messages
}
override fun onLoadMoreContents(position: Long) {
if (position and ILoadMoreSupportAdapter.START == 0L) return
if (ILoadMoreSupportAdapter.START !in position) return
val message = adapter.getMessage(adapter.messageRange.endInclusive) ?: return
setLoadMoreIndicatorPosition(position)
val param = GetMessagesTask.LoadMoreMessageTaskParam(context, accountKey, conversationId,

View File

@ -47,7 +47,6 @@ class ClearDatabasesPreference(
resolver.delete(uri, null, null)
}
resolver.delete(Activities.AboutMe.CONTENT_URI, null, null)
resolver.delete(Activities.ByFriends.CONTENT_URI, null, null)
resolver.delete(SavedSearches.CONTENT_URI, null, null)
// TODO clear all notifications

View File

@ -7,11 +7,11 @@ import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import android.webkit.MimeTypeMap
import okio.ByteString
import org.mariotaku.commons.logansquare.LoganSquareMapperFinder
import org.mariotaku.mediaviewer.library.FileCache
import org.mariotaku.twidere.TwidereConstants
import org.mariotaku.twidere.TwidereConstants.AUTHORITY_TWIDERE_CACHE
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_TYPE
import org.mariotaku.twidere.annotation.CacheFileType
import org.mariotaku.twidere.model.CacheMetadata
import org.mariotaku.twidere.task.SaveFileTask
@ -20,7 +20,7 @@ import org.mariotaku.twidere.util.dagger.GeneralComponentHelper
import java.io.ByteArrayInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
import java.io.InputStream
import javax.inject.Inject
/**
@ -45,7 +45,7 @@ class CacheProvider : ContentProvider() {
if (metadata != null) {
return metadata.contentType
}
val type = uri.getQueryParameter(TwidereConstants.QUERY_PARAM_TYPE)
val type = uri.getQueryParameter(QUERY_PARAM_TYPE)
when (type) {
CacheFileType.IMAGE -> {
val file = fileCache.get(getCacheKey(uri)) ?: return null
@ -95,37 +95,43 @@ class CacheProvider : ContentProvider() {
}
class CacheFileTypeCallback(private val context: Context, @CacheFileType private val type: String?) : SaveFileTask.FileInfoCallback {
override fun getFilename(source: Uri): String {
var cacheKey = getCacheKey(source)
class ContentUriFileInfo(
private val context: Context,
private val uri: Uri,
@CacheFileType override val cacheFileType: String?
) : SaveFileTask.FileInfo, CacheFileTypeSupport {
override val fileName: String by lazy {
var cacheKey = getCacheKey(uri)
val indexOfSsp = cacheKey.indexOf("://")
if (indexOfSsp != -1) {
cacheKey = cacheKey.substring(indexOfSsp + 3)
}
return cacheKey.replace("[^\\w\\d_]".toRegex(), specialCharacter.toString())
return@lazy cacheKey.replace("[^\\w\\d_]".toRegex(), specialCharacter.toString())
}
override fun getMimeType(source: Uri): String? {
if (type == null || source.getQueryParameter(TwidereConstants.QUERY_PARAM_TYPE) != null) {
return context.contentResolver.getType(source)
}
val builder = source.buildUpon()
builder.appendQueryParameter(TwidereConstants.QUERY_PARAM_TYPE, type)
return context.contentResolver.getType(builder.build())
}
override fun getExtension(mimeType: String?): String? {
val typeLowered = mimeType?.toLowerCase(Locale.US) ?: return null
return when (typeLowered) {
// Hack for fanfou image type
"image/jpg" -> "jpg"
else -> MimeTypeMap.getSingleton().getExtensionFromMimeType(typeLowered)
override val mimeType: String? by lazy {
if (cacheFileType == null || uri.getQueryParameter(QUERY_PARAM_TYPE) != null) {
return@lazy context.contentResolver.getType(uri)
}
val builder = uri.buildUpon()
builder.appendQueryParameter(QUERY_PARAM_TYPE, cacheFileType)
return@lazy context.contentResolver.getType(builder.build())
}
override val specialCharacter: Char
get() = '_'
override fun inputStream(): InputStream {
return context.contentResolver.openInputStream(uri)
}
override fun close() {
// No-op
}
}
interface CacheFileTypeSupport {
val cacheFileType: String?
}
@ -134,10 +140,10 @@ class CacheProvider : ContentProvider() {
fun getCacheUri(key: String, @CacheFileType type: String?): Uri {
val builder = Uri.Builder()
builder.scheme(ContentResolver.SCHEME_CONTENT)
builder.authority(TwidereConstants.AUTHORITY_TWIDERE_CACHE)
builder.authority(AUTHORITY_TWIDERE_CACHE)
builder.appendPath(ByteString.encodeUtf8(key).base64Url())
if (type != null) {
builder.appendQueryParameter(TwidereConstants.QUERY_PARAM_TYPE, type)
builder.appendQueryParameter(QUERY_PARAM_TYPE, type)
}
return builder.build()
}
@ -145,7 +151,7 @@ class CacheProvider : ContentProvider() {
fun getCacheKey(uri: Uri): String {
if (ContentResolver.SCHEME_CONTENT != uri.scheme)
throw IllegalArgumentException(uri.toString())
if (TwidereConstants.AUTHORITY_TWIDERE_CACHE != uri.authority)
if (AUTHORITY_TWIDERE_CACHE != uri.authority)
throw IllegalArgumentException(uri.toString())
return ByteString.decodeBase64(uri.lastPathSegment).utf8()
}

View File

@ -45,7 +45,7 @@ class AddUserListMembersTask(
val nameFirst = preferences.getBoolean(KEY_NAME_FIRST)
val displayName = userColorNameManager.getDisplayName(user.key, user.name,
user.screen_name, nameFirst)
message = context.getString(R.string.added_user_to_list, displayName, result.data.name)
message = context.getString(R.string.message_toast_added_user_to_list, displayName, result.data.name)
} else {
val res = context.resources
message = res.getQuantityString(R.plurals.added_N_users_to_list, users.size, users.size,

View File

@ -20,7 +20,6 @@
package org.mariotaku.twidere.task
import android.content.Context
import android.net.Uri
import android.support.v4.app.DialogFragment
import org.mariotaku.twidere.activity.iface.IBaseActivity
import org.mariotaku.twidere.fragment.ProgressDialogFragment
@ -31,10 +30,9 @@ import java.io.File
*/
abstract class ProgressSaveFileTask(
context: Context,
source: Uri,
destination: File,
getMimeType: SaveFileTask.FileInfoCallback
) : SaveFileTask(context, source, destination, getMimeType) {
fileInfo: FileInfo
) : SaveFileTask(context, destination, fileInfo) {
override fun showProgress() {
(context as IBaseActivity<*>).executeAfterFragmentResumed { activity ->

View File

@ -20,35 +20,28 @@
package org.mariotaku.twidere.task
import android.content.Context
import android.net.Uri
import android.os.AsyncTask
import android.text.TextUtils.isEmpty
import android.util.Log
import okio.BufferedSink
import okio.Okio
import okio.Source
import android.webkit.MimeTypeMap
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.util.Utils
import java.io.Closeable
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.lang.ref.WeakReference
import java.util.*
abstract class SaveFileTask(
context: Context,
private val source: Uri,
private val destination: File,
private val getMimeType: SaveFileTask.FileInfoCallback
private val fileInfo: FileInfo
) : AsyncTask<Any, Any, SaveFileTask.SaveFileResult>() {
private val contextRef: WeakReference<Context>
init {
this.contextRef = WeakReference(context)
}
private val contextRef = WeakReference(context)
override fun doInBackground(vararg args: Any): SaveFileResult? {
val context = contextRef.get() ?: return null
return saveFile(context, source, getMimeType, destination, requiresValidExtension)
return saveFile(fileInfo, destination, requiresValidExtension)
}
override fun onCancelled() {
@ -81,14 +74,23 @@ abstract class SaveFileTask(
protected val context: Context?
get() = contextRef.get()
interface FileInfoCallback {
fun getFilename(source: Uri): String?
interface FileInfo : Closeable {
val fileName: String?
fun getMimeType(source: Uri): String?
val mimeType: String?
fun getExtension(mimeType: String?): String?
val fileExtension: String? get() {
val typeLowered = mimeType?.toLowerCase(Locale.US) ?: return null
return when (typeLowered) {
// Hack for fanfou image type
"image/jpg" -> "jpg"
else -> MimeTypeMap.getSingleton().getExtensionFromMimeType(typeLowered)
}
}
val specialCharacter: Char
fun inputStream(): InputStream
}
class SaveFileResult(savedFile: File, mimeType: String) {
@ -105,50 +107,42 @@ abstract class SaveFileTask(
companion object {
fun saveFile(context: Context, source: Uri,
fileInfoCallback: FileInfoCallback,
destinationDir: File, requiresValidExtension: Boolean): SaveFileResult? {
val cr = context.contentResolver
var ioSrc: Source? = null
var sink: BufferedSink? = null
try {
var name: String = fileInfoCallback.getFilename(source) ?: return null
fun saveFile(fileInfo: FileInfo, destinationDir: File, requiresValidExtension: Boolean) = try {
fileInfo.use {
var name: String = it.fileName ?: return null
if (isEmpty(name)) return null
if (name.length > 32) {
name = name.substring(0, 32)
}
val mimeType = fileInfoCallback.getMimeType(source) ?: return null
val extension = fileInfoCallback.getExtension(mimeType)
val mimeType = it.mimeType ?: return null
val extension = it.fileExtension
if (requiresValidExtension && extension == null) {
return null
}
if (!destinationDir.isDirectory && !destinationDir.mkdirs()) return null
var nameToSave = getFileNameWithExtension(name, extension,
fileInfoCallback.specialCharacter, null)
it.specialCharacter, null)
var saveFile = File(destinationDir, nameToSave)
if (saveFile.exists()) {
nameToSave = getFileNameWithExtension(name, extension,
fileInfoCallback.specialCharacter,
it.specialCharacter,
System.currentTimeMillis().toString())
saveFile = File(destinationDir, nameToSave)
}
val `in` = cr.openInputStream(source) ?: return null
ioSrc = Okio.source(`in`)
sink = Okio.buffer(Okio.sink(saveFile))
sink!!.writeAll(ioSrc)
sink.flush()
return SaveFileResult(saveFile, mimeType)
} catch (e: IOException) {
Log.w(LOGTAG, "Failed to save file", e)
return null
} finally {
Utils.closeSilently(sink)
Utils.closeSilently(ioSrc)
saveFile.outputStream().use { output ->
it.inputStream().use { input ->
input.copyTo(output)
}
}
return@use SaveFileResult(saveFile, mimeType)
}
} catch (e: IOException) {
Log.w(LOGTAG, "Failed to save file", e)
null
}
internal fun getFileNameWithExtension(name: String, extension: String?,
specialCharacter: Char, suffix: String?): String {
specialCharacter: Char, suffix: String?): String {
val sb = StringBuilder()
var end = name.length
if (extension != null) {

View File

@ -21,12 +21,8 @@ package org.mariotaku.twidere.task
import android.app.Activity
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Environment
import android.widget.Toast
import org.mariotaku.twidere.R
import org.mariotaku.twidere.annotation.CacheFileType
import org.mariotaku.twidere.provider.CacheProvider
import java.io.File
/**
@ -34,10 +30,9 @@ import java.io.File
*/
class SaveMediaToGalleryTask(
activity: Activity,
source: Uri,
destination: File,
type: String
) : ProgressSaveFileTask(activity, source, destination, CacheProvider.CacheFileTypeCallback(activity, type)) {
fileInfo: FileInfo,
destination: File
) : ProgressSaveFileTask(activity, destination, fileInfo) {
override fun onFileSaved(savedFile: File, mimeType: String?) {
val context = context ?: return
@ -51,24 +46,4 @@ class SaveMediaToGalleryTask(
Toast.makeText(context, R.string.message_toast_error_occurred, Toast.LENGTH_SHORT).show()
}
companion object {
fun create(activity: Activity, source: Uri, @CacheFileType type: String): SaveFileTask {
val pubDir: File
when (type) {
CacheFileType.VIDEO -> {
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
}
CacheFileType.IMAGE -> {
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
}
else -> {
pubDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
}
}
val saveDir = File(pubDir, "Twidere")
return SaveMediaToGalleryTask(activity, source, saveDir, type)
}
}
}

View File

@ -21,6 +21,7 @@ package org.mariotaku.twidere.text
import android.text.TextPaint
import android.text.style.CharacterStyle
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.constant.SharedPreferenceConstants.VALUE_LINK_HIGHLIGHT_OPTION_CODE_HIGHLIGHT
import org.mariotaku.twidere.constant.SharedPreferenceConstants.VALUE_LINK_HIGHLIGHT_OPTION_CODE_UNDERLINE
@ -28,10 +29,10 @@ import org.mariotaku.twidere.constant.SharedPreferenceConstants.VALUE_LINK_HIGHL
class TwidereHighLightStyle(private val option: Int) : CharacterStyle() {
override fun updateDrawState(ds: TextPaint) {
if (option and VALUE_LINK_HIGHLIGHT_OPTION_CODE_UNDERLINE != 0) {
if (VALUE_LINK_HIGHLIGHT_OPTION_CODE_UNDERLINE in option) {
ds.isUnderlineText = true
}
if (option and VALUE_LINK_HIGHLIGHT_OPTION_CODE_HIGHLIGHT != 0) {
if (VALUE_LINK_HIGHLIGHT_OPTION_CODE_HIGHLIGHT in option) {
ds.color = ds.linkColor
}
}

View File

@ -22,6 +22,7 @@ package org.mariotaku.twidere.text
import android.text.TextPaint
import android.text.style.URLSpan
import android.view.View
import org.mariotaku.ktextension.contains
import org.mariotaku.twidere.Constants
import org.mariotaku.twidere.constant.SharedPreferenceConstants.VALUE_LINK_HIGHLIGHT_OPTION_CODE_HIGHLIGHT
import org.mariotaku.twidere.constant.SharedPreferenceConstants.VALUE_LINK_HIGHLIGHT_OPTION_CODE_UNDERLINE
@ -46,10 +47,10 @@ class TwidereURLSpan(
}
override fun updateDrawState(ds: TextPaint) {
if (highlightStyle and VALUE_LINK_HIGHLIGHT_OPTION_CODE_UNDERLINE != 0) {
if (VALUE_LINK_HIGHLIGHT_OPTION_CODE_UNDERLINE in highlightStyle) {
ds.isUnderlineText = true
}
if (highlightStyle and VALUE_LINK_HIGHLIGHT_OPTION_CODE_HIGHLIGHT != 0) {
if (VALUE_LINK_HIGHLIGHT_OPTION_CODE_HIGHLIGHT in highlightStyle) {
ds.color = ds.linkColor
}
}

View File

@ -41,8 +41,8 @@ import org.mariotaku.twidere.annotation.NotificationType
import org.mariotaku.twidere.constant.IntentConstants
import org.mariotaku.twidere.constant.iWantMyStarsBackKey
import org.mariotaku.twidere.constant.nameFirstKey
import org.mariotaku.twidere.extension.model.getTitle
import org.mariotaku.twidere.extension.model.getSummaryText
import org.mariotaku.twidere.extension.model.getTitle
import org.mariotaku.twidere.extension.model.notificationDisabled
import org.mariotaku.twidere.extension.rawQuery
import org.mariotaku.twidere.model.*
@ -158,7 +158,7 @@ class ContentNotificationManager(
Expression.equalsArgs(Activities.ACCOUNT_KEY),
Expression.greaterThanArgs(Activities.POSITION_KEY)
).sql
val whereArgs = arrayOf(accountKey.toString(), position.toString())
val whereArgs = arrayOf(accountKey.toString(), "0")
@SuppressLint("Recycle")
val c = cr.query(Activities.AboutMe.CONTENT_URI, Activities.COLUMNS, where, whereArgs,
OrderBy(Activities.TIMESTAMP, false).sql) ?: return
@ -182,9 +182,10 @@ class ContentNotificationManager(
var timestamp: Long = -1
val filteredUserIds = DataStoreUtils.getFilteredUserIds(context)
var consumed = 0
val remaining = c.forEachRow(5) { cur, idx ->
val activity = ci.newObject(c)
val activity = ci.newObject(cur)
if (pref.isNotificationMentionsOnly && activity.action !in Activity.Action.MENTION_ACTIONS) {
return@forEachRow false
}
@ -217,14 +218,16 @@ class ContentNotificationManager(
pebbleNotificationStringBuilder.append(summary)
pebbleNotificationStringBuilder.append("\n")
}
consumed++
return@forEachRow true
}
if (remaining < 0) return
if (remaining > 0) {
style.addLine(resources.getString(R.string.and_N_more, count - c.position))
pebbleNotificationStringBuilder.append(resources.getString(R.string.and_N_more, count - c.position))
style.addLine(resources.getString(R.string.and_N_more, remaining))
pebbleNotificationStringBuilder.append(resources.getString(R.string.and_N_more, remaining))
}
val displayCount = 5 + remaining
val displayCount = consumed + remaining
if (displayCount <= 0) return
val title = resources.getQuantityString(R.plurals.N_new_interactions,
displayCount, displayCount)
builder.setContentTitle(title)
@ -325,6 +328,7 @@ class ContentNotificationManager(
moveToFirst()
var current = 0
while (!isAfterLast) {
@Suppress("ConvertTwoComparisonsToRangeCheck")
if (limit >= 0 && current >= limit) break
if (action(this, current)) {
current++

View File

@ -78,8 +78,6 @@ object DataStoreUtils {
TABLE_ID_STATUSES)
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Activities.AboutMe.CONTENT_PATH,
TABLE_ID_ACTIVITIES_ABOUT_ME)
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Activities.ByFriends.CONTENT_PATH,
TABLE_ID_ACTIVITIES_BY_FRIENDS)
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH,
TABLE_ID_DRAFTS)
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH,
@ -389,7 +387,6 @@ object DataStoreUtils {
when (id) {
TABLE_ID_STATUSES -> return Statuses.TABLE_NAME
TABLE_ID_ACTIVITIES_ABOUT_ME -> return Activities.AboutMe.TABLE_NAME
TABLE_ID_ACTIVITIES_BY_FRIENDS -> return Activities.ByFriends.TABLE_NAME
TABLE_ID_DRAFTS -> return Drafts.TABLE_NAME
TABLE_ID_FILTERED_USERS -> return Filters.Users.TABLE_NAME
TABLE_ID_FILTERED_KEYWORDS -> return Filters.Keywords.TABLE_NAME

View File

@ -52,7 +52,6 @@ class TwidereSQLiteOpenHelper(
db.beginTransaction()
db.execSQL(createTable(Statuses.TABLE_NAME, Statuses.COLUMNS, Statuses.TYPES, true))
db.execSQL(createTable(Activities.AboutMe.TABLE_NAME, Activities.AboutMe.COLUMNS, Activities.AboutMe.TYPES, true))
db.execSQL(createTable(Activities.ByFriends.TABLE_NAME, Activities.ByFriends.COLUMNS, Activities.ByFriends.TYPES, true))
db.execSQL(createTable(Drafts.TABLE_NAME, Drafts.COLUMNS, Drafts.TYPES, true))
db.setTransactionSuccessful()
db.endTransaction()
@ -225,8 +224,6 @@ class TwidereSQLiteOpenHelper(
safeUpgrade(db, Statuses.TABLE_NAME, Statuses.COLUMNS, Statuses.TYPES, true, null)
safeUpgrade(db, Activities.AboutMe.TABLE_NAME, Activities.AboutMe.COLUMNS,
Activities.AboutMe.TYPES, true, null)
safeUpgrade(db, Activities.ByFriends.TABLE_NAME, Activities.ByFriends.COLUMNS,
Activities.ByFriends.TYPES, true, null)
migrateDrafts(db)
safeUpgrade(db, CachedUsers.TABLE_NAME, CachedUsers.COLUMNS, CachedUsers.TYPES, true, null,
createConflictReplaceConstraint(CachedUsers.USER_KEY))
@ -254,6 +251,7 @@ class TwidereSQLiteOpenHelper(
db.beginTransaction()
db.execSQL(SQLQueryBuilder.dropTable(true, "network_usages").sql)
db.execSQL(SQLQueryBuilder.dropTable(true, "mentions").sql)
db.execSQL(SQLQueryBuilder.dropTable(true, "activities_by_friends").sql)
createTriggers(db)
createIndices(db)
db.setTransactionSuccessful()

View File

@ -286,6 +286,15 @@ class ApplicationModule(private val application: Application) {
return application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
@Provides
fun okHttpClient(preferences: SharedPreferencesWrapper, dns: Dns, connectionPool: ConnectionPool,
cache: Cache): OkHttpClient {
val conf = HttpClientFactory.HttpClientConfiguration(preferences)
val builder = OkHttpClient.Builder()
HttpClientFactory.initOkHttpClient(conf, builder, dns, connectionPool, cache)
return builder.build()
}
@Provides
@Singleton
fun dataSourceFactory(preferences: SharedPreferencesWrapper, dns: Dns, connectionPool: ConnectionPool,

View File

@ -12,6 +12,7 @@ import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.RequestManager
import kotlinx.android.synthetic.main.list_item_status.view.*
import org.mariotaku.ktextension.applyFontFamily
@ -314,6 +315,10 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
}
if (status.media.isNotNullOrEmpty()) {
mediaLabel.displayMediaLabel(status.card_name, status.media, status.location,
status.place_full_name, status.is_possibly_sensitive)
if (!adapter.sensitiveContentEnabled && status.is_possibly_sensitive) {
// Sensitive content, show label instead of media view
mediaLabel.visibility = View.VISIBLE
@ -418,6 +423,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
private fun displayQuotedMedia(requestManager: RequestManager, status: ParcelableStatus) {
if (status.quoted_media?.isNotEmpty() ?: false) {
quotedMediaLabel.displayMediaLabel(null, status.quoted_media, null, null,
status.is_possibly_sensitive)
if (!adapter.sensitiveContentEnabled && status.is_possibly_sensitive) {
// Sensitive content, show label instead of media view
quotedMediaPreview.visibility = View.GONE
@ -559,35 +567,47 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
return !adapter.isCardActionsShown(RecyclerView.NO_POSITION)
}
private fun displayExtraTypeIcon(cardName: String?, media: Array<ParcelableMedia?>?,
private fun TextView.displayMediaLabel(cardName: String?, media: Array<ParcelableMedia?>?,
location: ParcelableLocation?, placeFullName: String?,
sensitive: Boolean) {
val icon = if (sensitive) {
R.drawable.ic_label_warning
if (sensitive) {
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, R.drawable.ic_label_warning, 0, 0, 0)
setText(R.string.label_sensitive)
} else if (media != null && media.isNotEmpty()) {
val type = media.type
if (type in videoTypes) {
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, R.drawable.ic_label_video, 0, 0, 0)
setText(R.string.label_video)
} else if (media.size > 1) {
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, R.drawable.ic_label_gallery, 0, 0, 0)
setText(R.string.label_photos)
} else {
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, R.drawable.ic_label_gallery, 0, 0, 0)
setText(R.string.label_photo)
}
} else {
R.drawable.ic_label_gallery
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(this, R.drawable.ic_label_gallery, 0, 0, 0)
setText(R.string.label_media)
}
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(mediaLabel, icon, 0, 0, 0)
mediaLabel.refreshDrawableState()
refreshDrawableState()
}
private inline val Array<ParcelableMedia?>.type: Int get() {
forEach { if (it != null) return it.type }
return 0
}
private fun hasVideo(media: Array<ParcelableMedia?>?): Boolean {
if (media == null) return false
media.filterNotNull().forEach {
when (it.type) {
ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF, ParcelableMedia.Type.EXTERNAL_PLAYER -> return true
}
return media.any { item ->
if (item == null) return@any false
return@any videoTypes.contains(item.type)
}
return false
}
internal class EventListener(holder: StatusViewHolder) : OnClickListener, OnLongClickListener {
val holderRef: WeakReference<StatusViewHolder>
init {
this.holderRef = WeakReference(holder)
}
private val holderRef = WeakReference(holder)
override fun onClick(v: View) {
val holder = holderRef.get() ?: return
@ -649,6 +669,9 @@ class StatusViewHolder(private val adapter: IStatusesAdapter<*>, itemView: View)
companion object {
const val layoutResource = R.layout.list_item_status
private val videoTypes = intArrayOf(ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF,
ParcelableMedia.Type.EXTERNAL_PLAYER)
}
}

View File

@ -154,7 +154,7 @@
<string name="action_add_tab">إضافة لسان</string>
<string name="action_add_to_filter">إضافة إلى لائحة الكتم</string>
<string name="action_add_to_list">إضافة إلى القائمة</string>
<string name="added_user_to_list">أُضيف <xliff:g id="user">%1$s</xliff:g> إلى قائمة "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">أُضيف <xliff:g id="user">%1$s</xliff:g> إلى قائمة "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">و <xliff:g id="count">%d</xliff:g> أخرون</string>
<string name="api_url_format">صيغة رابط الAPI</string>
<string name="app_description">برنامجك الخاص لتويتر</string>

View File

@ -181,7 +181,7 @@
<string name="action_add_tab">Amestar llingüeta</string>
<string name="action_add_to_filter">Amestar a Peñeres</string>
<string name="action_add_to_list">Amestar al llistáu</string>
<string name="added_user_to_list">Amestóse a <xliff:g id="user">%1$s</xliff:g> al llistáu «<xliff:g id="list">%2$s</xliff:g>».</string>
<string name="message_toast_added_user_to_list">Amestóse a <xliff:g id="user">%1$s</xliff:g> al llistáu «<xliff:g id="list">%2$s</xliff:g>».</string>
<string name="and_N_more">y <xliff:g id="count">%d</xliff:g> más</string>
<string name="api_url_format">Formatu d\'URL d\'API</string>
<string name="app_description">El to veceru propiu de Twitter</string>

View File

@ -156,7 +156,7 @@
<string name="action_add_tab">Afegeix una pestanya</string>
<string name="action_add_to_filter">Afegeix al filtre</string>
<string name="action_add_to_list">Afegeix a la llista</string>
<string name="added_user_to_list">S\'ha afegit l\'usuari <xliff:g id="user">%1$s</xliff:g> a la llista «<xliff:g id="list">%2$s</xliff:g>».</string>
<string name="message_toast_added_user_to_list">S\'ha afegit l\'usuari <xliff:g id="user">%1$s</xliff:g> a la llista «<xliff:g id="list">%2$s</xliff:g>».</string>
<string name="and_N_more">i <xliff:g id="count">%d</xliff:g> més</string>
<string name="api_url_format">Format de la URL de l\'API</string>
<string name="app_description">La vostra app de Twitter</string>

View File

@ -183,7 +183,7 @@
<string name="action_add_tab">Tab hinzufügen</string>
<string name="action_add_to_filter">Zum Filter hinzufügen</string>
<string name="action_add_to_list">Zur Liste hinzufügen</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> wurde zur Liste "<xliff:g id="list">%2$s</xliff:g>\" hinzugefügt.</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> wurde zur Liste "<xliff:g id="list">%2$s</xliff:g>\" hinzugefügt.</string>
<string name="and_N_more">und <xliff:g id="count">%d</xliff:g> weitere</string>
<string name="api_url_format">API URL Format</string>
<string name="app_description">Deine eigene Twitter-App</string>

View File

@ -180,7 +180,7 @@
<string name="action_add_tab">Agregar pestaña</string>
<string name="action_add_to_filter">Añadir a filtros</string>
<string name="action_add_to_list">Añadir a la lista</string>
<string name="added_user_to_list">Agregado <xliff:g id="user">%1$s</xliff:g> a la lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Agregado <xliff:g id="user">%1$s</xliff:g> a la lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">y <xliff:g id="count">%d</xliff:g> más</string>
<string name="api_url_format">Formato URL de la API</string>
<string name="app_description">Tu propia aplicación de Twitter</string>

View File

@ -164,7 +164,7 @@
<string name="action_add_tab">افزودن برگه</string>
<string name="action_add_to_filter">افزودن به پالایه</string>
<string name="action_add_to_list">افزودن به فهرست</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> را به فهرست «<xliff:g id="list">%2$s</xliff:g>» افزود.</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> را به فهرست «<xliff:g id="list">%2$s</xliff:g>» افزود.</string>
<string name="and_N_more">و <xliff:g id="count">%d</xliff:g> بیش‌تر</string>
<string name="api_url_format">قالب نشانی API</string>
<string name="app_description">کارهٔ توییتر خودتان</string>

View File

@ -161,7 +161,7 @@
<string name="action_add_tab">Lisää välilehti</string>
<string name="action_add_to_filter">Lisää suodattimeen</string>
<string name="action_add_to_list">Lisää listaan</string>
<string name="added_user_to_list">Lisättiin <xliff:g id="user">%1$s</xliff:g> listalle "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Lisättiin <xliff:g id="user">%1$s</xliff:g> listalle "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">ja <xliff:g id="count">%d</xliff:g> muuta</string>
<string name="api_url_format">API-URL:n muoto</string>
<string name="app_description">Oma Twitter-sovelluksesi</string>

View File

@ -183,7 +183,7 @@
<string name="action_add_tab">Ajouter un onglet</string>
<string name="action_add_to_filter">Ajouter au filtre</string>
<string name="action_add_to_list">Ajouter à la liste</string>
<string name="added_user_to_list">Utilisateur <xliff:g id="user">%1$s</xliff:g> ajouté à la liste "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Utilisateur <xliff:g id="user">%1$s</xliff:g> ajouté à la liste "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">et encore <xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">Format de l\'adresse API</string>
<string name="app_description">Votre propre application Twitter</string>

View File

@ -157,7 +157,7 @@
<string name="action_add_tab">Engadir tab.</string>
<string name="action_add_to_filter">Engadir a filtro</string>
<string name="action_add_to_list">Engadir á lista</string>
<string name="added_user_to_list">Engadiches a <xliff:g id="user">%1$s</xliff:g> á lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Engadiches a <xliff:g id="user">%1$s</xliff:g> á lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">e <xliff:g id="count">%d</xliff:g> máis</string>
<string name="api_url_format">Formato URL API</string>
<string name="app_description">A túa propia aplicación Twitter</string>

View File

@ -127,7 +127,7 @@
<string name="action_add_tab">Dodaj tab</string>
<string name="action_add_to_filter">Dodaj u filter</string>
<string name="action_add_to_list">Dodaj na listu</string>
<string name="added_user_to_list">Dodano <xliff:g id="user">%1$s</xliff:g> na listu "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Dodano <xliff:g id="user">%1$s</xliff:g> na listu "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">i <xliff:g id="count">%d</xliff:g> više</string>
<string name="api_url_format">API URL Format</string>
<!-- App name, normally you don't need to translate this. -->

View File

@ -168,7 +168,7 @@
<string name="action_add_tab">Fül hozzáadása</string>
<string name="action_add_to_filter">Hozzáadás a szűrőhöz</string>
<string name="action_add_to_list">Hozzáadás a listához</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> hozzáadva a listához: "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> hozzáadva a listához: "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">és még <xliff:g id="count">%d</xliff:g> elem</string>
<string name="api_url_format">API URL formátum</string>
<string name="app_description">A te Twitter alkalmazásod</string>

View File

@ -176,7 +176,7 @@
<string name="action_add_tab">Tambah tab</string>
<string name="action_add_to_filter">Tambahkan ke penyaringan</string>
<string name="action_add_to_list">Masukkan ke daftar</string>
<string name="added_user_to_list">Menambahkan <xliff:g id="user">%1$s</xliff:g> ke daftar "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Menambahkan <xliff:g id="user">%1$s</xliff:g> ke daftar "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">dan <xliff:g id="count">%d</xliff:g> lebih lagi</string>
<string name="api_url_format">Format URL API</string>
<string name="app_description">Aplikasi Twitter anda</string>

View File

@ -157,7 +157,7 @@
<string name="action_add_tab">Aggiungi tab</string>
<string name="action_add_to_filter">Aggiungi a filtro</string>
<string name="action_add_to_list">Aggiungi alla lista</string>
<string name="added_user_to_list">Aggiunto <xliff:g id="user">%1$s</xliff:g> alla lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Aggiunto <xliff:g id="user">%1$s</xliff:g> alla lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">e altri <xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">Formato API URL</string>
<string name="app_description">La tua Twitter app</string>

View File

@ -110,7 +110,7 @@
<string name="action_add_member">הוסף משתמש</string>
<string name="action_add_tab">הוסף כרטיסייה</string>
<string name="action_add_to_list">הוסף לרשימה</string>
<string name="added_user_to_list">המשתמש <xliff:g id="user">%1$s</xliff:g> נוסף לרשימה "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">המשתמש <xliff:g id="user">%1$s</xliff:g> נוסף לרשימה "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">ועוד <xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">פורמט URL של הAPI</string>
<string name="app_description">אפליקציית טוויטר משלך</string>

View File

@ -183,7 +183,7 @@
<string name="action_add_tab">タブを追加</string>
<string name="action_add_to_filter">フィルタに追加</string>
<string name="action_add_to_list">リストに追加</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g>さんをリスト"<xliff:g id="list">%2$s</xliff:g>\"に追加しました。</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g>さんをリスト"<xliff:g id="list">%2$s</xliff:g>\"に追加しました。</string>
<string name="and_N_more"><xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">API URL の形式</string>
<string name="app_description">あなた自身の Twitter アプリ</string>

View File

@ -156,7 +156,7 @@
<string name="action_add_tab">탭 추가</string>
<string name="action_add_to_filter">필터에 추가</string>
<string name="action_add_to_list">리스트에 추가</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g>님을 "<xliff:g id="list">%2$s</xliff:g>\" 리스트에 추가하였습니다.</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g>님을 "<xliff:g id="list">%2$s</xliff:g>\" 리스트에 추가하였습니다.</string>
<string name="and_N_more"><xliff:g id="count">%d</xliff:g>개 이상</string>
<string name="api_url_format">API 주소 형식</string>
<string name="app_description">당신만의 트위터 앱</string>

View File

@ -158,7 +158,7 @@
<string name="action_add_tab">Voeg een tabblad toe</string>
<string name="action_add_to_filter">Voeg toe aan filter</string>
<string name="action_add_to_list">Toevoegen aan lijst</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> toegevoegd aan lijst \'\'<xliff:g id="list">%2$s</xliff:g>\'\'.</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> toegevoegd aan lijst \'\'<xliff:g id="list">%2$s</xliff:g>\'\'.</string>
<string name="and_N_more">en <xliff:g id="count">%d</xliff:g> meer</string>
<string name="api_url_format">API URL-formaat</string>
<string name="app_description">Je eigen Twitter-app</string>

View File

@ -143,7 +143,7 @@
<string name="action_add_tab">Legg til fane</string>
<string name="action_add_to_filter">Legg til filter</string>
<string name="action_add_to_list">Legg til listen</string>
<string name="added_user_to_list">La <xliff:g id="user">%1$s</xliff:g> til listen «<xliff:g id="list">%2$s</xliff:g>».</string>
<string name="message_toast_added_user_to_list">La <xliff:g id="user">%1$s</xliff:g> til listen «<xliff:g id="list">%2$s</xliff:g>».</string>
<string name="and_N_more">og <xliff:g id="count">%d</xliff:g> flere</string>
<string name="api_url_format">API-URL-format</string>
<string name="app_description">Din egen Twitter-app</string>

View File

@ -113,7 +113,7 @@
<string name="action_add_tab">Dodaj zakładkę</string>
<string name="action_add_to_filter">Dodaj do filtra</string>
<string name="action_add_to_list">Dodaj do listy</string>
<string name="added_user_to_list">Dodano <xliff:g id="user">%1$s</xliff:g> do listy "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Dodano <xliff:g id="user">%1$s</xliff:g> do listy "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">oraz <xliff:g id="count">%d</xliff:g> więcej</string>
<string name="api_url_format">Format API URL</string>
<!-- App name, normally you don't need to translate this. -->

View File

@ -163,7 +163,7 @@
<string name="action_add_tab">Adicionar aba</string>
<string name="action_add_to_filter">Adicionar filtro</string>
<string name="action_add_to_list">Adicionar à lista</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> foi adicionado à lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> foi adicionado à lista "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">e <xliff:g id="count">%d</xliff:g> mais</string>
<string name="api_url_format">Formato da URL da API</string>
<string name="app_description">Seu próprio app de Twitter</string>

View File

@ -178,7 +178,7 @@
<string name="action_add_tab">Добавить вкладку</string>
<string name="action_add_to_filter">Добавить в фильтр</string>
<string name="action_add_to_list">Добавить в список</string>
<string name="added_user_to_list">Добавлен <xliff:g id="user">%1$s</xliff:g> в список "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Добавлен <xliff:g id="user">%1$s</xliff:g> в список "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">и еще <xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">Формат API URL</string>
<string name="app_description">Ваш собственный Твиттер-клиент</string>

View File

@ -160,7 +160,7 @@
<string name="action_add_tab">เพิ่มแถบ</string>
<string name="action_add_to_filter">เพิ่มไปยังตัวกรอง</string>
<string name="action_add_to_list">เพิ่มไปยังรายการ</string>
<string name="added_user_to_list">เพิ่ม <xliff:g id="user">%1$s</xliff:g> เข้าสู่ลิสต์ <xliff:g id="list">%2$s</xliff:g> แล้ว</string>
<string name="message_toast_added_user_to_list">เพิ่ม <xliff:g id="user">%1$s</xliff:g> เข้าสู่ลิสต์ <xliff:g id="list">%2$s</xliff:g> แล้ว</string>
<string name="and_N_more">และอีก <xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">รูปแบบ URL ของ API</string>
<string name="app_description">แอพทวิตเตอร์ของคุณเอง</string>

View File

@ -159,7 +159,7 @@
<string name="action_add_tab">Sekme ekle</string>
<string name="action_add_to_filter">Filtre ekle</string>
<string name="action_add_to_list">Listeye ekle</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> listeye eklendi "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> listeye eklendi "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">ve <xliff:g id="count">%d</xliff:g> daha</string>
<string name="api_url_format">API URL Format</string>
<string name="app_description">Senin Twitter uygulaman</string>

View File

@ -160,7 +160,7 @@
<string name="action_add_tab">Додати вкладку</string>
<string name="action_add_to_filter">Додати до фільтру</string>
<string name="action_add_to_list">Додати до списку</string>
<string name="added_user_to_list">Додано <xliff:g id="user">%1$s</xliff:g> до списку "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="message_toast_added_user_to_list">Додано <xliff:g id="user">%1$s</xliff:g> до списку "<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">і ще <xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">Формат API URL</string>
<string name="app_description">Свій власний додаток Twitter</string>

View File

@ -183,7 +183,7 @@
<string name="action_add_tab">添加标签页</string>
<string name="action_add_to_filter">添加到过滤器</string>
<string name="action_add_to_list">添加到列表</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> 被添加到了列表 “<xliff:g id="list">%2$s</xliff:g></string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> 被添加到了列表 “<xliff:g id="list">%2$s</xliff:g></string>
<string name="and_N_more">和其他 <xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">API URL 格式</string>
<string name="app_description">属于您的 Twitter 应用</string>

View File

@ -183,7 +183,7 @@
<string name="action_add_tab">新增標籤頁</string>
<string name="action_add_to_filter">新增到過濾器</string>
<string name="action_add_to_list">新增到列表</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> 被新增到了 "<xliff:g id="list">%2$s</xliff:g>\" 列表</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> 被新增到了 "<xliff:g id="list">%2$s</xliff:g>\" 列表</string>
<string name="and_N_more">和其他<xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">API URL 格式</string>
<string name="app_description">您的 Twitter 應用程式</string>

View File

@ -183,7 +183,7 @@
<string name="action_add_tab">新增標籤頁</string>
<string name="action_add_to_filter">新增到過濾器</string>
<string name="action_add_to_list">新增到列表</string>
<string name="added_user_to_list"><xliff:g id="user">%1$s</xliff:g> 被新增到了 "<xliff:g id="list">%2$s</xliff:g>\" 列表</string>
<string name="message_toast_added_user_to_list"><xliff:g id="user">%1$s</xliff:g> 被新增到了 "<xliff:g id="list">%2$s</xliff:g>\" 列表</string>
<string name="and_N_more">和其他<xliff:g id="count">%d</xliff:g></string>
<string name="api_url_format">API URL 格式</string>
<string name="app_description">您的 Twitter 應用程式</string>

View File

@ -167,10 +167,10 @@
android:drawableStart="@drawable/ic_label_gallery"
android:gravity="center_vertical"
android:minHeight="@dimen/element_size_small"
android:text="@string/media"
android:textAppearance="?android:textAppearanceSmall"
android:textStyle="bold"
app:iabColor="?android:textColorSecondary"/>
app:iabColor="?android:textColorSecondary"
tools:text="@string/label_media"/>
<org.mariotaku.twidere.view.CardMediaContainer
android:id="@+id/mediaPreview"
@ -265,10 +265,10 @@
android:drawableLeft="@drawable/ic_label_gallery"
android:drawableStart="@drawable/ic_label_gallery"
android:gravity="center_vertical"
android:text="@string/media"
android:textAppearance="?android:textAppearanceSmall"
android:textStyle="bold"
app:iabColor="?android:textColorSecondary"
tools:text="@string/label_media"
tools:visibility="gone"/>
<org.mariotaku.twidere.view.CardMediaContainer

View File

@ -150,58 +150,56 @@
<string name="activities_about_me">Activities about me</string>
<string name="activities_by_friends">Activities by friends</string>
<string name="activity_about_me_favorite"><xliff:g id="user">%s</xliff:g> favorited your tweet.</string>
<string name="activity_about_me_favorite_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited your tweet.</string>
<string name="activity_about_me_favorited_media_tagged"><xliff:g id="user">%s</xliff:g> favorited a tweet you were tagged in.</string>
<string name="activity_about_me_favorited_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited a tweet you were tagged in.</string>
<string name="activity_about_me_favorited_mention"><xliff:g id="user">%s</xliff:g> favorited a tweet you were mentioned in.</string>
<string name="activity_about_me_favorited_mention_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited a tweet you were mentioned in.</string>
<string name="activity_about_me_favorited_retweet"><xliff:g id="user">%s</xliff:g> favorited your retweet.</string>
<string name="activity_about_me_favorited_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited your retweet.</string>
<string name="activity_about_me_follow"><xliff:g id="user">%s</xliff:g> is following you.</string>
<string name="activity_about_me_follow_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> are following you.</string>
<string name="activity_about_me_like"><xliff:g id="user">%s</xliff:g> liked your tweet.</string>
<string name="activity_about_me_like_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked your tweet.</string>
<string name="activity_about_me_liked_media_tagged"><xliff:g id="user">%s</xliff:g> liked a tweet you were tagged in.</string>
<string name="activity_about_me_liked_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked a tweet you were tagged in.</string>
<string name="activity_about_me_liked_mention"><xliff:g id="user">%s</xliff:g> liked a tweet you were mentioned in.</string>
<string name="activity_about_me_liked_mention_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked a tweet you were mentioned in.</string>
<string name="activity_about_me_liked_retweet"><xliff:g id="user">%s</xliff:g> liked your retweet.</string>
<string name="activity_about_me_liked_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked your retweet.</string>
<string name="activity_about_me_list_member_added"><xliff:g id="user">%s</xliff:g> added you to list.</string>
<string name="activity_about_me_list_member_added_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> added you to their lists.</string>
<string name="activity_about_me_list_member_added_with_name"><xliff:g id="user">%1$s</xliff:g> added you to list <xliff:g id="list">%2$s</xliff:g>".</string>
<string name="activity_about_me_media_tagged"><xliff:g id="user">%s</xliff:g> tagged you.</string>
<string name="activity_about_me_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> tagged you.</string>
<string name="activity_about_me_retweet"><xliff:g id="user">%s</xliff:g> retweeted your tweet.</string>
<string name="activity_about_me_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted your tweet.</string>
<string name="activity_about_me_retweeted_media_tagged"><xliff:g id="user">%s</xliff:g> retweeted a tweet you were tagged in.</string>
<string name="activity_about_me_retweeted_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted a tweet you were tagged in.</string>
<string name="activity_about_me_retweeted_mention"><xliff:g id="user">%s</xliff:g> retweeted a tweet you were mentioned in.</string>
<string name="activity_about_me_retweeted_mention_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted a tweet you were mentioned in.</string>
<string name="activity_about_me_retweeted_retweet"><xliff:g id="user">%s</xliff:g> retweeted your retweet.</string>
<string name="activity_about_me_retweeted_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted your retweet.</string>
<string name="activity_by_friends_favorite"><xliff:g id="user">%1$s</xliff:g> favorited <xliff:g id="target">%2$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_favorite_multi"><xliff:g id="user">%1$s</xliff:g> favorited <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_follow"><xliff:g id="user">%1$s</xliff:g> is following <xliff:g id="target">%2$s</xliff:g>.</string>
<string name="activity_by_friends_follow_multi"><xliff:g id="user">%1$s</xliff:g> is following <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>.</string>
<string name="activity_by_friends_like"><xliff:g id="user">%1$s</xliff:g> liked <xliff:g id="target">%2$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_like_multi"><xliff:g id="user">%1$s</xliff:g> liked <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_list_created"><xliff:g id="user">%1$s</xliff:g> created list <xliff:g id="target">%2$s</xliff:g>.</string>
<string name="activity_by_friends_list_created_multi"><xliff:g id="user">%1$s</xliff:g> created list <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%1$s</xliff:g>.</string>
<string name="activity_by_friends_list_member_added"><xliff:g id="user">%1$s</xliff:g> added <xliff:g id="target">%2$s</xliff:g> to list.</string>
<string name="activity_by_friends_list_member_added_multi"><xliff:g id="user">%1$s</xliff:g> added <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%1$s</xliff:g> to list.</string>
<string name="activity_by_friends_retweet"><xliff:g id="user">%1$s</xliff:g> retweeted <xliff:g id="target">%2$s</xliff:g>\'s tweet.</string>
<string name="activity_by_friends_retweet_multi"><xliff:g id="user">%1$s</xliff:g> retweeted <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet.</string>
<string name="activity_joined_twitter"><xliff:g id="user">%s</xliff:g> joined Twitter.</string>
<string name="activity_joined_twitter_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> joined Twitter.</string>
<string name="activity_about_me_favorite"><xliff:g id="user">%s</xliff:g> favorited</string>
<string name="activity_about_me_favorite_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited</string>
<string name="activity_about_me_favorited_media_tagged"><xliff:g id="user">%s</xliff:g> favorited a tweet you were tagged in</string>
<string name="activity_about_me_favorited_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited a tweet you were tagged in</string>
<string name="activity_about_me_favorited_mention"><xliff:g id="user">%s</xliff:g> favorited a tweet you were mentioned in</string>
<string name="activity_about_me_favorited_mention_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited a tweet you were mentioned in</string>
<string name="activity_about_me_favorited_retweet"><xliff:g id="user">%s</xliff:g> favorited your retweet</string>
<string name="activity_about_me_favorited_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> favorited your retweet</string>
<string name="activity_about_me_follow"><xliff:g id="user">%s</xliff:g> is following you</string>
<string name="activity_about_me_follow_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> are following you</string>
<string name="activity_about_me_like"><xliff:g id="user">%s</xliff:g> liked</string>
<string name="activity_about_me_like_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked</string>
<string name="activity_about_me_liked_media_tagged"><xliff:g id="user">%s</xliff:g> liked a tweet you were tagged in</string>
<string name="activity_about_me_liked_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked a tweet you were tagged in</string>
<string name="activity_about_me_liked_mention"><xliff:g id="user">%s</xliff:g> liked a tweet you were mentioned in</string>
<string name="activity_about_me_liked_mention_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked a tweet you were mentioned in</string>
<string name="activity_about_me_liked_retweet"><xliff:g id="user">%s</xliff:g> liked your retweet</string>
<string name="activity_about_me_liked_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> liked your retweet</string>
<string name="activity_about_me_list_member_added"><xliff:g id="user">%s</xliff:g> added you to list</string>
<string name="activity_about_me_list_member_added_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> added you to their lists</string>
<string name="activity_about_me_list_member_added_with_name"><xliff:g id="user">%1$s</xliff:g> added you to list <xliff:g id="list">%2$s</xliff:g>"</string>
<string name="activity_about_me_media_tagged"><xliff:g id="user">%s</xliff:g> tagged you</string>
<string name="activity_about_me_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> tagged you</string>
<string name="activity_about_me_retweet"><xliff:g id="user">%s</xliff:g> retweeted</string>
<string name="activity_about_me_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted</string>
<string name="activity_about_me_retweeted_media_tagged"><xliff:g id="user">%s</xliff:g> retweeted a tweet you were tagged in</string>
<string name="activity_about_me_retweeted_media_tagged_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted a tweet you were tagged in</string>
<string name="activity_about_me_retweeted_mention"><xliff:g id="user">%s</xliff:g> retweeted a tweet you were mentioned in</string>
<string name="activity_about_me_retweeted_mention_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted a tweet you were mentioned in</string>
<string name="activity_about_me_retweeted_retweet"><xliff:g id="user">%s</xliff:g> retweeted your retweet</string>
<string name="activity_about_me_retweeted_retweet_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> retweeted your retweet</string>
<string name="activity_by_friends_favorite"><xliff:g id="user">%1$s</xliff:g> favorited <xliff:g id="target">%2$s</xliff:g>\'s tweet</string>
<string name="activity_by_friends_favorite_multi"><xliff:g id="user">%1$s</xliff:g> favorited <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet</string>
<string name="activity_by_friends_follow"><xliff:g id="user">%1$s</xliff:g> is following <xliff:g id="target">%2$s</xliff:g></string>
<string name="activity_by_friends_follow_multi"><xliff:g id="user">%1$s</xliff:g> is following <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g></string>
<string name="activity_by_friends_like"><xliff:g id="user">%1$s</xliff:g> liked <xliff:g id="target">%2$s</xliff:g>\'s tweet</string>
<string name="activity_by_friends_like_multi"><xliff:g id="user">%1$s</xliff:g> liked <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet</string>
<string name="activity_by_friends_list_created"><xliff:g id="user">%1$s</xliff:g> created list <xliff:g id="target">%2$s</xliff:g></string>
<string name="activity_by_friends_list_created_multi"><xliff:g id="user">%1$s</xliff:g> created list <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%1$s</xliff:g></string>
<string name="activity_by_friends_list_member_added"><xliff:g id="user">%1$s</xliff:g> added <xliff:g id="target">%2$s</xliff:g> to list</string>
<string name="activity_by_friends_list_member_added_multi"><xliff:g id="user">%1$s</xliff:g> added <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%1$s</xliff:g> to list</string>
<string name="activity_by_friends_retweet"><xliff:g id="user">%1$s</xliff:g> retweeted <xliff:g id="target">%2$s</xliff:g>\'s tweet</string>
<string name="activity_by_friends_retweet_multi"><xliff:g id="user">%1$s</xliff:g> retweeted <xliff:g id="target">%2$s</xliff:g> and <xliff:g id="other">%3$s</xliff:g>\'s tweet</string>
<string name="activity_joined_twitter"><xliff:g id="user">%s</xliff:g> joined Twitter</string>
<string name="activity_joined_twitter_multi"><xliff:g id="user">%1$s</xliff:g> and <xliff:g id="other">%2$s</xliff:g> joined Twitter</string>
<string name="add_account">Add account</string>
<string name="add_host_mapping">Add host mapping</string>
<string name="add_image">Add image</string>
<string name="added_user_to_list">Added <xliff:g id="user">%1$s</xliff:g> to list \"<xliff:g id="list">%2$s</xliff:g>\".</string>
<string name="and_N_more">and <xliff:g id="count">%d</xliff:g> more</string>
<string name="api_url_format">API URL Format</string>
@ -574,6 +572,11 @@
<string name="mark_as_sensitive">Mark as sensitive</string>
<string name="media">Media</string>
<string name="label_media">Media</string>
<string name="label_sensitive">Sensitive</string>
<string name="label_photo">Photo</string>
<string name="label_photos">Photos</string>
<string name="label_video">Video</string>
<string name="media_preload">Media preload</string>
<string name="media_preview">Media preview</string>
<string name="media_preview_style">Media preview style</string>
@ -622,8 +625,9 @@
~ periods should be omitted
-->
<eat-comment/>
<string name="message_toast_accepted_users_follow_request">Accepted <xliff:g id="user">%s</xliff:g>\'s follow request.</string>
<string name="message_toast_accepted_users_follow_request">Accepted <xliff:g id="user">%s</xliff:g>\'s follow request</string>
<string name="message_toast_added_to_filter">Added to filter</string>
<string name="message_toast_added_user_to_list">Added <xliff:g id="user">%1$s</xliff:g> to list \"<xliff:g id="list">%2$s</xliff:g>\"</string>
<string name="message_toast_already_logged_in">You have already logged in</string>
<string name="message_toast_cannot_get_location">Can\'t get your location</string>
<string name="message_toast_compose_write_storage_no_permission">Permission required to delete taken photo/video</string>