├── .gitignore ├── .travis.yml ├── README.MD ├── build.gradle ├── gallerymodule ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── es │ │ └── guiguegon │ │ └── gallerymodule │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── es │ │ └── guiguegon │ │ └── gallerymodule │ │ ├── GalleryActivity.java │ │ ├── GalleryFragment.java │ │ ├── GalleryHelper.java │ │ ├── adapters │ │ ├── GalleryAdapter.java │ │ └── SelectableAdapter.java │ │ ├── helpers │ │ ├── CameraHelper.java │ │ ├── GalleryHelper.java │ │ └── PermissionsManager.java │ │ ├── model │ │ └── GalleryMedia.java │ │ └── utils │ │ ├── FileUtils.java │ │ ├── ImageUtils.java │ │ ├── ScreenUtils.java │ │ ├── TextureCameraPreview.java │ │ └── TimeUtils.java │ └── res │ ├── drawable │ ├── bg_gallery_item_selected.xml │ ├── bg_gradient_item_gallery.xml │ ├── ic_add_photo.xml │ └── ic_check.xml │ ├── layout │ ├── activity_gallery.xml │ ├── dialog_gallery.xml │ ├── dialog_max_number.xml │ ├── fragment_gallery.xml │ ├── item_gallery.xml │ └── item_gallery_header.xml │ ├── menu │ └── menu_gallery.xml │ ├── values │ ├── colors.xml │ ├── dimens.xml │ └── strings.xml │ └── xml │ └── provider_paths.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── es │ │ └── guiguegon │ │ └── gallerymodule │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── es │ │ │ └── guiguegon │ │ │ └── gallerymodule │ │ │ └── sample │ │ │ └── MainActivity.java │ └── res │ │ ├── drawable │ │ └── ic_add_photo.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── es │ └── guiguegon │ └── gallerymodule │ └── ExampleUnitTest.java ├── screenshots ├── gallery.png ├── gallery_multiselection.png └── take_photo.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | 37 | # Crashlytics configuations 38 | com_crashlytics_export_strings.xml 39 | 40 | 41 | # Signing files 42 | .signing/ 43 | 44 | *.iml 45 | .idea 46 | 47 | # OS-specific files 48 | .DS_Store 49 | .DS_Store? 50 | ._* 51 | .Spotlight-V100 52 | .Trashes 53 | ehthumbs.db 54 | Thumbs.db -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | android: 5 | components: 6 | - tools 7 | - build-tools-24.0.2 8 | - android-24 9 | - extra-android-m2repository 10 | 11 | sudo: false 12 | 13 | cache: 14 | directories: 15 | - $HOME/.m2 16 | 17 | script: 18 | ./gradlew build -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/guiguegon/GalleryModule.svg?branch=master)](https://travis-ci.org/guiguegon/GalleryModule) 2 | [![](https://jitpack.io/v/guiguegon/GalleryModule.svg)](https://jitpack.io/#guiguegon/GalleryModule) 3 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-GalleryModule-green.svg?style=true)](https://android-arsenal.com/details/1/4382) 4 | 5 | # GalleryModule 6 | Have you ever faced the need of build a gallery for your users to pick a image? or to take a new photo to use it as avatar? GalleryModule 7 | is just that in one library. The user will be prompted to pick one or multiple images from the device and also take new photos or videos 8 | 9 | ## Web 10 | See GalleryModule post series in my [blog](http://guiguegon.es/blog/) 11 | 12 | ## Download 13 | Add the following dependency to your gradle file 14 | ```java 15 | compile 'es.guiguegon:gallerymodule:1.3.1' 16 | ``` 17 | 18 | or use JitPack [![](https://jitpack.io/v/guiguegon/GalleryModule.svg)](https://jitpack.io/#guiguegon/GalleryModule) 19 | 20 | ## Usage 21 | The minimum API is 15. Working with API 24 22 | 23 | ```java 24 | public void openGallery() { 25 | startActivityForResult(new GalleryHelper() 26 | .setMultiselection(true) 27 | .setMaxSelectedItems(maxSelectedItems) 28 | .setShowVideos(showVideos)) 29 | .getCallingIntent(this), REQUEST_CODE_GALLERY); 30 | } 31 | 32 | @Override 33 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 34 | super.onActivityResult(requestCode, resultCode, data); 35 | if (requestCode == REQUEST_CODE_GALLERY && resultCode == RESULT_OK) { 36 | List galleryMedias = 37 | data.getParcelableArrayListExtra(GalleryActivity.RESULT_GALLERY_MEDIA_LIST); 38 | 39 | } 40 | } 41 | ``` 42 | 43 | The results are returned within data Intent of **onActivityResult**. GalleryMedia is a simple model for returning the data. 44 | 45 | ```java 46 | public class GalleryMedia implements Comparable, Parcelable { 47 | 48 | long id; 49 | String mediaUri; 50 | String mimeType; 51 | long duration; 52 | long dateTaken; 53 | ... 54 | } 55 | ``` 56 | 57 | ## Parameters 58 | 59 | + **multiselection** allows to pick more than one picture/video 60 | + **maxSelectedItems** limit the number of items that can be selected 61 | + **showVideos** show gallery videos or not 62 | 63 | ## Screenshots 64 | 65 | ![screenshot](screenshots/gallery.png) 66 | ![screenshot](screenshots/gallery_multiselection.png) 67 | ![screenshot](screenshots/take_photo.png) 68 | 69 | License 70 | ======= 71 | 72 | Copyright 2016 Guillermo Guerrero González 73 | 74 | Licensed under the Apache License, Version 2.0 (the "License"); 75 | you may not use this file except in compliance with the License. 76 | You may obtain a copy of the License at 77 | 78 | http://www.apache.org/licenses/LICENSE-2.0 79 | 80 | Unless required by applicable law or agreed to in writing, software 81 | distributed under the License is distributed on an "AS IS" BASIS, 82 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 83 | See the License for the specific language governing permissions and 84 | limitations under the License. 85 | 86 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | allprojects { 3 | repositories { 4 | jcenter() 5 | } 6 | } 7 | 8 | buildscript { 9 | repositories { 10 | jcenter() 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:2.1.3' 14 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 15 | classpath 'me.tatarka:gradle-retrolambda:3.3.0-beta4' 16 | 17 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.1' 18 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 19 | // NOTE: Do not place your application dependencies here; they belong 20 | // in the individual module build.gradle files 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } 27 | -------------------------------------------------------------------------------- /gallerymodule/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /gallerymodule/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | apply plugin: 'me.tatarka.retrolambda' 4 | apply plugin: 'com.jfrog.bintray' 5 | apply plugin: 'com.github.dcendents.android-maven' 6 | 7 | def versionLibCode = 3 8 | def versionLibName = '1.3.1' 9 | 10 | Properties properties = new Properties() 11 | if (project.rootProject.file('local.properties').exists()) { 12 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 13 | } 14 | 15 | ext { 16 | // Where you will see your artifact in Bintray's web interface 17 | // The "bintrayName" should match the name of the Bintray repo. 18 | bintrayRepo = 'maven' 19 | bintrayName = 'GalleryModule' 20 | 21 | // Maven metadata 22 | publishedGroupId = 'es.guiguegon' 23 | libraryName = 'GalleryModule' 24 | // Save yourself a head ache, and set this equal to the name of the Android Studio library 25 | // module. The artifact name needs to match the name of the library. 26 | artifact = 'gallerymodule' 27 | 28 | libraryDescription = 29 | 'Just a simple activity that show all the images and videos in a gallery and allows to select one and retrieve it' 30 | libraryVersion = versionLibName 31 | 32 | developerId = 'guiguegon' 33 | developerName = 'Guillermo Guerrero González' 34 | developerEmail = 'guiguegon@gmail.com' 35 | } 36 | 37 | bintray { 38 | user = properties.getProperty("bintray.user") 39 | key = properties.getProperty("bintray.apikey") 40 | configurations = ['archives'] 41 | pkg { 42 | repo = "maven" 43 | name = "GalleryModule" 44 | licenses = ["Apache-2.0"] 45 | publish = true 46 | version { 47 | name = versionLibName 48 | desc = 'Just a simple activity that show all the images and videos in a gallery and allows to select one and retrieve it' 49 | vcsUrl = 'https://github.com/guiguegon/gallerymodule.git' 50 | } 51 | } 52 | } 53 | 54 | android { 55 | compileSdkVersion 24 56 | buildToolsVersion "24.0.2" 57 | 58 | defaultConfig { 59 | minSdkVersion 15 60 | targetSdkVersion 24 61 | versionCode versionLibCode 62 | versionName versionLibName 63 | } 64 | 65 | compileOptions { 66 | sourceCompatibility JavaVersion.VERSION_1_8 67 | targetCompatibility JavaVersion.VERSION_1_8 68 | } 69 | 70 | buildTypes { 71 | release { 72 | minifyEnabled false 73 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 74 | } 75 | } 76 | 77 | packagingOptions { 78 | exclude 'META-INF/ASL2.0' 79 | exclude 'META-INF/LICENSE' 80 | exclude 'META-INF/NOTICE' 81 | } 82 | 83 | lintOptions { 84 | abortOnError false 85 | } 86 | } 87 | 88 | dependencies { 89 | final SUPPORT_LIBRARY_VERSION = '24.2.1' 90 | final AUTOVALUE_VERSION = '1.2' 91 | 92 | compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" 93 | compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION" 94 | 95 | compile "com.google.auto.value:auto-value:$AUTOVALUE_VERSION" 96 | apt "com.google.auto.value:auto-value:$AUTOVALUE_VERSION" 97 | apt 'com.ryanharter.auto.value:auto-value-parcel:0.2.4-rc2' 98 | 99 | //Dexter (permissions) 100 | compile 'com.karumi:dexter:2.3.0' 101 | 102 | //Glide 103 | compile 'com.github.bumptech.glide:glide:3.7.0' 104 | 105 | //RxAndroid 106 | compile 'io.reactivex:rxandroid:1.2.1' 107 | // Because RxAndroid releases are few and far between, it is recommended you also 108 | // explicitly depend on RxJava's latest version for bug fixes and new features. 109 | compile 'io.reactivex:rxjava:1.1.6' 110 | } 111 | 112 | if (project.rootProject.file('local.properties').exists()) { 113 | apply from: 'https://raw.githubusercontent.com/borjabravo10/JCenter/master/installv1.gradle' 114 | apply from: 'https://raw.githubusercontent.com/borjabravo10/JCenter/master/bintrayv1.gradle' 115 | } 116 | 117 | allprojects { 118 | tasks.withType(Javadoc) { 119 | options.addStringOption('Xdoclint:none', '-quiet') 120 | options.addStringOption('encoding', 'UTF-8') 121 | } 122 | } 123 | 124 | bintrayUpload.mustRunAfter install 125 | task remoteUpload(dependsOn: ['install', 'bintrayUpload']) 126 | -------------------------------------------------------------------------------- /gallerymodule/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/guillermoguerrero/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /gallerymodule/src/androidTest/java/es/guiguegon/gallerymodule/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /gallerymodule/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 10 | 15 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/GalleryActivity.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v4.app.FragmentTransaction; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.MenuItem; 10 | import com.karumi.dexter.Dexter; 11 | import java.util.List; 12 | 13 | public class GalleryActivity extends AppCompatActivity { 14 | 15 | public static final String EXTRA_MULTISELECTION = "extra_multiselection"; 16 | public static final String EXTRA_SHOW_VIDEOS = "extra_show_videos"; 17 | public static final String EXTRA_MAX_SELECTED_ITEMS = "extra_max_selected_items"; 18 | public static final String RESULT_GALLERY_MEDIA_LIST = "result_gallery_media_list"; 19 | private boolean multiselection; 20 | private boolean showVideos; 21 | private int maxSelectedItems; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | Dexter.initialize(this); 27 | if (savedInstanceState == null) { 28 | multiselection = getIntent().getBooleanExtra(EXTRA_MULTISELECTION, false); 29 | showVideos = getIntent().getBooleanExtra(EXTRA_SHOW_VIDEOS, true); 30 | maxSelectedItems = getIntent().getIntExtra(EXTRA_MAX_SELECTED_ITEMS, Integer.MAX_VALUE); 31 | } 32 | setContentView(R.layout.activity_gallery); 33 | } 34 | 35 | @Override 36 | protected void onPostCreate(@Nullable Bundle savedInstanceState) { 37 | super.onPostCreate(savedInstanceState); 38 | if (savedInstanceState == null) { 39 | replaceFragment(R.id.fragment_content, 40 | GalleryFragment.newInstance(multiselection, maxSelectedItems, showVideos)); 41 | } 42 | } 43 | 44 | @Override 45 | public boolean onOptionsItemSelected(MenuItem item) { 46 | switch (item.getItemId()) { 47 | case android.R.id.home: 48 | onBackPressed(); 49 | return true; 50 | default: 51 | } 52 | return super.onOptionsItemSelected(item); 53 | } 54 | 55 | @Override 56 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 57 | super.onActivityResult(requestCode, resultCode, data); 58 | List fragments = getSupportFragmentManager().getFragments(); 59 | if (fragments != null) { 60 | for (Fragment fragment : fragments) { 61 | if (fragment != null) { 62 | fragment.onActivityResult(requestCode, resultCode, data); 63 | } 64 | } 65 | } 66 | } 67 | 68 | protected void replaceFragment(int containerViewId, Fragment fragment) { 69 | FragmentTransaction fragmentTransaction = this.getSupportFragmentManager() 70 | .beginTransaction(); 71 | fragmentTransaction.replace(containerViewId, fragment, fragment.getClass() 72 | .getSimpleName()); 73 | fragmentTransaction.commit(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/GalleryFragment.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.app.Dialog; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.Fragment; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.support.v7.widget.StaggeredGridLayoutManager; 13 | import android.support.v7.widget.Toolbar; 14 | import android.view.LayoutInflater; 15 | import android.view.Menu; 16 | import android.view.MenuInflater; 17 | import android.view.MenuItem; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.view.Window; 21 | import android.widget.Button; 22 | import android.widget.ProgressBar; 23 | import android.widget.TextView; 24 | import android.widget.Toast; 25 | import es.guiguegon.gallerymodule.adapters.GalleryAdapter; 26 | import es.guiguegon.gallerymodule.helpers.CameraHelper; 27 | import es.guiguegon.gallerymodule.helpers.GalleryHelper; 28 | import es.guiguegon.gallerymodule.helpers.PermissionsManager; 29 | import es.guiguegon.gallerymodule.model.GalleryMedia; 30 | import es.guiguegon.gallerymodule.utils.ScreenUtils; 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | 34 | public class GalleryFragment extends Fragment 35 | implements GalleryAdapter.OnGalleryClickListener, GalleryHelper.GalleryHelperListener { 36 | 37 | private static final String ARGUMENT_MULTISELECTION = "argument_multiselection"; 38 | private static final String ARGUMENT_SHOW_VIDEOS = "argument_show_videos"; 39 | private static final String ARGUMENT_MAX_SELECTED_ITEMS = "argument_max_selected_items"; 40 | private static final String KEY_GALLERY_MEDIA = "key_gallery_media"; 41 | private static final String KEY_SELECTED_POSITION = "key_selected_position"; 42 | 43 | private Toolbar toolbar; 44 | private RecyclerView galleryRecyclerView; 45 | private ProgressBar loadingProgressBar; 46 | private Button btnRetry; 47 | private TextView emptyTextview; 48 | 49 | private ArrayList galleryMedias = new ArrayList<>(); 50 | private ArrayList selectedPositions = new ArrayList<>(); 51 | 52 | private GalleryAdapter galleryAdapter; 53 | private StaggeredGridLayoutManager staggeredGridLayoutManager; 54 | private CameraHelper cameraHelper; 55 | private GalleryHelper galleryHelper; 56 | 57 | private MenuItem checkItem; 58 | private Dialog dialog; 59 | private boolean multiselection; 60 | private boolean showVideos; 61 | private int maxSelectedItems; 62 | 63 | static GalleryFragment newInstance(boolean multiselection, int maxSelectedItems, 64 | boolean showVideos) { 65 | GalleryFragment fragment = new GalleryFragment(); 66 | Bundle arguments = new Bundle(); 67 | arguments.putBoolean(ARGUMENT_MULTISELECTION, multiselection); 68 | arguments.putBoolean(ARGUMENT_SHOW_VIDEOS, showVideos); 69 | arguments.putInt(ARGUMENT_MAX_SELECTED_ITEMS, maxSelectedItems); 70 | fragment.setArguments(arguments); 71 | return fragment; 72 | } 73 | 74 | @Override 75 | public void onCreate(@Nullable Bundle savedInstanceState) { 76 | super.onCreate(savedInstanceState); 77 | if (savedInstanceState == null) { 78 | multiselection = getArguments().getBoolean(ARGUMENT_MULTISELECTION, false); 79 | showVideos = getArguments().getBoolean(ARGUMENT_SHOW_VIDEOS, false); 80 | maxSelectedItems = 81 | getArguments().getInt(ARGUMENT_MAX_SELECTED_ITEMS, Integer.MAX_VALUE); 82 | } 83 | setHasOptionsMenu(true); 84 | } 85 | 86 | @Override 87 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 88 | Bundle savedInstanceState) { 89 | galleryHelper = GalleryHelper.getInstance(); 90 | cameraHelper = CameraHelper.getInstance(); 91 | galleryHelper.onCreate(getContext(), this); 92 | cameraHelper.onCreate(getContext()); 93 | return inflater.inflate(R.layout.fragment_gallery, container, false); 94 | } 95 | 96 | @Override 97 | public void onDestroyView() { 98 | super.onDestroyView(); 99 | galleryHelper.onDestroy(); 100 | cameraHelper.onDestroy(); 101 | } 102 | 103 | @Override 104 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 105 | super.onViewCreated(view, savedInstanceState); 106 | toolbar = (Toolbar) view.findViewById(R.id.toolbar); 107 | galleryRecyclerView = (RecyclerView) view.findViewById(R.id.gallery_recycler_view); 108 | loadingProgressBar = (ProgressBar) view.findViewById(R.id.loading_progress_bar); 109 | btnRetry = (Button) view.findViewById(R.id.btn_retry); 110 | emptyTextview = (TextView) view.findViewById(R.id.empty_textview); 111 | setupUi(); 112 | if (savedInstanceState == null) { 113 | init(); 114 | } 115 | } 116 | 117 | @Override 118 | public void onSaveInstanceState(Bundle outState) { 119 | outState.putParcelableArrayList(KEY_GALLERY_MEDIA, galleryMedias); 120 | outState.putIntegerArrayList(KEY_SELECTED_POSITION, 121 | galleryAdapter.getSelectedItemsPosition()); 122 | outState.putBoolean(ARGUMENT_MULTISELECTION, multiselection); 123 | outState.putBoolean(ARGUMENT_SHOW_VIDEOS, showVideos); 124 | outState.putInt(ARGUMENT_MAX_SELECTED_ITEMS, maxSelectedItems); 125 | super.onSaveInstanceState(outState); 126 | } 127 | 128 | @Override 129 | public void onViewStateRestored(@Nullable Bundle savedInstanceState) { 130 | super.onViewStateRestored(savedInstanceState); 131 | if (savedInstanceState != null) { 132 | galleryMedias = savedInstanceState.getParcelableArrayList(KEY_GALLERY_MEDIA); 133 | selectedPositions = savedInstanceState.getIntegerArrayList(KEY_SELECTED_POSITION); 134 | multiselection = savedInstanceState.getBoolean(ARGUMENT_MULTISELECTION); 135 | showVideos = savedInstanceState.getBoolean(ARGUMENT_SHOW_VIDEOS); 136 | maxSelectedItems = savedInstanceState.getInt(ARGUMENT_MAX_SELECTED_ITEMS); 137 | afterConfigChange(); 138 | } 139 | } 140 | 141 | @Override 142 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 143 | super.onActivityResult(requestCode, resultCode, data); 144 | GalleryMedia galleryMedia = 145 | cameraHelper.onGetPictureIntentResults(requestCode, resultCode, data); 146 | if (galleryMedia != null) { 147 | onGalleryMedia(galleryMedia); 148 | } 149 | } 150 | 151 | protected void setupUi() { 152 | setToolbar(toolbar); 153 | int columns = getMaxColumns(); 154 | galleryAdapter = new GalleryAdapter(getContext(), columns); 155 | staggeredGridLayoutManager = 156 | new StaggeredGridLayoutManager(columns, StaggeredGridLayoutManager.VERTICAL); 157 | galleryRecyclerView.setLayoutManager(staggeredGridLayoutManager); 158 | galleryRecyclerView.setAdapter(galleryAdapter); 159 | galleryAdapter.setMultiselection(multiselection); 160 | galleryAdapter.setMaxSelectedItems(maxSelectedItems); 161 | galleryAdapter.setOnGalleryClickListener(this); 162 | btnRetry.setOnClickListener(this::onButtonRetryClick); 163 | } 164 | 165 | protected void init() { 166 | getGalleryMedia(); 167 | } 168 | 169 | protected void afterConfigChange() { 170 | fillGalleryMedia(); 171 | galleryAdapter.setMultiselection(multiselection); 172 | galleryAdapter.setMaxSelectedItems(maxSelectedItems); 173 | } 174 | 175 | @Override 176 | public void onPause() { 177 | super.onPause(); 178 | if (dialog != null) { 179 | dialog.dismiss(); 180 | dialog = null; 181 | } 182 | } 183 | 184 | public int getMaxColumns() { 185 | int widthRecyclerViewMediaFiles = ScreenUtils.getScreenWidth(getContext()); 186 | int sizeItemsRecyclerView = 187 | getResources().getDimensionPixelSize(R.dimen.gallery_item_min_width); 188 | return widthRecyclerViewMediaFiles / sizeItemsRecyclerView; 189 | } 190 | 191 | private void fillGalleryMedia() { 192 | if (!galleryMedias.isEmpty()) { 193 | galleryAdapter.addGalleryImage(galleryMedias); 194 | galleryAdapter.setSelectedPositions(selectedPositions); 195 | hideEmptyList(); 196 | } else { 197 | showEmptyList(); 198 | } 199 | } 200 | 201 | private void showLoading() { 202 | loadingProgressBar.setVisibility(View.VISIBLE); 203 | } 204 | 205 | private void hideLoading() { 206 | loadingProgressBar.setVisibility(View.GONE); 207 | } 208 | 209 | private void showEmptyList() { 210 | galleryRecyclerView.setVisibility(View.GONE); 211 | emptyTextview.setVisibility(View.VISIBLE); 212 | } 213 | 214 | private void hideEmptyList() { 215 | galleryRecyclerView.setVisibility(View.VISIBLE); 216 | emptyTextview.setVisibility(View.GONE); 217 | btnRetry.setVisibility(View.GONE); 218 | } 219 | 220 | public void showRetry() { 221 | btnRetry.setVisibility(View.VISIBLE); 222 | } 223 | 224 | protected void setToolbar(Toolbar toolbar) { 225 | ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar); 226 | ((AppCompatActivity) getActivity()).getSupportActionBar() 227 | .setDisplayHomeAsUpEnabled(true); 228 | ((AppCompatActivity) getActivity()).getSupportActionBar() 229 | .setHomeButtonEnabled(true); 230 | } 231 | 232 | @Override 233 | public void onGalleryClick(GalleryMedia galleryMedia) { 234 | if (multiselection) { 235 | handleToolbarState(); 236 | } else { 237 | onGalleryMediaSelected(galleryMedia); 238 | } 239 | } 240 | 241 | @Override 242 | public void onCameraClick() { 243 | try { 244 | PermissionsManager.requestMultiplePermissions((ViewGroup) getView(), 245 | () -> camera(getActivity()), Manifest.permission.CAMERA, 246 | Manifest.permission.WRITE_EXTERNAL_STORAGE); 247 | } catch (Exception e) { 248 | showError(getString(R.string.gallery_exception_necessary_permissions)); 249 | } 250 | } 251 | 252 | public void getGalleryMedia() { 253 | try { 254 | PermissionsManager.requestMultiplePermissions((ViewGroup) getView(), 255 | this::getGalleryImages, this::showRetry, 256 | Manifest.permission.WRITE_EXTERNAL_STORAGE); 257 | } catch (Exception e) { 258 | showError(getString(R.string.gallery_exception_necessary_permissions)); 259 | showRetry(); 260 | } 261 | } 262 | 263 | public void camera(Activity activity) { 264 | if (showVideos) { 265 | dialog = new Dialog(getContext()); 266 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 267 | dialog.setContentView(R.layout.dialog_gallery); 268 | Button takePhotoButton = (Button) dialog.findViewById(R.id.gallery_take_photo); 269 | takePhotoButton.setOnClickListener(v -> { 270 | dialog.dismiss(); 271 | cameraHelper.dispatchGetPictureIntent(activity); 272 | dialog = null; 273 | }); 274 | Button recordVideoButton = (Button) dialog.findViewById(R.id.gallery_record_video); 275 | recordVideoButton.setOnClickListener(v -> { 276 | dialog.dismiss(); 277 | cameraHelper.dispatchGetVideoIntent(activity); 278 | dialog = null; 279 | }); 280 | dialog.show(); 281 | } else { 282 | cameraHelper.dispatchGetPictureIntent(activity); 283 | } 284 | } 285 | 286 | public void getGalleryImages() { 287 | galleryHelper.getGalleryAsync(showVideos); 288 | showLoading(); 289 | } 290 | 291 | public void showError(String message) { 292 | hideLoading(); 293 | Toast.makeText(getContext(), message, Toast.LENGTH_LONG) 294 | .show(); 295 | } 296 | 297 | public void onGalleryMedia(GalleryMedia galleryMedia) { 298 | this.galleryMedias.add(0, galleryMedia); 299 | galleryAdapter.addGalleryImage(galleryMedia); 300 | hideEmptyList(); 301 | } 302 | 303 | public void onGalleryMedia(List galleryMedias) { 304 | this.galleryMedias.addAll(0, galleryMedias); 305 | galleryAdapter.addGalleryImage(galleryMedias); 306 | hideEmptyList(); 307 | } 308 | 309 | public void onGalleryMediaSelected(ArrayList galleryMedias) { 310 | Intent intent = new Intent(); 311 | intent.putParcelableArrayListExtra(GalleryActivity.RESULT_GALLERY_MEDIA_LIST, 312 | galleryMedias); 313 | getActivity().setResult(Activity.RESULT_OK, intent); 314 | getActivity().supportFinishAfterTransition(); 315 | } 316 | 317 | public void onGalleryMediaSelected(GalleryMedia galleryMedia) { 318 | ArrayList galleryMedias = new ArrayList<>(); 319 | galleryMedias.add(galleryMedia); 320 | onGalleryMediaSelected(galleryMedias); 321 | } 322 | 323 | void onButtonRetryClick(View view) { 324 | init(); 325 | } 326 | 327 | @Override 328 | public void onGalleryReady(List galleryMedias) { 329 | hideLoading(); 330 | onGalleryMedia(galleryMedias); 331 | } 332 | 333 | @Override 334 | public void onGalleryError() { 335 | hideLoading(); 336 | showError(getString(R.string.gallery_something_went_wrong)); 337 | showRetry(); 338 | } 339 | 340 | /** Menu */ 341 | @Override 342 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 343 | super.onCreateOptionsMenu(menu, inflater); 344 | inflater.inflate(R.menu.menu_gallery, menu); 345 | checkItem = menu.findItem(R.id.gallery_action_check); 346 | handleToolbarState(); 347 | } 348 | 349 | private void handleToolbarState() { 350 | int selectedItemCount = galleryAdapter.getSelectedItemCount(); 351 | if (selectedItemCount > 0) { 352 | checkItem.setVisible(true); 353 | toolbar.setTitle(String.format(getString(R.string.gallery_toolbar_title_selected), 354 | String.valueOf(selectedItemCount))); 355 | } else { 356 | checkItem.setVisible(false); 357 | toolbar.setTitle(R.string.gallery_toolbar_title); 358 | } 359 | } 360 | 361 | @Override 362 | public boolean onOptionsItemSelected(MenuItem item) { 363 | if (item.getItemId() == R.id.gallery_action_check) { 364 | onGalleryMediaSelected(galleryAdapter.getSelectedItems()); 365 | return true; 366 | } 367 | return super.onOptionsItemSelected(item); 368 | } 369 | } -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/GalleryHelper.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | 6 | /** 7 | * Created by guiguegon on 22/09/2016. 8 | */ 9 | 10 | public class GalleryHelper { 11 | 12 | private boolean showVideos = true; 13 | private boolean multiselection; 14 | private int maxSelectedItems; 15 | 16 | public GalleryHelper() { 17 | } 18 | 19 | public GalleryHelper setMultiselection(boolean multiselection) { 20 | this.multiselection = multiselection; 21 | return this; 22 | } 23 | 24 | public GalleryHelper setMaxSelectedItems(int maxSelectedItems) { 25 | this.maxSelectedItems = maxSelectedItems; 26 | return this; 27 | } 28 | 29 | public GalleryHelper setShowVideos(boolean showVideos) { 30 | this.showVideos = showVideos; 31 | return this; 32 | } 33 | 34 | public Intent getCallingIntent(Context context) { 35 | Intent intent = new Intent(context, GalleryActivity.class); 36 | intent.putExtra(GalleryActivity.EXTRA_MULTISELECTION, multiselection); 37 | intent.putExtra(GalleryActivity.EXTRA_MAX_SELECTED_ITEMS, maxSelectedItems); 38 | intent.putExtra(GalleryActivity.EXTRA_SHOW_VIDEOS, showVideos); 39 | return intent; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/adapters/GalleryAdapter.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.adapters; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.support.v7.widget.StaggeredGridLayoutManager; 6 | import android.util.Log; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | import android.widget.ImageView; 12 | import android.widget.TextView; 13 | import es.guiguegon.gallerymodule.R; 14 | import es.guiguegon.gallerymodule.model.GalleryMedia; 15 | import es.guiguegon.gallerymodule.utils.ImageUtils; 16 | import es.guiguegon.gallerymodule.utils.ScreenUtils; 17 | import es.guiguegon.gallerymodule.utils.TextureCameraPreview; 18 | import es.guiguegon.gallerymodule.utils.TimeUtils; 19 | import java.lang.ref.WeakReference; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | public class GalleryAdapter extends SelectableAdapter { 24 | 25 | private final static int VIEW_HOLDER_TYPE_HEADER = 1; 26 | private final static int VIEW_HOLDER_TYPE_ITEM = 2; 27 | private final String TAG = "[" + this.getClass().getSimpleName() + "]"; 28 | private ArrayList galleryMedias; 29 | private WeakReference onGalleryClickListenerWeak; 30 | private boolean multiselection; 31 | private int itemWidth; 32 | private int itemHeight; 33 | private int maxSelectedItems = Integer.MAX_VALUE; 34 | 35 | public GalleryAdapter(Context context, int columns) { 36 | galleryMedias = new ArrayList<>(); 37 | itemWidth = ScreenUtils.getScreenWidth(context) / columns; 38 | itemHeight = context.getResources().getDimensionPixelSize(R.dimen.gallery_item_height); 39 | } 40 | 41 | public void setMultiselection(boolean multiselection) { 42 | this.multiselection = multiselection; 43 | } 44 | 45 | public void setMaxSelectedItems(int maxSelectedItems) { 46 | this.maxSelectedItems = maxSelectedItems; 47 | } 48 | 49 | public void setOnGalleryClickListener(OnGalleryClickListener onGalleryClickListener) { 50 | this.onGalleryClickListenerWeak = new WeakReference<>(onGalleryClickListener); 51 | } 52 | 53 | public void addGalleryImage(GalleryMedia galleryMedia) { 54 | this.galleryMedias.add(0, galleryMedia); 55 | notifySelectableAdapterItemInserted(1); 56 | } 57 | 58 | public void addGalleryImage(List galleryMedias) { 59 | this.galleryMedias.addAll(galleryMedias); 60 | notifyItemRangeInserted(1, galleryMedias.size()); 61 | } 62 | 63 | public ArrayList getSelectedItems() { 64 | ArrayList galleryMedias = new ArrayList<>(); 65 | for (Integer position : getSelectedItemsPosition()) { 66 | galleryMedias.add(this.galleryMedias.get(position - 1)); 67 | } 68 | return galleryMedias; 69 | } 70 | 71 | @Override 72 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { 73 | View v; 74 | switch (viewType) { 75 | case VIEW_HOLDER_TYPE_HEADER: 76 | v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_gallery_header, viewGroup, false); 77 | return new GalleryHeaderViewHolder(v); 78 | case VIEW_HOLDER_TYPE_ITEM: 79 | default: 80 | v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_gallery, viewGroup, false); 81 | return new GalleryItemViewHolder(v); 82 | } 83 | } 84 | 85 | @Override 86 | public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { 87 | try { 88 | final ViewGroup.LayoutParams layoutParams = viewHolder.itemView.getLayoutParams(); 89 | StaggeredGridLayoutManager.LayoutParams sglayoutParams = 90 | (StaggeredGridLayoutManager.LayoutParams) layoutParams; 91 | if (viewHolder instanceof GalleryHeaderViewHolder) { 92 | sglayoutParams.setFullSpan(true); 93 | fill((GalleryHeaderViewHolder) viewHolder); 94 | } else { 95 | sglayoutParams.setFullSpan(false); 96 | fill((GalleryItemViewHolder) viewHolder, galleryMedias.get(position - 1), position); 97 | } 98 | viewHolder.itemView.setLayoutParams(sglayoutParams); 99 | } catch (Exception e) { 100 | Log.e(TAG, "[onBindViewHolder] ", e); 101 | } 102 | } 103 | 104 | @Override 105 | public int getItemViewType(int position) { 106 | switch (position) { 107 | case 0: 108 | return VIEW_HOLDER_TYPE_HEADER; 109 | default: 110 | return VIEW_HOLDER_TYPE_ITEM; 111 | } 112 | } 113 | 114 | @Override 115 | public int getItemCount() { 116 | return galleryMedias.size() + 1; 117 | } 118 | 119 | public void fill(GalleryItemViewHolder galleryItemViewHolder, final GalleryMedia galleryMedia, int position) { 120 | galleryItemViewHolder.galleryItemSelected.setSelected(isSelected(position)); 121 | Context context = galleryItemViewHolder.itemView.getContext(); 122 | ImageUtils.loadImageFromUri(context, galleryMedia.mediaUri(), 123 | galleryItemViewHolder.galleryItem, itemWidth, 124 | itemHeight); 125 | if (galleryMedia.isVideo()) { 126 | galleryItemViewHolder.galleryItemVideoDuration.setText( 127 | TimeUtils.getTimeFromVideoDuration(galleryMedia.duration())); 128 | galleryItemViewHolder.galleryItemVideoDuration.setVisibility(View.VISIBLE); 129 | } else { 130 | galleryItemViewHolder.galleryItemVideoDuration.setVisibility(View.GONE); 131 | } 132 | galleryItemViewHolder.galleryItemLayout.setOnClickListener(v -> { 133 | if (multiselection && canSelectItem(position)) { 134 | toggleSelection(position); 135 | } 136 | onGalleryClickListenerWeak.get().onGalleryClick(galleryMedia); 137 | }); 138 | } 139 | 140 | private boolean canSelectItem(int position) { 141 | return isSelected(position) || getSelectedItemCount() < maxSelectedItems; 142 | } 143 | 144 | public void fill(GalleryHeaderViewHolder galleryHeaderViewHolder) { 145 | galleryHeaderViewHolder.galleryCameraLayout.setOnClickListener( 146 | v -> onGalleryClickListenerWeak.get().onCameraClick()); 147 | } 148 | 149 | public interface OnGalleryClickListener { 150 | void onGalleryClick(GalleryMedia galleryMedia); 151 | 152 | void onCameraClick(); 153 | } 154 | 155 | public class GalleryItemViewHolder extends RecyclerView.ViewHolder { 156 | 157 | View galleryItemSelected; 158 | ImageView galleryItem; 159 | ImageView galleryGradient; 160 | FrameLayout galleryItemLayout; 161 | TextView galleryItemVideoDuration; 162 | 163 | public GalleryItemViewHolder(View v) { 164 | super(v); 165 | galleryItem = (ImageView) v.findViewById(R.id.gallery_item); 166 | galleryGradient = (ImageView) v.findViewById(R.id.gallery_gradient); 167 | galleryItemVideoDuration = (TextView) v.findViewById(R.id.gallery_video_duration); 168 | galleryItemLayout = (FrameLayout) v.findViewById(R.id.gallery_item_layout); 169 | galleryItemSelected = v.findViewById(R.id.gallery_item_selected); 170 | galleryItem.getLayoutParams().width = itemWidth; 171 | galleryGradient.getLayoutParams().width = itemWidth; 172 | } 173 | } 174 | 175 | public class GalleryHeaderViewHolder extends RecyclerView.ViewHolder { 176 | 177 | FrameLayout galleryCameraLayout; 178 | TextureCameraPreview galleryCameraPreview; 179 | 180 | public GalleryHeaderViewHolder(View v) { 181 | super(v); 182 | galleryCameraLayout = (FrameLayout) v.findViewById(R.id.gallery_camera_layout); 183 | galleryCameraPreview = (TextureCameraPreview) v.findViewById(R.id.gallery_camera_preview); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/adapters/SelectableAdapter.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.adapters; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.util.SparseBooleanArray; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public abstract class SelectableAdapter extends RecyclerView.Adapter { 9 | 10 | private SparseBooleanArray selectedItems; 11 | 12 | public SelectableAdapter() { 13 | selectedItems = new SparseBooleanArray(); 14 | } 15 | 16 | /* 17 | * Indicates if the item at position position is selected 18 | * @param position Position of the item to check 19 | * @return true if the item is selected, false otherwise 20 | */ 21 | public boolean isSelected(int position) { 22 | return getSelectedItemsPosition().contains(position); 23 | } 24 | 25 | /** 26 | * Toggle the selection status of the item at a given position 27 | * 28 | * @param position Position of the item to toggle the selection status for 29 | */ 30 | public void toggleSelection(int position) { 31 | if (selectedItems.get(position, false)) { 32 | selectedItems.delete(position); 33 | } else { 34 | selectedItems.put(position, true); 35 | } 36 | notifyItemChanged(position); 37 | } 38 | 39 | /** 40 | * Toggle the selection status of the items position contained in ArrayList 41 | * 42 | * @param selectedPositions Positions of the items to toggle the selection status for 43 | */ 44 | public void setSelectedPositions(ArrayList selectedPositions) { 45 | for (Integer integer : selectedPositions) { 46 | toggleSelection(integer); 47 | } 48 | } 49 | 50 | /** 51 | * Clear the selection status for all items 52 | */ 53 | public void clearSelection() { 54 | List selection = getSelectedItemsPosition(); 55 | selectedItems.clear(); 56 | for (Integer i : selection) { 57 | notifyItemChanged(i); 58 | } 59 | } 60 | 61 | public void selectAll(int position) { 62 | selectedItems.put(position, true); 63 | notifyItemChanged(position); 64 | } 65 | 66 | public void unSelectAll(int position) { 67 | selectedItems.delete(position); 68 | notifyItemChanged(position); 69 | } 70 | 71 | public void clearSelected() { 72 | selectedItems.clear(); 73 | } 74 | 75 | /** 76 | * Count the selected items 77 | * 78 | * @return Selected items count 79 | */ 80 | public int getSelectedItemCount() { 81 | return selectedItems.size(); 82 | } 83 | 84 | protected void notifySelectableAdapterItemInserted(int itemInsertedPosition) { 85 | List selection = getSelectedItemsPosition(); 86 | selectedItems.clear(); 87 | for (Integer position : selection) { 88 | if (position < itemInsertedPosition) { 89 | selectedItems.put(position, true); 90 | } else { 91 | selectedItems.put(position + 1, true); 92 | } 93 | } 94 | notifyDataSetChanged(); 95 | } 96 | 97 | protected void notifySelectableAdapterItemRemoved(int itemInsertedPosition) { 98 | List selection = getSelectedItemsPosition(); 99 | selectedItems.clear(); 100 | for (Integer position : selection) { 101 | if (position < itemInsertedPosition) { 102 | selectedItems.put(position, true); 103 | } else { 104 | selectedItems.put(position - 1, true); 105 | } 106 | } 107 | notifyDataSetChanged(); 108 | } 109 | 110 | /** 111 | * Indicates the list of selected items 112 | * 113 | * @return List of selected items ids 114 | */ 115 | public ArrayList getSelectedItemsPosition() { 116 | ArrayList items = new ArrayList<>(selectedItems.size()); 117 | for (int i = 0; i < selectedItems.size(); ++i) { 118 | items.add(selectedItems.keyAt(i)); 119 | } 120 | return items; 121 | } 122 | } -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/helpers/CameraHelper.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.helpers; 2 | 3 | import android.app.Activity; 4 | import android.content.ContentValues; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.media.MediaMetadataRetriever; 8 | import android.net.Uri; 9 | import android.provider.MediaStore; 10 | import android.support.v4.content.FileProvider; 11 | import android.util.Log; 12 | import es.guiguegon.gallerymodule.BuildConfig; 13 | import es.guiguegon.gallerymodule.model.GalleryMedia; 14 | import es.guiguegon.gallerymodule.utils.FileUtils; 15 | import java.io.File; 16 | 17 | /** 18 | * Created by guiguegon on 23/10/2015. 19 | */ 20 | public class CameraHelper { 21 | 22 | private static final int REQUEST_CODE_CAMERA = 15; 23 | private static final String MIME_TYPE_IMAGE = "image/jpeg"; 24 | private static final String MIME_TYPE_VIDEO = "video/mp4"; 25 | private static CameraHelper mInstance; 26 | private final String TAG = "[" + this.getClass().getSimpleName() + "]"; 27 | private Uri mediaUri; 28 | private String mediaPath; 29 | private String mimeType; 30 | private Context context; 31 | 32 | private CameraHelper() { 33 | } 34 | 35 | public static CameraHelper getInstance() { 36 | if (mInstance == null) { 37 | mInstance = new CameraHelper(); 38 | } 39 | return mInstance; 40 | } 41 | 42 | public void onCreate(Context context) { 43 | this.context = context; 44 | } 45 | 46 | public void onDestroy() { 47 | this.context = null; 48 | } 49 | 50 | public void dispatchGetPictureIntent(Activity activity) { 51 | try { 52 | Intent cameraIntent = new Intent(); 53 | cameraIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); 54 | ContentValues values = new ContentValues(1); 55 | mimeType = MIME_TYPE_IMAGE; 56 | values.put(MediaStore.Images.Media.MIME_TYPE, mimeType); 57 | File file = FileUtils.getOutputMediaFile(FileUtils.MEDIA_TYPE_IMAGE); 58 | mediaPath = file != null ? file.getAbsolutePath() : null; 59 | mediaUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", file); 60 | cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, mediaUri); 61 | cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 62 | activity.startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA); 63 | } catch (Exception e) { 64 | Log.e(TAG, "[dispatchGetPictureIntent]", e); 65 | } 66 | } 67 | 68 | public void dispatchGetVideoIntent(Activity activity) { 69 | try { 70 | Intent cameraIntent = new Intent(); 71 | cameraIntent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); 72 | ContentValues values = new ContentValues(1); 73 | mimeType = MIME_TYPE_VIDEO; 74 | values.put(MediaStore.Images.Media.MIME_TYPE, mimeType); 75 | File file = FileUtils.getOutputMediaFile(FileUtils.MEDIA_TYPE_VIDEO); 76 | mediaPath = file != null ? file.getAbsolutePath() : null; 77 | mediaUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", file); 78 | cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, mediaUri); 79 | cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 80 | activity.startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA); 81 | } catch (Exception e) { 82 | Log.e(TAG, "[dispatchGetVideoIntent]", e); 83 | } 84 | } 85 | 86 | public GalleryMedia onGetPictureIntentResults(final int requestCode, final int resultCode, final Intent data) { 87 | if (requestCode == REQUEST_CODE_CAMERA) { 88 | if (resultCode == Activity.RESULT_OK) { 89 | galleryAddPic(); 90 | long duration = 0; 91 | if (MIME_TYPE_VIDEO.equals(mimeType)) { 92 | duration = getGalleryMediaDuration(); 93 | } 94 | return GalleryMedia.create(0, mediaPath, mimeType, duration, 0); 95 | } 96 | } 97 | return null; 98 | } 99 | 100 | private long getGalleryMediaDuration() { 101 | MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 102 | retriever.setDataSource(context, mediaUri); 103 | return Long.valueOf( 104 | retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); 105 | 106 | } 107 | 108 | private void galleryAddPic() { 109 | if (context != null) { 110 | Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 111 | mediaScanIntent.setData(Uri.fromFile(new File(mediaPath))); 112 | context.sendBroadcast(mediaScanIntent); 113 | } 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/helpers/GalleryHelper.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.helpers; 2 | 3 | import android.content.Context; 4 | import android.database.Cursor; 5 | import android.provider.MediaStore; 6 | import android.support.annotation.UiThread; 7 | import android.support.annotation.WorkerThread; 8 | import android.util.Log; 9 | import es.guiguegon.gallerymodule.model.GalleryMedia; 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | import rx.Observable; 14 | import rx.android.schedulers.AndroidSchedulers; 15 | import rx.schedulers.Schedulers; 16 | 17 | /** 18 | * Created by guiguegon on 23/10/2015. 19 | */ 20 | public class GalleryHelper { 21 | 22 | private static GalleryHelper mInstance; 23 | private final String TAG = "[" + this.getClass() 24 | .getSimpleName() + "]"; 25 | private GalleryHelperListener galleryHelperListener; 26 | private Context context; 27 | 28 | private GalleryHelper() { 29 | } 30 | 31 | public static GalleryHelper getInstance() { 32 | if (mInstance == null) { 33 | mInstance = new GalleryHelper(); 34 | } 35 | return mInstance; 36 | } 37 | 38 | public void onCreate(Context context, GalleryHelperListener galleryHelperListener) { 39 | this.context = context; 40 | this.galleryHelperListener = galleryHelperListener; 41 | } 42 | 43 | public void onDestroy() { 44 | this.context = null; 45 | this.galleryHelperListener = null; 46 | } 47 | 48 | public void getGalleryAsync(boolean showVideos) { 49 | final Observable> observable = 50 | Observable.create((Observable.OnSubscribe>) subscriber -> { 51 | subscriber.onNext(getGallery(showVideos)); 52 | subscriber.onCompleted(); 53 | }) 54 | .subscribeOn(Schedulers.io()) 55 | .observeOn(AndroidSchedulers.mainThread()); 56 | observable.subscribe(this::onGalleryMedia, this::onGalleryError); 57 | } 58 | 59 | @UiThread 60 | private void onGalleryMedia(List galleryMedias) { 61 | if (galleryHelperListener != null) { 62 | galleryHelperListener.onGalleryReady(galleryMedias); 63 | } 64 | } 65 | 66 | @UiThread 67 | private void onGalleryError(Throwable throwable) { 68 | Log.e(TAG, "[onGalleryError]", throwable); 69 | if (galleryHelperListener != null) { 70 | galleryHelperListener.onGalleryError(); 71 | } 72 | } 73 | 74 | @WorkerThread 75 | private List getGallery(boolean showVideos) { 76 | List galleryMedias = new ArrayList<>(); 77 | galleryMedias.addAll(getGalleryImages()); 78 | if (showVideos) { 79 | galleryMedias.addAll(getGalleryVideos()); 80 | } 81 | Collections.sort(galleryMedias); 82 | return galleryMedias; 83 | } 84 | 85 | @WorkerThread 86 | private List getGalleryImages() { 87 | ArrayList galleryMedias = new ArrayList<>(); 88 | try { 89 | final String[] columns = { 90 | MediaStore.Images.Media.DATA, MediaStore.Images.Media._ID, 91 | MediaStore.Images.Media.MIME_TYPE, MediaStore.Images.Media.DATE_TAKEN 92 | }; 93 | final String orderBy = MediaStore.Images.Media.DATE_TAKEN; 94 | Cursor imageCursor = context.getContentResolver() 95 | .query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, columns, null, null, 96 | orderBy + " DESC"); 97 | if (imageCursor != null) { 98 | int dataColumnIndex = imageCursor.getColumnIndex(MediaStore.Images.Media.DATA); 99 | int idColumnIndex = imageCursor.getColumnIndex(MediaStore.Images.Media._ID); 100 | int mimeTypeColumIndex = 101 | imageCursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE); 102 | int dateTakenColumIndex = 103 | imageCursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN); 104 | imageCursor.moveToFirst(); 105 | int imageCount = imageCursor.getCount(); 106 | for (int i = 0; i < imageCount; i++) { 107 | galleryMedias.add(GalleryMedia.create(imageCursor.getLong(idColumnIndex), 108 | imageCursor.getString(dataColumnIndex), 109 | imageCursor.getString(mimeTypeColumIndex), 0, 110 | imageCursor.getLong(dateTakenColumIndex))); 111 | imageCursor.moveToNext(); 112 | } 113 | imageCursor.close(); 114 | } 115 | } catch (Exception e) { 116 | Log.e(TAG, "[getGalleryImages]", e); 117 | } 118 | return galleryMedias; 119 | } 120 | 121 | @WorkerThread 122 | private List getGalleryVideos() { 123 | ArrayList galleryMedias = new ArrayList<>(); 124 | try { 125 | final String[] columns = { 126 | MediaStore.Video.Media.DATA, MediaStore.Video.Media._ID, 127 | MediaStore.Video.Media.MIME_TYPE, MediaStore.Video.Media.DATE_TAKEN, 128 | MediaStore.Video.Media.DURATION 129 | }; 130 | final String orderBy = MediaStore.Video.Media.DATE_TAKEN; 131 | Cursor videoCursor = context.getContentResolver() 132 | .query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, columns, null, null, 133 | orderBy + " DESC"); 134 | if (videoCursor != null) { 135 | int dataColumnIndex = videoCursor.getColumnIndex(MediaStore.Video.Media.DATA); 136 | int idColumnIndex = videoCursor.getColumnIndex(MediaStore.Video.Media._ID); 137 | int mimeTypeColumIndex = 138 | videoCursor.getColumnIndex(MediaStore.Video.Media.MIME_TYPE); 139 | int dateTakenColumIndex = 140 | videoCursor.getColumnIndex(MediaStore.Video.Media.DATE_TAKEN); 141 | int durationColumIndex = 142 | videoCursor.getColumnIndex(MediaStore.Video.Media.DURATION); 143 | videoCursor.moveToFirst(); 144 | int videoCount = videoCursor.getCount(); 145 | for (int i = 0; i < videoCount; i++) { 146 | galleryMedias.add(GalleryMedia.create(videoCursor.getLong(idColumnIndex), 147 | videoCursor.getString(dataColumnIndex), 148 | videoCursor.getString(mimeTypeColumIndex), 149 | videoCursor.getLong(durationColumIndex), 150 | videoCursor.getLong(dateTakenColumIndex))); 151 | videoCursor.moveToNext(); 152 | } 153 | videoCursor.close(); 154 | } 155 | } catch (Exception e) { 156 | Log.e(TAG, "[getGalleryVideos]", e); 157 | } 158 | return galleryMedias; 159 | } 160 | 161 | public interface GalleryHelperListener { 162 | void onGalleryReady(List galleryMedias); 163 | 164 | void onGalleryError(); 165 | } 166 | } 167 | 168 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/helpers/PermissionsManager.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.helpers; 2 | 3 | import android.view.ViewGroup; 4 | import com.karumi.dexter.Dexter; 5 | import com.karumi.dexter.MultiplePermissionsReport; 6 | import com.karumi.dexter.PermissionToken; 7 | import com.karumi.dexter.listener.PermissionRequest; 8 | import com.karumi.dexter.listener.multi.CompositeMultiplePermissionsListener; 9 | import com.karumi.dexter.listener.multi.MultiplePermissionsListener; 10 | import com.karumi.dexter.listener.multi.SnackbarOnAnyDeniedMultiplePermissionsListener; 11 | import es.guiguegon.gallerymodule.R; 12 | import java.util.List; 13 | 14 | public class PermissionsManager { 15 | public static void requestMultiplePermissions(ViewGroup rootView, 16 | final OnAllPermissionsGrantedListener onAllPermissionsGrantedListener, String... permissions) { 17 | requestMultiplePermissions(rootView, onAllPermissionsGrantedListener, null, permissions); 18 | } 19 | 20 | public static void requestMultiplePermissions(ViewGroup rootView, 21 | final OnAllPermissionsGrantedListener onAllPermissionsGrantedListener, 22 | final OnPermissionsDeniedListener onPermissionsDeniedListener, String... permissions) { 23 | MultiplePermissionsListener multiplePermissionsListener = new MultiplePermissionsListener() { 24 | @Override 25 | public void onPermissionsChecked(MultiplePermissionsReport report) { 26 | if (report.areAllPermissionsGranted()) { 27 | onAllPermissionsGrantedListener.onAllPermissionsGranted(); 28 | } else { 29 | if (onPermissionsDeniedListener != null) { 30 | onPermissionsDeniedListener.onPermissionsDenied(); 31 | } 32 | } 33 | } 34 | 35 | @Override 36 | public void onPermissionRationaleShouldBeShown(List permissions, PermissionToken token) { 37 | token.continuePermissionRequest(); 38 | } 39 | }; 40 | MultiplePermissionsListener deniedMultiplePermissionsListener = 41 | SnackbarOnAnyDeniedMultiplePermissionsListener.Builder. 42 | with(rootView, R.string.gallery_exception_necessary_permissions) 43 | .withOpenSettingsButton("Settings") 44 | .build(); 45 | Dexter.checkPermissions(new CompositeMultiplePermissionsListener(multiplePermissionsListener, 46 | deniedMultiplePermissionsListener), permissions); 47 | } 48 | 49 | public interface OnAllPermissionsGrantedListener { 50 | void onAllPermissionsGranted(); 51 | } 52 | 53 | public interface OnPermissionsDeniedListener { 54 | void onPermissionsDenied(); 55 | } 56 | } -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/model/GalleryMedia.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.model; 2 | 3 | import android.os.Parcelable; 4 | import android.support.annotation.NonNull; 5 | import com.google.auto.value.AutoValue; 6 | import es.guiguegon.gallerymodule.utils.FileUtils; 7 | 8 | /** 9 | * Created by guillermoguerrero on 21/4/16. 10 | */ 11 | @AutoValue 12 | public abstract class GalleryMedia implements Comparable, Parcelable { 13 | 14 | public static GalleryMedia create(long id, String mediaUri, String mimeType, long duration, 15 | long dateTaken) { 16 | return new AutoValue_GalleryMedia(id, mediaUri, mimeType, duration, dateTaken); 17 | } 18 | 19 | public abstract long id(); 20 | 21 | public abstract String mediaUri(); 22 | 23 | public abstract String mimeType(); 24 | 25 | public abstract long duration(); 26 | 27 | public abstract long dateTaken(); 28 | 29 | public boolean isVideo() { 30 | return mimeType().contains(FileUtils.VIDEO_MIME_TYPE); 31 | } 32 | 33 | @Override 34 | public int compareTo(@NonNull GalleryMedia another) { 35 | return Long.valueOf(another.dateTaken()) 36 | .compareTo(this.dateTaken()); 37 | } 38 | } -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.utils; 2 | 3 | import android.content.ContentResolver; 4 | import android.net.Uri; 5 | import android.os.Environment; 6 | import android.provider.MediaStore; 7 | import android.util.Log; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.util.Calendar; 11 | 12 | /** 13 | * Created by guillermoguerrero on 2/6/16. 14 | */ 15 | public class FileUtils { 16 | 17 | public static final int MEDIA_TYPE_VIDEO = 1; 18 | public static final int MEDIA_TYPE_IMAGE = 2; 19 | public final static String VIDEO_MIME_TYPE = "video"; 20 | private static final String TAG = "[FileUtils]"; 21 | private final static String MEDIA_FOLDER = ""; 22 | 23 | private FileUtils() { 24 | //empty constructor 25 | } 26 | 27 | public static File getOutputMediaFile(int type) { 28 | File mediaStorageDir = 29 | new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), MEDIA_FOLDER); 30 | if (!mediaStorageDir.exists() && !mediaStorageDir.mkdirs()) { 31 | Log.e(TAG, "Failed to create directory " + MEDIA_FOLDER); 32 | return null; 33 | } 34 | String timeStamp = now(); 35 | File mediaFile; 36 | switch (type) { 37 | case MEDIA_TYPE_IMAGE: 38 | mediaFile = new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpeg"); 39 | break; 40 | case MEDIA_TYPE_VIDEO: 41 | mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_" + timeStamp + ".mp4"); 42 | break; 43 | default: 44 | mediaFile = null; 45 | break; 46 | } 47 | return mediaFile; 48 | } 49 | 50 | public static File createFileFromPath(String path) throws IOException { 51 | return new File(path); 52 | } 53 | 54 | public static void deleteFile(ContentResolver contentResolver, Uri imageUri) { 55 | try { 56 | File file = createFileFromPath(imageUri.getPath()); 57 | file.delete(); 58 | String canonicalPath; 59 | try { 60 | canonicalPath = file.getCanonicalPath(); 61 | } catch (IOException e) { 62 | Log.e(TAG, "[deleteFile]", e); 63 | canonicalPath = file.getAbsolutePath(); 64 | } 65 | final Uri uri = MediaStore.Files.getContentUri("external"); 66 | final int result = contentResolver.delete(uri, MediaStore.Files.FileColumns.DATA + "=?", 67 | new String[] { canonicalPath }); 68 | if (result == 0) { 69 | final String absolutePath = file.getAbsolutePath(); 70 | if (!absolutePath.equals(canonicalPath)) { 71 | contentResolver.delete(uri, MediaStore.Files.FileColumns.DATA + "=?", 72 | new String[] { absolutePath }); 73 | } 74 | } 75 | } catch (Exception e) { 76 | Log.e(TAG, "[deleteFile]", e); 77 | } 78 | } 79 | 80 | public static String now() { 81 | return String.valueOf(Calendar.getInstance().getTimeInMillis()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/utils/ImageUtils.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.utils; 2 | /** 3 | * Created by guillermoguerrero on 2/6/16. 4 | */ 5 | 6 | import android.content.Context; 7 | import android.util.Log; 8 | import android.widget.ImageView; 9 | import com.bumptech.glide.Glide; 10 | 11 | public class ImageUtils { 12 | 13 | private static final String TAG = "[ScreenUtils]"; 14 | 15 | private ImageUtils() { 16 | //empty contructor 17 | } 18 | 19 | public static void loadImageFromUri(Context context, String imageUri, ImageView imageView, int width, int height) { 20 | try { 21 | Glide.with(context).load(imageUri).override(width, height).into(imageView); 22 | } catch (Exception e) { 23 | Log.e(TAG, "[loadImageFromUri]", e); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/utils/ScreenUtils.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.Point; 5 | import android.util.Log; 6 | import android.view.Display; 7 | import android.view.WindowManager; 8 | 9 | /** 10 | * Created by guillermoguerrero on 2/6/16. 11 | */ 12 | public class ScreenUtils { 13 | 14 | private static final String TAG = "[ScreenUtils]"; 15 | 16 | private ScreenUtils() { 17 | //empty constructor 18 | } 19 | 20 | public static int getScreenWidth(Context context) { 21 | try { 22 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 23 | Display display = windowManager.getDefaultDisplay(); 24 | Point size = new Point(); 25 | display.getSize(size); 26 | return size.x; 27 | } catch (Exception e) { 28 | Log.e(TAG, "[getScreenWidth]", e); 29 | } 30 | return 0; 31 | } 32 | 33 | public static int getScreenHeight(Context context) { 34 | try { 35 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 36 | Display display = windowManager.getDefaultDisplay(); 37 | Point size = new Point(); 38 | display.getSize(size); 39 | return size.y; 40 | } catch (Exception e) { 41 | Log.e(TAG, "[getScreenHeight]", e); 42 | } 43 | return 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/utils/TextureCameraPreview.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package es.guiguegon.gallerymodule.utils; 18 | 19 | import android.Manifest; 20 | import android.content.Context; 21 | import android.graphics.SurfaceTexture; 22 | import android.hardware.Camera; 23 | import android.os.Handler; 24 | import android.os.HandlerThread; 25 | import android.os.Looper; 26 | import android.util.AttributeSet; 27 | import android.view.Display; 28 | import android.view.Gravity; 29 | import android.view.Surface; 30 | import android.view.TextureView; 31 | import android.view.ViewGroup; 32 | import android.view.WindowManager; 33 | import android.widget.FrameLayout; 34 | import es.guiguegon.gallerymodule.helpers.PermissionsManager; 35 | import java.util.List; 36 | 37 | /** 38 | * A {@link TextureView} that can be adjusted to a specified aspect ratio. 39 | */ 40 | public class TextureCameraPreview extends TextureView implements TextureView.SurfaceTextureListener { 41 | 42 | private static final String CAMERA_THREAD = "camera_thread"; 43 | 44 | private static final String TAG = "[" + TextureCameraPreview.class.getSimpleName() + "]"; 45 | 46 | private Camera mCamera; 47 | private HandlerThread cameraThread; 48 | private Handler cameraHandler; 49 | private Handler mainHandler; 50 | private SurfaceTexture surfaceTexture; 51 | 52 | public TextureCameraPreview(Context context, AttributeSet attrs) { 53 | super(context, attrs, 0); 54 | setSurfaceTextureListener(this); 55 | } 56 | 57 | @Override 58 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 59 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 60 | int width = MeasureSpec.getSize(widthMeasureSpec); 61 | int height = MeasureSpec.getSize(heightMeasureSpec); 62 | setMeasuredDimension(width, height); 63 | setCameraRotation(); 64 | } 65 | 66 | @Override 67 | public void onSurfaceTextureAvailable(SurfaceTexture arg0, int arg1, int arg2) { 68 | this.surfaceTexture = arg0; 69 | if (cameraThread == null) { 70 | initCameraThread(); 71 | } 72 | checkPermission(); 73 | } 74 | 75 | private void checkPermission() { 76 | try { 77 | PermissionsManager.requestMultiplePermissions((ViewGroup) getParent(), this::initCamera, 78 | Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE); 79 | } catch (Exception e) { 80 | //empty 81 | } 82 | } 83 | 84 | private void initCamera() { 85 | cameraHandler.post(() -> { 86 | getCameraInstance(); 87 | try { 88 | if (mCamera != null) { 89 | mCamera.setPreviewTexture(surfaceTexture); 90 | mCamera.startPreview(); 91 | mainHandler.removeCallbacksAndMessages(null); 92 | cameraHandler.removeCallbacksAndMessages(null); 93 | mainHandler.postDelayed(this::setCameraParameters, 500); 94 | } 95 | } catch (Throwable t) { 96 | //empty 97 | } 98 | }); 99 | } 100 | 101 | private void getCameraInstance() { 102 | try { 103 | mCamera = Camera.open(); 104 | } catch (Exception e) { 105 | //empty 106 | } 107 | } 108 | 109 | private void initCameraThread() { 110 | cameraThread = new HandlerThread(CAMERA_THREAD); 111 | cameraThread.start(); 112 | cameraHandler = new Handler(cameraThread.getLooper()); 113 | mainHandler = new Handler(Looper.getMainLooper()); 114 | } 115 | 116 | @Override 117 | public boolean onSurfaceTextureDestroyed(SurfaceTexture arg0) { 118 | surfaceTexture = null; 119 | mainHandler.removeCallbacksAndMessages(null); 120 | cameraHandler.removeCallbacksAndMessages(null); 121 | cameraHandler.postDelayed(() -> { 122 | try { 123 | if (mCamera != null) { 124 | mCamera.stopPreview(); 125 | mCamera.setPreviewCallback(null); 126 | mCamera.release(); 127 | mCamera = null; 128 | } 129 | } catch (Throwable t) { 130 | //empty 131 | } 132 | }, 500); 133 | return true; 134 | } 135 | 136 | @Override 137 | public void onSurfaceTextureSizeChanged(SurfaceTexture arg0, int arg1, int arg2) { 138 | //empty 139 | } 140 | 141 | @Override 142 | public void onSurfaceTextureUpdated(SurfaceTexture arg0) { 143 | //empty 144 | } 145 | 146 | private void setCameraParameters() { 147 | setCameraPreviewSize(); 148 | setContinuousFocus(); 149 | } 150 | 151 | private void setCameraPreviewSize() { 152 | if (mCamera != null && mCamera.getParameters() != null) { 153 | Camera.Size previewSize = mCamera.getParameters().getPreviewSize(); 154 | setLayoutParams(new FrameLayout.LayoutParams(previewSize.width, previewSize.height, Gravity.CENTER)); 155 | } 156 | } 157 | 158 | private void setCameraRotation() { 159 | android.hardware.Camera.CameraInfo camInfo = new android.hardware.Camera.CameraInfo(); 160 | android.hardware.Camera.getCameraInfo(getBackFacingCameraId(), camInfo); 161 | Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 162 | int rotation = display.getRotation(); 163 | int degrees = 0; 164 | switch (rotation) { 165 | case Surface.ROTATION_0: 166 | degrees = 0; 167 | break; 168 | case Surface.ROTATION_90: 169 | degrees = 90; 170 | break; 171 | case Surface.ROTATION_180: 172 | degrees = 180; 173 | break; 174 | case Surface.ROTATION_270: 175 | degrees = 270; 176 | break; 177 | } 178 | int result = (camInfo.orientation - degrees + 360) % 360; 179 | setRotation(result); 180 | } 181 | 182 | private void setContinuousFocus() { 183 | /* Set Auto focus */ 184 | if (mCamera != null) { 185 | Camera.Parameters parameters = mCamera.getParameters(); 186 | List focusModes = parameters.getSupportedFocusModes(); 187 | if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 188 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 189 | } 190 | mCamera.setParameters(parameters); 191 | } 192 | } 193 | 194 | private int getBackFacingCameraId() { 195 | int cameraId = -1; 196 | // Search for the front facing camera 197 | int numberOfCameras = Camera.getNumberOfCameras(); 198 | for (int i = 0; i < numberOfCameras; i++) { 199 | Camera.CameraInfo info = new Camera.CameraInfo(); 200 | Camera.getCameraInfo(i, info); 201 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { 202 | cameraId = i; 203 | break; 204 | } 205 | } 206 | return cameraId; 207 | } 208 | } -------------------------------------------------------------------------------- /gallerymodule/src/main/java/es/guiguegon/gallerymodule/utils/TimeUtils.java: -------------------------------------------------------------------------------- 1 | package es.guiguegon.gallerymodule.utils; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Created by guillermoguerrero on 17/08/16. 7 | */ 8 | public class TimeUtils { 9 | 10 | private TimeUtils() { 11 | //empty constructor 12 | } 13 | 14 | public static String getTimeFromVideoDuration(long duration) { 15 | return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(duration), 16 | TimeUnit.MILLISECONDS.toSeconds(duration) - TimeUnit.MINUTES.toSeconds( 17 | TimeUnit.MILLISECONDS.toMinutes(duration))); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gallerymodule/src/main/res/drawable/bg_gallery_item_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /gallerymodule/src/main/res/drawable/bg_gradient_item_gallery.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /gallerymodule/src/main/res/drawable/ic_add_photo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 22 | 34 | -------------------------------------------------------------------------------- /gallerymodule/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 18 | -------------------------------------------------------------------------------- /gallerymodule/src/main/res/layout/activity_gallery.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gallerymodule/src/main/res/layout/dialog_gallery.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |