fixed #429
close #691 trying to improve #692 tweaked media viewer swipe
This commit is contained in:
parent
857ba24bb0
commit
e7e37caff8
|
@ -6,10 +6,10 @@ import org.mariotaku.twidere.model.UserKey;
|
||||||
* Created by mariotaku on 16/1/28.
|
* Created by mariotaku on 16/1/28.
|
||||||
*/
|
*/
|
||||||
public class MediaExtra {
|
public class MediaExtra {
|
||||||
UserKey accountKey;
|
private UserKey accountKey;
|
||||||
boolean useThumbor = true;
|
private boolean useThumbor = true;
|
||||||
String fallbackUrl;
|
private String fallbackUrl;
|
||||||
boolean skipUrlReplacing;
|
private boolean skipUrlReplacing;
|
||||||
|
|
||||||
public UserKey getAccountKey() {
|
public UserKey getAccountKey() {
|
||||||
return accountKey;
|
return accountKey;
|
||||||
|
|
|
@ -48,37 +48,37 @@ import java.io.InputStream;
|
||||||
*/
|
*/
|
||||||
public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context context;
|
||||||
private final SharedPreferencesWrapper mPreferences;
|
private final SharedPreferencesWrapper preferences;
|
||||||
private final RestHttpClient mClient;
|
private final RestHttpClient client;
|
||||||
private final String mUserAgent;
|
private final String userAgent;
|
||||||
|
|
||||||
private Thumbor mThumbor;
|
private Thumbor thumbor;
|
||||||
|
|
||||||
public TwidereMediaDownloader(final Context context, SharedPreferencesWrapper preferences,
|
public TwidereMediaDownloader(final Context context, SharedPreferencesWrapper preferences,
|
||||||
RestHttpClient client) {
|
RestHttpClient client) {
|
||||||
mContext = context;
|
this.context = context;
|
||||||
mPreferences = preferences;
|
this.preferences = preferences;
|
||||||
mClient = client;
|
this.client = client;
|
||||||
mUserAgent = UserAgentUtils.getDefaultUserAgentStringSafe(context);
|
userAgent = UserAgentUtils.getDefaultUserAgentStringSafe(context);
|
||||||
reloadConnectivitySettings();
|
reloadConnectivitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reloadConnectivitySettings() {
|
public void reloadConnectivitySettings() {
|
||||||
if (mPreferences.getBoolean(KEY_THUMBOR_ENABLED)) {
|
if (preferences.getBoolean(KEY_THUMBOR_ENABLED)) {
|
||||||
final String address = mPreferences.getString(KEY_THUMBOR_ADDRESS, null);
|
final String address = preferences.getString(KEY_THUMBOR_ADDRESS, null);
|
||||||
final String securityKey = mPreferences.getString(KEY_THUMBOR_SECURITY_KEY, null);
|
final String securityKey = preferences.getString(KEY_THUMBOR_SECURITY_KEY, null);
|
||||||
if (address != null && URLUtil.isValidUrl(address)) {
|
if (address != null && URLUtil.isValidUrl(address)) {
|
||||||
if (TextUtils.isEmpty(securityKey)) {
|
if (TextUtils.isEmpty(securityKey)) {
|
||||||
mThumbor = Thumbor.create(address);
|
thumbor = Thumbor.create(address);
|
||||||
} else {
|
} else {
|
||||||
mThumbor = Thumbor.create(address, securityKey);
|
thumbor = Thumbor.create(address, securityKey);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mThumbor = null;
|
thumbor = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mThumbor = null;
|
thumbor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
||||||
skipUrlReplacing = ((MediaExtra) extra).isSkipUrlReplacing();
|
skipUrlReplacing = ((MediaExtra) extra).isSkipUrlReplacing();
|
||||||
}
|
}
|
||||||
if (!skipUrlReplacing) {
|
if (!skipUrlReplacing) {
|
||||||
final ParcelableMedia media = PreviewMediaExtractor.fromLink(url, mClient, extra);
|
final ParcelableMedia media = PreviewMediaExtractor.fromLink(url, client, extra);
|
||||||
if (media != null && media.media_url != null) {
|
if (media != null && media.media_url != null) {
|
||||||
return getInternal(media.media_url, extra);
|
return getInternal(media.media_url, extra);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
||||||
final String fallbackUrl = ((MediaExtra) extra).getFallbackUrl();
|
final String fallbackUrl = ((MediaExtra) extra).getFallbackUrl();
|
||||||
if (fallbackUrl != null) {
|
if (fallbackUrl != null) {
|
||||||
final ParcelableMedia media = PreviewMediaExtractor.fromLink(fallbackUrl,
|
final ParcelableMedia media = PreviewMediaExtractor.fromLink(fallbackUrl,
|
||||||
mClient, extra);
|
client, extra);
|
||||||
if (media != null && media.media_url != null) {
|
if (media != null && media.media_url != null) {
|
||||||
return getInternal(media.media_url, extra);
|
return getInternal(media.media_url, extra);
|
||||||
} else {
|
} else {
|
||||||
|
@ -124,7 +124,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
||||||
useThumbor = ((MediaExtra) extra).isUseThumbor();
|
useThumbor = ((MediaExtra) extra).isUseThumbor();
|
||||||
UserKey accountKey = ((MediaExtra) extra).getAccountKey();
|
UserKey accountKey = ((MediaExtra) extra).getAccountKey();
|
||||||
if (accountKey != null) {
|
if (accountKey != null) {
|
||||||
account = AccountUtils.getAccountDetails(AccountManager.get(mContext), accountKey, true);
|
account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true);
|
||||||
if (account != null) {
|
if (account != null) {
|
||||||
auth = CredentialsExtensionsKt.getAuthorization(account.credentials);
|
auth = CredentialsExtensionsKt.getAuthorization(account.credentials);
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
||||||
}
|
}
|
||||||
final Uri modifiedUri = getReplacedUri(uri, account != null ? account.credentials.api_url_format : null);
|
final Uri modifiedUri = getReplacedUri(uri, account != null ? account.credentials.api_url_format : null);
|
||||||
final MultiValueMap<String> additionalHeaders = new MultiValueMap<>();
|
final MultiValueMap<String> additionalHeaders = new MultiValueMap<>();
|
||||||
additionalHeaders.add("User-Agent", mUserAgent);
|
additionalHeaders.add("User-Agent", userAgent);
|
||||||
final String method = GET.METHOD;
|
final String method = GET.METHOD;
|
||||||
final String requestUri;
|
final String requestUri;
|
||||||
if (isAuthRequired(uri, account) && auth != null && auth.hasAuthorization()) {
|
if (isAuthRequired(uri, account) && auth != null && auth.hasAuthorization()) {
|
||||||
|
@ -152,8 +152,8 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
||||||
queries, null, null, null, null);
|
queries, null, null, null, null);
|
||||||
additionalHeaders.add("Authorization", auth.getHeader(endpoint, info));
|
additionalHeaders.add("Authorization", auth.getHeader(endpoint, info));
|
||||||
requestUri = modifiedUri.toString();
|
requestUri = modifiedUri.toString();
|
||||||
} else if (mThumbor != null && useThumbor) {
|
} else if (thumbor != null && useThumbor) {
|
||||||
requestUri = mThumbor.buildImage(modifiedUri.toString()).filter(ThumborUrlBuilder.quality(85)).toUrl();
|
requestUri = thumbor.buildImage(Uri.encode(modifiedUri.toString())).filter(ThumborUrlBuilder.quality(85)).toUrl();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
additionalHeaders.add("Accept", "image/webp, */*");
|
additionalHeaders.add("Accept", "image/webp, */*");
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ public class TwidereMediaDownloader implements MediaDownloader, Constants {
|
||||||
builder.url(requestUri);
|
builder.url(requestUri);
|
||||||
builder.headers(additionalHeaders);
|
builder.headers(additionalHeaders);
|
||||||
builder.tag(NoIntercept.INSTANCE);
|
builder.tag(NoIntercept.INSTANCE);
|
||||||
final HttpResponse resp = mClient.newCall(builder.build()).execute();
|
final HttpResponse resp = client.newCall(builder.build()).execute();
|
||||||
if (!resp.isSuccessful()) {
|
if (!resp.isSuccessful()) {
|
||||||
final String detailMessage = "Unable to get " + requestUri + ", response code: "
|
final String detailMessage = "Unable to get " + requestUri + ", response code: "
|
||||||
+ resp.getStatus();
|
+ resp.getStatus();
|
||||||
|
|
|
@ -338,6 +338,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher, APIEditorDi
|
||||||
setSignInButton()
|
setSignInButton()
|
||||||
if (result.alreadyLoggedIn) {
|
if (result.alreadyLoggedIn) {
|
||||||
result.updateAccount(am)
|
result.updateAccount(am)
|
||||||
|
deleteAccountData(contentResolver, result.user.key)
|
||||||
Toast.makeText(this, R.string.message_toast_already_logged_in, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.message_toast_already_logged_in, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
result.addAccount(am, preferences[randomizeAccountNameKey])
|
result.addAccount(am, preferences[randomizeAccountNameKey])
|
||||||
|
|
|
@ -29,7 +29,7 @@ interface IControlBarActivity {
|
||||||
fun notifyControlBarOffsetChanged() {}
|
fun notifyControlBarOffsetChanged() {}
|
||||||
|
|
||||||
interface ControlBarOffsetListener {
|
interface ControlBarOffsetListener {
|
||||||
fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float) {}
|
fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ControlBarShowHideHelper(private val activity: IControlBarActivity) {
|
class ControlBarShowHideHelper(private val activity: IControlBarActivity) {
|
||||||
|
|
|
@ -16,4 +16,6 @@ fun parcelableMediaTypeString(@ParcelableMedia.Type type: Int): String? {
|
||||||
ParcelableMedia.Type.VARIABLE_TYPE -> "variable"
|
ParcelableMedia.Type.VARIABLE_TYPE -> "variable"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ParcelableMedia.aspect_ratio: Double get() = this.width / this.height.toDouble()
|
|
@ -350,6 +350,8 @@ class AccountsDashboardFragment : BaseFragment(), LoaderCallbacks<AccountsInfo>,
|
||||||
val color = ContextCompat.getColor(context, R.color.material_red)
|
val color = ContextCompat.getColor(context, R.color.material_red)
|
||||||
val size = resources.getDimensionPixelSize(R.dimen.element_spacing_msmall)
|
val size = resources.getDimensionPixelSize(R.dimen.element_spacing_msmall)
|
||||||
menu.setMenuItemIcon(R.id.premium_features, BadgeDrawable(icon, color, size))
|
menu.setMenuItemIcon(R.id.premium_features, BadgeDrawable(icon, color, size))
|
||||||
|
} else {
|
||||||
|
menu.setMenuItemIcon(R.id.premium_features, R.drawable.ic_action_infinity)
|
||||||
}
|
}
|
||||||
var hasLists = false
|
var hasLists = false
|
||||||
var hasGroups = false
|
var hasGroups = false
|
||||||
|
|
|
@ -38,11 +38,11 @@ import org.mariotaku.twidere.extension.model.setPosition
|
||||||
import org.mariotaku.twidere.loader.AccountDetailsLoader
|
import org.mariotaku.twidere.loader.AccountDetailsLoader
|
||||||
import org.mariotaku.twidere.model.AccountDetails
|
import org.mariotaku.twidere.model.AccountDetails
|
||||||
import org.mariotaku.twidere.model.UserKey
|
import org.mariotaku.twidere.model.UserKey
|
||||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
|
||||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox
|
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||||
import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox
|
|
||||||
import org.mariotaku.twidere.util.DataStoreUtils
|
import org.mariotaku.twidere.util.DataStoreUtils
|
||||||
import org.mariotaku.twidere.util.IntentUtils
|
import org.mariotaku.twidere.util.IntentUtils
|
||||||
|
import org.mariotaku.twidere.util.deleteAccountData
|
||||||
import org.mariotaku.twidere.util.support.removeAccountSupport
|
import org.mariotaku.twidere.util.support.removeAccountSupport
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,6 +206,9 @@ class AccountsManagerFragment : BaseFragment(), LoaderManager.LoaderCallbacks<Li
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE YOUR ACCOUNT
|
||||||
|
*/
|
||||||
class AccountDeletionDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
|
class AccountDeletionDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener {
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||||
|
@ -215,19 +218,13 @@ class AccountsManagerFragment : BaseFragment(), LoaderManager.LoaderCallbacks<Li
|
||||||
when (which) {
|
when (which) {
|
||||||
DialogInterface.BUTTON_POSITIVE -> {
|
DialogInterface.BUTTON_POSITIVE -> {
|
||||||
val accountKey = account.getAccountKey(am)
|
val accountKey = account.getAccountKey(am)
|
||||||
|
deleteAccountData(resolver, accountKey)
|
||||||
am.removeAccountSupport(account)
|
am.removeAccountSupport(account)
|
||||||
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
|
|
||||||
val whereArgs = arrayOf(accountKey.toString())
|
|
||||||
// Also delete tweets related to the account we previously
|
|
||||||
// deleted.
|
|
||||||
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
|
|
||||||
resolver.delete(Mentions.CONTENT_URI, where, whereArgs)
|
|
||||||
resolver.delete(Inbox.CONTENT_URI, where, whereArgs)
|
|
||||||
resolver.delete(Outbox.CONTENT_URI, where, whereArgs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val context = context
|
val context = context
|
||||||
val builder = AlertDialog.Builder(context)
|
val builder = AlertDialog.Builder(context)
|
||||||
|
|
|
@ -14,7 +14,9 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.MediaController
|
import android.widget.MediaController
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.SeekBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import com.commonsware.cwac.layouts.AspectLockedFrameLayout.AspectRatioSource
|
||||||
import edu.tsinghua.hotmobi.HotMobiLogger
|
import edu.tsinghua.hotmobi.HotMobiLogger
|
||||||
import edu.tsinghua.hotmobi.model.MediaDownloadEvent
|
import edu.tsinghua.hotmobi.model.MediaDownloadEvent
|
||||||
import kotlinx.android.synthetic.main.layout_media_viewer_texture_video_view.*
|
import kotlinx.android.synthetic.main.layout_media_viewer_texture_video_view.*
|
||||||
|
@ -25,6 +27,7 @@ import org.mariotaku.twidere.R
|
||||||
import org.mariotaku.twidere.TwidereConstants.EXTRA_ACCOUNT_KEY
|
import org.mariotaku.twidere.TwidereConstants.EXTRA_ACCOUNT_KEY
|
||||||
import org.mariotaku.twidere.TwidereConstants.EXTRA_MEDIA
|
import org.mariotaku.twidere.TwidereConstants.EXTRA_MEDIA
|
||||||
import org.mariotaku.twidere.activity.MediaViewerActivity
|
import org.mariotaku.twidere.activity.MediaViewerActivity
|
||||||
|
import org.mariotaku.twidere.activity.iface.IControlBarActivity
|
||||||
import org.mariotaku.twidere.model.ParcelableMedia
|
import org.mariotaku.twidere.model.ParcelableMedia
|
||||||
import org.mariotaku.twidere.model.UserKey
|
import org.mariotaku.twidere.model.UserKey
|
||||||
import org.mariotaku.twidere.util.media.MediaExtra
|
import org.mariotaku.twidere.util.media.MediaExtra
|
||||||
|
@ -32,14 +35,105 @@ import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPreparedListener,
|
class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPreparedListener,
|
||||||
MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, View.OnClickListener {
|
MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener, View.OnClickListener, IControlBarActivity.ControlBarOffsetListener {
|
||||||
|
|
||||||
private var mPlayAudio: Boolean = false
|
private var playAudio: Boolean = false
|
||||||
private var mVideoProgressRunnable: VideoPlayProgressRunnable? = null
|
|
||||||
private var mediaPlayer: MediaPlayer? = null
|
private var mediaPlayer: MediaPlayer? = null
|
||||||
private var mMediaPlayerError: Int = 0
|
private var mediaPlayerError: Int = 0
|
||||||
|
private var videoProgressRunnable: VideoPlayProgressRunnable? = null
|
||||||
private var mediaDownloadEvent: MediaDownloadEvent? = null
|
private var mediaDownloadEvent: MediaDownloadEvent? = null
|
||||||
|
|
||||||
|
private val isLoopEnabled: Boolean get() = arguments.getBoolean(EXTRA_LOOP, false)
|
||||||
|
|
||||||
|
private val media: ParcelableMedia? get() = arguments.getParcelable<ParcelableMedia>(EXTRA_MEDIA)
|
||||||
|
|
||||||
|
private val accountKey: UserKey get() = arguments.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
|
||||||
|
|
||||||
|
private var aspectRatioSource = object : AspectRatioSource {
|
||||||
|
override fun getHeight(): Int {
|
||||||
|
val height = media?.height ?: 0
|
||||||
|
if (height <= 0) return view!!.measuredHeight
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getWidth(): Int {
|
||||||
|
val width = media?.width ?: 0
|
||||||
|
if (width <= 0) return view!!.measuredWidth
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
|
||||||
|
var handler: Handler? = videoViewProgress.handler
|
||||||
|
if (handler == null) {
|
||||||
|
handler = Handler(activity.mainLooper)
|
||||||
|
}
|
||||||
|
|
||||||
|
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
|
||||||
|
// Play audio by default if ringer mode on
|
||||||
|
playAudio = am.ringerMode == AudioManager.RINGER_MODE_NORMAL
|
||||||
|
|
||||||
|
videoProgressRunnable = VideoPlayProgressRunnable(handler, videoViewProgress,
|
||||||
|
durationLabel, positionLabel, videoView)
|
||||||
|
|
||||||
|
|
||||||
|
videoViewOverlay.setOnClickListener(this)
|
||||||
|
videoView.setOnPreparedListener(this)
|
||||||
|
videoView.setOnErrorListener(this)
|
||||||
|
videoView.setOnCompletionListener(this)
|
||||||
|
|
||||||
|
playPauseButton.setOnClickListener(this)
|
||||||
|
volumeButton.setOnClickListener(this)
|
||||||
|
videoControl.visibility = View.GONE
|
||||||
|
videoContainer.setAspectRatioSource(aspectRatioSource)
|
||||||
|
videoViewProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
|
private var paused: Boolean = false
|
||||||
|
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
if (!fromUser) return
|
||||||
|
val mp = mediaPlayer ?: return
|
||||||
|
val duration = mp.duration
|
||||||
|
if (duration <= 0) return
|
||||||
|
mp.seekTo(Math.round(duration * (progress.toFloat() / seekBar.max)))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {
|
||||||
|
paused = pauseVideo()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {
|
||||||
|
if (paused) {
|
||||||
|
resumeVideo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
startLoading(false)
|
||||||
|
setMediaViewVisible(false)
|
||||||
|
updateVolume()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onAttach(context: Context?) {
|
||||||
|
super.onAttach(context)
|
||||||
|
if (context is IControlBarActivity) {
|
||||||
|
context.registerControlBarOffsetListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetach() {
|
||||||
|
val activity = activity
|
||||||
|
if (activity is IControlBarActivity) {
|
||||||
|
activity.unregisterControlBarOffsetListener(this)
|
||||||
|
}
|
||||||
|
super.onDetach()
|
||||||
|
}
|
||||||
|
|
||||||
override fun getDownloadExtra(): Any? {
|
override fun getDownloadExtra(): Any? {
|
||||||
val extra = MediaExtra()
|
val extra = MediaExtra()
|
||||||
extra.isUseThumbor = false
|
extra.isUseThumbor = false
|
||||||
|
@ -50,29 +144,24 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
|
||||||
return extra
|
return extra
|
||||||
}
|
}
|
||||||
|
|
||||||
val isLoopEnabled: Boolean
|
|
||||||
get() = arguments.getBoolean(EXTRA_LOOP, false)
|
|
||||||
|
|
||||||
override fun isAbleToLoad(): Boolean {
|
override fun isAbleToLoad(): Boolean {
|
||||||
return downloadUri != null
|
return downloadUri != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun getDownloadUri(): Uri? {
|
override fun getDownloadUri(): Uri? {
|
||||||
val bestVideoUrlAndType = getBestVideoUrlAndType(media,
|
val bestVideoUrlAndType = getBestVideoUrlAndType(media, SUPPORTED_VIDEO_TYPES)
|
||||||
SUPPORTED_VIDEO_TYPES)
|
|
||||||
if (bestVideoUrlAndType != null && bestVideoUrlAndType.first != null) {
|
if (bestVideoUrlAndType != null && bestVideoUrlAndType.first != null) {
|
||||||
return Uri.parse(bestVideoUrlAndType.first)
|
return Uri.parse(bestVideoUrlAndType.first)
|
||||||
}
|
}
|
||||||
return arguments.getParcelable<Uri>(SubsampleImageViewerFragment.EXTRA_MEDIA_URI)
|
return arguments.getParcelable<Uri>(SubsampleImageViewerFragment.EXTRA_MEDIA_URI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun displayMedia(result: CacheDownloadLoader.Result) {
|
override fun displayMedia(result: CacheDownloadLoader.Result) {
|
||||||
videoView.setVideoURI(result.cacheUri)
|
videoView.setVideoURI(result.cacheUri)
|
||||||
videoControl.visibility = View.GONE
|
videoControl.visibility = View.GONE
|
||||||
setMediaViewVisible(true)
|
setMediaViewVisible(true)
|
||||||
val activity = activity
|
activity.supportInvalidateOptionsMenu()
|
||||||
activity?.supportInvalidateOptionsMenu()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun recycleMedia() {
|
override fun recycleMedia() {
|
||||||
|
@ -81,29 +170,31 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
|
||||||
|
|
||||||
override fun onCompletion(mp: MediaPlayer) {
|
override fun onCompletion(mp: MediaPlayer) {
|
||||||
updatePlayerState()
|
updatePlayerState()
|
||||||
// mVideoViewProgress.removeCallbacks(mVideoProgressRunnable);
|
}
|
||||||
// mVideoViewProgress.setVisibility(View.GONE);
|
|
||||||
|
override fun onControlBarOffsetChanged(activity: IControlBarActivity, offset: Float) {
|
||||||
|
videoControl.translationY = (1 - offset) * videoControl.height
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
|
override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
|
||||||
mediaPlayer = null
|
mediaPlayer = null
|
||||||
videoViewProgress.removeCallbacks(mVideoProgressRunnable)
|
videoViewProgress.removeCallbacks(videoProgressRunnable)
|
||||||
videoViewProgress.visibility = View.GONE
|
videoViewProgress.visibility = View.GONE
|
||||||
videoControl.visibility = View.GONE
|
videoControl.visibility = View.GONE
|
||||||
mMediaPlayerError = what
|
mediaPlayerError = what
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepared(mp: MediaPlayer) {
|
override fun onPrepared(mp: MediaPlayer) {
|
||||||
if (userVisibleHint) {
|
if (userVisibleHint) {
|
||||||
mediaPlayer = mp
|
mediaPlayer = mp
|
||||||
mMediaPlayerError = 0
|
mediaPlayerError = 0
|
||||||
mp.setScreenOnWhilePlaying(true)
|
mp.setScreenOnWhilePlaying(true)
|
||||||
updateVolume()
|
updateVolume()
|
||||||
mp.isLooping = isLoopEnabled
|
mp.isLooping = isLoopEnabled
|
||||||
mp.start()
|
mp.start()
|
||||||
videoViewProgress.visibility = View.VISIBLE
|
videoViewProgress.visibility = View.VISIBLE
|
||||||
videoViewProgress.post(mVideoProgressRunnable)
|
videoViewProgress.post(videoProgressRunnable)
|
||||||
updatePlayerState()
|
updatePlayerState()
|
||||||
videoControl.visibility = View.VISIBLE
|
videoControl.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
@ -111,10 +202,10 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
|
||||||
|
|
||||||
private fun updateVolume() {
|
private fun updateVolume() {
|
||||||
|
|
||||||
volumeButton.setImageResource(if (mPlayAudio) R.drawable.ic_action_speaker_max else R.drawable.ic_action_speaker_muted)
|
volumeButton.setImageResource(if (playAudio) R.drawable.ic_action_speaker_max else R.drawable.ic_action_speaker_muted)
|
||||||
val mp = mediaPlayer ?: return
|
val mp = mediaPlayer ?: return
|
||||||
try {
|
try {
|
||||||
if (mPlayAudio) {
|
if (playAudio) {
|
||||||
mp.setVolume(1f, 1f)
|
mp.setVolume(1f, 1f)
|
||||||
} else {
|
} else {
|
||||||
mp.setVolume(0f, 0f)
|
mp.setVolume(0f, 0f)
|
||||||
|
@ -136,103 +227,28 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
|
|
||||||
var handler: Handler? = videoViewProgress.handler
|
|
||||||
if (handler == null) {
|
|
||||||
handler = Handler(activity.mainLooper)
|
|
||||||
}
|
|
||||||
|
|
||||||
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
|
||||||
|
|
||||||
// Play audio by default if ringer mode on
|
|
||||||
mPlayAudio = am.ringerMode == AudioManager.RINGER_MODE_NORMAL
|
|
||||||
|
|
||||||
mVideoProgressRunnable = VideoPlayProgressRunnable(handler, videoViewProgress,
|
|
||||||
durationLabel, positionLabel, videoView)
|
|
||||||
|
|
||||||
|
|
||||||
videoViewOverlay.setOnClickListener(this)
|
|
||||||
videoView.setOnPreparedListener(this)
|
|
||||||
videoView.setOnErrorListener(this)
|
|
||||||
videoView.setOnCompletionListener(this)
|
|
||||||
|
|
||||||
playPauseButton.setOnClickListener(this)
|
|
||||||
volumeButton.setOnClickListener(this)
|
|
||||||
videoControl.visibility = View.GONE
|
|
||||||
startLoading(false)
|
|
||||||
setMediaViewVisible(false)
|
|
||||||
updateVolume()
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SwitchIntDef")
|
|
||||||
private fun getBestVideoUrlAndType(media: ParcelableMedia?,
|
|
||||||
supportedTypes: Array<String>): Pair<String, String>? {
|
|
||||||
if (media == null) return null
|
|
||||||
when (media.type) {
|
|
||||||
ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF -> {
|
|
||||||
if (media.video_info == null) {
|
|
||||||
return Pair.create<String, String>(media.media_url, null)
|
|
||||||
}
|
|
||||||
val firstMatch = media.video_info.variants.first { variant ->
|
|
||||||
supportedTypes.any { it.equals(variant.content_type, ignoreCase = true) }
|
|
||||||
} ?: return null
|
|
||||||
return Pair.create(firstMatch.url, firstMatch.content_type)
|
|
||||||
}
|
|
||||||
ParcelableMedia.Type.CARD_ANIMATED_GIF -> {
|
|
||||||
return Pair.create<String, String>(media.media_url, "video/mp4")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.volumeButton -> {
|
R.id.volumeButton -> {
|
||||||
mPlayAudio = !mPlayAudio
|
playAudio = !playAudio
|
||||||
updateVolume()
|
updateVolume()
|
||||||
}
|
}
|
||||||
R.id.playPauseButton -> {
|
R.id.playPauseButton -> {
|
||||||
val mp = mediaPlayer
|
val mp = mediaPlayer ?: return
|
||||||
if (mp != null) {
|
if (mp.isPlaying) {
|
||||||
if (mp.isPlaying) {
|
mp.pause()
|
||||||
mp.pause()
|
} else {
|
||||||
} else {
|
mp.start()
|
||||||
mp.start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
updatePlayerState()
|
updatePlayerState()
|
||||||
}
|
}
|
||||||
R.id.videoViewOverlay -> {
|
R.id.videoViewOverlay -> {
|
||||||
val activity = activity as MediaViewerActivity
|
val activity = activity as MediaViewerActivity
|
||||||
if (videoControl.visibility == View.VISIBLE) {
|
activity.setBarVisibility(!activity.isBarShowing)
|
||||||
videoControl.visibility = View.GONE
|
|
||||||
activity.setBarVisibility(false)
|
|
||||||
} else {
|
|
||||||
videoControl.visibility = View.VISIBLE
|
|
||||||
activity.setBarVisibility(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePlayerState() {
|
|
||||||
val mp = mediaPlayer
|
|
||||||
if (mp != null) {
|
|
||||||
val playing = mp.isPlaying
|
|
||||||
playPauseButton.contentDescription = getString(if (playing) R.string.pause else R.string.play)
|
|
||||||
playPauseButton.setImageResource(if (playing) R.drawable.ic_action_pause else R.drawable.ic_action_play_arrow)
|
|
||||||
} else {
|
|
||||||
playPauseButton.contentDescription = getString(R.string.play)
|
|
||||||
playPauseButton.setImageResource(R.drawable.ic_action_play_arrow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateMediaView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateMediaView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
return inflater.inflate(R.layout.layout_media_viewer_texture_video_view, container, false)
|
return inflater.inflate(R.layout.layout_media_viewer_texture_video_view, container, false)
|
||||||
}
|
}
|
||||||
|
@ -267,11 +283,62 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val media: ParcelableMedia?
|
|
||||||
get() = arguments.getParcelable<ParcelableMedia>(EXTRA_MEDIA)
|
|
||||||
|
|
||||||
private val accountKey: UserKey
|
@SuppressLint("SwitchIntDef")
|
||||||
get() = arguments.getParcelable<UserKey>(EXTRA_ACCOUNT_KEY)
|
private fun getBestVideoUrlAndType(media: ParcelableMedia?, supportedTypes: Array<String>): Pair<String, String>? {
|
||||||
|
if (media == null) return null
|
||||||
|
when (media.type) {
|
||||||
|
ParcelableMedia.Type.VIDEO, ParcelableMedia.Type.ANIMATED_GIF -> {
|
||||||
|
if (media.video_info == null) {
|
||||||
|
return Pair.create<String, String>(media.media_url, null)
|
||||||
|
}
|
||||||
|
val firstMatch = media.video_info.variants.first { variant ->
|
||||||
|
supportedTypes.any { it.equals(variant.content_type, ignoreCase = true) }
|
||||||
|
} ?: return null
|
||||||
|
return Pair.create(firstMatch.url, firstMatch.content_type)
|
||||||
|
}
|
||||||
|
ParcelableMedia.Type.CARD_ANIMATED_GIF -> {
|
||||||
|
return Pair.create<String, String>(media.media_url, "video/mp4")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePlayerState() {
|
||||||
|
val mp = mediaPlayer
|
||||||
|
if (mp != null) {
|
||||||
|
val playing = mp.isPlaying
|
||||||
|
playPauseButton.contentDescription = getString(if (playing) R.string.pause else R.string.play)
|
||||||
|
playPauseButton.setImageResource(if (playing) R.drawable.ic_action_pause else R.drawable.ic_action_play_arrow)
|
||||||
|
} else {
|
||||||
|
playPauseButton.contentDescription = getString(R.string.play)
|
||||||
|
playPauseButton.setImageResource(R.drawable.ic_action_play_arrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pauseVideo(): Boolean {
|
||||||
|
val mp = mediaPlayer ?: return false
|
||||||
|
var result = false
|
||||||
|
if (mp.isPlaying) {
|
||||||
|
mp.pause()
|
||||||
|
result = true
|
||||||
|
}
|
||||||
|
updatePlayerState()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resumeVideo(): Boolean {
|
||||||
|
val mp = mediaPlayer ?: return false
|
||||||
|
var result = false
|
||||||
|
if (!mp.isPlaying) {
|
||||||
|
mp.start()
|
||||||
|
result = true
|
||||||
|
}
|
||||||
|
updatePlayerState()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
private class VideoPlayProgressRunnable internal constructor(
|
private class VideoPlayProgressRunnable internal constructor(
|
||||||
private val handler: Handler,
|
private val handler: Handler,
|
||||||
|
@ -302,7 +369,7 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
|
||||||
|
|
||||||
const val EXTRA_LOOP = "loop"
|
const val EXTRA_LOOP = "loop"
|
||||||
private val SUPPORTED_VIDEO_TYPES: Array<String>
|
private val SUPPORTED_VIDEO_TYPES: Array<String>
|
||||||
private val FALLBACK_VIDEO_TYPES: Array<String>
|
private val FALLBACK_VIDEO_TYPES: Array<String> = arrayOf("video/mp4")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||||
|
@ -310,7 +377,6 @@ class VideoPageFragment : CacheDownloadMediaViewerFragment(), MediaPlayer.OnPrep
|
||||||
} else {
|
} else {
|
||||||
SUPPORTED_VIDEO_TYPES = arrayOf("video/webm", "video/mp4")
|
SUPPORTED_VIDEO_TYPES = arrayOf("video/webm", "video/mp4")
|
||||||
}
|
}
|
||||||
FALLBACK_VIDEO_TYPES = arrayOf("video/mp4")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package org.mariotaku.twidere.util
|
package org.mariotaku.twidere.util
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -11,6 +12,7 @@ import org.mariotaku.twidere.constant.filterPossibilitySensitiveStatusesKey
|
||||||
import org.mariotaku.twidere.constant.filterUnavailableQuoteStatusesKey
|
import org.mariotaku.twidere.constant.filterUnavailableQuoteStatusesKey
|
||||||
import org.mariotaku.twidere.model.DraftCursorIndices
|
import org.mariotaku.twidere.model.DraftCursorIndices
|
||||||
import org.mariotaku.twidere.model.ParcelableStatus.FilterFlags
|
import org.mariotaku.twidere.model.ParcelableStatus.FilterFlags
|
||||||
|
import org.mariotaku.twidere.model.UserKey
|
||||||
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,3 +102,14 @@ fun deleteDrafts(context: Context, draftIds: LongArray): Int {
|
||||||
}
|
}
|
||||||
return context.contentResolver.delete(Drafts.CONTENT_URI, where, whereArgs)
|
return context.contentResolver.delete(Drafts.CONTENT_URI, where, whereArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteAccountData(resolver: ContentResolver, accountKey: UserKey) {
|
||||||
|
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
|
||||||
|
val whereArgs = arrayOf(accountKey.toString())
|
||||||
|
// Also delete tweets related to the account we previously
|
||||||
|
// deleted.
|
||||||
|
resolver.delete(Statuses.CONTENT_URI, where, whereArgs)
|
||||||
|
resolver.delete(Mentions.CONTENT_URI, where, whereArgs)
|
||||||
|
resolver.delete(DirectMessages.Inbox.CONTENT_URI, where, whereArgs)
|
||||||
|
resolver.delete(DirectMessages.Outbox.CONTENT_URI, where, whereArgs)
|
||||||
|
}
|
|
@ -69,7 +69,7 @@ class ReadStateManager(context: Context) {
|
||||||
preferences.unregisterOnSharedPreferenceChangeListener(listener)
|
preferences.unregisterOnSharedPreferenceChangeListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads fun setPosition(key: String, keyId: String, position: Long, acceptOlder: Boolean = false): Boolean {
|
fun setPosition(key: String, keyId: String, position: Long, acceptOlder: Boolean = false): Boolean {
|
||||||
if (TextUtils.isEmpty(key)) return false
|
if (TextUtils.isEmpty(key)) return false
|
||||||
val set: MutableSet<String> = preferences.getStringSet(key, null) ?: CompactHashSet<String>()
|
val set: MutableSet<String> = preferences.getStringSet(key, null) ?: CompactHashSet<String>()
|
||||||
val prefix = keyId + ":"
|
val prefix = keyId + ":"
|
||||||
|
|
|
@ -46,25 +46,22 @@ class MediaSwipeCloseContainer(context: Context, attrs: AttributeSet? = null) :
|
||||||
val container = this@MediaSwipeCloseContainer
|
val container = this@MediaSwipeCloseContainer
|
||||||
val minVel = ViewConfiguration.get(context).scaledMinimumFlingVelocity
|
val minVel = ViewConfiguration.get(context).scaledMinimumFlingVelocity
|
||||||
when {
|
when {
|
||||||
yvel > minVel -> {
|
yvel > minVel && childTop > 0 -> {
|
||||||
// Settle downward
|
// Settle downward
|
||||||
container.dragHelper.settleCapturedViewAt(0, container.height)
|
container.dragHelper.settleCapturedViewAt(0, container.height)
|
||||||
}
|
}
|
||||||
yvel < -minVel -> {
|
yvel < -minVel && childTop < 0 -> {
|
||||||
// Settle upward
|
// Settle upward
|
||||||
container.dragHelper.settleCapturedViewAt(0, -container.height)
|
container.dragHelper.settleCapturedViewAt(0, -container.height)
|
||||||
|
|
||||||
}
|
}
|
||||||
else -> when {
|
yvel <= 0 && childTop < -container.height / 4 -> {
|
||||||
childTop < -container.height / 4 -> {
|
container.dragHelper.smoothSlideViewTo(releasedChild, 0, -container.height)
|
||||||
container.dragHelper.smoothSlideViewTo(releasedChild, 0, -container.height)
|
}
|
||||||
}
|
yvel >= 0 && childTop > container.height / 4 -> {
|
||||||
childTop > container.height / 4 -> {
|
container.dragHelper.smoothSlideViewTo(releasedChild, 0, container.height)
|
||||||
container.dragHelper.smoothSlideViewTo(releasedChild, 0, container.height)
|
}
|
||||||
}
|
else -> {
|
||||||
else -> {
|
container.dragHelper.smoothSlideViewTo(releasedChild, 0, 0)
|
||||||
container.dragHelper.smoothSlideViewTo(releasedChild, 0, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewCompat.postInvalidateOnAnimation(container)
|
ViewCompat.postInvalidateOnAnimation(container)
|
||||||
|
|
|
@ -26,11 +26,18 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center">
|
android:layout_gravity="center">
|
||||||
|
|
||||||
<com.sprylab.android.widget.TextureVideoView
|
<com.commonsware.cwac.layouts.AspectLockedFrameLayout
|
||||||
android:id="@+id/videoView"
|
android:id="@+id/videoContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_centerInParent="true"/>
|
android:layout_centerInParent="true">
|
||||||
|
|
||||||
|
<com.sprylab.android.widget.TextureVideoView
|
||||||
|
android:id="@+id/videoView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
||||||
|
|
||||||
|
</com.commonsware.cwac.layouts.AspectLockedFrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/videoViewOverlay"
|
android:id="@+id/videoViewOverlay"
|
||||||
|
@ -72,7 +79,7 @@
|
||||||
android:textSize="@dimen/text_size_extra_small"
|
android:textSize="@dimen/text_size_extra_small"
|
||||||
tools:text="--:--"/>
|
tools:text="--:--"/>
|
||||||
|
|
||||||
<ProgressBar
|
<SeekBar
|
||||||
android:id="@+id/videoViewProgress"
|
android:id="@+id/videoViewProgress"
|
||||||
style="?android:progressBarStyleHorizontal"
|
style="?android:progressBarStyleHorizontal"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -81,8 +88,7 @@
|
||||||
android:layout_toEndOf="@+id/playPauseButton"
|
android:layout_toEndOf="@+id/playPauseButton"
|
||||||
android:layout_toLeftOf="@+id/volumeButton"
|
android:layout_toLeftOf="@+id/volumeButton"
|
||||||
android:layout_toRightOf="@+id/playPauseButton"
|
android:layout_toRightOf="@+id/playPauseButton"
|
||||||
android:layout_toStartOf="@+id/volumeButton"
|
android:layout_toStartOf="@+id/volumeButton"/>
|
||||||
android:indeterminate="false"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/durationLabel"
|
android:id="@+id/durationLabel"
|
||||||
|
|
Loading…
Reference in New Issue