├── .gitignore ├── README.md ├── src └── main │ ├── AndroidManifest.xml │ ├── res │ ├── layout │ │ ├── text_view.xml │ │ ├── activity_single_fragment.xml │ │ ├── loading_image_view.xml │ │ ├── fragment_blocking_progress.xml │ │ └── toolbar.xml │ └── values │ │ ├── styles.xml │ │ └── attrs.xml │ └── java │ └── com │ └── stablekernel │ └── standardlib │ ├── ToastUtil.java │ ├── ErrorHandler.java │ ├── DisplayUtils.java │ ├── HtmlFormatter.java │ ├── SparseArrayUtils.java │ ├── Log.java │ ├── AuthRetryInterceptor.java │ ├── SingleFragmentActivityIntentFactory.java │ ├── SingleFragmentActivity.java │ ├── LoadingImageView.java │ ├── BlockingProgressFragment.java │ ├── NetworkUtils.java │ ├── OkCancelFragment.java │ ├── RxFragment.java │ ├── Rx.java │ └── FloatLabelLayout.java ├── proguard-rules.pro └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StandardLib 2 | The missing Android framework classes 3 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/res/layout/text_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/ToastUtil.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | public class ToastUtil { 7 | public static void show(Context context, int messageId) { 8 | Toast.makeText(context, messageId, Toast.LENGTH_SHORT).show(); 9 | } 10 | 11 | public static void show(Context context, String message) { 12 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/res/layout/activity_single_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/res/layout/loading_image_view.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | public class ErrorHandler { 7 | public static void handleError(Context context, Throwable e) { 8 | ErrorHandler.handleError(context, null, e); 9 | } 10 | 11 | public static void handleError(Context context, String message, Throwable e) { 12 | Log.e(context.getClass().getSimpleName(), e.getLocalizedMessage(), e); 13 | if (BuildConfig.DEBUG || message != null) { 14 | Toast.makeText(context, message == null ? e.getLocalizedMessage() : message, Toast.LENGTH_LONG).show(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /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/ross/sdks/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 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/DisplayUtils.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.content.Context; 4 | 5 | public class DisplayUtils { 6 | 7 | public static float dpFromPixels(Context context, int pixels) { 8 | return pixels / context.getResources().getDisplayMetrics().density; 9 | } 10 | 11 | public static float pixelsFromDp(Context context, int dp) { 12 | return dp * context.getResources().getDisplayMetrics().density; 13 | } 14 | 15 | public static int getScreenWidthInPixels(Context context) { 16 | return context.getResources().getDisplayMetrics().widthPixels; 17 | } 18 | 19 | public static int getScreenHeightInPixels(Context context) { 20 | return context.getResources().getDisplayMetrics().heightPixels; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/res/layout/fragment_blocking_progress.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/HtmlFormatter.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.text.Editable; 4 | import android.text.Html; 5 | import android.text.Spanned; 6 | 7 | import org.xml.sax.XMLReader; 8 | 9 | public class HtmlFormatter { 10 | 11 | public static Spanned format(String html, Boolean withImages) { 12 | if (!withImages) { 13 | html = html.replaceAll("", ""); 14 | } 15 | return Html.fromHtml(html, null, new Html.TagHandler() { 16 | @Override 17 | public void handleTag(boolean opening, 18 | String tag, 19 | Editable output, 20 | XMLReader xmlReader) { 21 | if (opening && tag.equals("li")) { 22 | output.append("\n") 23 | .append("\t\u2022 "); 24 | } 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/SparseArrayUtils.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.support.v4.util.LongSparseArray; 4 | import android.support.v4.util.SparseArrayCompat; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class SparseArrayUtils { 10 | public static List asList(SparseArrayCompat sparseArray) { 11 | if (sparseArray == null) { 12 | return null; 13 | } 14 | 15 | ArrayList list = new ArrayList<>(sparseArray.size()); 16 | for (int i = 0; i < sparseArray.size(); i++) { 17 | list.add(sparseArray.valueAt(i)); 18 | } 19 | return list; 20 | } 21 | 22 | public static List asList(LongSparseArray sparseArray) { 23 | if (sparseArray == null) { 24 | return null; 25 | } 26 | 27 | ArrayList list = new ArrayList<>(sparseArray.size()); 28 | for (int i = 0; i < sparseArray.size(); i++) { 29 | list.add(sparseArray.valueAt(i)); 30 | } 31 | return list; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/Log.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | /** 4 | * This Log class is a proxy to the standard android.util.Log except 5 | * that it will disable debug logging for release builds. 6 | */ 7 | public class Log { 8 | private static boolean DEBUG = true; 9 | 10 | /** 11 | * Since gradle does not support passing the BuildConfig.DEBUG value 12 | * down to library modules, you will need to call this this method in 13 | * your Application.onCreate() method like so: 14 | *

15 | * Log.setDebug(BuildConfig.DEBUG); 16 | * 17 | * @param isDebug 18 | */ 19 | public static void setDebug(boolean isDebug) { 20 | DEBUG = isDebug; 21 | } 22 | 23 | public static void d(String tag, String msg) { 24 | if (DEBUG) android.util.Log.d(tag, msg); 25 | } 26 | 27 | public static void e(String tag, String msg, Throwable e) { 28 | android.util.Log.e(tag, msg, e); 29 | } 30 | 31 | public static void w(String tag, String msg) { 32 | android.util.Log.w(tag, msg); 33 | } 34 | 35 | public static void d(String tag, String msg, Throwable throwable) { 36 | android.util.Log.d(tag, msg, throwable); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/AuthRetryInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import com.squareup.okhttp.Interceptor; 4 | import com.squareup.okhttp.Request; 5 | import com.squareup.okhttp.Response; 6 | 7 | import java.io.IOException; 8 | 9 | import retrofit.RetrofitError; 10 | 11 | public abstract class AuthRetryInterceptor implements Interceptor { 12 | 13 | private static final String TAG = "AuthRetryInterceptor"; 14 | private final Object lockObject = new Object(); 15 | 16 | @Override 17 | public Response intercept(Chain chain) throws IOException { 18 | String token = getAccessToken(); 19 | Request.Builder builder = chain.request().newBuilder(); 20 | 21 | Response response; 22 | try { 23 | response = chain.proceed(builder.build()); 24 | } catch (IOException e) { 25 | throw RetrofitError.networkError(e.getLocalizedMessage(), e); 26 | } catch (Throwable e) { 27 | throw RetrofitError.unexpectedError(e.getLocalizedMessage(), e); 28 | } 29 | 30 | if (response.code() == 401) { 31 | synchronized (lockObject) { 32 | String currentToken = getAccessToken(); 33 | if (currentToken != null && currentToken.equals(token)) { 34 | return performRefresh(chain, builder); 35 | } 36 | } 37 | } 38 | return response; 39 | } 40 | 41 | private Response performRefresh(Chain chain, Request.Builder builder) throws IOException { 42 | try { 43 | IOException exception = refreshAccessToken(); 44 | Response response = chain.proceed(builder.build()); 45 | if (response.code() < 200 || response.code() >= 300) { 46 | throw exception; 47 | } 48 | return response; 49 | } catch (IOException e) { 50 | performRefreshFailure(e); 51 | throw e; 52 | } 53 | } 54 | 55 | protected abstract String getAccessToken(); 56 | 57 | protected abstract IOException refreshAccessToken(); 58 | 59 | protected abstract void performRefreshFailure(IOException e); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/SingleFragmentActivityIntentFactory.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v4.app.Fragment; 7 | 8 | @Deprecated 9 | public class SingleFragmentActivityIntentFactory { 10 | 11 | public Class activityClass = SingleFragmentActivity.class; 12 | 13 | /** 14 | * This allows you to create an Intent that will start a new SingleFragmentActivity that 15 | * automatically instantiates the Fragment class specified and adds it to the fragment container 16 | * 17 | * @param context 18 | * @param fragmentClass 19 | * @return Intent 20 | */ 21 | public Intent newIntent(Context context, Class fragmentClass) { 22 | return newIntent(context, fragmentClass, null); 23 | } 24 | 25 | /** 26 | * This allows you to create an Intent that will start a new SingleFragmentActivity that 27 | * automatically instantiates the Fragment class specified and adds it to the fragment container. 28 | *

29 | * This method is useful but somewhat misleading since your Fragment instance is only used to 30 | * get the Class and Fragment Arguments to pass along to the SingleFragmentActivity 31 | * 32 | * @param context 33 | * @param fragment 34 | * @return 35 | */ 36 | @Deprecated 37 | public Intent newIntent(Context context, Fragment fragment) { 38 | return newIntent(context, fragment.getClass(), fragment.getArguments()); 39 | } 40 | 41 | /** 42 | * This allows you to create an Intent that will start a new SingleFragmentActivity that 43 | * automatically instantiates the Fragment class specified, adds it to the fragment container, 44 | * and passes along the Fragment Arguments. 45 | * 46 | * @param context 47 | * @param fragmentName 48 | * @param fragmentArgs 49 | * @param 50 | * @return 51 | */ 52 | public Intent newIntent(Context context, Class fragmentName, Bundle fragmentArgs) { 53 | Intent intent = new Intent(context, activityClass); 54 | Bundle extras; 55 | if (fragmentArgs != null) { 56 | extras = new Bundle(fragmentArgs); 57 | } else { 58 | extras = new Bundle(); 59 | } 60 | extras.putString(SingleFragmentActivity.EXTRA_FRAGMENT_NAME, fragmentName.getCanonicalName()); 61 | intent.putExtras(extras); 62 | return intent; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/SingleFragmentActivity.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.MenuItem; 8 | 9 | public class SingleFragmentActivity extends AppCompatActivity { 10 | public static final String EXTRA_FRAGMENT_NAME = "EXTRA_FRAGMENT_NAME"; 11 | private Toolbar toolbar; 12 | private boolean traceLog; 13 | 14 | protected void enableTraceLog(boolean enable) { 15 | traceLog = enable; 16 | } 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | if (traceLog) Log.d("TRACE", "--> SingleFragmentActivity.onCreate()"); 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_single_fragment); 23 | toolbar = (Toolbar) findViewById(R.id.toolbar); 24 | setSupportActionBar(toolbar); 25 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 26 | 27 | if (savedInstanceState == null) { 28 | getSupportFragmentManager().beginTransaction() 29 | .replace(R.id.fragment_container, getFragment()) 30 | .commit(); 31 | } 32 | if (traceLog) Log.d("TRACE", "<-- SingleFragmentActivity.onCreate()"); 33 | } 34 | 35 | protected Fragment getFragment() { 36 | String fragmentName = getIntent().getStringExtra(EXTRA_FRAGMENT_NAME); 37 | if (fragmentName == null) { 38 | throw new RuntimeException("You must either provide a fragment name or override getFragment() in a subclass to provide a fragment instance"); 39 | } 40 | 41 | return Fragment.instantiate(this, fragmentName, getIntent().getExtras()); 42 | } 43 | 44 | @Override 45 | public boolean onOptionsItemSelected(MenuItem item) { 46 | if (item.getItemId() == android.R.id.home) { 47 | Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); 48 | if (null != currentFragment && currentFragment.onOptionsItemSelected(item)) { 49 | return true; 50 | } else { 51 | finish(); 52 | return true; 53 | } 54 | } else { 55 | return super.onOptionsItemSelected(item); 56 | } 57 | } 58 | 59 | protected void handleError(Throwable e) { 60 | handleError(null, e); 61 | } 62 | 63 | protected void handleError(String message, Throwable e) { 64 | ErrorHandler.handleError(this, message, e); 65 | } 66 | 67 | protected Toolbar getToolbar() { 68 | return toolbar; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/LoadingImageView.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.drawable.Drawable; 6 | import android.support.annotation.AttrRes; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.util.AttributeSet; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.widget.FrameLayout; 13 | import android.widget.ImageView; 14 | import android.widget.ProgressBar; 15 | 16 | /** 17 | * LoadingImageView displays an arbitrary image, such as an icon, overlayed with a progressBar. 18 | * The LoadingImageView class subclasses FrameLayout class, but implements ImageView class to display images. 19 | * It can load images from various sources (such as resources or content providers). There is also a getter 20 | * method to access the underlying ImageView so images can be loaded using libraries such as Picasso. 21 | * 22 | * @attr ref R.styleable#LoadingImageView_imageSrc 23 | * @attr ref R.styleable#LoadingImageView_imageScaleType 24 | */ 25 | 26 | public class LoadingImageView extends FrameLayout { 27 | private ImageView imageView; 28 | private ProgressBar progressBar; 29 | 30 | public LoadingImageView(@NonNull Context context) { 31 | this(context, null); 32 | } 33 | 34 | public LoadingImageView(@NonNull Context context, @Nullable AttributeSet attrs) { 35 | this(context, attrs, 0); 36 | } 37 | 38 | public LoadingImageView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { 39 | super(context, attrs, defStyleAttr); 40 | 41 | View view = LayoutInflater.from(context).inflate(R.layout.loading_image_view, this, true); 42 | imageView = (ImageView) view.findViewById(R.id.imageView); 43 | progressBar = (ProgressBar) view.findViewById(R.id.progressBar); 44 | 45 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LoadingImageView); 46 | 47 | Drawable image = a.getDrawable(R.styleable.LoadingImageView_imageSrc); 48 | int scaleType = a.getInteger(R.styleable.LoadingImageView_imageScaleType, -1); 49 | 50 | imageView.setImageDrawable(image); 51 | 52 | if (scaleType >= 0) { 53 | ImageView.ScaleType selectedScaleType = ImageView.ScaleType.values()[scaleType]; 54 | imageView.setScaleType(selectedScaleType); 55 | } 56 | 57 | a.recycle(); 58 | } 59 | 60 | public void setImageResource(int resourceId, @Nullable String contentDescription) { 61 | imageView.setImageResource(resourceId); 62 | imageView.setContentDescription(contentDescription); 63 | } 64 | 65 | public void setImageDrawable(Drawable drawable, @Nullable String contentDescription) { 66 | imageView.setImageDrawable(drawable); 67 | imageView.setContentDescription(contentDescription); 68 | } 69 | 70 | public void showProgessBar(boolean isVisible) { 71 | progressBar.setVisibility(isVisible ? VISIBLE : GONE); 72 | } 73 | 74 | public ImageView getImageView() { 75 | return imageView; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/BlockingProgressFragment.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.DialogFragment; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.view.Window; 14 | import android.widget.TextView; 15 | 16 | import rx.Subscription; 17 | 18 | import static android.view.View.*; 19 | 20 | public class BlockingProgressFragment extends DialogFragment { 21 | public static final String TAG = BlockingProgressFragment.class.getSimpleName(); 22 | private static final String ARGS_PROGRESS_TEXT = "ARGS_PROGRESS_TEXT"; 23 | private Subscription subscription; 24 | private String progressText; 25 | 26 | public static BlockingProgressFragment newInstance() { 27 | return new BlockingProgressFragment(); 28 | } 29 | 30 | public static BlockingProgressFragment newInstance(String progressText) { 31 | BlockingProgressFragment fragment = new BlockingProgressFragment(); 32 | Bundle args = new Bundle(); 33 | args.putString(ARGS_PROGRESS_TEXT, progressText); 34 | fragment.setArguments(args); 35 | return fragment; 36 | } 37 | 38 | @Override 39 | public void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | 42 | if (getArguments() != null && getArguments().containsKey(ARGS_PROGRESS_TEXT)) { 43 | progressText = getArguments().getString(ARGS_PROGRESS_TEXT); 44 | } 45 | } 46 | 47 | @NonNull 48 | @Override 49 | public Dialog onCreateDialog(Bundle savedInstanceState) { 50 | Dialog dialog = super.onCreateDialog(savedInstanceState); 51 | dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); 52 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); 53 | return dialog; 54 | } 55 | 56 | @Override 57 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 58 | return inflater.inflate(R.layout.fragment_blocking_progress, container, false); 59 | } 60 | 61 | @Override 62 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 63 | super.onViewCreated(view, savedInstanceState); 64 | 65 | if (progressText != null) { 66 | TextView progressTextView = (TextView) view.findViewById(R.id.progress_text); 67 | progressTextView.setText(progressText); 68 | progressTextView.setVisibility(VISIBLE); 69 | } 70 | } 71 | 72 | @Override 73 | public void onCancel(DialogInterface dialog) { 74 | super.onCancel(dialog); 75 | if (subscription != null) { 76 | subscription.unsubscribe(); 77 | subscription = null; 78 | } 79 | } 80 | 81 | public void setOnCancelListener(Subscription subscription) { 82 | this.subscription = subscription; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.ContentResolver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.content.pm.ResolveInfo; 9 | import android.net.ConnectivityManager; 10 | import android.net.NetworkInfo; 11 | import android.os.Build; 12 | import android.provider.Settings; 13 | 14 | import java.lang.reflect.Field; 15 | 16 | import static android.os.Build.VERSION.SDK_INT; 17 | import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; 18 | import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 19 | 20 | public class NetworkUtils { 21 | 22 | public static boolean isNetworkAvailable(Context context) { 23 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 24 | NetworkInfo networkInfo = cm.getActiveNetworkInfo(); 25 | return !(networkInfo == null || !networkInfo.isConnectedOrConnecting()); 26 | } 27 | 28 | /** 29 | * Stolen with love from: http://stackoverflow.com/a/20752580/289641 30 | *

31 | * Checks whether the "Avoid poor networks" setting (named "Auto network switch" on 32 | * some Samsung devices) is enabled, which can in some instances interfere with Wi-Fi. 33 | * 34 | * @return true if the "Avoid poor networks" or "Auto network switch" setting is enabled 35 | */ 36 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 37 | public static boolean isWatchdogEnabled(Context context) { 38 | final int SETTING_UNKNOWN = -1; 39 | final int SETTING_ENABLED = 1; 40 | final String AVOID_POOR = "wifi_watchdog_poor_network_test_enabled"; 41 | final String WATCHDOG_CLASS = "android.net.wifi.WifiWatchdogStateMachine"; 42 | final String DEFAULT_ENABLED = "DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED"; 43 | final ContentResolver contentResolver = context.getContentResolver(); 44 | 45 | int result; 46 | 47 | switch (SDK_INT) { 48 | case JELLY_BEAN_MR1: 49 | //Setting was moved from Secure to Global as of JB MR1 50 | result = Settings.Global.getInt(contentResolver, AVOID_POOR, SETTING_UNKNOWN); 51 | break; 52 | case ICE_CREAM_SANDWICH_MR1: 53 | result = Settings.Secure.getInt(contentResolver, AVOID_POOR, SETTING_UNKNOWN); 54 | break; 55 | default: 56 | //Poor network avoidance not introduced until ICS MR1 57 | //See android.provider.Settings.java 58 | return false; 59 | } 60 | 61 | //Exit here if the setting value is known 62 | if (result != SETTING_UNKNOWN) { 63 | return (result == SETTING_ENABLED); 64 | } 65 | 66 | //Setting does not exist in database, so it has never been changed. 67 | //It will be initialized to the default value. 68 | if (SDK_INT >= JELLY_BEAN_MR1) { 69 | //As of JB MR1, a constant was added to WifiWatchdogStateMachine to determine 70 | //the default behavior of the Avoid Poor Networks setting. 71 | try { 72 | //In the case of any failures here, take the safe route and assume the 73 | //setting is disabled to avoid disrupting the user with false information 74 | Class wifiWatchdog = Class.forName(WATCHDOG_CLASS); 75 | Field defValue = wifiWatchdog.getField(DEFAULT_ENABLED); 76 | if (!defValue.isAccessible()) { 77 | defValue.setAccessible(true); 78 | } 79 | return defValue.getBoolean(null); 80 | } catch (IllegalAccessException ex) { 81 | return false; 82 | } catch (NoSuchFieldException ex) { 83 | return false; 84 | } catch (ClassNotFoundException ex) { 85 | return false; 86 | } catch (IllegalArgumentException ex) { 87 | return false; 88 | } 89 | } else { 90 | //Prior to JB MR1, the default for the Avoid Poor Networks setting was 91 | //to enable it unless explicitly disabled 92 | return true; 93 | } 94 | } 95 | 96 | public static boolean activityExists(Context context, Intent intent) { 97 | final PackageManager mgr = context.getPackageManager(); 98 | final ResolveInfo info = mgr.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); 99 | return (info != null); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/OkCancelFragment.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v7.app.AlertDialog; 11 | 12 | public class OkCancelFragment extends DialogFragment { 13 | public static final String TAG = OkCancelFragment.class.getSimpleName(); 14 | 15 | public static final String DATA_ENTITY_ID = "DATA_ENTITY_ID"; 16 | 17 | private static final String ARGS_ENTITY_ID = "ARGS_ENTITY_ID"; 18 | private static final String ARGS_TITLE = "ARGS_TITLE"; 19 | private static final String ARGS_MESSAGE = "ARGS_MESSAGE"; 20 | private static final String ARGS_MESSAGE_ID = "ARGS_MESSAGE_ID"; 21 | private static final String ARGS_TITLE_ID = "ARGS_TITLE_ID"; 22 | 23 | private String title; 24 | private String message; 25 | private int entityId; 26 | private int messageId; 27 | private int titleId; 28 | 29 | public static OkCancelFragment newInstance(int messageId) { 30 | OkCancelFragment fragment = new OkCancelFragment(); 31 | Bundle args = new Bundle(); 32 | args.putInt(ARGS_MESSAGE_ID, messageId); 33 | fragment.setArguments(args); 34 | return fragment; 35 | } 36 | 37 | public static OkCancelFragment newInstance(int titleId, int messageId, int id) { 38 | OkCancelFragment fragment = new OkCancelFragment(); 39 | Bundle args = new Bundle(); 40 | args.putInt(ARGS_TITLE_ID, titleId); 41 | args.putInt(ARGS_MESSAGE_ID, messageId); 42 | args.putInt(ARGS_ENTITY_ID, id); 43 | fragment.setArguments(args); 44 | return fragment; 45 | } 46 | 47 | public static OkCancelFragment newInstance(String title, String message, int id) { 48 | OkCancelFragment fragment = new OkCancelFragment(); 49 | Bundle args = new Bundle(); 50 | args.putString(ARGS_TITLE, title); 51 | args.putString(ARGS_MESSAGE, message); 52 | args.putInt(ARGS_ENTITY_ID, id); 53 | fragment.setArguments(args); 54 | return fragment; 55 | } 56 | 57 | public static OkCancelFragment newInstance(String title, String message) { 58 | return newInstance(title, message, -1); 59 | } 60 | 61 | public static OkCancelFragment newInstance(int titleId, int messageId) { 62 | return newInstance(titleId, messageId, -1); 63 | } 64 | 65 | @Override 66 | public void onCreate(Bundle savedInstanceState) { 67 | super.onCreate(savedInstanceState); 68 | 69 | titleId = getArguments().getInt(ARGS_TITLE_ID, -1); 70 | if (titleId == -1) { 71 | title = getArguments().getString(ARGS_TITLE); 72 | } 73 | 74 | messageId = getArguments().getInt(ARGS_MESSAGE_ID, -1); 75 | if (messageId == -1) { 76 | message = getArguments().getString(ARGS_MESSAGE); 77 | } 78 | entityId = getArguments().getInt(ARGS_ENTITY_ID); 79 | } 80 | 81 | @NonNull 82 | @Override 83 | public Dialog onCreateDialog(Bundle savedInstanceState) { 84 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) 85 | .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 86 | @Override 87 | public void onClick(DialogInterface dialog, int which) { 88 | Intent data = new Intent(); 89 | data.putExtra(DATA_ENTITY_ID, entityId); 90 | if (getTargetFragment() != null) { 91 | getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, data); 92 | } 93 | } 94 | }) 95 | .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 96 | @Override 97 | public void onClick(DialogInterface dialog, int which) { 98 | if (getTargetFragment() != null) { 99 | getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, null); 100 | } 101 | } 102 | }); 103 | 104 | 105 | if (messageId != -1) { 106 | builder.setMessage(messageId); 107 | } else if (message != null) { 108 | builder.setMessage(message); 109 | } 110 | if (titleId != -1) { 111 | builder.setTitle(titleId); 112 | } else if (message != null) { 113 | builder.setTitle(title); 114 | } 115 | 116 | return builder.create(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/RxFragment.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.DialogFragment; 5 | import android.support.v7.widget.Toolbar; 6 | import android.widget.Toast; 7 | 8 | import rx.Observable; 9 | import rx.Observer; 10 | import rx.Subscription; 11 | import rx.functions.Action0; 12 | 13 | @SuppressWarnings("UnusedDeclaration") 14 | public abstract class RxFragment extends DialogFragment { 15 | 16 | private BlockingProgressFragment blockingProgressFragment; 17 | private Toolbar toolbar; 18 | 19 | @Override 20 | public void onActivityCreated(Bundle savedInstanceState) { 21 | super.onActivityCreated(savedInstanceState); 22 | toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar); 23 | } 24 | 25 | abstract protected boolean isDebug(); 26 | 27 | protected void showBlockingProgress() { 28 | showBlockingProgress(null); 29 | } 30 | 31 | protected void showBlockingProgress(Subscription subscription) { 32 | blockingProgressFragment = BlockingProgressFragment.newInstance(); 33 | blockingProgressFragment.show(getFragmentManager(), BlockingProgressFragment.TAG); 34 | if (subscription != null) { 35 | blockingProgressFragment.setOnCancelListener(subscription); 36 | } else { 37 | blockingProgressFragment.setCancelable(false); 38 | } 39 | } 40 | 41 | public void handleError(Throwable e) { 42 | handleError(null, e); 43 | } 44 | 45 | protected void handleError(final String message, final Throwable e) { 46 | dismissBlockingProgress(); 47 | 48 | Log.e(getClass().getSimpleName(), e.getLocalizedMessage(), e); 49 | if (isDebug() || message != null) { 50 | if (getActivity() != null) { 51 | getActivity().runOnUiThread(new Runnable() { 52 | @Override 53 | public void run() { 54 | Toast.makeText(getActivity(), message == null ? e.getLocalizedMessage() : message, Toast.LENGTH_LONG).show(); 55 | } 56 | }); 57 | } 58 | } 59 | } 60 | 61 | protected void dismissBlockingProgress() { 62 | if (blockingProgressFragment != null) { 63 | blockingProgressFragment.dismiss(); 64 | blockingProgressFragment = null; 65 | } 66 | } 67 | 68 | public Toolbar getToolbar() { 69 | if (toolbar == null) { 70 | throw new RuntimeException("Toolbar has not been set. Make sure not to call getToolbar() until onViewCreated() at the earliest."); 71 | } 72 | return toolbar; 73 | } 74 | 75 | @Deprecated 76 | protected Subscription blockingSubscribe(Observable observable, final Observer observer) { 77 | Subscription subscription = bind(observable 78 | .doOnTerminate(new Action0() { 79 | @Override 80 | public void call() { 81 | dismissBlockingProgress(); 82 | } 83 | }) 84 | ) 85 | .subscribe(observer); 86 | showBlockingProgress(subscription); 87 | return subscription; 88 | } 89 | 90 | @Deprecated 91 | protected Subscription subscribe(Observable observable, Observer observer) { 92 | return bind(observable).subscribe(observer); 93 | } 94 | 95 | @Deprecated 96 | protected Observable bind(Observable observable) { 97 | return observable.compose(Rx.bind(this)); 98 | } 99 | 100 | //TODO: move this to a util class 101 | @Deprecated 102 | protected void toast(int messageId) { 103 | ToastUtil.show(getActivity(), messageId); 104 | } 105 | 106 | //TODO: move this to a util class 107 | @Deprecated 108 | protected void toast(String message) { 109 | ToastUtil.show(getActivity(), message); 110 | } 111 | 112 | @Deprecated 113 | protected int getColor(int colorRes) { 114 | return getResources().getColor(colorRes); 115 | } 116 | 117 | //TODO: move this to a util class 118 | @Deprecated 119 | protected int getDimensionPixelOffset(int dpResource) { 120 | return getResources().getDimensionPixelOffset(dpResource); 121 | } 122 | 123 | //TODO: move this to a util class 124 | @Deprecated 125 | protected void debugToast(int messageResId) { 126 | if (isDebug()) { 127 | Toast.makeText(getActivity(), messageResId, Toast.LENGTH_SHORT).show(); 128 | } 129 | } 130 | 131 | public void onCompleted() { 132 | //nothing by default 133 | } 134 | 135 | public void onError(Throwable e) { 136 | handleError(e); 137 | } 138 | 139 | public void noop(Object o) { 140 | 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/Rx.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | import android.support.v4.app.Fragment; 7 | 8 | import rx.Observable; 9 | import rx.Subscriber; 10 | import rx.android.schedulers.AndroidSchedulers; 11 | import rx.annotations.Experimental; 12 | import rx.schedulers.Schedulers; 13 | 14 | /** 15 | * WIP 16 | *

17 | * A composable version of RxJava helpers to avoid the need to subclass from a 18 | * base RxFragment etc. 19 | */ 20 | @Experimental 21 | public final class Rx { 22 | public static Observable.Transformer bind(final Activity activity) { 23 | return new Observable.Transformer() { 24 | @Override 25 | public Observable call(Observable observable) { 26 | return observable 27 | .subscribeOn(Schedulers.io()) 28 | .observeOn(AndroidSchedulers.mainThread()); 29 | //TODO: add protection against invalid Activity state 30 | } 31 | }; 32 | } 33 | 34 | /*** 35 | * Use this to make an Observable run in a background threadpool and 36 | * handle the result on the UI thread. This will complete the Observable 37 | * when the Fragment is being destroyed 38 | * 39 | * @param fragment 40 | * @param 41 | * @return 42 | */ 43 | public static Observable.Transformer bind(final Fragment fragment) { 44 | return new Observable.Transformer() { 45 | @Override 46 | public Observable call(Observable observable) { 47 | return observable 48 | .subscribeOn(Schedulers.io()) 49 | .observeOn(AndroidSchedulers.mainThread()) 50 | .lift(new SafeFragmentOperator(fragment)); 51 | } 52 | }; 53 | } 54 | 55 | public static void init(Application application) { 56 | application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 57 | @Override 58 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 59 | 60 | } 61 | 62 | @Override 63 | public void onActivityStarted(Activity activity) { 64 | 65 | } 66 | 67 | @Override 68 | public void onActivityResumed(Activity activity) { 69 | 70 | } 71 | 72 | @Override 73 | public void onActivityPaused(Activity activity) { 74 | 75 | } 76 | 77 | @Override 78 | public void onActivityStopped(Activity activity) { 79 | 80 | } 81 | 82 | @Override 83 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 84 | 85 | } 86 | 87 | @Override 88 | public void onActivityDestroyed(Activity activity) { 89 | 90 | } 91 | }); 92 | } 93 | 94 | private static class SafeFragmentOperator implements Observable.Operator { 95 | private static final String TAG = "SafeFragmentOperator"; 96 | private Fragment fragment; 97 | 98 | public SafeFragmentOperator(Fragment fragment) { 99 | this.fragment = fragment; 100 | } 101 | 102 | @Override 103 | public Subscriber call(final Subscriber subscriber) { 104 | 105 | return new Subscriber() { 106 | @Override 107 | public void onCompleted() { 108 | if (fragment != null && fragment.isAdded() && fragment.getActivity() != null && !fragment.getActivity().isFinishing()) { 109 | subscriber.onCompleted(); 110 | } else { 111 | Log.d(TAG, "Did not call onCompleted(). Fragment is in an invalid state"); 112 | unsubscribe(); 113 | fragment = null; 114 | } 115 | } 116 | 117 | @Override 118 | public void onError(Throwable e) { 119 | if (fragment != null && fragment.isAdded() && fragment.getActivity() != null && !fragment.getActivity().isFinishing()) { 120 | subscriber.onError(e); 121 | } else { 122 | unsubscribe(); 123 | fragment = null; 124 | } 125 | } 126 | 127 | @Override 128 | public void onNext(T t) { 129 | if (fragment != null && fragment.isAdded() && fragment.getActivity() != null && !fragment.getActivity().isFinishing()) { 130 | subscriber.onNext(t); 131 | } else { 132 | unsubscribe(); 133 | fragment = null; 134 | } 135 | } 136 | }; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/stablekernel/standardlib/FloatLabelLayout.java: -------------------------------------------------------------------------------- 1 | package com.stablekernel.standardlib; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.os.Build; 7 | import android.support.v4.view.ViewCompat; 8 | import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; 9 | import android.text.Editable; 10 | import android.text.TextUtils; 11 | import android.text.TextWatcher; 12 | import android.util.AttributeSet; 13 | import android.util.TypedValue; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.view.animation.AnimationUtils; 18 | import android.view.animation.Interpolator; 19 | import android.widget.EditText; 20 | import android.widget.LinearLayout; 21 | import android.widget.TextView; 22 | 23 | 24 | /** 25 | * @deprecated Use TextInputLayout from the Material Design Support library since support v22.2.0 26 | */ 27 | @Deprecated 28 | public class FloatLabelLayout extends LinearLayout { 29 | 30 | private static final long ANIMATION_DURATION = 150; 31 | 32 | private static final float DEFAULT_LABEL_PADDING_LEFT = 3f; 33 | private static final float DEFAULT_LABEL_PADDING_TOP = 4f; 34 | private static final float DEFAULT_LABEL_PADDING_RIGHT = 3f; 35 | private static final float DEFAULT_LABEL_PADDING_BOTTOM = 4f; 36 | public static final int DEFAULT_LABEL_TEXT_SIZE = 12; 37 | 38 | private EditText mEditText; 39 | private TextView mLabel; 40 | 41 | private CharSequence mHint; 42 | private Interpolator mInterpolator; 43 | 44 | public FloatLabelLayout(Context context) { 45 | this(context, null); 46 | } 47 | 48 | public FloatLabelLayout(Context context, AttributeSet attrs) { 49 | this(context, attrs, 0); 50 | } 51 | 52 | public FloatLabelLayout(Context context, AttributeSet attrs, int defStyle) { 53 | super(context, attrs, defStyle); 54 | 55 | setOrientation(VERTICAL); 56 | 57 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FloatLabelLayout); 58 | 59 | int leftPadding = a.getDimensionPixelSize( 60 | R.styleable.FloatLabelLayout_floatLabelPaddingLeft, 61 | dipsToPix(DEFAULT_LABEL_PADDING_LEFT)); 62 | int topPadding = a.getDimensionPixelSize( 63 | R.styleable.FloatLabelLayout_floatLabelPaddingTop, 64 | dipsToPix(DEFAULT_LABEL_PADDING_TOP)); 65 | int rightPadding = a.getDimensionPixelSize( 66 | R.styleable.FloatLabelLayout_floatLabelPaddingRight, 67 | dipsToPix(DEFAULT_LABEL_PADDING_RIGHT)); 68 | int bottomPadding = a.getDimensionPixelSize( 69 | R.styleable.FloatLabelLayout_floatLabelPaddingBottom, 70 | dipsToPix(DEFAULT_LABEL_PADDING_BOTTOM)); 71 | int textSize = a.getDimensionPixelSize( 72 | R.styleable.FloatLabelLayout_floatLabelTextSize, 73 | dipsToPix(DEFAULT_LABEL_TEXT_SIZE)); 74 | int textColor = a.getColor( 75 | R.styleable.FloatLabelLayout_floatLabelTextColor, 76 | Color.BLACK); 77 | 78 | mHint = a.getText(R.styleable.FloatLabelLayout_floatLabelHint); 79 | 80 | mLabel = (TextView) LayoutInflater.from(context).inflate(R.layout.text_view, this, false); 81 | mLabel.setPadding(leftPadding, topPadding, rightPadding, bottomPadding); 82 | mLabel.setVisibility(INVISIBLE); 83 | mLabel.setText(mHint); 84 | mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 85 | mLabel.setTextColor(textColor); 86 | 87 | ViewCompat.setPivotX(mLabel, 0f); 88 | ViewCompat.setPivotY(mLabel, 0f); 89 | 90 | a.recycle(); 91 | 92 | addView(mLabel, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 93 | 94 | if (!isInEditMode()) { 95 | mInterpolator = AnimationUtils.loadInterpolator(context, 96 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP 97 | ? android.R.interpolator.fast_out_slow_in 98 | : android.R.anim.decelerate_interpolator); 99 | } 100 | } 101 | 102 | @Override 103 | public final void addView(View child, int index, ViewGroup.LayoutParams params) { 104 | if (child instanceof EditText) { 105 | setEditText((EditText) child); 106 | } 107 | 108 | // Carry on adding the View... 109 | super.addView(child, index, params); 110 | } 111 | 112 | private void setEditText(EditText editText) { 113 | // If we already have an EditText, throw an exception 114 | if (mEditText != null) { 115 | throw new IllegalArgumentException("We already have an EditText, can only have one"); 116 | } 117 | mEditText = editText; 118 | int padding = (int) DisplayUtils.dpFromPixels(getContext(), 4); 119 | mEditText.setPadding(mEditText.getPaddingLeft(),padding,mEditText.getPaddingRight(),mEditText.getPaddingBottom()); 120 | 121 | // Update the label visibility with no animation 122 | updateLabelVisibility(false); 123 | 124 | // Add a TextWatcher so that we know when the text input has changed 125 | mEditText.addTextChangedListener(new TextWatcher() { 126 | @Override 127 | public void afterTextChanged(Editable s) { 128 | updateLabelVisibility(false); 129 | } 130 | 131 | @Override 132 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 133 | } 134 | 135 | @Override 136 | public void onTextChanged(CharSequence s, int start, int before, int count) { 137 | } 138 | }); 139 | 140 | // Add focus listener to the EditText so that we can notify the label that it is activated. 141 | // Allows the use of a ColorStateList for the text color on the label 142 | mEditText.setOnFocusChangeListener(new OnFocusChangeListener() { 143 | @Override 144 | public void onFocusChange(View view, boolean focused) { 145 | updateLabelVisibility(true); 146 | } 147 | }); 148 | 149 | // If we do not have a valid hint, try and retrieve it from the EditText 150 | if (TextUtils.isEmpty(mHint)) { 151 | setHint(mEditText.getHint()); 152 | } 153 | } 154 | 155 | @Override 156 | public void setEnabled(boolean enabled) { 157 | mEditText.setEnabled(enabled); 158 | super.setEnabled(enabled); 159 | } 160 | 161 | private void updateLabelVisibility(boolean animate) { 162 | boolean hasText = !TextUtils.isEmpty(mEditText.getText()); 163 | boolean isFocused = mEditText.isFocused(); 164 | 165 | mLabel.setActivated(isFocused); 166 | 167 | if (hasText || isFocused) { 168 | // We should be showing the label so do so if it isn't already 169 | if (mLabel.getVisibility() != VISIBLE) { 170 | showLabel(animate); 171 | } 172 | } else { 173 | // We should not be showing the label so hide it 174 | if (mLabel.getVisibility() == VISIBLE) { 175 | hideLabel(animate); 176 | } 177 | } 178 | } 179 | 180 | /** 181 | * @return the {@link android.widget.EditText} text input 182 | */ 183 | public EditText getEditText() { 184 | return mEditText; 185 | } 186 | 187 | /** 188 | * @return the {@link android.widget.TextView} label 189 | */ 190 | public TextView getLabel() { 191 | return mLabel; 192 | } 193 | 194 | /** 195 | * Set the hint to be displayed in the floating label 196 | */ 197 | public void setHint(CharSequence hint) { 198 | mHint = hint; 199 | mLabel.setText(hint); 200 | } 201 | 202 | /** 203 | * Show the label 204 | */ 205 | private void showLabel(boolean animate) { 206 | if (animate) { 207 | mLabel.setVisibility(View.VISIBLE); 208 | ViewCompat.setTranslationY(mLabel, mLabel.getHeight()); 209 | 210 | float scale = mEditText.getTextSize() / mLabel.getTextSize(); 211 | ViewCompat.setScaleX(mLabel, scale); 212 | ViewCompat.setScaleY(mLabel, scale); 213 | 214 | ViewCompat.animate(mLabel) 215 | .translationY(0f) 216 | .scaleY(1f) 217 | .scaleX(1f) 218 | .setDuration(ANIMATION_DURATION) 219 | .setListener(null) 220 | .setInterpolator(mInterpolator).start(); 221 | } else { 222 | mLabel.setVisibility(VISIBLE); 223 | } 224 | 225 | mEditText.setHint(null); 226 | } 227 | 228 | /** 229 | * Hide the label 230 | */ 231 | private void hideLabel(boolean animate) { 232 | if (animate) { 233 | float scale = mEditText.getTextSize() / mLabel.getTextSize(); 234 | ViewCompat.setScaleX(mLabel, 1f); 235 | ViewCompat.setScaleY(mLabel, 1f); 236 | ViewCompat.setTranslationY(mLabel, 0f); 237 | 238 | ViewCompat.animate(mLabel) 239 | .translationY(mLabel.getHeight()) 240 | .setDuration(ANIMATION_DURATION) 241 | .scaleX(scale) 242 | .scaleY(scale) 243 | .setListener(new ViewPropertyAnimatorListenerAdapter() { 244 | @Override 245 | public void onAnimationEnd(View view) { 246 | mLabel.setVisibility(INVISIBLE); 247 | mEditText.setHint(mHint); 248 | } 249 | }) 250 | .setInterpolator(mInterpolator).start(); 251 | } else { 252 | mLabel.setVisibility(INVISIBLE); 253 | mEditText.setHint(mHint); 254 | } 255 | } 256 | 257 | /** 258 | * Helper method to convert dips to pixels. 259 | */ 260 | private int dipsToPix(float dps) { 261 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dps, 262 | getResources().getDisplayMetrics()); 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | --------------------------------------------------------------------------------