From 79559696ddcd96b00ad818e6c72e6cfddfcab18e Mon Sep 17 00:00:00 2001 From: Mostafa <123456> Date: Tue, 28 Oct 2025 00:47:37 +0330 Subject: [PATCH] bug fixed theme system changed VideoView changed to TextureView now we cane animate its alpha --- SmartFileBrowser/build.gradle | 2 +- SmartFileBrowser/src/main/AndroidManifest.xml | 6 +- .../acitivties/FileBrowserMainActivity.java | 36 +- .../acitivties/VideoViewActivity.java | 2 +- .../adapters/GalleryAdapter.java | 39 +- .../customClasses/MyVideoView.java | 8 +- .../customClasses/TextureVideoView.java | 786 ++++++++++++++++++ .../viewModel/Repository.java | 58 +- .../res/drawable/sfb_avd_check_to_uncheck.xml | 6 +- .../sfb_avd_check_to_uncheck_without_tick.xml | 4 +- .../res/drawable/sfb_avd_uncheck_to_check.xml | 8 +- .../sfb_avd_uncheck_to_check_without_tick.xml | 4 +- .../res/drawable/sfb_check_box_checked.xml | 6 +- .../res/drawable/sfb_check_box_unchecked.xml | 8 +- .../res/drawable/sfb_checked_without_tick.xml | 4 +- .../drawable/sfb_ic_circle_tick_filled.xml | 4 +- .../drawable/sfb_unchecked_without_tick.xml | 4 +- .../res/layout/activity_file_browser_main.xml | 2 +- .../main/res/layout/activity_video_view.xml | 2 - .../main/res/layout/item_gallery_layout.xml | 4 +- .../src/main/res/values/attrs.xml | 4 + .../src/main/res/values/colors.xml | 4 - .../src/main/res/values/styles.xml | 34 +- 23 files changed, 958 insertions(+), 77 deletions(-) create mode 100644 SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/TextureVideoView.java diff --git a/SmartFileBrowser/build.gradle b/SmartFileBrowser/build.gradle index 7c9f3bf..f81b2b9 100644 --- a/SmartFileBrowser/build.gradle +++ b/SmartFileBrowser/build.gradle @@ -34,7 +34,7 @@ android { } } group = 'ir.smartdevelopers' -version = '2.0.5' +version = '2.0.7' dependencies { diff --git a/SmartFileBrowser/src/main/AndroidManifest.xml b/SmartFileBrowser/src/main/AndroidManifest.xml index 0839b27..bb56706 100644 --- a/SmartFileBrowser/src/main/AndroidManifest.xml +++ b/SmartFileBrowser/src/main/AndroidManifest.xml @@ -27,20 +27,20 @@ + android:theme="@style/sfb_BaseAppTheme" /> diff --git a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/FileBrowserMainActivity.java b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/FileBrowserMainActivity.java index 69b61e1..108ca10 100644 --- a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/FileBrowserMainActivity.java +++ b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/FileBrowserMainActivity.java @@ -11,12 +11,14 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -49,7 +51,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; import androidx.appcompat.content.res.AppCompatResources; +import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.AppCompatTextView; import androidx.constraintlayout.widget.Group; import androidx.core.app.ActivityCompat; @@ -57,6 +61,7 @@ import androidx.core.app.SharedElementCallback; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; +import androidx.core.content.res.ResourcesCompat; import androidx.core.graphics.Insets; import androidx.core.graphics.drawable.DrawableKt; import androidx.core.view.ViewCompat; @@ -69,6 +74,7 @@ import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import com.aurelhubert.ahbottomnavigation.AHBottomNavigation; import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem; @@ -82,6 +88,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import ir.smartdevelopers.smartfilebrowser.R; @@ -196,6 +204,7 @@ public class FileBrowserMainActivity extends AppCompatActivity { // when it closing private final AtomicBoolean mIsCanceled = new AtomicBoolean(true); // + private ExecutorService mExecutorService; private void getDataFromIntent() { mShowVideosInGallery = getIntent().getBooleanExtra("mShowVideosInGallery", true); @@ -243,18 +252,25 @@ public boolean accept(File pathname) { }; } + @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(R.style.sfb_AppTheme); + + + super.onCreate(savedInstanceState); + Window window = getWindow(); + window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); // window.setSharedElementsUseOverlay(false); WindowCompat.enableEdgeToEdge(window); window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + window.getDecorView().setBackground(new ColorDrawable(Color.TRANSPARENT)); window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + // window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); window.setDimAmount(0.6f); - window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); window.setAllowEnterTransitionOverlap(false); Transition transition = TransitionInflater.from(this).inflateTransition(R.transition.iten_transition_in); window.setSharedElementExitTransition(transition); @@ -273,7 +289,6 @@ public void onMapSharedElements(List names, Map sharedElem } }); - super.onCreate(savedInstanceState); setContentView(R.layout.activity_file_browser_main); mIsCanceled.set(true); mSelectionFileViewModel = new ViewModelProvider(this).get(SelectionFileViewModel.class); @@ -339,11 +354,12 @@ public void onChanged(List fileBrowserModels) { // // if (mShowGalleryTab) { + mExecutorService = Executors.newFixedThreadPool(3); int spanCount = getResources().getInteger(R.integer.sfb_gallery_grid); int gapSpace = getResources().getDimensionPixelSize(R.dimen.sfb_gallery_gap_size); mGalleryLayoutManager = new GalleryLayoutManager(this, spanCount); - mGalleryAdapter = new GalleryAdapter(mSelectionFileViewModel.getSelectedFiles()); + mGalleryAdapter = new GalleryAdapter(mSelectionFileViewModel.getSelectedFiles(),mExecutorService); mGalleryAdapter.setCanSelectMultiple(mCanSelectMultipleInGallery); mGalleryAdapter.setOnItemClickListener(mGalleryModelItemClickListener); mGalleryAdapter.setOnItemSelectListener(mOnFileItemSelectListener); @@ -511,9 +527,12 @@ private void manageEdgeToEdge() { @Override protected void onDestroy() { - super.onDestroy(); -// mCallbackViewModel.clearCallback(); + if (mExecutorService != null){ + mExecutorService.shutdown(); + } mResultListener.clear(); + super.onDestroy(); + } @Override @@ -818,7 +837,10 @@ private AHBottomNavigationItem createNavItem(int colorAttr, int titleRes, int ic value.data); } private void initViews(Bundle savedInstanceState) { - +// ContextThemeWrapper wrapper = new ContextThemeWrapper(this,R.style.sfb_BaseAppTheme); +// Drawable okDrawable = VectorDrawableCompat.create(wrapper.getResources(),R.drawable.sfb_ic_circle_tick_filled,wrapper.getTheme()); +// //okDrawable.setTintList(null); +// btnSelectionOk.setImageDrawable(okDrawable); int[] attrs={R.attr.SFBColorGallery,R.attr.SFBColorPDF,R.attr.SFBColorAudio,R.attr.SFBColorFile}; if (mShowGalleryTab) { diff --git a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/VideoViewActivity.java b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/VideoViewActivity.java index 74af5b5..d87ea53 100644 --- a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/VideoViewActivity.java +++ b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/acitivties/VideoViewActivity.java @@ -226,7 +226,7 @@ private void showThumbnail(boolean animate) { private void hideThumbNaile(boolean animate) { if (mVideoPrapered && mTransitionEnds){ if (animate){ - imgThumbnailHolder.animate().setDuration(animationDuration).alpha(0) + imgThumbnailHolder.animate().setDuration(animationDuration).alpha(0f) .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(@NonNull ValueAnimator animation) { diff --git a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/adapters/GalleryAdapter.java b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/adapters/GalleryAdapter.java index 7529f67..b57a345 100644 --- a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/adapters/GalleryAdapter.java +++ b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/adapters/GalleryAdapter.java @@ -1,7 +1,9 @@ package ir.smartdevelopers.smartfilebrowser.adapters; +import android.content.Context; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.media.MediaMetadataRetriever; import android.net.Uri; import android.view.LayoutInflater; import android.view.View; @@ -32,9 +34,12 @@ import com.bumptech.glide.request.target.Target; import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.ExecutorService; import ir.smartdevelopers.smartfilebrowser.R; import ir.smartdevelopers.smartfilebrowser.customClasses.FileUtil; @@ -65,9 +70,11 @@ public class GalleryAdapter extends RecyclerView.Adapter selectedFiles) { - mGalleryModels=new ArrayList<>(); + public GalleryAdapter(List selectedFiles, ExecutorService orientationRetrieverService) { + mOrientationRetrieverService = orientationRetrieverService; + mGalleryModels=new ArrayList<>(); mSelectedFiles=selectedFiles; setHasStableIds(true); } @@ -377,7 +384,9 @@ void bindView(GalleryModel model){ .transition(DrawableTransitionOptions.withCrossFade()) .into(mImageView); - + if (model.getOrientation() == -1 && mOrientationRetrieverService != null){ + mOrientationRetrieverService.execute(new OrientationRetriever(model,itemView.getContext())); + } if (model.getType()== FileUtil.TYPE_VIDEO){ txtVideoDuration.setVisibility(View.VISIBLE); txtVideoDuration.setText(model.getDurationTime()); @@ -467,4 +476,28 @@ public List getSelectedModels(){ public void setSelectedFiles(List selectedFiles) { mSelectedFiles = selectedFiles; } + private static class OrientationRetriever implements Runnable { + private GalleryModel mModel; + private WeakReference wContext; + + private OrientationRetriever(GalleryModel model,Context context) { + mModel = model; + wContext = new WeakReference<>(context.getApplicationContext()); + } + + @Override + public void run() { + try(MediaMetadataRetriever retriever = new MediaMetadataRetriever()){ + retriever.setDataSource(wContext.get(),mModel.getUri()); + String rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + if (rotation != null){ + mModel.setOrientation(Integer.parseInt(rotation)); + }else { + mModel.setOrientation(0); + } + } catch (IOException e) { + mModel.setOrientation(0); + } + } + } } diff --git a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/MyVideoView.java b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/MyVideoView.java index c92f419..d297cda 100644 --- a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/MyVideoView.java +++ b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/MyVideoView.java @@ -10,12 +10,13 @@ import java.io.IOException; -public class MyVideoView extends VideoView { +public class MyVideoView extends TextureVideoView { private int mVideoWidth; private int mVideoHeight; private int mVideoRotation; public MyVideoView(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs,0); + } public MyVideoView(Context context, AttributeSet attrs, int defStyle) { @@ -23,7 +24,8 @@ public MyVideoView(Context context, AttributeSet attrs, int defStyle) { } public MyVideoView(Context context) { - super(context); + this(context,null); + } diff --git a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/TextureVideoView.java b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/TextureVideoView.java new file mode 100644 index 0000000..7be7ea2 --- /dev/null +++ b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/customClasses/TextureVideoView.java @@ -0,0 +1,786 @@ +package ir.smartdevelopers.smartfilebrowser.customClasses; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.SurfaceTexture; +import android.media.AudioAttributes; +import android.media.AudioFocusRequest; +import android.media.AudioManager; +import android.media.MediaFormat; +import android.media.MediaPlayer; +import android.net.Uri; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Pair; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.TextureView; +import android.view.View; +import android.widget.MediaController; +import android.widget.VideoView; + +import androidx.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Vector; + +public class TextureVideoView extends TextureView implements MediaController.MediaPlayerControl { + private static final String TAG = "VideoView"; + + // all possible internal states + private static final int STATE_ERROR = -1; + private static final int STATE_IDLE = 0; + private static final int STATE_PREPARING = 1; + private static final int STATE_PREPARED = 2; + private static final int STATE_PLAYING = 3; + private static final int STATE_PAUSED = 4; + private static final int STATE_PLAYBACK_COMPLETED = 5; + + private final Vector> mPendingSubtitleTracks = new Vector<>(); + + // settable by the client + private Uri mUri; + private Map mHeaders; + + // mCurrentState is a VideoView object's current state. + // mTargetState is the state that a method caller intends to reach. + // For instance, regardless the VideoView object's current state, + // calling pause() intends to bring the object to a target state + // of STATE_PAUSED. + private int mCurrentState = STATE_IDLE; + private int mTargetState = STATE_IDLE; + + // All the stuff we need for playing and showing a video + private Surface mSurface = null; + private MediaPlayer mMediaPlayer = null; + private int mAudioSession; + private int mVideoWidth; + private int mVideoHeight; + private int mSurfaceWidth; + private int mSurfaceHeight; + private MediaController mMediaController; + private MediaPlayer.OnCompletionListener mOnCompletionListener; + private MediaPlayer.OnPreparedListener mOnPreparedListener; + private int mCurrentBufferPercentage; + private MediaPlayer.OnErrorListener mOnErrorListener; + private MediaPlayer.OnInfoListener mOnInfoListener; + private int mSeekWhenPrepared; // recording the seek position while preparing + private boolean mCanPause; + private boolean mCanSeekBack; + private boolean mCanSeekForward; + private AudioManager mAudioManager; + private int mAudioFocusType = AudioManager.AUDIOFOCUS_GAIN; // legacy focus gain + private AudioAttributes mAudioAttributes; + private Context mContext; + + + public TextureVideoView(Context context) { + this(context, null); + } + + public TextureVideoView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextureVideoView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TextureVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + mContext = context; + mVideoWidth = 0; + mVideoHeight = 0; + + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mAudioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build(); + + setSurfaceTextureListener(mSHCallback); +// getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " + // + MeasureSpec.toString(heightMeasureSpec) + ")"); + + int width = getDefaultSize(mVideoWidth, widthMeasureSpec); + int height = getDefaultSize(mVideoHeight, heightMeasureSpec); + if (mVideoWidth > 0 && mVideoHeight > 0) { + + int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec); + int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec); + int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec); + int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec); + + if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) { + // the size is fixed + width = widthSpecSize; + height = heightSpecSize; + + // for compatibility, we adjust size based on aspect ratio + if (mVideoWidth * height < width * mVideoHeight) { + //Log.i("@@@", "image too wide, correcting"); + width = height * mVideoWidth / mVideoHeight; + } else if (mVideoWidth * height > width * mVideoHeight) { + //Log.i("@@@", "image too tall, correcting"); + height = width * mVideoHeight / mVideoWidth; + } + } else if (widthSpecMode == View.MeasureSpec.EXACTLY) { + // only the width is fixed, adjust the height to match aspect ratio if possible + width = widthSpecSize; + height = width * mVideoHeight / mVideoWidth; + if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { + // couldn't match aspect ratio within the constraints + height = heightSpecSize; + } + } else if (heightSpecMode == View.MeasureSpec.EXACTLY) { + // only the height is fixed, adjust the width to match aspect ratio if possible + height = heightSpecSize; + width = height * mVideoWidth / mVideoHeight; + if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { + // couldn't match aspect ratio within the constraints + width = widthSpecSize; + } + } else { + // neither the width nor the height are fixed, try to use actual video size + width = mVideoWidth; + height = mVideoHeight; + if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { + // too tall, decrease both width and height + height = heightSpecSize; + width = height * mVideoWidth / mVideoHeight; + } + if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { + // too wide, decrease both width and height + width = widthSpecSize; + height = width * mVideoHeight / mVideoWidth; + } + } + } else { + // no size yet, just adopt the given spec sizes + } + setMeasuredDimension(width, height); + } + + @Override + public CharSequence getAccessibilityClassName() { + return VideoView.class.getName(); + } + + public int resolveAdjustedSize(int desiredSize, int measureSpec) { + return getDefaultSize(desiredSize, measureSpec); + } + + /** + * Sets video path. + * + * @param path the path of the video. + */ + public void setVideoPath(String path) { + setVideoURI(Uri.parse(path)); + } + + /** + * Sets video URI. + * + * @param uri the URI of the video. + */ + public void setVideoURI(Uri uri) { + setVideoURI(uri, null); + } + + /** + * Sets video URI using specific headers. + * + * @param uri the URI of the video. + * @param headers the headers for the URI request. + * Note that the cross domain redirection is allowed by default, but that can be + * changed with key/value pairs through the headers parameter with + * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value + * to disallow or allow cross domain redirection. + */ + public void setVideoURI(Uri uri, Map headers) { + mUri = uri; + mHeaders = headers; + mSeekWhenPrepared = 0; + openVideo(); + requestLayout(); + invalidate(); + } + + /** + * Sets which type of audio focus will be requested during the playback, or configures playback + * to not request audio focus. Valid values for focus requests are + * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT}, + * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and + * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use + * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be + * requested when playback starts. You can for instance use this when playing a silent animation + * through this class, and you don't want to affect other audio applications playing in the + * background. + * + * @param focusGain the type of audio focus gain that will be requested, or + * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during playback. + */ + public void setAudioFocusRequest(int focusGain) { + if (focusGain != AudioManager.AUDIOFOCUS_NONE + && focusGain != AudioManager.AUDIOFOCUS_GAIN + && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT + && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK + && focusGain != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) { + throw new IllegalArgumentException("Illegal audio focus type " + focusGain); + } + mAudioFocusType = focusGain; + } + + /** + * Sets the {@link AudioAttributes} to be used during the playback of the video. + * + * @param attributes non-null AudioAttributes. + */ + public void setAudioAttributes(@androidx.annotation.NonNull AudioAttributes attributes) { + if (attributes == null) { + throw new IllegalArgumentException("Illegal null AudioAttributes"); + } + mAudioAttributes = attributes; + } + + + public void stopPlayback() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + mAudioManager.abandonAudioFocus(null); + } + } + + private void openVideo() { + if (mUri == null || mSurface == null) { + // not ready for playback just yet, will try again later + return; + } + // we shouldn't clear the target state, because somebody might have + // called start() previously + release(false); + + if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { + // TODO this should have a focus listener + + mAudioManager.requestAudioFocus(null,mAudioFocusType,0); + } + + try { + mMediaPlayer = new MediaPlayer(); + + + if (mAudioSession != 0) { + mMediaPlayer.setAudioSessionId(mAudioSession); + } else { + mAudioSession = mMediaPlayer.getAudioSessionId(); + } + mMediaPlayer.setOnPreparedListener(mPreparedListener); + mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); + mMediaPlayer.setOnCompletionListener(mCompletionListener); + mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setOnInfoListener(mInfoListener); + mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); + mCurrentBufferPercentage = 0; + mMediaPlayer.setDataSource(mContext, mUri, mHeaders); + mMediaPlayer.setSurface(mSurface); + mMediaPlayer.setAudioAttributes(mAudioAttributes); + mMediaPlayer.setScreenOnWhilePlaying(true); + mMediaPlayer.prepareAsync(); + + // we don't set the target state here either, but preserve the + // target state that was there before. + mCurrentState = STATE_PREPARING; + attachMediaController(); + } catch (IOException ex) { + Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } catch (IllegalArgumentException ex) { + Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } finally { + mPendingSubtitleTracks.clear(); + } + } + + public void setMediaController(MediaController controller) { + if (mMediaController != null) { + mMediaController.hide(); + } + mMediaController = controller; + attachMediaController(); + } + + private void attachMediaController() { + if (mMediaPlayer != null && mMediaController != null) { + mMediaController.setMediaPlayer(this); + View anchorView = this.getParent() instanceof View ? + (View) this.getParent() : this; + mMediaController.setAnchorView(anchorView); + mMediaController.setEnabled(isInPlaybackState()); + } + } + + MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener = + new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + if (mVideoWidth != 0 && mVideoHeight != 0) { +// getHolder().setFixedSize(mVideoWidth, mVideoHeight); + requestLayout(); + } + } + }; + + MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { + public void onPrepared(MediaPlayer mp) { + mCurrentState = STATE_PREPARED; + + // Get the capabilities of the player for this stream + + mCanPause = mCanSeekBack = mCanSeekForward = true; + + + if (mOnPreparedListener != null) { + mOnPreparedListener.onPrepared(mMediaPlayer); + } + if (mMediaController != null) { + mMediaController.setEnabled(true); + } + mVideoWidth = mp.getVideoWidth(); + mVideoHeight = mp.getVideoHeight(); + + int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call + if (seekToPosition != 0) { + seekTo(seekToPosition); + } + if (mVideoWidth != 0 && mVideoHeight != 0) { + //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight); +// getHolder().setFixedSize(mVideoWidth, mVideoHeight); + if (mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) { + // We didn't actually change the size (it was already at the size + // we need), so we won't get a "surface changed" callback, so + // start the video here instead of in the callback. + if (mTargetState == STATE_PLAYING) { + start(); + if (mMediaController != null) { + mMediaController.show(); + } + } else if (!isPlaying() && + (seekToPosition != 0 || getCurrentPosition() > 0)) { + if (mMediaController != null) { + // Show the media controls when we're paused into a video and make 'em stick. + mMediaController.show(0); + } + } + } + } else { + // We don't know the video size yet, but should start anyway. + // The video size might be reported to us later. + if (mTargetState == STATE_PLAYING) { + start(); + } + } + } + }; + + private MediaPlayer.OnCompletionListener mCompletionListener = + new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + mCurrentState = STATE_PLAYBACK_COMPLETED; + mTargetState = STATE_PLAYBACK_COMPLETED; + if (mMediaController != null) { + mMediaController.hide(); + } + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { + mAudioManager.abandonAudioFocus(null); + } + } + }; + + private MediaPlayer.OnInfoListener mInfoListener = + new MediaPlayer.OnInfoListener() { + public boolean onInfo(MediaPlayer mp, int arg1, int arg2) { + if (mOnInfoListener != null) { + mOnInfoListener.onInfo(mp, arg1, arg2); + } + return true; + } + }; + + private MediaPlayer.OnErrorListener mErrorListener = + new MediaPlayer.OnErrorListener() { + public boolean onError(MediaPlayer mp, int framework_err, int impl_err) { + Log.d(TAG, "Error: " + framework_err + "," + impl_err); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + if (mMediaController != null) { + mMediaController.hide(); + } + + /* If an error handler has been supplied, use it and finish. */ + if (mOnErrorListener != null) { + if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) { + return true; + } + } + + /* Otherwise, pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + */ + if (getWindowToken() != null) { + Resources r = mContext.getResources(); + int messageId; + + if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + messageId = android.R.string.VideoView_error_text_invalid_progressive_playback; + } else { + messageId = android.R.string.VideoView_error_text_unknown; + } + + new AlertDialog.Builder(mContext) + .setMessage(messageId) + .setPositiveButton(android.R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + /* If we get here, there is no onError listener, so + * at least inform them that the video is over. + */ + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }) + .setCancelable(false) + .show(); + } + return true; + } + }; + + private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = + new MediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(MediaPlayer mp, int percent) { + mCurrentBufferPercentage = percent; + } + }; + + /** + * Register a callback to be invoked when the media file + * is loaded and ready to go. + * + * @param l The callback that will be run + */ + public void setOnPreparedListener(MediaPlayer.OnPreparedListener l) { + mOnPreparedListener = l; + } + + /** + * Register a callback to be invoked when the end of a media file + * has been reached during playback. + * + * @param l The callback that will be run + */ + public void setOnCompletionListener(MediaPlayer.OnCompletionListener l) { + mOnCompletionListener = l; + } + + /** + * Register a callback to be invoked when an error occurs + * during playback or setup. If no listener is specified, + * or if the listener returned false, VideoView will inform + * the user of any errors. + * + * @param l The callback that will be run + */ + public void setOnErrorListener(MediaPlayer.OnErrorListener l) { + mOnErrorListener = l; + } + + /** + * Register a callback to be invoked when an informational event + * occurs during playback or setup. + * + * @param l The callback that will be run + */ + public void setOnInfoListener(MediaPlayer.OnInfoListener l) { + mOnInfoListener = l; + } + SurfaceTextureListener mSHCallback = new SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { + mSurfaceWidth = width; + mSurfaceHeight = height; + mSurface = new Surface(surface); + openVideo(); + } + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { + mSurface = null; + if (mMediaController != null) mMediaController.hide(); + release(true); + return true; + } + + @Override + public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { + mSurfaceWidth = width; + mSurfaceHeight = height; + boolean isValidState = (mTargetState == STATE_PLAYING); + boolean hasValidSize = (mVideoWidth == width && mVideoHeight == height); + if (mMediaPlayer != null && isValidState && hasValidSize) { + if (mSeekWhenPrepared != 0) { + seekTo(mSeekWhenPrepared); + } + start(); + } + } + + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { + + } + }; + + + /* + * release the media player in any state + */ + private void release(boolean cleartargetstate) { + if (mMediaPlayer != null) { + mMediaPlayer.reset(); + mMediaPlayer.release(); + mMediaPlayer = null; + mPendingSubtitleTracks.clear(); + mCurrentState = STATE_IDLE; + if (cleartargetstate) { + mTargetState = STATE_IDLE; + } + if (mAudioFocusType != AudioManager.AUDIOFOCUS_NONE) { + mAudioManager.abandonAudioFocus(null); + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN + && isInPlaybackState() && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN + && isInPlaybackState() && mMediaController != null) { + toggleMediaControlsVisiblity(); + } + return super.onTrackballEvent(ev); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_VOLUME_MUTE && + keyCode != KeyEvent.KEYCODE_MENU && + keyCode != KeyEvent.KEYCODE_CALL && + keyCode != KeyEvent.KEYCODE_ENDCALL; + if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) { + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || + keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } else { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { + if (!mMediaPlayer.isPlaying()) { + start(); + mMediaController.hide(); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { + if (mMediaPlayer.isPlaying()) { + pause(); + mMediaController.show(); + } + return true; + } else { + toggleMediaControlsVisiblity(); + } + } + + return super.onKeyDown(keyCode, event); + } + + private void toggleMediaControlsVisiblity() { + if (mMediaController.isShowing()) { + mMediaController.hide(); + } else { + mMediaController.show(); + } + } + + @Override + public void start() { + if (isInPlaybackState()) { + mMediaPlayer.start(); + mCurrentState = STATE_PLAYING; + } + mTargetState = STATE_PLAYING; + } + + @Override + public void pause() { + if (isInPlaybackState()) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.pause(); + mCurrentState = STATE_PAUSED; + } + } + mTargetState = STATE_PAUSED; + } + + public void suspend() { + release(false); + } + + public void resume() { + openVideo(); + } + + @Override + public int getDuration() { + if (isInPlaybackState()) { + return mMediaPlayer.getDuration(); + } + + return -1; + } + + @Override + public int getCurrentPosition() { + if (isInPlaybackState()) { + return mMediaPlayer.getCurrentPosition(); + } + return 0; + } + + @Override + public void seekTo(int msec) { + if (isInPlaybackState()) { + mMediaPlayer.seekTo(msec); + mSeekWhenPrepared = 0; + } else { + mSeekWhenPrepared = msec; + } + } + + @Override + public boolean isPlaying() { + return isInPlaybackState() && mMediaPlayer.isPlaying(); + } + + @Override + public int getBufferPercentage() { + if (mMediaPlayer != null) { + return mCurrentBufferPercentage; + } + return 0; + } + + private boolean isInPlaybackState() { + return (mMediaPlayer != null && + mCurrentState != STATE_ERROR && + mCurrentState != STATE_IDLE && + mCurrentState != STATE_PREPARING); + } + + @Override + public boolean canPause() { + return mCanPause; + } + + @Override + public boolean canSeekBackward() { + return mCanSeekBack; + } + + @Override + public boolean canSeekForward() { + return mCanSeekForward; + } + + @Override + public int getAudioSessionId() { + if (mAudioSession == 0) { + MediaPlayer foo = new MediaPlayer(); + mAudioSession = foo.getAudioSessionId(); + foo.release(); + } + return mAudioSession; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + } + +} + + diff --git a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/viewModel/Repository.java b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/viewModel/Repository.java index 573a121..8a1b9ad 100644 --- a/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/viewModel/Repository.java +++ b/SmartFileBrowser/src/main/java/ir/smartdevelopers/smartfilebrowser/viewModel/Repository.java @@ -6,6 +6,8 @@ import android.content.ContentUris; import android.content.Context; import android.database.Cursor; +import android.media.MediaDataSource; +import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -19,8 +21,10 @@ import java.io.File; import java.io.FileFilter; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; @@ -70,26 +74,38 @@ public void getGalleryMediaList(String selection, String[] selectionArgs, boolea mExecutorService.execute(()->{ // String[] imageProjection = {MediaStore.Images.Media._ID, - MediaStore.Images.Media.DATA, MediaStore.Images.Media.DATE_MODIFIED, - MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.MIME_TYPE, - MediaStore.Images.Media.WIDTH,MediaStore.Images.Media.HEIGHT,MediaStore.Images.Media.ORIENTATION}; - - Cursor externalImageCursor = mContentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageProjection, + MediaStore.Images.Media.DATA, + MediaStore.Images.Media.DATE_MODIFIED, + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.MIME_TYPE, + MediaStore.Images.Media.WIDTH, + MediaStore.Images.Media.HEIGHT, + MediaStore.Images.Media.ORIENTATION}; + + Cursor externalImageCursor = mContentResolver.query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, imageProjection, finalSelection, selectionArgs, MediaStore.Images.Media.DATE_MODIFIED + " DESC"); - galleryModelList.addAll(getGalleryModel(externalImageCursor, imageProjection)); + galleryModelList.addAll(getGalleryModel(externalImageCursor, imageProjection,false)); // // if (showVideosInGallery) { String[] videoProjection = {MediaStore.Video.Media._ID, - MediaStore.Video.Media.DATA, MediaStore.Video.Media.DATE_MODIFIED, - MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.MIME_TYPE, - MediaStore.Images.Media.WIDTH,MediaStore.Images.Media.HEIGHT, - MediaStore.Images.Media.ORIENTATION, + MediaStore.Video.Media.DATA, + MediaStore.Video.Media.DATE_MODIFIED, + MediaStore.Video.Media.DISPLAY_NAME, + MediaStore.Video.Media.MIME_TYPE, + MediaStore.Video.Media.WIDTH, + MediaStore.Video.Media.HEIGHT, MediaStore.Video.Media.DURATION}; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + videoProjection = Arrays.copyOf(videoProjection,videoProjection.length+1); + videoProjection[videoProjection.length-1] = MediaStore.Video.Media.ORIENTATION; + } Cursor externalVideoCursor = mContentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, videoProjection, finalSelection, selectionArgs, MediaStore.Video.Media.DATE_MODIFIED + " DESC"); - galleryModelList.addAll(getGalleryModel(externalVideoCursor, videoProjection)); + galleryModelList.addAll(getGalleryModel(externalVideoCursor, videoProjection,true)); + } // Collections.sort(galleryModelList); @@ -160,7 +176,7 @@ private Set getAlbumModels(Cursor cursor,String[] projection){ return albumModels; } - private List getGalleryModel(Cursor cursor,String[] projection){ + private List getGalleryModel(Cursor cursor,String[] projection,boolean isVideo){ List galleryModelList=new ArrayList<>(); if (cursor==null){ @@ -173,10 +189,15 @@ private List getGalleryModel(Cursor cursor,String[] projection){ int mimeTypeIndex=cursor.getColumnIndex(projection[4]); int widthIndex = cursor.getColumnIndex(projection[5]); int heighIndex = cursor.getColumnIndex(projection[6]); - int orientationIndex = cursor.getColumnIndex(projection[7]); + int orientationIndex = -1; + if (!isVideo){ + orientationIndex = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION); + }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + orientationIndex = cursor.getColumnIndex(MediaStore.Video.Media.ORIENTATION); + } int durationIndex=-1; - if (projection.length==9){ - durationIndex=cursor.getColumnIndex(projection[8]); + if (isVideo){ + durationIndex=cursor.getColumnIndex(MediaStore.Video.Media.DURATION); } while (cursor.moveToNext()){ @@ -188,7 +209,7 @@ private List getGalleryModel(Cursor cursor,String[] projection){ model.setType(FileUtil.getFileTypeCode(cursor.getString(mimeTypeIndex))); model.setWidth(cursor.getInt(widthIndex)); model.setHeight(cursor.getInt(heighIndex)); - model.setOrientation(cursor.getInt(orientationIndex)); + if (model.getType()==FileUtil.TYPE_VIDEO && durationIndex!=-1){ model.setDuration(cursor.getLong(durationIndex)); @@ -196,6 +217,11 @@ private List getGalleryModel(Cursor cursor,String[] projection){ }else { model.setUri(ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,model.getId())); } + if (orientationIndex != -1){ + model.setOrientation(cursor.getInt(orientationIndex)); + } else { + model.setOrientation(-1); + } galleryModelList.add(model); } cursor.close(); diff --git a/SmartFileBrowser/src/main/res/drawable/sfb_avd_check_to_uncheck.xml b/SmartFileBrowser/src/main/res/drawable/sfb_avd_check_to_uncheck.xml index c100482..c260900 100644 --- a/SmartFileBrowser/src/main/res/drawable/sfb_avd_check_to_uncheck.xml +++ b/SmartFileBrowser/src/main/res/drawable/sfb_avd_check_to_uncheck.xml @@ -16,7 +16,7 @@ diff --git a/SmartFileBrowser/src/main/res/drawable/sfb_avd_uncheck_to_check.xml b/SmartFileBrowser/src/main/res/drawable/sfb_avd_uncheck_to_check.xml index 9256f2f..5aa9851 100644 --- a/SmartFileBrowser/src/main/res/drawable/sfb_avd_uncheck_to_check.xml +++ b/SmartFileBrowser/src/main/res/drawable/sfb_avd_uncheck_to_check.xml @@ -16,12 +16,12 @@ + android:fillColor="?attr/SFBCheckboxStrokeColor"/> diff --git a/SmartFileBrowser/src/main/res/drawable/sfb_check_box_checked.xml b/SmartFileBrowser/src/main/res/drawable/sfb_check_box_checked.xml index 0725270..ec0f017 100644 --- a/SmartFileBrowser/src/main/res/drawable/sfb_check_box_checked.xml +++ b/SmartFileBrowser/src/main/res/drawable/sfb_check_box_checked.xml @@ -13,7 +13,7 @@ + android:fillColor="?attr/SFBCheckboxStrokeColor"/> @@ -31,13 +31,13 @@ diff --git a/SmartFileBrowser/src/main/res/drawable/sfb_ic_circle_tick_filled.xml b/SmartFileBrowser/src/main/res/drawable/sfb_ic_circle_tick_filled.xml index b429f33..85e1771 100644 --- a/SmartFileBrowser/src/main/res/drawable/sfb_ic_circle_tick_filled.xml +++ b/SmartFileBrowser/src/main/res/drawable/sfb_ic_circle_tick_filled.xml @@ -5,8 +5,8 @@ android:viewportHeight="100"> + android:fillColor="?attr/SFBCheckboxFillColor"/> + android:fillColor="?attr/SFBCheckboxOnFillColor"/> diff --git a/SmartFileBrowser/src/main/res/drawable/sfb_unchecked_without_tick.xml b/SmartFileBrowser/src/main/res/drawable/sfb_unchecked_without_tick.xml index a22ee58..1a308f1 100644 --- a/SmartFileBrowser/src/main/res/drawable/sfb_unchecked_without_tick.xml +++ b/SmartFileBrowser/src/main/res/drawable/sfb_unchecked_without_tick.xml @@ -8,7 +8,7 @@ diff --git a/SmartFileBrowser/src/main/res/layout/activity_file_browser_main.xml b/SmartFileBrowser/src/main/res/layout/activity_file_browser_main.xml index d9bb6c4..2eb657b 100644 --- a/SmartFileBrowser/src/main/res/layout/activity_file_browser_main.xml +++ b/SmartFileBrowser/src/main/res/layout/activity_file_browser_main.xml @@ -210,7 +210,7 @@ android:visibility="gone" tools:visibility="visible" android:translationZ="16dp" - android:background="@color/color_gallery_dim" + android:backgroundTint="@color/color_gallery_dim" > + @@ -23,4 +24,7 @@ + + + \ No newline at end of file diff --git a/SmartFileBrowser/src/main/res/values/colors.xml b/SmartFileBrowser/src/main/res/values/colors.xml index cd097cd..019e8bf 100644 --- a/SmartFileBrowser/src/main/res/values/colors.xml +++ b/SmartFileBrowser/src/main/res/values/colors.xml @@ -10,10 +10,6 @@ #65FFFFFF #660D0C0C #1E88E5 - ?attr/colorSecondary - @color/white - ?attr/colorOnSecondary - ?attr/colorOnSecondary #000000 #006874 diff --git a/SmartFileBrowser/src/main/res/values/styles.xml b/SmartFileBrowser/src/main/res/values/styles.xml index 93ce6f1..1ebd7c7 100644 --- a/SmartFileBrowser/src/main/res/values/styles.xml +++ b/SmartFileBrowser/src/main/res/values/styles.xml @@ -1,26 +1,40 @@ - + + + + -