├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── bools.xml │ │ │ │ ├── ids.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── arrays.xml │ │ │ │ └── strings.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-sw600dp │ │ │ │ └── bools.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── segunfamisa │ │ │ │ └── base │ │ │ │ ├── ui │ │ │ │ ├── base │ │ │ │ │ ├── BasePresenter.java │ │ │ │ │ ├── BaseView.java │ │ │ │ │ ├── BaseDialogFragment.java │ │ │ │ │ ├── BaseFragment.java │ │ │ │ │ └── BaseActivity.java │ │ │ │ └── main │ │ │ │ │ ├── MainContract.java │ │ │ │ │ └── MainActivity.java │ │ │ │ ├── model │ │ │ │ └── BaseModel.java │ │ │ │ ├── di │ │ │ │ ├── HasComponent.java │ │ │ │ ├── ActivityContext.java │ │ │ │ ├── ApplicationContext.java │ │ │ │ ├── PerActivity.java │ │ │ │ ├── components │ │ │ │ │ ├── ActivityComponent.java │ │ │ │ │ └── ApplicationComponent.java │ │ │ │ └── modules │ │ │ │ │ ├── ActivityModule.java │ │ │ │ │ └── ApplicationModule.java │ │ │ │ ├── utils │ │ │ │ ├── ListUtils.java │ │ │ │ ├── Config.java │ │ │ │ ├── Clock.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── NetworkUtils.java │ │ │ │ ├── DateUtils.java │ │ │ │ ├── PreferenceUtils.java │ │ │ │ ├── Logger.java │ │ │ │ ├── DeviceUuidFactory.java │ │ │ │ ├── AppUtils.java │ │ │ │ ├── DialogUtils.java │ │ │ │ ├── BitmapUtils.java │ │ │ │ ├── UriUtils.java │ │ │ │ └── MediaPickerDialog.java │ │ │ │ ├── navigator │ │ │ │ └── Navigator.java │ │ │ │ └── App.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── segunfamisa │ │ │ └── base │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── segunfamisa │ │ └── base │ │ └── ApplicationTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segunfamisa/BaseAppTemplate/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/values/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segunfamisa/BaseAppTemplate/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segunfamisa/BaseAppTemplate/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segunfamisa/BaseAppTemplate/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/ui/base/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.ui.base; 2 | 3 | public interface BasePresenter { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segunfamisa/BaseAppTemplate/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/segunfamisa/BaseAppTemplate/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/ui/base/BaseView.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.ui.base; 2 | 3 | public interface BaseView { 4 | void setPresenter(T presenter); 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | BaseAppTemplate.iml 4 | /app/app.iml 5 | /local.properties 6 | /.idea/workspace.xml 7 | /.idea/libraries 8 | .DS_Store 9 | /build 10 | /captures 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/model/BaseModel.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.model; 2 | 3 | /** 4 | * Base Model 5 | * 6 | * Created by Segun Famisa on 14/02/2016. 7 | */ 8 | public interface BaseModel { 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 29 23:13:25 WAT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/HasComponent.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di; 2 | 3 | /** 4 | * Interface representing a contract for clients that contain components for dependency injection 5 | * 6 | * Created by Segun Famisa on 14/02/2016. 7 | */ 8 | public interface HasComponent { 9 | C getComponent(); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/ListUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import java.util.Collection; 4 | 5 | /** 6 | * Created by Segun Famisa {segunfamisa@gmail.com} on 9/17/15. 7 | */ 8 | public class ListUtils { 9 | 10 | public static boolean isEmpty(Collection items){ 11 | return items == null || items.size() < 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/ui/main/MainContract.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.ui.main; 2 | 3 | import com.segunfamisa.base.ui.base.BasePresenter; 4 | import com.segunfamisa.base.ui.base.BaseView; 5 | 6 | public interface MainContract { 7 | interface View extends BaseView { 8 | 9 | } 10 | 11 | interface Presenter extends BasePresenter { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/ActivityContext.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | /** 9 | * Created by Segun Famisa on 14/02/2016. 10 | */ 11 | @Qualifier 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ActivityContext { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/test/java/com/segunfamisa/base/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/ApplicationContext.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | /** 9 | * Created by Segun Famisa on 14/02/2016. 10 | */ 11 | @Qualifier 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ApplicationContext { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/Config.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import com.segunfamisa.base.App; 4 | import com.segunfamisa.base.R; 5 | 6 | /** 7 | * Config classes 8 | * 9 | * Created by Segun Famisa on 14/02/2016. 10 | */ 11 | public class Config { 12 | 13 | public static String APPNAME = App.getApplicationComponent().context().getString(R.string.app_name); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/navigator/Navigator.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.navigator; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | /** 7 | * Class to handle navigation around the application 8 | * 9 | * Created by Segun Famisa on 14/02/2016. 10 | */ 11 | @Singleton 12 | public class Navigator { 13 | 14 | @Inject 15 | public Navigator() {} 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/segunfamisa/base/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base; 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 | } -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/ui/base/BaseDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.ui.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.DialogFragment; 5 | 6 | 7 | /** 8 | * Created by segunfamisa on 9/9/15. 9 | */ 10 | public class BaseDialogFragment extends DialogFragment { 11 | 12 | 13 | @Override 14 | public void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/PerActivity.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * Scope to indicate a module is bound by the lifecycle of an activity 10 | * 11 | * Created by Segun Famisa on 14/02/2016. 12 | */ 13 | @Scope 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface PerActivity { 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/ui/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.ui.main; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.segunfamisa.base.R; 6 | import com.segunfamisa.base.ui.base.BaseActivity; 7 | 8 | public class MainActivity extends BaseActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/components/ActivityComponent.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di.components; 2 | 3 | import android.app.Activity; 4 | 5 | import com.segunfamisa.base.di.PerActivity; 6 | import com.segunfamisa.base.di.modules.ActivityModule; 7 | 8 | import dagger.Component; 9 | 10 | /** 11 | * Created by Segun Famisa on 14/02/2016. 12 | */ 13 | @PerActivity 14 | @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) 15 | public interface ActivityComponent { 16 | 17 | //exposed to sub-graph 18 | Activity activity(); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Take a new photo 5 | Choose an existing photo 6 | 7 | 8 | 9 | Record a new video 10 | Choose an existing video 11 | 12 | 13 | 14 | Record new audio 15 | Choose an audio file 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/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/segunfamisa/Documents/Softwares/adt-bundle-mac-x86_64-20130729/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 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/Clock.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import java.util.Calendar; 4 | 5 | /** 6 | * Created by Segun Famisa {segunfamisa@gmail.com} on 9/17/15. 7 | */ 8 | public class Clock { 9 | 10 | 11 | private static Clock sInstance; 12 | private Calendar calendarInstance; 13 | 14 | public static Clock getInstance(){ 15 | if(sInstance == null) 16 | sInstance = new Clock(); 17 | 18 | return sInstance; 19 | } 20 | 21 | private Calendar _calendarInstance(){ 22 | return calendarInstance != null ? (Calendar)calendarInstance.clone() : Calendar.getInstance(); 23 | } 24 | 25 | public Calendar getCalendar(){ 26 | return getInstance()._calendarInstance(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/modules/ActivityModule.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di.modules; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | 6 | import com.segunfamisa.base.di.ActivityContext; 7 | import com.segunfamisa.base.di.PerActivity; 8 | 9 | import dagger.Module; 10 | import dagger.Provides; 11 | 12 | /** 13 | * Created by Segun Famisa on 14/02/2016. 14 | */ 15 | @Module 16 | public class ActivityModule { 17 | 18 | private Activity activity; 19 | 20 | public ActivityModule(Activity activity) { 21 | this.activity = activity; 22 | } 23 | 24 | @Provides 25 | @PerActivity 26 | Activity provideActivity() { 27 | return this.activity; 28 | } 29 | 30 | @Provides 31 | @ActivityContext 32 | Context provideContext() { 33 | return activity; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/components/ApplicationComponent.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di.components; 2 | 3 | import android.content.Context; 4 | 5 | import com.segunfamisa.base.di.modules.ApplicationModule; 6 | import com.segunfamisa.base.navigator.Navigator; 7 | import com.segunfamisa.base.ui.base.BaseActivity; 8 | import com.segunfamisa.base.ui.base.BaseFragment; 9 | import com.segunfamisa.base.utils.PreferenceUtils; 10 | 11 | import javax.inject.Singleton; 12 | 13 | import dagger.Component; 14 | 15 | /** 16 | * Created by Segun Famisa on 14/02/2016. 17 | */ 18 | @Singleton 19 | @Component(modules = ApplicationModule.class) 20 | public interface ApplicationComponent { 21 | void inject(BaseActivity baseActivity); 22 | void inject(BaseFragment baseFragment); 23 | 24 | Context context(); 25 | PreferenceUtils preferenceUtils(); 26 | Navigator navigator(); 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | /** 6 | * Created by segunfamisa on 9/2/15. 7 | */ 8 | public class StringUtils { 9 | 10 | /** 11 | * Gets whether or not a string is null, empty or "null" 12 | * @param str string to check 13 | * @return true if the string is null, empty or "null" 14 | */ 15 | public static boolean isEmpty(String str){ 16 | return TextUtils.isEmpty(str) || str.equalsIgnoreCase("null"); 17 | } 18 | 19 | /** 20 | * This method nullifies a string. 21 | * 22 | * @param value string to nullify 23 | * @return the input string if the string is not empty, null otherwise 24 | */ 25 | public static String nullify(String value){ 26 | if(isEmpty(value)){ 27 | return null; 28 | } 29 | return value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | /** 8 | * Network related utils 9 | * 10 | * Created by Segun Famisa on 14/02/2016. 11 | */ 12 | public class NetworkUtils { 13 | 14 | 15 | /** 16 | * Checks whether network is connected 17 | * 18 | * @param context context 19 | * @return true if the network is connected, false otherwise 20 | */ 21 | public static boolean isNetworkConnected(Context context) { 22 | ConnectivityManager cm = 23 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 24 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 25 | return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/App.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base; 2 | 3 | import android.app.Application; 4 | 5 | import com.segunfamisa.base.di.components.ApplicationComponent; 6 | import com.segunfamisa.base.di.components.DaggerApplicationComponent; 7 | import com.segunfamisa.base.di.modules.ApplicationModule; 8 | 9 | /** 10 | * Created by Segun Famisa on 13/02/2016. 11 | */ 12 | public class App extends Application { 13 | 14 | private static ApplicationComponent applicationComponent; 15 | 16 | @Override 17 | public void onCreate() { 18 | super.onCreate(); 19 | 20 | this.initializeInjector(); 21 | 22 | } 23 | 24 | private void initializeInjector() { 25 | this.applicationComponent = DaggerApplicationComponent.builder() 26 | .applicationModule(new ApplicationModule(this)) 27 | .build(); 28 | } 29 | 30 | public static ApplicationComponent getApplicationComponent() { 31 | return applicationComponent; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BaseAppTemplate 3 | 4 | 5 | OK 6 | Something went wrong! 7 | 8 | Unable to access camera 9 | 10 | 11 | In order to use this feature, this app requires 12 | permission to access your phone camera, click OK to allow access 13 | 14 | 15 | Unable to access storage 16 | 17 | 18 | In order to use this feature, this app requires permission to access your phone storage, click OK to allow access 19 | 20 | 21 | Unable to create image 22 | Unable to create video 23 | Choose from gallery 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | 4 | android { 5 | compileSdkVersion rootProject.compileSdkVersion 6 | buildToolsVersion rootProject.buildToolsVersion 7 | 8 | defaultConfig { 9 | applicationId "com.segunfamisa.base" 10 | minSdkVersion rootProject.minSdkVersion 11 | targetSdkVersion rootProject.targetSdkVersion 12 | versionCode rootProject.versionCode 13 | versionName rootProject.versionName 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | testCompile "junit:junit:$rootProject.jUnitVersion" 26 | 27 | //dagger 28 | apt "com.google.dagger:dagger-compiler:$rootProject.daggerVersion" 29 | compile "com.google.dagger:dagger:$rootProject.daggerVersion" 30 | provided "javax.annotation:jsr250-api:$rootProject.jsr250Version" 31 | 32 | // support libraries 33 | compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion" 34 | compile "com.android.support:design:$rootProject.supportLibraryVersion" 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/di/modules/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.di.modules; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.segunfamisa.base.App; 7 | import com.segunfamisa.base.navigator.Navigator; 8 | import com.segunfamisa.base.utils.PreferenceUtils; 9 | 10 | import javax.inject.Singleton; 11 | 12 | import dagger.Module; 13 | import dagger.Provides; 14 | 15 | /** 16 | * Created by Segun Famisa on 13/02/2016. 17 | */ 18 | @Module 19 | public class ApplicationModule { 20 | 21 | Application mApplication; 22 | 23 | public ApplicationModule(App application) { 24 | mApplication = application; 25 | } 26 | 27 | @Provides 28 | @Singleton 29 | Application provideApplication() { 30 | return mApplication; 31 | } 32 | 33 | @Provides 34 | @Singleton 35 | Context provideAppContext() { 36 | return mApplication.getApplicationContext(); 37 | } 38 | 39 | @Provides 40 | @Singleton 41 | Navigator provideNavigator() { 42 | return new Navigator(); 43 | } 44 | 45 | @Provides 46 | @Singleton 47 | PreferenceUtils providePreferences(Application application) { 48 | return PreferenceUtils.init(application); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/ui/base/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.ui.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | 6 | import com.segunfamisa.base.App; 7 | import com.segunfamisa.base.utils.AppUtils; 8 | 9 | /** 10 | * Base Fragment to be used by all other fragments 11 | * 12 | * Created by Segun Famisa on 14/02/2016. 13 | */ 14 | public class BaseFragment extends Fragment { 15 | 16 | @Override 17 | public void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | 20 | //inject 21 | 22 | } 23 | 24 | public App getApplication() { 25 | if(getActivity() != null) { 26 | return (App)getActivity().getApplication(); 27 | } else { 28 | return null; 29 | } 30 | } 31 | 32 | public void closeFragment(){ 33 | if(getActivity() != null){ 34 | if(getActivity().getSupportFragmentManager().getBackStackEntryCount() > 0){ 35 | hideKeyboard(); 36 | getActivity().getSupportFragmentManager().popBackStack(); 37 | } 38 | else{ 39 | hideKeyboard(); 40 | getActivity().finish(); 41 | } 42 | } 43 | } 44 | 45 | protected void hideKeyboard() { 46 | AppUtils.hideKeyboard(getActivity()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/DateUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | 6 | /** 7 | * Created by Segun Famisa {segunfamisa@gmail.com} on 9/17/15. 8 | */ 9 | public class DateUtils { 10 | 11 | 12 | /** 13 | * This method extracts the day from a long milliseconds 14 | * @param dateInMilliseconds 15 | * @return String of the day 16 | */ 17 | public static String getDay(long dateInMilliseconds){ 18 | Calendar cal = Clock.getInstance().getCalendar(); 19 | cal.setTimeInMillis(dateInMilliseconds); 20 | 21 | int day = cal.get(Calendar.DAY_OF_MONTH); 22 | 23 | String dayStr = null; 24 | if(day < 10) 25 | dayStr = "0"+day; 26 | else 27 | dayStr = day+""; 28 | 29 | return dayStr; 30 | } 31 | 32 | /** 33 | * 34 | * @param dateInMilliseconds 35 | * @return short 36 | */ 37 | public static String getMonthShort(long dateInMilliseconds){ 38 | return getMonth(dateInMilliseconds, "MMM"); 39 | } 40 | 41 | public static String getMonth(long dateInMilliseconds, String format){ 42 | Calendar cal = Clock.getInstance().getCalendar(); 43 | cal.setTimeInMillis(dateInMilliseconds); 44 | SimpleDateFormat sdf = null; 45 | try{ 46 | sdf = new SimpleDateFormat(format); 47 | } 48 | catch (Exception e){ 49 | sdf = new SimpleDateFormat("MMMM"); 50 | } 51 | 52 | return sdf.format(cal.getTime()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/PreferenceUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import javax.inject.Inject; 7 | 8 | /** 9 | * Created by segunfamisa on 8/31/15. 10 | */ 11 | public class PreferenceUtils { 12 | 13 | private static PreferenceUtils sInstance; 14 | 15 | private static Context sContext; 16 | 17 | SharedPreferences prefs; 18 | 19 | /** 20 | * String for the SharedPreference file 21 | */ 22 | private static final String PREFS = "prefs"; 23 | 24 | /** 25 | * key for storing the gcm id; 26 | */ 27 | private static final String PREFS_GCM_TOKEN = "prefs_gcm_token"; 28 | 29 | private static final String PREFS_DEVICE_ID = "prefs_device_id"; 30 | 31 | @Inject 32 | public PreferenceUtils() {} 33 | 34 | private PreferenceUtils(Context context) { 35 | sContext = context; 36 | sInstance = this; 37 | prefs = sContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE); 38 | } 39 | 40 | public static PreferenceUtils init(Context context) { 41 | sInstance = new PreferenceUtils(context); 42 | return sInstance; 43 | } 44 | 45 | public String getGcmToken(){ 46 | return prefs.getString(PREFS_GCM_TOKEN, null); 47 | } 48 | 49 | public boolean saveGcmToken(String gcmId) { 50 | SharedPreferences.Editor editor = prefs.edit(); 51 | editor.putString(PREFS_GCM_TOKEN, gcmId); 52 | return editor.commit(); 53 | } 54 | 55 | public String getDeviceId(){ 56 | return prefs.getString(PREFS_DEVICE_ID, null); 57 | } 58 | 59 | public void setDeviceId(String deviceId) { 60 | deviceId = StringUtils.nullify(deviceId); 61 | SharedPreferences.Editor editor = prefs.edit(); 62 | editor.putString(PREFS_DEVICE_ID, deviceId); 63 | editor.commit(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/Logger.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Created by segunfamisa on 9/1/15. 7 | */ 8 | public class Logger { 9 | private static String TAG_DEFAULT = Config.APPNAME; 10 | 11 | private static boolean DEBUG_ON = true; 12 | 13 | /** 14 | * Method to do a debug log 15 | * @param tag 16 | * @param message 17 | */ 18 | public static void d(String tag, String message){ 19 | if(DEBUG_ON){ 20 | Log.d(tag, message); 21 | } 22 | } 23 | 24 | public static void d(String message){ 25 | d(TAG_DEFAULT, message); 26 | } 27 | 28 | public static void d(Class cls, String message){ 29 | d(cls.getSimpleName(), message); 30 | } 31 | 32 | 33 | /** 34 | * Method to do a warning log 35 | * @param tag 36 | * @param message 37 | */ 38 | public static void w(String tag, String message){ 39 | if(DEBUG_ON){ 40 | Log.w(tag, message); 41 | } 42 | } 43 | 44 | public static void w(String message){ 45 | w(TAG_DEFAULT, message); 46 | } 47 | 48 | public static void w(Class cls, String message){ 49 | w(cls.getSimpleName(), message); 50 | } 51 | 52 | 53 | /** 54 | * Method to do an error log 55 | * @param tag 56 | * @param message 57 | */ 58 | public static void e(String tag, String message){ 59 | if(DEBUG_ON){ 60 | Log.e(tag, message); 61 | } 62 | } 63 | 64 | public static void e(String message){ 65 | e(TAG_DEFAULT, message); 66 | } 67 | 68 | public static void e(Class cls, String message){ 69 | e(cls.getSimpleName(), message); 70 | } 71 | 72 | 73 | /** 74 | * Method to do an info tag 75 | * @param tag 76 | * @param message 77 | */ 78 | public static void i(String tag, String message){ 79 | if(DEBUG_ON){ 80 | Log.i(tag, message); 81 | } 82 | } 83 | 84 | public static void i(String message){ 85 | i(TAG_DEFAULT, message); 86 | } 87 | 88 | public static void i(Class cls, String message){ 89 | i(cls.getSimpleName(), message); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/DeviceUuidFactory.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.content.Context; 4 | import android.provider.Settings.Secure; 5 | import android.telephony.TelephonyManager; 6 | 7 | import java.io.UnsupportedEncodingException; 8 | import java.util.UUID; 9 | 10 | /** 11 | * @author segunfamisa 12 | * 13 | */ 14 | public class DeviceUuidFactory { 15 | protected volatile static UUID uuid; 16 | final static String ANDROID_2_2_ID = "9774d56d682e549c"; 17 | PreferenceUtils prefs; 18 | 19 | public DeviceUuidFactory(Context context){ 20 | if(uuid == null){ 21 | synchronized (DeviceUuidFactory.class) { 22 | if(uuid == null){ 23 | prefs = PreferenceUtils.init(context); 24 | final String id = prefs.getDeviceId(); 25 | if(id != null){ 26 | uuid = UUID.fromString(id); 27 | } 28 | else{ 29 | final String androidID = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID); 30 | // The above code returns a unique android id for all os versions apart from froyo 31 | try{ 32 | if(!ANDROID_2_2_ID.equals(androidID)){ 33 | uuid = UUID.nameUUIDFromBytes(androidID.getBytes("utf-8")); 34 | } 35 | else{ 36 | final String deviceId =((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId(); 37 | uuid = deviceId != null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf-8")) : UUID.randomUUID(); 38 | } 39 | } 40 | catch(UnsupportedEncodingException e){ 41 | throw new RuntimeException(e); 42 | } 43 | 44 | prefs.setDeviceId(uuid.toString()); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | public UUID getUUID(){ 52 | return uuid; 53 | } 54 | 55 | public String getStringUUID(){ 56 | return uuid.toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Android App Base template 2 | When you're starting to write an app, there are certain classes, methods and lines of code you 3 | implement over and over. So, I thought I should have base template usable across all my apps. 4 | I hope to write an Android Studio plugin to generate this automatically later in the future. 5 | 6 | This template contains some of my favourite packages, config, classes and methods. 7 | In my research, I found alot of boiler plate templates, some of which I adapted some techniques 8 | and structures from. This is still under development, and many ideas were sourced from various places. 9 | 10 | 11 | ##Features 12 | 13 | 1. Dependency Injection using Dagger: This section is still not fully developed; 14 | I'm just wrapping my head around DI with dagger 15 | 2. `Navigator` class to handle Navigation. 16 | 3. `MediaPickerFragment` (with Android Marshmallow Permission). 17 | 4. Base classes - `BaseActivity`, `BaseFragment`, `BasePresenter` 18 | 6. Util classes & methods 19 | 1. `AppUtils` with the following util methods: 20 | 1. `getAppVersion()` 21 | 2. `isTablet()` 22 | 3. `isServiceRunning()` 23 | 4. `hideKeyboard()` 24 | 2. `BitmapUtils` with the following util methods: 25 | 1. `decodeBitmapFromStream()` 26 | 2. `calculateInSampleSize()` 27 | 3. `DateUtils` with the following util methods: 28 | 1. `getDay()` 29 | 2. `getMonthShort()` 30 | 3. `getMonth()` 31 | 4. `DeviceUuidFactory` to retrieve the device id of the device. 32 | 5. `DialogUtils` 33 | 6. `ListUtils` 34 | 7. `Logger` - for logging. Different from `android.util.Log` by a flag `DEBUG_ON` which you can use to toggle whether or not you want to show the log 35 | 8. `NetworkUtils` with the following util methods: 36 | 1. `isNetworkConnected()` 37 | 9. `PreferenceUtils` - very much like a Preference manager for your SharedPreferences 38 | 10. `StringUtils` - String utils with the following util methods: 39 | 1. `isEmpty()` - that returns true if the String is null, empty or "null" 40 | 2. `nullify()` - to return the value of a string or null if the string is empty 41 | 11. `UriUtils` - utils to convert a URI to a file path. This is useful when you're picking files from the device. 42 | File picking is fragmented and occurs differently on different versions of Android. 43 | This class helps to convert such into file paths. 44 | 45 | ##Usage 46 | This part is still work in progress :) 47 | 48 | ##Todo 49 | 1. Add Usage 50 | 2. Add testing templates 51 | 3. Write AndroidStudio/IntelliJ template to automatically crate this, with the correct package name 52 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/AppUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityManager; 5 | import android.content.Context; 6 | import android.content.pm.PackageInfo; 7 | import android.content.pm.PackageManager; 8 | import android.view.inputmethod.InputMethodManager; 9 | 10 | import com.segunfamisa.base.R; 11 | 12 | /** 13 | * Util methods for activity componenets 14 | * 15 | * Created by Segun Famisa on 14/02/2016. 16 | */ 17 | public class AppUtils { 18 | 19 | /** 20 | * Utility method for retrieving the appversion 21 | * 22 | * @param context context 23 | * @return the app version name 24 | */ 25 | public static String getAppVersion(Context context){ 26 | try{ 27 | PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); 28 | return pInfo.versionName; 29 | } 30 | catch (PackageManager.NameNotFoundException nnfe){ 31 | nnfe.printStackTrace(); 32 | return ""; 33 | } 34 | } 35 | 36 | /** 37 | * utility method to determine if a device is a tablet 38 | * @param context 39 | * @return 40 | */ 41 | public static boolean isTablet(Context context){ 42 | if(context == null) return false; 43 | boolean isTablet = context.getResources().getBoolean(R.bool.isTablet); 44 | return isTablet; 45 | } 46 | 47 | /** 48 | * Determines whether or not a service is running 49 | * 50 | * @param context context 51 | * @param serviceClass class of the service 52 | * @return true if the service is running, false otherwise 53 | */ 54 | public static boolean isServiceRunning(Context context, Class serviceClass) { 55 | ActivityManager manager = 56 | (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 57 | for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { 58 | if (serviceClass.getName().equals(service.service.getClassName())) { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | /** 66 | * Hides keyboard 67 | * @param activity activity contexr 68 | */ 69 | public static void hideKeyboard(Activity activity) { 70 | InputMethodManager imm = 71 | (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 72 | imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/DialogUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.app.Dialog; 4 | import android.app.ProgressDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.support.v7.app.AlertDialog; 8 | 9 | import com.segunfamisa.base.R; 10 | 11 | /** 12 | * Class for creating simple dialogs 13 | * 14 | * Created by Segun Famisa on 14/02/2016. 15 | */ 16 | public class DialogUtils { 17 | 18 | /** 19 | * Creates a simple ok dialog 20 | * 21 | * @param context context 22 | * @param title title 23 | * @param message message 24 | * @return {@link AlertDialog} 25 | */ 26 | public static Dialog createSimpleOkDialog(Context context, String title, String message) { 27 | return createSimpleOkDialog(context, title, message, null); 28 | } 29 | 30 | /** 31 | * Creates a simple ok dialog 32 | * 33 | * @param context context 34 | * @param title title 35 | * @param message message 36 | * @param okClickListener click listener 37 | * 38 | * @return {@link AlertDialog} 39 | */ 40 | public static Dialog createSimpleOkDialog(Context context, String title, String message, 41 | DialogInterface.OnClickListener okClickListener) { 42 | return createSimpleOkDialog(context, title, message, 43 | context.getString(R.string.dialog_action_ok), okClickListener); 44 | } 45 | 46 | /** 47 | * 48 | * @param context context 49 | * @param title title 50 | * @param message message 51 | * @param okButtonText text to display on the ok button 52 | * @param okClickListener click listener 53 | * 54 | * @return {@link AlertDialog} 55 | */ 56 | public static Dialog createSimpleOkDialog(Context context, String title, String message, 57 | String okButtonText, DialogInterface.OnClickListener okClickListener) { 58 | AlertDialog.Builder alertDialog = new AlertDialog.Builder(context) 59 | .setTitle(title) 60 | .setMessage(message) 61 | .setNeutralButton(okButtonText, okClickListener); 62 | return alertDialog.create(); 63 | } 64 | 65 | /** 66 | * Creates a progress dialog with 67 | * 68 | * @param context activity context 69 | * @param message 70 | * @return 71 | */ 72 | public static ProgressDialog createProgressDialog(Context context, String message) { 73 | ProgressDialog progressDialog = new ProgressDialog(context); 74 | progressDialog.setMessage(message); 75 | return progressDialog; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/BitmapUtils.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.net.Uri; 7 | 8 | import java.io.InputStream; 9 | 10 | /** 11 | * Created by Segun Famisa on 14/02/2016. 12 | */ 13 | public class BitmapUtils { 14 | 15 | /** 16 | * To decode and scale a bitmap from URI stream 17 | * @param context context 18 | * @param uri uri containing the image 19 | * @param reqWidth required width 20 | * @param reqHeight required height 21 | * @return the decoded {@link Bitmap} if successful or null if it failed 22 | */ 23 | public static Bitmap decodeBitmapFromStream(Context context, Uri uri, int reqWidth, int reqHeight) { 24 | try { 25 | InputStream input = context.getContentResolver().openInputStream(uri); 26 | 27 | BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options(); 28 | onlyBoundsOptions.inJustDecodeBounds = true; 29 | BitmapFactory.decodeStream(input, null, onlyBoundsOptions); 30 | 31 | if(input != null) { 32 | input.close(); 33 | } 34 | 35 | if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) 36 | return null; 37 | 38 | BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 39 | bitmapOptions.inSampleSize = calculateInSampleSize(onlyBoundsOptions, reqWidth, reqHeight); 40 | bitmapOptions.inDither=true;//optional 41 | bitmapOptions.inPreferredConfig=Bitmap.Config.ARGB_8888;//optional 42 | input = context.getContentResolver().openInputStream(uri); 43 | Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions); 44 | 45 | if(input != null) { 46 | input.close(); 47 | } 48 | 49 | return bitmap; 50 | 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | return null; 54 | } 55 | } 56 | 57 | public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){ 58 | final int height = options.outHeight; 59 | final int width = options.outWidth; 60 | int inSampleSize = 1; 61 | 62 | if(height > reqHeight || width > reqWidth) { 63 | final int halfHeight = height / 2; 64 | final int halfWidth = width / 2; 65 | 66 | while ((halfHeight / inSampleSize) > reqHeight 67 | && (halfWidth / inSampleSize) > reqWidth) { 68 | inSampleSize *= 2; 69 | } 70 | } 71 | return inSampleSize; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/ui/base/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.ui.base; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentTransaction; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | 8 | import com.segunfamisa.base.App; 9 | import com.segunfamisa.base.di.components.ActivityComponent; 10 | import com.segunfamisa.base.di.components.ApplicationComponent; 11 | import com.segunfamisa.base.di.components.DaggerActivityComponent; 12 | import com.segunfamisa.base.di.modules.ActivityModule; 13 | import com.segunfamisa.base.navigator.Navigator; 14 | import com.segunfamisa.base.utils.AppUtils; 15 | import com.segunfamisa.base.utils.PreferenceUtils; 16 | 17 | import javax.inject.Inject; 18 | 19 | public abstract class BaseActivity extends AppCompatActivity { 20 | 21 | private ActivityComponent mActivityComponent; 22 | 23 | @Inject 24 | PreferenceUtils mPrefs; 25 | 26 | @Inject 27 | Navigator mNavigator; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | 33 | this.getApplicationComponent().inject(this); 34 | mPrefs = getApplicationComponent().preferenceUtils(); 35 | mNavigator = getApplicationComponent().navigator(); 36 | } 37 | 38 | /** 39 | * Gets the application component for dependency injection 40 | * 41 | * @return {@link ApplicationComponent} 42 | */ 43 | private ApplicationComponent getApplicationComponent() { 44 | return ((App) getApplication()).getApplicationComponent(); 45 | } 46 | 47 | /** 48 | * Gets the activity component for dependecy injection 49 | * 50 | * @return {@link ActivityComponent} 51 | */ 52 | protected ActivityComponent getActivityComponent() { 53 | if(mActivityComponent == null) { 54 | mActivityComponent = DaggerActivityComponent.builder() 55 | .applicationComponent(getApplicationComponent()) 56 | .activityModule(new ActivityModule(this)) 57 | .build(); 58 | } 59 | 60 | return mActivityComponent; 61 | } 62 | 63 | /** 64 | * Gets the activity module for dependency injection 65 | * 66 | * @return {@link ActivityModule} 67 | */ 68 | protected ActivityModule getActivityModule() { 69 | return new ActivityModule(this); 70 | } 71 | 72 | /** 73 | * Adds a fragment to a container view 74 | * 75 | * @param containerViewId container view id 76 | * @param fragment fragment to add 77 | * @param addToBackStack flag whether to add to back stack or not 78 | */ 79 | protected void addFragment(int containerViewId, Fragment fragment, boolean addToBackStack) { 80 | if(containerViewId > -1 && fragment != null) { 81 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 82 | ft.add(containerViewId, fragment, fragment.getClass().getName()); 83 | if(addToBackStack) { 84 | ft.addToBackStack(null); 85 | } 86 | ft.commit(); 87 | } 88 | } 89 | 90 | /** 91 | * Replaces a fragment in a container view. 92 | * 93 | * @param containerViewId container view id 94 | * @param fragment fragment to replace 95 | * @param addToBackStack flag whether or not to add to backstack 96 | */ 97 | protected void replaceFragment(int containerViewId, Fragment fragment, boolean addToBackStack) { 98 | if(containerViewId > -1 && fragment != null) { 99 | FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 100 | ft.replace(containerViewId, fragment, fragment.getClass().getName()); 101 | if(addToBackStack) { 102 | ft.addToBackStack(null); 103 | } 104 | ft.commit(); 105 | } 106 | } 107 | 108 | /** 109 | * Closes the fragments embedded in this activity, if there are no fragments on the backstack, 110 | * it finishes the activity 111 | */ 112 | private void close() { 113 | if(getSupportFragmentManager().getBackStackEntryCount() > 0) { 114 | getSupportFragmentManager().popBackStack(); 115 | } else { 116 | finish(); 117 | } 118 | } 119 | 120 | /** 121 | * Hides the keyboard 122 | */ 123 | protected void hideKeyboard() { 124 | AppUtils.hideKeyboard(this); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/UriUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package com.segunfamisa.base.utils; 5 | 6 | import android.annotation.SuppressLint; 7 | import android.content.ContentUris; 8 | import android.content.Context; 9 | import android.database.Cursor; 10 | import android.net.Uri; 11 | import android.os.Build; 12 | import android.os.Environment; 13 | import android.provider.DocumentsContract; 14 | import android.provider.MediaStore; 15 | 16 | /** 17 | * @author segunfamisa 18 | * 19 | */ 20 | public class UriUtils { 21 | 22 | /** 23 | * Get a file path from a Uri. This will get the the path for Storage Access 24 | * Framework Documents, as well as the _data field for the MediaStore and 25 | * other file-based ContentProviders. 26 | * 27 | * @param context The context. 28 | * @param uri The Uri to query. 29 | * @author paulburke 30 | */ 31 | @SuppressLint("NewApi") 32 | public static String getPath(final Context context, final Uri uri) { 33 | 34 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 35 | 36 | // DocumentProvider 37 | if (isKitKat) { 38 | if(DocumentsContract.isDocumentUri(context, uri)){ 39 | // ExternalStorageProvider 40 | if (isExternalStorageDocument(uri)) { 41 | final String docId = DocumentsContract.getDocumentId(uri); 42 | final String[] split = docId.split(":"); 43 | final String type = split[0]; 44 | 45 | if ("primary".equalsIgnoreCase(type)) { 46 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 47 | } 48 | 49 | // TODO handle non-primary volumes 50 | } 51 | // DownloadsProvider 52 | else if (isDownloadsDocument(uri)) { 53 | 54 | final String id = DocumentsContract.getDocumentId(uri); 55 | final Uri contentUri = ContentUris.withAppendedId( 56 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 57 | 58 | return getDataColumn(context, contentUri, null, null); 59 | } 60 | // MediaProvider 61 | else if (isMediaDocument(uri)) { 62 | final String docId = DocumentsContract.getDocumentId(uri); 63 | final String[] split = docId.split(":"); 64 | final String type = split[0]; 65 | 66 | Uri contentUri = null; 67 | if ("image".equals(type)) { 68 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 69 | } else if ("video".equals(type)) { 70 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 71 | } else if ("audio".equals(type)) { 72 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 73 | } 74 | 75 | final String selection = "_id=?"; 76 | final String[] selectionArgs = new String[] { 77 | split[1] 78 | }; 79 | 80 | return getDataColumn(context, contentUri, selection, selectionArgs); 81 | } 82 | } 83 | 84 | // MediaStore (and general) 85 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 86 | return getDataColumn(context, uri, null, null); 87 | } 88 | // File 89 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 90 | return uri.getPath(); 91 | } 92 | } 93 | // MediaStore (and general) 94 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 95 | return getDataColumn(context, uri, null, null); 96 | } 97 | // File 98 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 99 | return uri.getPath(); 100 | } 101 | 102 | return null; 103 | } 104 | 105 | /** 106 | * Get the value of the data column for this Uri. This is useful for 107 | * MediaStore Uris, and other file-based ContentProviders. 108 | * 109 | * @param context The context. 110 | * @param uri The Uri to query. 111 | * @param selection (Optional) Filter used in the query. 112 | * @param selectionArgs (Optional) Selection arguments used in the query. 113 | * @return The value of the _data column, which is typically a file path. 114 | */ 115 | public static String getDataColumn(Context context, Uri uri, String selection, 116 | String[] selectionArgs) { 117 | 118 | Cursor cursor = null; 119 | final String column = "_data"; 120 | final String[] projection = { 121 | column 122 | }; 123 | 124 | try { 125 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 126 | null); 127 | if (cursor != null && cursor.moveToFirst()) { 128 | final int column_index = cursor.getColumnIndexOrThrow(column); 129 | return cursor.getString(column_index); 130 | } 131 | } finally { 132 | if (cursor != null) 133 | cursor.close(); 134 | } 135 | return null; 136 | } 137 | 138 | 139 | /** 140 | * @param uri The Uri to check. 141 | * @return Whether the Uri authority is ExternalStorageProvider. 142 | */ 143 | public static boolean isExternalStorageDocument(Uri uri) { 144 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 145 | } 146 | 147 | /** 148 | * @param uri The Uri to check. 149 | * @return Whether the Uri authority is DownloadsProvider. 150 | */ 151 | public static boolean isDownloadsDocument(Uri uri) { 152 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 153 | } 154 | 155 | /** 156 | * @param uri The Uri to check. 157 | * @return Whether the Uri authority is MediaProvider. 158 | */ 159 | public static boolean isMediaDocument(Uri uri) { 160 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /app/src/main/java/com/segunfamisa/base/utils/MediaPickerDialog.java: -------------------------------------------------------------------------------- 1 | package com.segunfamisa.base.utils; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.app.Dialog; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.content.Intent; 9 | import android.content.pm.PackageManager; 10 | import android.graphics.Bitmap; 11 | import android.net.Uri; 12 | import android.os.Bundle; 13 | import android.os.Environment; 14 | import android.provider.MediaStore; 15 | import android.provider.Settings; 16 | import android.support.annotation.NonNull; 17 | import android.support.v4.app.DialogFragment; 18 | import android.support.v4.app.Fragment; 19 | import android.support.v4.app.FragmentTransaction; 20 | import android.support.v4.content.ContextCompat; 21 | import android.support.v7.app.AlertDialog; 22 | import android.text.TextUtils; 23 | import android.view.Window; 24 | 25 | import com.segunfamisa.base.R; 26 | import com.segunfamisa.base.ui.base.BaseActivity; 27 | import com.segunfamisa.base.ui.base.BaseDialogFragment; 28 | import com.segunfamisa.base.ui.base.BaseFragment; 29 | 30 | import java.io.File; 31 | import java.io.FileOutputStream; 32 | import java.io.Serializable; 33 | import java.text.SimpleDateFormat; 34 | import java.util.Date; 35 | 36 | /** 37 | * Media Picker dialog 38 | * 39 | * Created by segunfamisa on 9/9/15. 40 | */ 41 | public class MediaPickerDialog extends BaseDialogFragment { 42 | /** 43 | * String for log tags 44 | */ 45 | private static String TAG = "MediaPicker"; 46 | 47 | /** 48 | * Directory for Truppr 49 | */ 50 | public static final String DIR_NAME = Config.APPNAME; 51 | 52 | /** 53 | * Flag for Image media type 54 | */ 55 | public static final int TYPE_IMAGE = 0; 56 | 57 | /** 58 | * Flag for Video media type 59 | */ 60 | public static final int TYPE_VIDEO = 1; 61 | 62 | /** 63 | * Flag for Audio media type 64 | */ 65 | public static final int TYPE_AUDIO = 2; 66 | 67 | /** 68 | * Flag for All media types 69 | */ 70 | public static final int TYPE_ALL = 10; 71 | 72 | /** 73 | * Duration for video recording 74 | */ 75 | public static final int MAX_DURATION_VIDEO = 15; 76 | 77 | /** 78 | * variable to hold the media type 79 | */ 80 | private int mMediaType = -1; 81 | private String mTitle; 82 | 83 | 84 | 85 | /** 86 | * Listener for picked media 87 | */ 88 | private MediaPickedListener mMediaPickedListener; 89 | 90 | /** 91 | * Intent Request code for recording image 92 | */ 93 | private final static int REQ_CAPTURE_IMAGE = 100; 94 | 95 | /** 96 | * Intent request code for recording video 97 | */ 98 | private final static int REQ_CAPTURE_VIDEO = 200; 99 | 100 | /** 101 | * Intent request code for picking image 102 | */ 103 | private final static int REQ_PICK_IMAGE = 300; 104 | 105 | /** 106 | * Intent request code for picking video 107 | */ 108 | private final static int REQ_PICK_VIDEO = 400; 109 | 110 | /** 111 | * Key for saving instance state of media type 112 | */ 113 | private static final String KEY_MEDIATYPE = "key_media_type"; 114 | 115 | /** 116 | * Key for saving instance state of the listener 117 | */ 118 | private static final String KEY_LISTENER = "key_media_picked_listener"; 119 | 120 | /** 121 | * Key for saving instance state for title of the dialog 122 | */ 123 | private static final String KEY_TITLE = "key_title"; 124 | 125 | /** 126 | * Create an instance of the fragment 127 | * @param title 128 | * @param mediaType 129 | * @return 130 | */ 131 | public static DialogFragment newInstance(String title, int mediaType){ 132 | return newInstance(title, mediaType, null); 133 | } 134 | 135 | /** 136 | * Create an instance of the fragment 137 | * @param title 138 | * @param mediaType 139 | * @param mediaPickedListener 140 | * @return 141 | */ 142 | public static DialogFragment newInstance(String title, int mediaType, MediaPickedListener mediaPickedListener){ 143 | MediaPickerDialog frag = new MediaPickerDialog(); 144 | frag.mMediaType = mediaType; 145 | frag.mTitle = title; 146 | frag.mMediaPickedListener = mediaPickedListener; 147 | return frag; 148 | } 149 | 150 | public MediaPickedListener getMediaPickedListener(){ 151 | return mMediaPickedListener; 152 | } 153 | 154 | @Override 155 | public void onCreate(Bundle savedInstanceState) { 156 | super.onCreate(savedInstanceState); 157 | 158 | if(savedInstanceState != null){ 159 | if(savedInstanceState.containsKey(KEY_MEDIATYPE)){ 160 | mMediaType = savedInstanceState.getInt(KEY_MEDIATYPE); 161 | } 162 | if(savedInstanceState.containsKey(KEY_TITLE)){ 163 | mTitle = savedInstanceState.getString(KEY_TITLE); 164 | } 165 | } 166 | } 167 | 168 | @Override 169 | public void onSaveInstanceState(Bundle outState) { 170 | outState.putInt(KEY_MEDIATYPE, mMediaType); 171 | outState.putString(KEY_TITLE, mTitle); 172 | super.onSaveInstanceState(outState); 173 | } 174 | 175 | @Override 176 | public void onAttach(Activity activity) { 177 | super.onAttach(activity); 178 | try{ 179 | mMediaPickedListener = (MediaPickerDialog.MediaPickedListener)getActivity(); 180 | } 181 | catch(ClassCastException cce){ 182 | throw new ClassCastException("The activity must implement " + MediaPickerDialog.MediaPickedListener.class.getSimpleName()); 183 | } 184 | } 185 | 186 | @NonNull 187 | @Override 188 | public Dialog onCreateDialog(Bundle savedInstanceState) { 189 | Dialog dialog = super.onCreateDialog(savedInstanceState); 190 | if(TextUtils.isEmpty(mTitle)){ 191 | //set window to have no title 192 | dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); 193 | } 194 | else{ 195 | dialog.setTitle(mTitle); 196 | } 197 | 198 | //create alert dialog 199 | AlertDialog.Builder mBuilder = new AlertDialog.Builder(getActivity()); 200 | 201 | //set title 202 | if(!TextUtils.isEmpty(mTitle)) 203 | mBuilder.setTitle(mTitle); 204 | 205 | 206 | //items array res 207 | int itemsRes = -1; 208 | 209 | //select items array based on type 210 | switch(mMediaType){ 211 | case TYPE_AUDIO: 212 | itemsRes = R.array.items_dialog_media_audio; 213 | break; 214 | case TYPE_VIDEO: 215 | itemsRes = R.array.items_dialog_media_video; 216 | break; 217 | case TYPE_IMAGE: 218 | itemsRes = R.array.items_dialog_media_image; 219 | default: 220 | break; 221 | } 222 | 223 | if(itemsRes != -1){ 224 | //set items && click listener 225 | mBuilder.setItems(itemsRes, new DialogInterface.OnClickListener() { 226 | @Override 227 | public void onClick(DialogInterface dialogInterface, int i) { 228 | switch(i){ 229 | case 0: 230 | //launch fragment that handles media actions 231 | //0 defaults to creating a new media 232 | startPickerFragment(mMediaType, MediaPickerFragment.ACTION_NEW, mMediaPickedListener); 233 | break; 234 | case 1: 235 | //1 choosing exisiting media 236 | startPickerFragment(mMediaType, MediaPickerFragment.ACTION_PICK, mMediaPickedListener); 237 | break; 238 | } 239 | } 240 | }); 241 | 242 | //create dialog 243 | dialog = mBuilder.create(); 244 | } 245 | 246 | return dialog; 247 | } 248 | 249 | 250 | private void startPickerFragment(int mediaType, int action, MediaPickedListener mediaPickedListener){ 251 | Fragment frag = MediaPickerFragment.newInstance(mediaType, action); 252 | 253 | FragmentTransaction ft = getFragmentManager().beginTransaction(); 254 | ft.add(R.id.container, frag, frag.getClass().getSimpleName()); 255 | ft.addToBackStack(null); 256 | ft.commit(); 257 | } 258 | 259 | 260 | public interface MediaPickedListener extends Serializable { 261 | void onMediaItemPicked(Uri fileUri, int mediaType); 262 | void onCancel(String message); 263 | } 264 | 265 | public static class MediaPickerFragment extends BaseFragment { 266 | 267 | /** 268 | * Action for new media item 269 | */ 270 | public static final int ACTION_NEW = 0; 271 | 272 | /** 273 | * Action for picking existing media item 274 | */ 275 | public static final int ACTION_PICK = 1; 276 | 277 | /** 278 | * Variable for holding the required action 279 | */ 280 | public int mAction = -1; 281 | 282 | /** 283 | * Uri object for medai 284 | */ 285 | private Uri mMediaUri; 286 | 287 | /** 288 | * Listener for picked media 289 | */ 290 | private MediaPickedListener mMediaPickedListener; 291 | 292 | /** 293 | * variable to hold the media type 294 | */ 295 | private int mMediaType = -1; 296 | 297 | private static final int REQUEST_PERMISSION_STORAGE = 100; 298 | 299 | private static final int REQUEST_PERMISSION_CAMERA = 200; 300 | 301 | /** 302 | * Get instance of the picker fragment 303 | * @param mediaType 304 | * @param action 305 | * @return 306 | */ 307 | public static Fragment newInstance(int mediaType, int action){ 308 | MediaPickerFragment frag = new MediaPickerFragment(); 309 | frag.mMediaType = mediaType; 310 | frag.mAction = action; 311 | return frag; 312 | } 313 | 314 | 315 | 316 | /** Create a file Uri for saving an image or video 317 | * 318 | */ 319 | private static Uri getOutputMediaFileUri(int type){ 320 | File output = getOutputMediaFile(type); 321 | if(output != null) 322 | return Uri.fromFile(output); 323 | 324 | return null; 325 | } 326 | 327 | 328 | /** 329 | * Create a File for saving an image or video 330 | */ 331 | private static File getOutputMediaFile(int type){ 332 | //check storage state 333 | if(Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)){ 334 | File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( 335 | Environment.DIRECTORY_PICTURES), DIR_NAME); 336 | // Create the storage directory if it does not exist 337 | if (! mediaStorageDir.exists()){ 338 | boolean created = mediaStorageDir.mkdirs(); 339 | boolean isDirectory = mediaStorageDir.isDirectory(); 340 | 341 | if (! (created || isDirectory)){ 342 | 343 | return null; 344 | } 345 | } 346 | 347 | // Create a media file name 348 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 349 | File mediaFile; 350 | if (type == TYPE_IMAGE){ 351 | mediaFile = new File(mediaStorageDir.getPath() + File.separator + 352 | "IMG_"+ timeStamp + ".jpg"); 353 | } else if(type == TYPE_VIDEO) { 354 | mediaFile = new File(mediaStorageDir.getPath() + File.separator + 355 | "VID_"+ timeStamp + ".mp4"); 356 | } else { 357 | return null; 358 | } 359 | 360 | return mediaFile; 361 | } 362 | 363 | return null; 364 | } 365 | 366 | @Override 367 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 368 | if(requestCode == REQUEST_PERMISSION_STORAGE) { 369 | //for the storage permission request 370 | //check if the grantResults array is not empty and it contains PackageManager.PERMISSION_GRANTED 371 | if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ 372 | createNewMedia(mMediaType); 373 | } else{ 374 | //permission has been denied. Disable the feature. 375 | } 376 | } else if(requestCode == REQUEST_PERMISSION_CAMERA) { 377 | if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ 378 | createNewMedia(mMediaType); 379 | } else{ 380 | //permission has been denied. Disable the feature. 381 | } 382 | } else{ 383 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 384 | } 385 | } 386 | 387 | private void createNewMedia(int mediaType){ 388 | if(ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) 389 | != PackageManager.PERMISSION_GRANTED) { 390 | if(shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { 391 | DialogUtils.createSimpleOkDialog(getContext(), getString(R.string.title_dialog_storage_permission), 392 | getString(R.string.title_dialog_storage_permission), new DialogInterface.OnClickListener() { 393 | @Override 394 | public void onClick(DialogInterface dialog, int which) { 395 | showInstalledAppDetails(getContext()); 396 | } 397 | }).show(); 398 | } else { 399 | //request for permission. You can listen for a response via onRequestPermissionsResult method. 400 | requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_STORAGE); 401 | } 402 | } else if(ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) 403 | != PackageManager.PERMISSION_GRANTED){ 404 | if(shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) { 405 | DialogUtils.createSimpleOkDialog(getContext(), getString(R.string.title_dialog_camera_permission), 406 | getString(R.string.message_dialog_camera_permission), new DialogInterface.OnClickListener() { 407 | @Override 408 | public void onClick(DialogInterface dialog, int which) { 409 | showInstalledAppDetails(getContext()); 410 | } 411 | }).show(); 412 | } else { 413 | //request for permission. You can listen for a response via onRequestPermissionsResult method. 414 | requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA); 415 | } 416 | } else { 417 | Intent intent; 418 | 419 | switch (mediaType) { 420 | case TYPE_IMAGE: 421 | intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 422 | mMediaUri = getOutputMediaFileUri(TYPE_IMAGE); 423 | if (mMediaUri != null) { 424 | intent.putExtra(MediaStore.EXTRA_OUTPUT, mMediaUri); 425 | startActivityForResult(intent, REQ_CAPTURE_IMAGE); 426 | } else { 427 | //if listener isn't empty, send back error 428 | if (mMediaPickedListener != null) { 429 | mMediaPickedListener.onCancel(getString(R.string.picker_error_image)); 430 | } 431 | } 432 | break; 433 | 434 | case TYPE_VIDEO: 435 | intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); 436 | mMediaUri = getOutputMediaFileUri(TYPE_VIDEO); 437 | if (mMediaUri != null) { 438 | intent.putExtra(MediaStore.EXTRA_OUTPUT, mMediaUri); 439 | intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, MAX_DURATION_VIDEO); 440 | startActivityForResult(intent, REQ_CAPTURE_VIDEO); 441 | } else { 442 | //if listener isn't empty, send back error 443 | if (mMediaPickedListener != null) { 444 | mMediaPickedListener.onCancel(getString(R.string.picker_error_video)); 445 | } 446 | } 447 | break; 448 | 449 | default: 450 | 451 | break; 452 | } 453 | } 454 | 455 | } 456 | 457 | /** 458 | * Method to launch the app info settings page 459 | * @param context 460 | */ 461 | public static void showInstalledAppDetails(Context context) { 462 | if (context == null) { 463 | return; 464 | } 465 | final Intent intent = new Intent(); 466 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 467 | intent.addCategory(Intent.CATEGORY_DEFAULT); 468 | intent.setData(Uri.parse("package:" + context.getPackageName())); 469 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 470 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); 471 | intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 472 | context.startActivity(intent); 473 | 474 | } 475 | 476 | private void choseExistingMedia(int mediaType){ 477 | Intent intent = null; 478 | 479 | switch (mediaType){ 480 | case TYPE_IMAGE: 481 | intent = new Intent(); 482 | intent.setAction(Intent.ACTION_GET_CONTENT); 483 | intent.setType("image/*"); 484 | 485 | startActivityForResult(Intent.createChooser(intent, getString(R.string.picker_title_image)), REQ_PICK_IMAGE); 486 | break; 487 | 488 | case TYPE_VIDEO: 489 | intent = new Intent(); 490 | intent.setAction(Intent.ACTION_GET_CONTENT); 491 | intent.setType("video/*, images/*"); 492 | 493 | startActivityForResult(Intent.createChooser(intent, getString(R.string.picker_title_image)), REQ_PICK_IMAGE); 494 | break; 495 | } 496 | } 497 | 498 | @Override 499 | public void onCreate(Bundle savedInstanceState) { 500 | super.onCreate(savedInstanceState); 501 | 502 | //start the picking action 503 | switch (mAction){ 504 | case ACTION_NEW: 505 | createNewMedia(mMediaType); 506 | break; 507 | case ACTION_PICK: 508 | choseExistingMedia(mMediaType); 509 | break; 510 | } 511 | } 512 | 513 | @Override 514 | public void onAttach(Context context) { 515 | super.onAttach(context); 516 | 517 | try{ 518 | mMediaPickedListener = (MediaPickerDialog.MediaPickedListener)getActivity(); 519 | } 520 | catch(ClassCastException cce){ 521 | throw new ClassCastException("The activity must implement " + MediaPickerDialog.MediaPickedListener.class.getSimpleName()); 522 | } 523 | } 524 | 525 | @Override 526 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 527 | super.onActivityResult(requestCode, resultCode, data); 528 | //retrieve data 529 | if(resultCode == BaseActivity.RESULT_OK){ 530 | //for user capture requests 531 | if(requestCode == REQ_CAPTURE_IMAGE || requestCode == REQ_CAPTURE_VIDEO){ 532 | if(mMediaType == TYPE_IMAGE) { 533 | scaleImage(mMediaUri); 534 | } else { 535 | mMediaPickedListener.onMediaItemPicked(mMediaUri, mMediaType); 536 | closeFragment(); 537 | } 538 | } 539 | 540 | if(requestCode == REQ_PICK_IMAGE || requestCode == REQ_PICK_VIDEO){ 541 | mMediaUri = Uri.fromFile(new File(UriUtils.getPath(getContext(), data.getData()))); 542 | 543 | if(mMediaPickedListener != null && mMediaUri != null){ 544 | if(mMediaType == TYPE_IMAGE) { 545 | scaleImage(mMediaUri); 546 | } else { 547 | mMediaPickedListener.onMediaItemPicked(mMediaUri, mMediaType); 548 | closeFragment(); 549 | } 550 | } 551 | } 552 | } 553 | else if(resultCode == BaseActivity.RESULT_CANCELED){ 554 | //user cancelled 555 | closeFragment(); 556 | } 557 | } 558 | 559 | private void scaleImage(Uri imageUri) { 560 | Bitmap bmp = BitmapUtils.decodeBitmapFromStream(getActivity(), imageUri, 512, 512); 561 | if(bmp != null) { 562 | try { 563 | File file = new File(imageUri.getPath()); 564 | FileOutputStream fos = new FileOutputStream(file); 565 | bmp.compress(Bitmap.CompressFormat.JPEG, 80, fos); 566 | 567 | mMediaPickedListener.onMediaItemPicked(Uri.fromFile(file), mMediaType); 568 | 569 | closeFragment(); 570 | } catch (Exception e) { 571 | e.printStackTrace(); 572 | } 573 | } 574 | } 575 | } 576 | } 577 | --------------------------------------------------------------------------------