(試験実装)アプリ設定に「添付メディア/内蔵メディアビューアを使う」を追加
|
@ -88,6 +88,9 @@ dependencies {
|
|||
|
||||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||
|
||||
|
||||
implementation 'com.google.android.exoplayer:exoplayer:r2.5.4'
|
||||
}
|
||||
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
|
|
@ -204,6 +204,10 @@
|
|||
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
|
||||
/>
|
||||
|
||||
<activity
|
||||
android:name=".ActMediaViewer"
|
||||
/>
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="100.0"
|
||||
|
@ -221,6 +225,14 @@
|
|||
/>
|
||||
</provider>
|
||||
|
||||
<receiver android:name=".DownloadReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED" />
|
||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
<!-- okhttp3クライアントを指定する必要があるため、マニフェスト経由での組み込みは行わない -->
|
||||
<!--<meta-data-->
|
||||
<!--android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"-->
|
||||
|
|
|
@ -106,6 +106,7 @@ public class ActAppSetting extends AppCompatActivity
|
|||
Switch swShortAcctLocalUser;
|
||||
Switch swDisableEmojiAnimation;
|
||||
Switch swAllowNonSpaceBeforeEmojiShortcode;
|
||||
Switch swUseInternalMediaViewer;
|
||||
|
||||
Spinner spBackButtonAction;
|
||||
Spinner spUITheme;
|
||||
|
@ -231,6 +232,8 @@ public class ActAppSetting extends AppCompatActivity
|
|||
swAllowNonSpaceBeforeEmojiShortcode= findViewById( R.id.swAllowNonSpaceBeforeEmojiShortcode );
|
||||
swAllowNonSpaceBeforeEmojiShortcode.setOnCheckedChangeListener( this );
|
||||
|
||||
swUseInternalMediaViewer= findViewById( R.id.swUseInternalMediaViewer );
|
||||
swUseInternalMediaViewer.setOnCheckedChangeListener( this );
|
||||
|
||||
cbNotificationSound = findViewById( R.id.cbNotificationSound );
|
||||
cbNotificationVibration = findViewById( R.id.cbNotificationVibration );
|
||||
|
@ -395,7 +398,7 @@ public class ActAppSetting extends AppCompatActivity
|
|||
swShortAcctLocalUser.setChecked( pref.getBoolean( Pref.KEY_SHORT_ACCT_LOCAL_USER, false ) );
|
||||
swDisableEmojiAnimation.setChecked( pref.getBoolean( Pref.KEY_DISABLE_EMOJI_ANIMATION, false ) );
|
||||
swAllowNonSpaceBeforeEmojiShortcode.setChecked( pref.getBoolean( Pref.KEY_ALLOW_NON_SPACE_BEFORE_EMOJI_SHORTCODE, false ) );
|
||||
|
||||
swUseInternalMediaViewer.setChecked( pref.getBoolean( Pref.KEY_USE_INTERNAL_MEDIA_VIEWER, false ) );
|
||||
// Switch with default true
|
||||
swDisableFastScroller.setChecked( pref.getBoolean( Pref.KEY_DISABLE_FAST_SCROLLER, true ) );
|
||||
swPriorChrome.setChecked( pref.getBoolean( Pref.KEY_PRIOR_CHROME, true ) );
|
||||
|
@ -470,6 +473,7 @@ public class ActAppSetting extends AppCompatActivity
|
|||
.putBoolean( Pref.KEY_SHORT_ACCT_LOCAL_USER, swShortAcctLocalUser.isChecked() )
|
||||
.putBoolean( Pref.KEY_DISABLE_EMOJI_ANIMATION, swDisableEmojiAnimation.isChecked() )
|
||||
.putBoolean( Pref.KEY_ALLOW_NON_SPACE_BEFORE_EMOJI_SHORTCODE, swAllowNonSpaceBeforeEmojiShortcode.isChecked() )
|
||||
.putBoolean( Pref.KEY_USE_INTERNAL_MEDIA_VIEWER, swUseInternalMediaViewer.isChecked() )
|
||||
|
||||
|
||||
.putBoolean( Pref.KEY_NOTIFICATION_SOUND, cbNotificationSound.isChecked() )
|
||||
|
|
|
@ -1825,28 +1825,8 @@ public class ActMain extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
do{
|
||||
if( pref.getBoolean( Pref.KEY_PRIOR_CHROME, true ) ){
|
||||
try{
|
||||
// 初回はChrome指定で試す
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor( Styler.getAttributeColor( this, R.attr.colorPrimary ) ).setShowTitle( true );
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
customTabsIntent.intent.setComponent( new ComponentName( "com.android.chrome", "com.google.android.apps.chrome.Main" ) );
|
||||
customTabsIntent.launchUrl( this, Uri.parse( url ) );
|
||||
break;
|
||||
}catch( Throwable ex2 ){
|
||||
log.e( ex2, "openChromeTab: missing chrome. retry to other application." );
|
||||
}
|
||||
}
|
||||
|
||||
// chromeがないなら ResolverActivity でアプリを選択させる
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor( Styler.getAttributeColor( this, R.attr.colorPrimary ) ).setShowTitle( true );
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
customTabsIntent.launchUrl( this, Uri.parse( url ) );
|
||||
|
||||
}while( false );
|
||||
App1.openCustomTab( this,url);
|
||||
|
||||
|
||||
}catch( Throwable ex ){
|
||||
// log.trace( ex );
|
||||
|
|
|
@ -0,0 +1,570 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.ui.SimpleExoPlayerView;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import jp.juggler.subwaytooter.api.TootApiResult;
|
||||
import jp.juggler.subwaytooter.api.TootApiTask;
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.dialog.ActionsDialog;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
import jp.juggler.subwaytooter.view.PinchBitmapView;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class ActMediaViewer extends AppCompatActivity implements View.OnClickListener {
|
||||
|
||||
static final LogCategory log = new LogCategory( "ActMediaViewer" );
|
||||
|
||||
static String encodeMediaList( @Nullable TootAttachment.List list ){
|
||||
JSONArray a = new JSONArray();
|
||||
if( list != null ){
|
||||
for( TootAttachment ta : list ){
|
||||
try{
|
||||
JSONObject item = ta.encodeJSON();
|
||||
a.put( item );
|
||||
}catch( JSONException ex ){
|
||||
log.e( ex, "encode failed." );
|
||||
}
|
||||
}
|
||||
}
|
||||
return a.toString();
|
||||
}
|
||||
|
||||
static TootAttachment.List decodeMediaList( @Nullable String src ){
|
||||
TootAttachment.List dst_list = new TootAttachment.List();
|
||||
if( src != null ){
|
||||
try{
|
||||
JSONArray a = new JSONArray( src );
|
||||
for( int i = 0, ie = a.length() ; i < ie ; ++ i ){
|
||||
JSONObject obj = a.optJSONObject( i );
|
||||
TootAttachment ta = TootAttachment.parse( obj );
|
||||
if( ta != null ) dst_list.add( ta );
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
log.e( ex, "decodeMediaList failed." );
|
||||
}
|
||||
}
|
||||
return dst_list;
|
||||
}
|
||||
|
||||
static final String EXTRA_IDX = "idx";
|
||||
static final String EXTRA_DATA = "data";
|
||||
|
||||
public static void open( @NonNull ActMain activity, @NonNull TootAttachment.List list, int idx ){
|
||||
Intent intent = new Intent( activity, ActMediaViewer.class );
|
||||
intent.putExtra( EXTRA_IDX, idx );
|
||||
JSONArray a = new JSONArray();
|
||||
for( TootAttachment ta : list ){
|
||||
try{
|
||||
JSONObject item = ta.encodeJSON();
|
||||
a.put( item );
|
||||
}catch( JSONException ex ){
|
||||
log.e( ex, "encode failed." );
|
||||
}
|
||||
}
|
||||
intent.putExtra( EXTRA_DATA, encodeMediaList( list ) );
|
||||
activity.startActivity( intent );
|
||||
}
|
||||
|
||||
int idx;
|
||||
TootAttachment.List media_list;
|
||||
|
||||
@Override protected void onSaveInstanceState( Bundle outState ){
|
||||
super.onSaveInstanceState( outState );
|
||||
outState.putInt( EXTRA_IDX, idx );
|
||||
outState.putString( EXTRA_DATA, encodeMediaList( media_list ) );
|
||||
}
|
||||
|
||||
@Override protected void onCreate( @Nullable Bundle savedInstanceState ){
|
||||
super.onCreate( savedInstanceState );
|
||||
App1.setActivityTheme( this, true );
|
||||
requestWindowFeature( Window.FEATURE_NO_TITLE );
|
||||
|
||||
if( savedInstanceState == null ){
|
||||
Intent intent = getIntent();
|
||||
this.idx = intent.getIntExtra( EXTRA_IDX, idx );
|
||||
this.media_list = decodeMediaList( intent.getStringExtra( EXTRA_DATA ) );
|
||||
}else{
|
||||
this.idx = savedInstanceState.getInt( EXTRA_IDX );
|
||||
this.media_list = decodeMediaList( savedInstanceState.getString( EXTRA_DATA ) );
|
||||
}
|
||||
|
||||
if( idx < 0 || idx >= media_list.size() ) idx = 0;
|
||||
|
||||
initUI();
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@Override protected void onDestroy(){
|
||||
super.onDestroy();
|
||||
pbvImage.setBitmap( null );
|
||||
exoPlayer.release();
|
||||
exoPlayer = null;
|
||||
}
|
||||
|
||||
PinchBitmapView pbvImage;
|
||||
View btnPrevious;
|
||||
View btnNext;
|
||||
TextView tvError;
|
||||
SimpleExoPlayer exoPlayer;
|
||||
SimpleExoPlayerView exoView;
|
||||
|
||||
void initUI(){
|
||||
setContentView( R.layout.act_media_viewer );
|
||||
pbvImage = findViewById( R.id.pbvImage );
|
||||
btnPrevious = findViewById( R.id.btnPrevious );
|
||||
btnNext = findViewById( R.id.btnNext );
|
||||
exoView = findViewById( R.id.exoView );
|
||||
|
||||
tvError = findViewById( R.id.tvError );
|
||||
|
||||
boolean enablePaging = media_list.size() > 1;
|
||||
btnPrevious.setEnabled( enablePaging );
|
||||
btnNext.setEnabled( enablePaging );
|
||||
btnPrevious.setAlpha( enablePaging ? 1f : 0.3f );
|
||||
btnNext.setAlpha( enablePaging ? 1f : 0.3f );
|
||||
|
||||
btnPrevious.setOnClickListener( this );
|
||||
btnNext.setOnClickListener( this );
|
||||
findViewById( R.id.btnDownload ).setOnClickListener( this );
|
||||
findViewById( R.id.btnMore ).setOnClickListener( this );
|
||||
|
||||
// findViewById( R.id.btnBrowser ).setOnClickListener( this );
|
||||
// findViewById( R.id.btnShare ).setOnClickListener( this );
|
||||
// findViewById( R.id.btnCopy ).setOnClickListener( this );
|
||||
|
||||
pbvImage.setCallback( new PinchBitmapView.Callback() {
|
||||
@Override public void onSwipe( int delta ){
|
||||
if( isDestroyed() ) return;
|
||||
loadDelta( delta );
|
||||
}
|
||||
} );
|
||||
|
||||
exoPlayer = ExoPlayerFactory.newSimpleInstance( this, new DefaultTrackSelector());
|
||||
|
||||
exoView.setPlayer( exoPlayer );
|
||||
|
||||
exoPlayer.addListener( new Player.EventListener() {
|
||||
@Override public void onTimelineChanged( Timeline timeline, Object manifest ){
|
||||
log.d("exoPlayer onTimelineChanged");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged( TrackGroupArray trackGroups, TrackSelectionArray trackSelections ){
|
||||
log.d("exoPlayer onTracksChanged");
|
||||
|
||||
}
|
||||
|
||||
@Override public void onLoadingChanged( boolean isLoading ){
|
||||
log.d("exoPlayer onLoadingChanged");
|
||||
}
|
||||
|
||||
@Override public void onPlayerStateChanged( boolean playWhenReady, int playbackState ){
|
||||
log.d("exoPlayer onPlayerStateChanged %s %s",playWhenReady,playbackState);
|
||||
|
||||
}
|
||||
|
||||
@Override public void onRepeatModeChanged( int repeatMode ){
|
||||
log.d("exoPlayer onRepeatModeChanged %d",repeatMode);
|
||||
}
|
||||
|
||||
@Override public void onPlayerError( ExoPlaybackException error ){
|
||||
log.d("exoPlayer onPlayerError");
|
||||
Utils.showToast( ActMediaViewer.this,error,"player error." );
|
||||
}
|
||||
|
||||
@Override public void onPositionDiscontinuity(){
|
||||
log.d("exoPlayer onPositionDiscontinuity");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged( PlaybackParameters playbackParameters ){
|
||||
log.d("exoPlayer onPlaybackParametersChanged");
|
||||
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
void loadDelta( int delta ){
|
||||
if( media_list.size() < 2 ) return;
|
||||
int size = media_list.size();
|
||||
idx = ( idx + size + delta ) % size;
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
void load(){
|
||||
|
||||
exoPlayer.stop();
|
||||
|
||||
if( media_list.isEmpty() ){
|
||||
pbvImage.setVisibility( View.GONE );
|
||||
exoView.setVisibility( View.GONE );
|
||||
tvError.setVisibility( View.VISIBLE );
|
||||
tvError.setText( R.string.media_attachment_empty );
|
||||
return;
|
||||
}
|
||||
|
||||
TootAttachment ta = media_list.get( idx );
|
||||
|
||||
// TODO ta.description をどこかに表示する
|
||||
|
||||
if( TootAttachment.TYPE_IMAGE.equals( ta.type )){
|
||||
loadBitmap(ta);
|
||||
}else if( TootAttachment.TYPE_VIDEO.equals( ta.type ) || TootAttachment.TYPE_GIFV.equals( ta.type ) ){
|
||||
pbvImage.setVisibility( View.GONE );
|
||||
loadVideo(ta);
|
||||
}else{
|
||||
// maybe TYPE_UNKNOWN
|
||||
showError( getString( R.string.media_attachment_type_error, ta.type ) );
|
||||
}
|
||||
}
|
||||
|
||||
void showError(@NonNull String message){
|
||||
exoView.setVisibility( View.GONE );
|
||||
pbvImage.setVisibility( View.GONE );
|
||||
tvError.setVisibility( View.VISIBLE );
|
||||
tvError.setText( message );
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
void loadVideo(TootAttachment ta){
|
||||
|
||||
final String url = ta.getLargeUrl( App1.pref );
|
||||
if( url == null ){
|
||||
showError( "missing media attachment url.");
|
||||
return;
|
||||
}
|
||||
|
||||
tvError.setVisibility( View.GONE );
|
||||
pbvImage.setVisibility( View.GONE );
|
||||
exoView.setVisibility( View.VISIBLE );
|
||||
|
||||
DefaultBandwidthMeter defaultBandwidthMeter =new DefaultBandwidthMeter();
|
||||
ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory();
|
||||
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(
|
||||
this
|
||||
, Util.getUserAgent(this, getString(R.string.app_name))
|
||||
, defaultBandwidthMeter
|
||||
);
|
||||
|
||||
MediaSource mediaSource = new ExtractorMediaSource( Uri.parse( url )
|
||||
, dataSourceFactory
|
||||
, extractorsFactory
|
||||
, App1.getAppState( this ).handler
|
||||
, new ExtractorMediaSource.EventListener() {
|
||||
@Override public void onLoadError( IOException error ){
|
||||
showError( Utils.formatError( error,"load error." ));
|
||||
}
|
||||
}
|
||||
);
|
||||
exoPlayer.prepare(mediaSource);
|
||||
exoPlayer.setPlayWhenReady( true);
|
||||
if( TootAttachment.TYPE_GIFV.equals( ta.type ) ){
|
||||
exoPlayer.setRepeatMode( Player.REPEAT_MODE_ALL );
|
||||
}else{
|
||||
exoPlayer.setRepeatMode( Player.REPEAT_MODE_OFF );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
void loadBitmap(TootAttachment ta){
|
||||
final String url = ta.getLargeUrl( App1.pref );
|
||||
if( url == null ){
|
||||
showError( "missing media attachment url.");
|
||||
return;
|
||||
}
|
||||
|
||||
tvError.setVisibility( View.GONE );
|
||||
exoView.setVisibility( View.GONE );
|
||||
pbvImage.setVisibility( View.VISIBLE );
|
||||
pbvImage.setBitmap( null );
|
||||
|
||||
new TootApiTask( this, true ) {
|
||||
|
||||
private final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
|
||||
private Bitmap decodeBitmap( byte[] data, @SuppressWarnings("SameParameterValue") int pixel_max ){
|
||||
publishApiProgress( "image decoding.." );
|
||||
options.inJustDecodeBounds = true;
|
||||
options.inScaled = false;
|
||||
options.outWidth = 0;
|
||||
options.outHeight = 0;
|
||||
BitmapFactory.decodeByteArray( data, 0, data.length, options );
|
||||
int w = options.outWidth;
|
||||
int h = options.outHeight;
|
||||
if( w <= 0 || h <= 0 ){
|
||||
log.e( "can't decode bounds." );
|
||||
return null;
|
||||
}
|
||||
int bits = 0;
|
||||
while( w > pixel_max || h > pixel_max ){
|
||||
++ bits;
|
||||
w >>= 1;
|
||||
h >>= 1;
|
||||
}
|
||||
options.inJustDecodeBounds = false;
|
||||
options.inSampleSize = 1 << bits;
|
||||
return BitmapFactory.decodeByteArray( data, 0, data.length, options );
|
||||
}
|
||||
|
||||
@NonNull TootApiResult getHttpCached( @NonNull String url ){
|
||||
Response response;
|
||||
|
||||
try{
|
||||
okhttp3.Request request = new okhttp3.Request.Builder()
|
||||
.url( url )
|
||||
.cacheControl( App1.CACHE_5MIN )
|
||||
.build();
|
||||
|
||||
publishApiProgress( getString( R.string.request_api, request.method(), url ) );
|
||||
Call call = App1.ok_http_client2.newCall( request );
|
||||
response = call.execute();
|
||||
}catch( Throwable ex ){
|
||||
return new TootApiResult( Utils.formatError( ex, "network error." ) );
|
||||
}
|
||||
|
||||
if( ! response.isSuccessful() ){
|
||||
return new TootApiResult( Utils.formatResponse( response, "response error" ) );
|
||||
}
|
||||
|
||||
try{
|
||||
//noinspection ConstantConditions
|
||||
data = response.body().bytes();
|
||||
return new TootApiResult( "" );
|
||||
}catch( Throwable ex ){
|
||||
return new TootApiResult( Utils.formatError( ex, "content error." ) );
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
Bitmap bitmap;
|
||||
|
||||
@Override protected TootApiResult doInBackground( Void... voids ){
|
||||
TootApiResult result = getHttpCached( url );
|
||||
if( data == null ) return result;
|
||||
this.bitmap = decodeBitmap( data, 2048 );
|
||||
if( bitmap == null ) return new TootApiResult( "image decode failed." );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override protected void handleResult( @Nullable TootApiResult result ){
|
||||
if( bitmap != null ){
|
||||
pbvImage.setBitmap( bitmap );
|
||||
return;
|
||||
}
|
||||
if( result != null ) Utils.showToast( ActMediaViewer.this, true, result.error );
|
||||
}
|
||||
}.executeOnExecutor( App1.task_executor );
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override public void onClick( View v ){
|
||||
try{
|
||||
switch( v.getId() ){
|
||||
|
||||
case R.id.btnPrevious:
|
||||
loadDelta( - 1 );
|
||||
break;
|
||||
case R.id.btnNext:
|
||||
loadDelta( + 1 );
|
||||
break;
|
||||
case R.id.btnDownload:
|
||||
download( media_list.get( idx ) );
|
||||
break;
|
||||
|
||||
// case R.id.btnBrowser:
|
||||
// share( Intent.ACTION_VIEW, media_list.get( idx ) );
|
||||
// break;
|
||||
// case R.id.btnShare:
|
||||
// share( Intent.ACTION_SEND, media_list.get( idx ) );
|
||||
// break;
|
||||
// case R.id.btnCopy:
|
||||
// copy( media_list.get( idx ) );
|
||||
// break;
|
||||
|
||||
case R.id.btnMore:
|
||||
more( media_list.get( idx ) );
|
||||
break;
|
||||
}
|
||||
}catch( Throwable ex ){
|
||||
Utils.showToast( this, ex, "action failed." );
|
||||
}
|
||||
}
|
||||
|
||||
void download( @NonNull TootAttachment ta ){
|
||||
|
||||
String url = ta.getLargeUrl( App1.pref );
|
||||
if( url == null ) return;
|
||||
|
||||
DownloadManager downLoadManager = (DownloadManager) getSystemService( DOWNLOAD_SERVICE );
|
||||
if( downLoadManager == null ){
|
||||
Utils.showToast( this, false, "download manager is not on your device." );
|
||||
return;
|
||||
}
|
||||
|
||||
String fname = url.replaceFirst( "https?://", "" ).replaceAll( "[^.\\w\\d_-]+", "-" );
|
||||
if( fname.length() >= 20 ) fname = fname.substring( fname.length() - 20 );
|
||||
|
||||
DownloadManager.Request request = new DownloadManager.Request( Uri.parse( url ) );
|
||||
request.setDestinationInExternalPublicDir( Environment.DIRECTORY_DOWNLOADS, fname );
|
||||
request.setTitle( fname );
|
||||
request.setAllowedNetworkTypes( DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI );
|
||||
|
||||
//メディアスキャンを許可する
|
||||
request.allowScanningByMediaScanner();
|
||||
|
||||
//ダウンロード中・ダウンロード完了時にも通知を表示する
|
||||
request.setNotificationVisibility( DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED );
|
||||
|
||||
downLoadManager.enqueue( request );
|
||||
Utils.showToast( this, false, R.string.downloading );
|
||||
}
|
||||
|
||||
void share( String action, @NonNull TootAttachment ta ){
|
||||
String url = ta.getLargeUrl( App1.pref );
|
||||
if( url == null ) return;
|
||||
|
||||
try{
|
||||
Intent intent = new Intent( action );
|
||||
intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
|
||||
if( action.equals( Intent.ACTION_SEND ) ){
|
||||
intent.setType( "text/plain" );
|
||||
intent.putExtra( Intent.EXTRA_TEXT, url );
|
||||
}else{
|
||||
intent.setData( Uri.parse( url ) );
|
||||
}
|
||||
|
||||
startActivity( intent );
|
||||
}catch( Throwable ex ){
|
||||
Utils.showToast( this, ex, "can't open app." );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void copy( @NonNull TootAttachment ta ){
|
||||
String url = ta.getLargeUrl( App1.pref );
|
||||
if( url == null ) return;
|
||||
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
|
||||
if( cm == null ){
|
||||
Utils.showToast( this, false, "can't access to ClipboardManager" );
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
//クリップボードに格納するItemを作成
|
||||
ClipData.Item item = new ClipData.Item( url );
|
||||
|
||||
String[] mimeType = new String[ 1 ];
|
||||
mimeType[ 0 ] = ClipDescription.MIMETYPE_TEXT_PLAIN;
|
||||
|
||||
//クリップボードに格納するClipDataオブジェクトの作成
|
||||
ClipData cd = new ClipData( new ClipDescription( "media URL", mimeType ), item );
|
||||
|
||||
//クリップボードにデータを格納
|
||||
cm.setPrimaryClip( cd );
|
||||
|
||||
Utils.showToast( this, false, R.string.url_is_copied );
|
||||
|
||||
}catch( Throwable ex ){
|
||||
Utils.showToast( this, ex, "clipboard access failed." );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void more( @NonNull TootAttachment ta ){
|
||||
ActionsDialog ad = new ActionsDialog();
|
||||
|
||||
ad.addAction( getString( R.string.open_in_browser ), new Runnable() {
|
||||
@Override public void run(){
|
||||
share( Intent.ACTION_VIEW, media_list.get( idx ) );
|
||||
}
|
||||
} );
|
||||
ad.addAction( getString( R.string.share_url ), new Runnable() {
|
||||
@Override public void run(){
|
||||
share( Intent.ACTION_SEND, media_list.get( idx ) );
|
||||
}
|
||||
} );
|
||||
ad.addAction( getString( R.string.copy_url ), new Runnable() {
|
||||
@Override public void run(){
|
||||
copy( media_list.get( idx ) );
|
||||
}
|
||||
} );
|
||||
|
||||
addMoreMenu( ad, "url", ta.url, Intent.ACTION_VIEW );
|
||||
addMoreMenu( ad, "remote_url", ta.remote_url, Intent.ACTION_VIEW );
|
||||
addMoreMenu( ad, "preview_url", ta.preview_url, Intent.ACTION_VIEW );
|
||||
addMoreMenu( ad, "text_url", ta.text_url, Intent.ACTION_VIEW );
|
||||
|
||||
ad.show( this, null );
|
||||
}
|
||||
|
||||
void addMoreMenu( ActionsDialog ad, String caption_prefix, final String url, final String action ){
|
||||
if( TextUtils.isEmpty( url ) ) return;
|
||||
|
||||
String caption = getString( R.string.open_browser_of, caption_prefix );
|
||||
|
||||
ad.addAction( caption, new Runnable() {
|
||||
@Override public void run(){
|
||||
try{
|
||||
Intent intent = new Intent( action, Uri.parse( url ) );
|
||||
intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
|
||||
startActivity( intent );
|
||||
}catch( Throwable ex ){
|
||||
Utils.showToast( ActMediaViewer.this, ex, "can't open app." );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
|
@ -3,12 +3,16 @@ package jp.juggler.subwaytooter;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.GlideBuilder;
|
||||
|
@ -26,6 +30,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import jp.juggler.subwaytooter.api.entity.TootAttachment;
|
||||
import jp.juggler.subwaytooter.table.AcctColor;
|
||||
import jp.juggler.subwaytooter.table.AcctSet;
|
||||
import jp.juggler.subwaytooter.table.MutedApp;
|
||||
|
@ -220,7 +225,7 @@ public class App1 extends Application {
|
|||
|
||||
public static OkHttpClient ok_http_client;
|
||||
|
||||
private static OkHttpClient ok_http_client2;
|
||||
public static OkHttpClient ok_http_client2;
|
||||
|
||||
// public static final boolean USE_OLD_EMOJIONE = false;
|
||||
// public static Typeface typeface_emoji;
|
||||
|
@ -490,4 +495,38 @@ public class App1 extends Application {
|
|||
allow_non_space_before_emoji_shortcode = pref.getBoolean( Pref.KEY_ALLOW_NON_SPACE_BEFORE_EMOJI_SHORTCODE,false );
|
||||
}
|
||||
|
||||
|
||||
// Chrome Custom Tab を開く
|
||||
public static void openCustomTab( @NonNull Activity activity, @NonNull String url ){
|
||||
try{
|
||||
if( pref.getBoolean( Pref.KEY_PRIOR_CHROME, true ) ){
|
||||
try{
|
||||
// 初回はChrome指定で試す
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor( Styler.getAttributeColor( activity, R.attr.colorPrimary ) ).setShowTitle( true );
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
customTabsIntent.intent.setComponent( new ComponentName( "com.android.chrome", "com.google.android.apps.chrome.Main" ) );
|
||||
customTabsIntent.launchUrl( activity, Uri.parse( url ) );
|
||||
return;
|
||||
}catch( Throwable ex2 ){
|
||||
log.e( ex2, "openChromeTab: missing chrome. retry to other application." );
|
||||
}
|
||||
}
|
||||
|
||||
// chromeがないなら ResolverActivity でアプリを選択させる
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor( Styler.getAttributeColor( activity, R.attr.colorPrimary ) ).setShowTitle( true );
|
||||
CustomTabsIntent customTabsIntent = builder.build();
|
||||
customTabsIntent.launchUrl( activity, Uri.parse( url ) );
|
||||
}catch(Throwable ex){
|
||||
log.e( ex, "openCustomTab: failed." );
|
||||
}
|
||||
}
|
||||
|
||||
public static void openCustomTab( @NonNull Activity activity, @NonNull TootAttachment ta){
|
||||
String url = ta.getLargeUrl(pref);
|
||||
if( url != null ){
|
||||
openCustomTab( activity, url );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -322,6 +322,7 @@ public class AppDataExporter {
|
|||
case Pref.KEY_SHORT_ACCT_LOCAL_USER:
|
||||
case Pref.KEY_DISABLE_EMOJI_ANIMATION:
|
||||
case Pref.KEY_ALLOW_NON_SPACE_BEFORE_EMOJI_SHORTCODE:
|
||||
case Pref.KEY_USE_INTERNAL_MEDIA_VIEWER:
|
||||
boolean bv = reader.nextBoolean();
|
||||
e.putBoolean( k, bv );
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package jp.juggler.subwaytooter;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class DownloadReceiver extends BroadcastReceiver {
|
||||
@Override public void onReceive( Context context, Intent intent ){
|
||||
if( intent == null ) return;
|
||||
String action = intent.getAction();
|
||||
if( action == null ) return;
|
||||
|
||||
if( DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals( action )){
|
||||
long id = intent.getLongExtra( DownloadManager.EXTRA_DOWNLOAD_ID, 0L );
|
||||
|
||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService( Context.DOWNLOAD_SERVICE );
|
||||
if( downloadManager != null ){
|
||||
DownloadManager.Query query = new DownloadManager.Query();
|
||||
query.setFilterById( id );
|
||||
Cursor cursor = downloadManager.query( query );
|
||||
try{
|
||||
if( cursor.moveToFirst() ){
|
||||
int idx_status = cursor.getColumnIndex( DownloadManager.COLUMN_STATUS );
|
||||
|
||||
int idx_title = cursor.getColumnIndex( DownloadManager.COLUMN_TITLE );
|
||||
String title = cursor.getString( idx_title );
|
||||
|
||||
if( DownloadManager.STATUS_SUCCESSFUL == cursor.getInt( idx_status ) ){
|
||||
Utils.showToast( context, false, context.getString( R.string.download_complete, title ) );
|
||||
}else{
|
||||
Utils.showToast( context, false, context.getString( R.string.download_failed, title ) );
|
||||
}
|
||||
}
|
||||
}finally{
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -922,22 +922,14 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
|||
|
||||
if( media_attachments == null ) return;
|
||||
|
||||
TootAttachment a = media_attachments.get( i );
|
||||
|
||||
String sv;
|
||||
if( Pref.pref( activity ).getBoolean( Pref.KEY_PRIOR_LOCAL_URL, false ) ){
|
||||
sv = a.url;
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
sv = a.remote_url;
|
||||
}
|
||||
}else{
|
||||
sv = a.remote_url;
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
sv = a.url;
|
||||
}
|
||||
if( App1.pref.getBoolean( Pref. KEY_USE_INTERNAL_MEDIA_VIEWER,false) ){
|
||||
ActMediaViewer.open( activity, media_attachments, i );
|
||||
return;
|
||||
}
|
||||
int pos = activity.nextPosition( column );
|
||||
activity.openChromeTab( pos, access_info, sv, false );
|
||||
|
||||
TootAttachment a = media_attachments.get( i );
|
||||
App1.openCustomTab( activity, a);
|
||||
|
||||
}catch( Throwable ex ){
|
||||
log.trace( ex );
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ public class Pref {
|
|||
static final String KEY_DONT_CONFIRM_BEFORE_CLOSE_COLUMN = "DontConfirmBeforeCloseColumn";
|
||||
|
||||
static final String KEY_BACK_BUTTON_ACTION = "back_button_action";
|
||||
static final String KEY_PRIOR_LOCAL_URL = "prior_local_url";
|
||||
public static final String KEY_PRIOR_LOCAL_URL = "prior_local_url";
|
||||
static final String KEY_DISABLE_FAST_SCROLLER = "disable_fast_scroller";
|
||||
static final String KEY_UI_THEME = "ui_theme";
|
||||
static final String KEY_SIMPLE_LIST = "simple_list";
|
||||
|
@ -56,7 +56,7 @@ public class Pref {
|
|||
static final String KEY_STREAM_LISTENER_CONFIG_DATA = "stream_listener_config_data";
|
||||
static final String KEY_TABLET_TOOT_DEFAULT_ACCOUNT = "tablet_toot_default_account";
|
||||
|
||||
static final String KEY_PRIOR_CHROME = "prior_chrome";
|
||||
public static final String KEY_PRIOR_CHROME = "prior_chrome";
|
||||
|
||||
static final String KEY_POST_BUTTON_BAR_AT_TOP = "post_button_bar_at_top";
|
||||
|
||||
|
@ -95,6 +95,8 @@ public class Pref {
|
|||
public static final String KEY_ALLOW_NON_SPACE_BEFORE_EMOJI_SHORTCODE = "allow_non_space_before_emoji_shortcode";
|
||||
|
||||
public static final String KEY_MEDIA_SIZE_MAX = "max_media_size";
|
||||
|
||||
public static final String KEY_USE_INTERNAL_MEDIA_VIEWER = "use_internal_media_viewer";
|
||||
|
||||
// 項目を追加したらAppDataExporter#importPref のswitch文も更新すること
|
||||
}
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
package jp.juggler.subwaytooter.api.entity;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jp.juggler.subwaytooter.Pref;
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class TootAttachment {
|
||||
|
||||
private static final LogCategory log = new LogCategory( "TootAttachment" );
|
||||
|
||||
|
||||
public static class List extends ArrayList< TootAttachment > {
|
||||
|
||||
}
|
||||
|
@ -52,7 +56,7 @@ public class TootAttachment {
|
|||
try{
|
||||
TootAttachment dst = new TootAttachment();
|
||||
dst.json = src;
|
||||
dst.id = Utils.optLongX(src, "id" );
|
||||
dst.id = Utils.optLongX( src, "id" );
|
||||
dst.type = Utils.optStringX( src, "type" );
|
||||
dst.url = Utils.optStringX( src, "url" );
|
||||
dst.remote_url = Utils.optStringX( src, "remote_url" );
|
||||
|
@ -67,6 +71,18 @@ public class TootAttachment {
|
|||
}
|
||||
}
|
||||
|
||||
@NonNull public JSONObject encodeJSON() throws JSONException{
|
||||
JSONObject dst = new JSONObject();
|
||||
dst.put( "id", Long.toString( id ) );
|
||||
if( type != null ) dst.put( "type", type );
|
||||
if( url != null ) dst.put( "url", url );
|
||||
if( remote_url != null ) dst.put( "remote_url", remote_url );
|
||||
if( preview_url != null ) dst.put( "preview_url", preview_url );
|
||||
if( text_url != null ) dst.put( "text_url", text_url );
|
||||
if( description != null ) dst.put( "description", description );
|
||||
return dst;
|
||||
}
|
||||
|
||||
@NonNull public static List parseList( JSONArray array ){
|
||||
List result = new List();
|
||||
if( array != null ){
|
||||
|
@ -82,6 +98,24 @@ public class TootAttachment {
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Nullable public String getLargeUrl( SharedPreferences pref ){
|
||||
String sv;
|
||||
if( pref.getBoolean( Pref.KEY_PRIOR_LOCAL_URL, false ) ){
|
||||
sv = this.url;
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
sv = this.remote_url;
|
||||
}
|
||||
}else{
|
||||
sv = this.remote_url;
|
||||
if( TextUtils.isEmpty( sv ) ){
|
||||
sv = this.url;
|
||||
}
|
||||
}
|
||||
return sv;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// v1.3 から 添付ファイルの画像のピクセルサイズが取得できるようになった
|
||||
|
|
|
@ -2,6 +2,7 @@ package jp.juggler.subwaytooter.util;
|
|||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -35,6 +36,7 @@ import android.os.Looper;
|
|||
import android.os.storage.StorageManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.customtabs.CustomTabsIntent;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
@ -64,6 +66,11 @@ import javax.xml.parsers.DocumentBuilder;
|
|||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
|
||||
import it.sephiroth.android.library.exif2.ExifInterface;
|
||||
import jp.juggler.subwaytooter.ActMain;
|
||||
import jp.juggler.subwaytooter.App1;
|
||||
import jp.juggler.subwaytooter.Pref;
|
||||
import jp.juggler.subwaytooter.R;
|
||||
import jp.juggler.subwaytooter.Styler;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
|
@ -874,7 +881,6 @@ public class Utils {
|
|||
return sb.toString().replaceAll( "\n+", "\n" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
public interface ScanViewCallback {
|
||||
void onScanView( View v );
|
||||
|
@ -1225,4 +1231,7 @@ public class Utils {
|
|||
Bundle b = data.getExtras();
|
||||
return b == null ? null : b.get( key );
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,377 @@
|
|||
package jp.juggler.subwaytooter.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
|
||||
import jp.juggler.subwaytooter.util.LogCategory;
|
||||
import jp.juggler.subwaytooter.util.Utils;
|
||||
|
||||
public class PinchBitmapView extends View {
|
||||
|
||||
static final LogCategory log = new LogCategory( "PinchImageView" );
|
||||
|
||||
public interface Callback {
|
||||
void onSwipe( int delta );
|
||||
}
|
||||
|
||||
public PinchBitmapView( Context context ){
|
||||
this( context, null );
|
||||
init( context );
|
||||
}
|
||||
|
||||
public PinchBitmapView( Context context, AttributeSet attrs ){
|
||||
this( context, attrs, 0 );
|
||||
init( context );
|
||||
}
|
||||
|
||||
public PinchBitmapView( Context context, AttributeSet attrs, int defStyle ){
|
||||
super( context, attrs, defStyle );
|
||||
init( context );
|
||||
}
|
||||
|
||||
@Override public boolean onTouchEvent( MotionEvent ev ){
|
||||
return handleTouchEvent( ev );
|
||||
}
|
||||
|
||||
private Bitmap bitmap;
|
||||
|
||||
private final Matrix matrix = new Matrix();
|
||||
private final Paint paint = new Paint();
|
||||
|
||||
float swipe_velocity;
|
||||
|
||||
Callback callback;
|
||||
|
||||
public void setCallback( Callback callback ){
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
private void init( Context context ){
|
||||
paint.setFilterBitmap( true );
|
||||
swipe_velocity = 100f * context.getResources().getDisplayMetrics().density;
|
||||
}
|
||||
|
||||
@Override protected void onSizeChanged( int w, int h, int oldw, int oldh ){
|
||||
super.onSizeChanged( w, h, oldw, oldh );
|
||||
initializeScale();
|
||||
}
|
||||
|
||||
public void setBitmap( Bitmap b ){
|
||||
if( bitmap != null ){
|
||||
bitmap.recycle();
|
||||
}
|
||||
this.bitmap = b;
|
||||
initializeScale();
|
||||
}
|
||||
|
||||
void initializeScale(){
|
||||
if( bitmap != null && ! bitmap.isRecycled() ){
|
||||
this.bitmap_w = bitmap.getWidth();
|
||||
this.bitmap_h = bitmap.getHeight();
|
||||
if( bitmap_w < 1f ) bitmap_w = 1f;
|
||||
if( bitmap_h < 1f ) bitmap_h = 1f;
|
||||
view_w = this.getWidth();
|
||||
view_h = this.getHeight();
|
||||
if( view_w < 1f ) view_w = 1f;
|
||||
if( view_h < 1f ) view_h = 1f;
|
||||
|
||||
this.bitmap_aspect = bitmap_w / bitmap_h;
|
||||
this.view_aspect = view_w / view_h;
|
||||
if( view_aspect > bitmap_aspect ){
|
||||
current_scale = view_h / bitmap_h;
|
||||
}else{
|
||||
current_scale = view_w / bitmap_w;
|
||||
}
|
||||
|
||||
float draw_w = bitmap_w * current_scale;
|
||||
float draw_h = bitmap_h * current_scale;
|
||||
|
||||
current_trans_x = ( view_w - draw_w ) / 2f;
|
||||
current_trans_y = ( view_h - draw_h ) / 2f;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override protected void onDraw( Canvas canvas ){
|
||||
super.onDraw( canvas );
|
||||
|
||||
if( bitmap != null && ! bitmap.isRecycled() ){
|
||||
matrix.reset();
|
||||
matrix.postScale( current_scale, current_scale );
|
||||
matrix.postTranslate( current_trans_x, current_trans_y );
|
||||
canvas.drawBitmap( bitmap, matrix, paint );
|
||||
}
|
||||
}
|
||||
|
||||
boolean handleTouchEvent( MotionEvent ev ){
|
||||
if( bitmap == null || bitmap.isRecycled() ) return false;
|
||||
|
||||
if( velocityTracker != null ){
|
||||
velocityTracker.addMovement( ev );
|
||||
}
|
||||
|
||||
int action = ev.getAction();
|
||||
|
||||
switch( action ){
|
||||
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
|
||||
if( velocityTracker != null ){
|
||||
velocityTracker.recycle();
|
||||
velocityTracker = null;
|
||||
}
|
||||
|
||||
velocityTracker = VelocityTracker.obtain();
|
||||
|
||||
bDrag = false;
|
||||
bImageMoved = false;
|
||||
startTracking( ev );
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_POINTER_DOWN:
|
||||
case MotionEvent.ACTION_POINTER_UP:
|
||||
bDrag = true;
|
||||
startTracking( ev );
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
nextTracking( ev );
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
nextTracking( ev );
|
||||
if( ! bDrag ){
|
||||
|
||||
performClick();
|
||||
}else if( ! bImageMoved ){
|
||||
|
||||
velocityTracker.computeCurrentVelocity( 1000 );
|
||||
float xv = velocityTracker.getXVelocity();
|
||||
log.d( "velocity %f", xv );
|
||||
if( xv >= swipe_velocity ){
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override public void run(){
|
||||
if( callback != null ) callback.onSwipe( - 1 );
|
||||
}
|
||||
} );
|
||||
}else if( xv <= - swipe_velocity ){
|
||||
Utils.runOnMainThread( new Runnable() {
|
||||
@Override public void run(){
|
||||
if( callback != null ) callback.onSwipe( 1 );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if( velocityTracker != null ){
|
||||
velocityTracker.recycle();
|
||||
velocityTracker = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
float touch_start_x;
|
||||
float touch_start_y;
|
||||
float touch_start_radius;
|
||||
float touch_start_trans_x;
|
||||
float touch_start_trans_y;
|
||||
float touch_start_scale;
|
||||
float view_aspect;
|
||||
float bitmap_aspect;
|
||||
float scale_min;
|
||||
float scale_max;
|
||||
float view_w;
|
||||
float view_h;
|
||||
float bitmap_w;
|
||||
float bitmap_h;
|
||||
|
||||
float current_scale;
|
||||
float current_trans_x;
|
||||
float current_trans_y;
|
||||
boolean bDrag;
|
||||
boolean bImageMoved = false;
|
||||
|
||||
float drag_width;
|
||||
|
||||
int last_pointer_count;
|
||||
|
||||
final PointerAvg pos = new PointerAvg();
|
||||
|
||||
VelocityTracker velocityTracker;
|
||||
|
||||
static class PointerAvg {
|
||||
float avg_x;
|
||||
float avg_y;
|
||||
float max_radius;
|
||||
int count;
|
||||
|
||||
void update( MotionEvent ev ){
|
||||
count = ev.getPointerCount();
|
||||
if( count <= 1 ){
|
||||
avg_x = ev.getX();
|
||||
avg_y = ev.getY();
|
||||
max_radius = 0f;
|
||||
}else{
|
||||
avg_x = 0f;
|
||||
avg_y = 0f;
|
||||
for( int i = 0 ; i < count ; ++ i ){
|
||||
avg_x += ev.getX( i );
|
||||
avg_y += ev.getY( i );
|
||||
}
|
||||
avg_x /= count;
|
||||
avg_y /= count;
|
||||
max_radius = 0f;
|
||||
for( int i = 0 ; i < count ; ++ i ){
|
||||
float dx = ev.getX( i ) - avg_x;
|
||||
float dy = ev.getY( i ) - avg_y;
|
||||
float delta = dx * dx + dy * dy;
|
||||
if( delta > max_radius ) max_radius = delta;
|
||||
}
|
||||
max_radius = (float) Math.sqrt( max_radius );
|
||||
if( max_radius < 0.5f ) max_radius = 0.5f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startTracking( MotionEvent ev ){
|
||||
pos.update( ev );
|
||||
last_pointer_count = pos.count;
|
||||
touch_start_x = pos.avg_x;
|
||||
touch_start_y = pos.avg_y;
|
||||
touch_start_radius = pos.max_radius;
|
||||
touch_start_trans_x = current_trans_x;
|
||||
touch_start_trans_y = current_trans_y;
|
||||
touch_start_scale = current_scale;
|
||||
|
||||
view_w = this.getWidth();
|
||||
view_h = this.getHeight();
|
||||
if( view_w < 1f ) view_w = 1f;
|
||||
if( view_h < 1f ) view_h = 1f;
|
||||
view_aspect = view_w / view_h;
|
||||
|
||||
if( view_aspect > bitmap_aspect ){
|
||||
// ビューの方が横長、画像の方が縦長
|
||||
// 縦方向のサイズを使って最小スケールを決める
|
||||
scale_min = view_h / bitmap_h / 2;
|
||||
// ビューの方が横長、画像の方が縦長
|
||||
// 横方向のサイズを使って最大スケールを決める
|
||||
scale_max = view_w / bitmap_w * 8;
|
||||
}else{
|
||||
scale_min = view_w / bitmap_w / 2;
|
||||
scale_max = view_h / bitmap_h * 8;
|
||||
}
|
||||
if( scale_max < scale_min ) scale_max = scale_min * 4;
|
||||
|
||||
drag_width = getResources().getDisplayMetrics().density * 8f;
|
||||
}
|
||||
|
||||
final Matrix tracking_matrix = new Matrix();
|
||||
final Matrix tracking_matrix_inv = new Matrix();
|
||||
final float[] points_dst = new float[ 2 ];
|
||||
final float[] points_src = new float[ 2 ];
|
||||
|
||||
void nextTracking( MotionEvent ev ){
|
||||
pos.update( ev );
|
||||
|
||||
if( pos.count != last_pointer_count ){
|
||||
log.d( "nextTracking: pointer count changed" );
|
||||
startTracking( ev );
|
||||
return;
|
||||
}
|
||||
|
||||
if( pos.count > 1 ){
|
||||
// pos.avg_x,y が画像の座標空間でどこに位置するか調べる
|
||||
tracking_matrix.reset();
|
||||
tracking_matrix.postScale( current_scale, current_scale );
|
||||
tracking_matrix.postTranslate( current_trans_x, current_trans_y );
|
||||
tracking_matrix.invert( tracking_matrix_inv );
|
||||
points_src[ 0 ] = pos.avg_x;
|
||||
points_src[ 1 ] = pos.avg_y;
|
||||
tracking_matrix_inv.mapPoints( points_dst, points_src );
|
||||
float avg_on_image_x = points_dst[ 0 ];
|
||||
float avg_on_image_y = points_dst[ 1 ];
|
||||
|
||||
// update scale
|
||||
float new_scale = touch_start_scale * pos.max_radius / touch_start_radius;
|
||||
new_scale = new_scale < scale_min ? scale_min : new_scale > scale_max ? scale_max : new_scale;
|
||||
current_scale = new_scale;
|
||||
|
||||
// pos.avg_x,y が画像の座標空間でどこに位置するか再び調べる
|
||||
tracking_matrix.reset();
|
||||
tracking_matrix.postScale( current_scale, current_scale );
|
||||
tracking_matrix.postTranslate( current_trans_x, current_trans_y );
|
||||
tracking_matrix.invert( tracking_matrix_inv );
|
||||
points_src[ 0 ] = pos.avg_x;
|
||||
points_src[ 1 ] = pos.avg_y;
|
||||
tracking_matrix_inv.mapPoints( points_dst, points_src );
|
||||
float avg_on_image_x2 = points_dst[ 0 ];
|
||||
float avg_on_image_y2 = points_dst[ 1 ];
|
||||
|
||||
// ズレた分 * scaleだけ移動させるとスケール変更時にタッチ中心がスクロールしないのではないか
|
||||
float delta_x = avg_on_image_x2 - avg_on_image_x;
|
||||
float delta_y = avg_on_image_y2 - avg_on_image_y;
|
||||
touch_start_trans_x += current_scale * delta_x;
|
||||
touch_start_trans_y += current_scale * delta_y;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// 平行移動
|
||||
{
|
||||
// start時から指を動かした量
|
||||
float move_x = pos.avg_x - touch_start_x;
|
||||
float move_y = pos.avg_y - touch_start_y;
|
||||
|
||||
if( Math.abs( move_x ) >= drag_width || Math.abs( move_y ) >= drag_width ){
|
||||
bDrag = true;
|
||||
}
|
||||
|
||||
// 画像の移動量
|
||||
float trans_x = touch_start_trans_x + move_x;
|
||||
float trans_y = touch_start_trans_y + move_y;
|
||||
|
||||
// 画像サイズとビューサイズを使って移動可能範囲をクリッピング
|
||||
float draw_w = bitmap_w * current_scale;
|
||||
float draw_h = bitmap_h * current_scale;
|
||||
if( draw_w <= view_w ){
|
||||
float remain = view_w - draw_w;
|
||||
trans_x = remain / 2f;
|
||||
}else{
|
||||
float remain = draw_w - view_w;
|
||||
trans_x = trans_x >= 0f ? 0f : trans_x < - remain ? - remain : trans_x;
|
||||
}
|
||||
if( draw_h <= view_h ){
|
||||
float remain = view_h - draw_h;
|
||||
trans_y = remain / 2f;
|
||||
}else{
|
||||
float remain = draw_h - view_h;
|
||||
trans_y = trans_y >= 0f ? 0f : trans_y < - remain ? - remain : trans_y;
|
||||
}
|
||||
|
||||
if( current_trans_x != trans_x || current_trans_y != trans_y ){
|
||||
bImageMoved = true;
|
||||
}
|
||||
|
||||
// TODO trans_x,trans_y を画像の移動量に反映させる
|
||||
current_trans_x = trans_x;
|
||||
current_trans_y = trans_y;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public boolean performClick(){
|
||||
initializeScale();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
After Width: | Height: | Size: 426 B |
After Width: | Height: | Size: 380 B |
After Width: | Height: | Size: 381 B |
After Width: | Height: | Size: 326 B |
After Width: | Height: | Size: 256 B |
After Width: | Height: | Size: 250 B |
After Width: | Height: | Size: 252 B |
After Width: | Height: | Size: 222 B |
After Width: | Height: | Size: 262 B |
After Width: | Height: | Size: 232 B |
After Width: | Height: | Size: 723 B |
After Width: | Height: | Size: 608 B |
After Width: | Height: | Size: 292 B |
After Width: | Height: | Size: 260 B |
After Width: | Height: | Size: 273 B |
After Width: | Height: | Size: 241 B |
After Width: | Height: | Size: 185 B |
After Width: | Height: | Size: 175 B |
After Width: | Height: | Size: 178 B |
After Width: | Height: | Size: 163 B |
After Width: | Height: | Size: 182 B |
After Width: | Height: | Size: 162 B |
After Width: | Height: | Size: 506 B |
After Width: | Height: | Size: 436 B |
After Width: | Height: | Size: 483 B |
After Width: | Height: | Size: 421 B |
After Width: | Height: | Size: 451 B |
After Width: | Height: | Size: 396 B |
After Width: | Height: | Size: 260 B |
After Width: | Height: | Size: 251 B |
After Width: | Height: | Size: 247 B |
After Width: | Height: | Size: 220 B |
After Width: | Height: | Size: 241 B |
After Width: | Height: | Size: 222 B |
After Width: | Height: | Size: 948 B |
After Width: | Height: | Size: 828 B |
After Width: | Height: | Size: 804 B |
After Width: | Height: | Size: 710 B |
After Width: | Height: | Size: 746 B |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 409 B |
After Width: | Height: | Size: 371 B |
After Width: | Height: | Size: 418 B |
After Width: | Height: | Size: 376 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 389 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.3 KiB |
|
@ -494,6 +494,24 @@
|
|||
android:text="@string/media_attachment"
|
||||
/>
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
style="@style/setting_row_label"
|
||||
android:text="@string/use_internal_media_viewer"
|
||||
/>
|
||||
|
||||
<LinearLayout style="@style/setting_row_form">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/swUseInternalMediaViewer"
|
||||
style="@style/setting_horizontal_stretch"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<View style="@style/setting_divider"/>
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/flContent"
|
||||
>
|
||||
<jp.juggler.subwaytooter.view.PinchBitmapView
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/pbvImage"
|
||||
/>
|
||||
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/exoView"
|
||||
/>
|
||||
<TextView
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/tvError"
|
||||
/>
|
||||
|
||||
</FrameLayout>
|
||||
<com.google.android.flexbox.FlexboxLayout
|
||||
|
||||
android:id="@+id/flFooter"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:flexWrap="wrap"
|
||||
app:alignContent="center"
|
||||
>
|
||||
<ImageButton
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:src="?attr/ic_left"
|
||||
android:id="@+id/btnPrevious"
|
||||
android:contentDescription="@string/previous"
|
||||
/>
|
||||
<ImageButton
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:src="?attr/ic_right"
|
||||
android:id="@+id/btnNext"
|
||||
android:contentDescription="@string/next"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:src="?attr/ic_download"
|
||||
android:id="@+id/btnDownload"
|
||||
android:contentDescription="@string/download"
|
||||
/>
|
||||
|
||||
<ImageButton
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:src="?attr/btn_more"
|
||||
android:id="@+id/btnMore"
|
||||
android:contentDescription="@string/more"
|
||||
/>
|
||||
|
||||
|
||||
<!--<ImageButton-->
|
||||
<!--android:layout_width="48dp"-->
|
||||
<!--android:layout_height="48dp"-->
|
||||
<!--android:minWidth="48dp"-->
|
||||
<!--android:src="?attr/ic_browser"-->
|
||||
<!--android:id="@+id/btnBrowser"-->
|
||||
<!--android:contentDescription="@string/browser"-->
|
||||
<!--/>-->
|
||||
<!--<ImageButton-->
|
||||
<!--android:layout_width="48dp"-->
|
||||
<!--android:layout_height="48dp"-->
|
||||
<!--android:minWidth="48dp"-->
|
||||
<!--android:src="?attr/ic_share"-->
|
||||
<!--android:id="@+id/btnShare"-->
|
||||
<!--android:contentDescription="@string/share"-->
|
||||
<!--/>-->
|
||||
<!--<ImageButton-->
|
||||
<!--android:layout_width="48dp"-->
|
||||
<!--android:layout_height="48dp"-->
|
||||
<!--android:minWidth="48dp"-->
|
||||
<!--android:src="?attr/ic_copy"-->
|
||||
<!--android:id="@+id/btnCopy"-->
|
||||
<!--android:contentDescription="@string/copy"-->
|
||||
<!--/>-->
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</LinearLayout>
|
|
@ -567,7 +567,24 @@
|
|||
<string name="toot_search_ts_of">Toot search(ts) \"%1$s\"</string>
|
||||
<string name="tootsearch">tootsearch(JP)</string>
|
||||
<string name="cant_handle_uri_of">Subway Tooter can\'t handle URI \"%1$s\". Please select other app.</string>
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<string name="use_internal_media_viewer">Use built-in media viewer</string>
|
||||
|
||||
|
||||
<string name="previous">Previous</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="url_is_copied">URL copied to clipboard</string>
|
||||
<string name="download_complete">Download completed. %1$s</string>
|
||||
<string name="download_failed">Download failed. %1$s</string>
|
||||
<string name="open_browser_of">Open \"%1$s\" in browser</string>
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="share_url">Share URL</string>
|
||||
<string name="copy_url">Copy URL to clipboard</string>
|
||||
<string name="media_attachment_empty">Missing media attachments.</string>
|
||||
<string name="media_attachment_type_error">The type of media attachment is \"%1$s\". ST can\'t handle it, but you can try open in browser.</string>
|
||||
|
||||
<!--<string name="abc_action_bar_home_description">Revenir à l\'accueil</string>-->
|
||||
<!--<string name="abc_action_bar_home_description_format">%1$s, %2$s</string>-->
|
||||
<!--<string name="abc_action_bar_home_subtitle_description_format">%1$s, %2$s, %3$s</string>-->
|
||||
<!--<string name="abc_action_bar_up_description">Revenir en haut de la page</string>-->
|
||||
|
|
|
@ -855,4 +855,20 @@
|
|||
<string name="toot_search_ts_of">トゥート検索(ts) \"%1$s\"</string>
|
||||
<string name="tootsearch">tootsearch</string>
|
||||
<string name="cant_handle_uri_of">Subway Tooter は URI \"%1$s\"を扱えません。別のアプリを選んでください。</string>
|
||||
<string name="use_internal_media_viewer">内蔵メディアビューアを使う</string>
|
||||
|
||||
<string name="previous">前</string>
|
||||
<string name="next">次</string>
|
||||
<string name="download">ダウンロード</string>
|
||||
<string name="downloading">ダウンロード中…</string>
|
||||
<string name="url_is_copied">URLをクリップボードにコピーしました</string>
|
||||
<string name="download_complete">ダウンロード完了 %1$s</string>
|
||||
<string name="download_failed">ダウンロード失敗 %1$s</string>
|
||||
<string name="open_browser_of">\"%1$s\"をブラウザで開く</string>
|
||||
<string name="open_in_browser">ブラウザで開く</string>
|
||||
<string name="share_url">URLを共有</string>
|
||||
<string name="copy_url">URLをクリップボードにコピー</string>
|
||||
<string name="media_attachment_empty">Missing media attachments.</string>
|
||||
<string name="media_attachment_type_error">The type of media attachment is \"%1$s\". ST can\'t handle it, but you can try open in browser.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -127,4 +127,11 @@
|
|||
<attr name="ic_list_tl" format="reference" />
|
||||
<attr name="ic_list_member" format="reference" />
|
||||
|
||||
<attr name="ic_download" format="reference" />
|
||||
<attr name="ic_browser" format="reference" />
|
||||
<attr name="ic_share" format="reference" />
|
||||
<attr name="ic_copy" format="reference" />
|
||||
<attr name="ic_left" format="reference" />
|
||||
<attr name="ic_right" format="reference" />
|
||||
|
||||
</resources>
|
|
@ -559,4 +559,18 @@
|
|||
<string name="media_attachment_max_byte_size">Media attachment maximum bytes size (unit:Mega bytes. default is 8)</string>
|
||||
<string name="tootsearch">tootsearch(JP)</string>
|
||||
<string name="cant_handle_uri_of">Subway Tooter can\'t handle URI \"%1$s\". Please select other app.</string>
|
||||
<string name="use_internal_media_viewer">Use built-in media viewer</string>
|
||||
<string name="previous">Previous</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="downloading">Downloading…</string>
|
||||
<string name="url_is_copied">URL copied to clipboard</string>
|
||||
<string name="download_complete">Download completed. %1$s</string>
|
||||
<string name="download_failed">Download failed. %1$s</string>
|
||||
<string name="open_browser_of">Open \"%1$s\" in browser</string>
|
||||
<string name="open_in_browser">Open in browser</string>
|
||||
<string name="share_url">Share URL</string>
|
||||
<string name="copy_url">Copy URL to clipboard</string>
|
||||
<string name="media_attachment_empty">Missing media attachments.</string>
|
||||
<string name="media_attachment_type_error">The type of media attachment is \"%1$s\". ST can\'t handle it, but you can try open in browser.</string>
|
||||
</resources>
|
||||
|
|
|
@ -98,6 +98,14 @@
|
|||
<item name="ic_list_tl">@drawable/ic_list_tl</item>
|
||||
<item name="ic_list_member">@drawable/ic_list_member</item>
|
||||
|
||||
<item name="ic_download">@drawable/ic_download</item>
|
||||
<item name="ic_browser">@drawable/ic_browser</item>
|
||||
<item name="ic_share">@drawable/ic_share</item>
|
||||
<item name="ic_copy">@drawable/ic_copy</item>
|
||||
<item name="ic_left">@drawable/ic_left</item>
|
||||
<item name="ic_right">@drawable/ic_right</item>
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Light.NoActionBar" parent="AppTheme.Light">
|
||||
|
@ -198,6 +206,13 @@
|
|||
<item name="ic_list_tl">@drawable/ic_list_tl_dark</item>
|
||||
<item name="ic_list_member">@drawable/ic_list_member_dark</item>
|
||||
|
||||
<item name="ic_download">@drawable/ic_download_dark</item>
|
||||
<item name="ic_browser">@drawable/ic_browser_dark</item>
|
||||
<item name="ic_share">@drawable/ic_share_dark</item>
|
||||
<item name="ic_copy">@drawable/ic_copy_dark</item>
|
||||
<item name="ic_left">@drawable/ic_left_dark</item>
|
||||
<item name="ic_right">@drawable/ic_right_dark</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dark.NoActionBar" parent="AppTheme.Dark">
|
||||
|
|