package com.darkrockstudios.apps.tminus.misc; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.View; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import com.darkrockstudios.apps.tminus.R; import com.darkrockstudios.apps.tminus.launchlibrary.Launch; import com.darkrockstudios.apps.tminus.launchlibrary.Mission; import org.joda.time.DateTime; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.TimeUnit; /** * Created by Adam on 6/30/13. * Dark Rock Studios * darkrockstudios.com */ public class Utilities { public static final String DATE_FORMAT = "HH:mm - dd MMM yyy"; public static String getFormattedTime( final long timeMs ) { final long day = TimeUnit.MILLISECONDS.toDays( timeMs ); final long hr = TimeUnit.MILLISECONDS.toHours( timeMs - TimeUnit.DAYS.toMillis( day ) ); final long min = TimeUnit.MILLISECONDS.toMinutes( timeMs - TimeUnit.DAYS .toMillis( day ) - TimeUnit.HOURS .toMillis( hr ) ); String timeStr = day + "d " + hr + "h " + min + "m"; return timeStr; } public static String getStatusText( final Launch launch, final Context context ) { final String status; if( launch != null ) { switch( launch.status ) { case 1: status = context.getString( R.string.COUNTDOWN_launch_status_green ); break; case 2: status = context.getString( R.string.COUNTDOWN_launch_status_red ); break; case 3: status = context.getString( R.string.COUNTDOWN_launch_status_success ); break; case 4: status = context.getString( R.string.COUNTDOWN_launch_status_fail ); break; default: status = ""; } } else { status = ""; } return status; } public static String getDateText( final DateTime date ) { final SimpleDateFormat formatter = new SimpleDateFormat( DATE_FORMAT ); return formatter.format( date.getMillis() ); } public static Drawable getLaunchTypeDrawable( final Collection<Mission> missions, final Context context ) { final Drawable type; Resources resources = context.getResources(); if( missions != null && missions.size() > 0 ) { if( missions.size() > 1 ) { Drawable[] types = new Drawable[ missions.size() ]; Iterator<Mission> it = missions.iterator(); for( int ii = 0; ii < missions.size(); ++ii ) { Mission mission = it.next(); types[ ii ] = resources.getDrawable( getLaunchTypeResource( mission.type ) ); } CyclicTransitionDrawable transitionDrawable = new CyclicTransitionDrawable( types ); final int transitionDuration = resources.getInteger( R.integer.transition_duration_ms ); final int transitionPause = resources.getInteger( R.integer.transition_pause_ms ); transitionDrawable.startTransition( transitionDuration, transitionPause ); type = transitionDrawable; } else { Mission mission = missions.iterator().next(); type = resources.getDrawable( getLaunchTypeResource( mission.type ) ); } } else { type = resources.getDrawable( R.drawable.ic_launch_type_unknown ); } return type; } public static int getLaunchTypeResource( final int type ) { final int resourceId; switch( type ) { case 1: resourceId = R.drawable.ic_launch_type_earth_science; break; case 2: resourceId = R.drawable.ic_launch_type_planet_science; break; case 3: resourceId = R.drawable.ic_launch_type_astrophysics; break; case 4: resourceId = R.drawable.ic_launch_type_heliophysics; break; case 5: resourceId = R.drawable.ic_launch_type_human_explore; break; case 6: resourceId = R.drawable.ic_launch_type_robotic_explore; break; case 7: resourceId = R.drawable.ic_launch_type_gov_secrete; break; case 8: resourceId = R.drawable.ic_launch_type_tourism; break; case 9: resourceId = R.drawable.ic_launch_type_unknown; break; default: resourceId = R.drawable.ic_launch_type_unknown; break; } return resourceId; } public static int getLaunchTypeStringResource( final int type ) { final int resourceId; switch( type ) { case 1: resourceId = R.string.MISSIONTYPE_earth_science; break; case 2: resourceId = R.string.MISSIONTYPE_earth_science; break; case 3: resourceId = R.string.MISSIONTYPE_astrophysics; break; case 4: resourceId = R.string.MISSIONTYPE_heliophysics; break; case 5: resourceId = R.string.MISSIONTYPE_human_explore; break; case 6: resourceId = R.string.MISSIONTYPE_robotic_explore; break; case 7: resourceId = R.string.MISSIONTYPE_gov_secret; break; case 8: resourceId = R.string.MISSIONTYPE_tourism; break; case 9: resourceId = R.string.MISSIONTYPE_unknown; break; default: resourceId = R.string.MISSIONTYPE_unknown; break; } return resourceId; } public static interface ZoomAnimationHandler { public void setCurrentAnimator( Animator animator ); public Animator getCurrentAnimator(); } public static void zoomImage( final ImageView thumbnailImage, final ImageView expandedImage, final View containerView, final ZoomAnimationHandler animatorHandler, final int animationDuration ) { // If there's an animation in progress, cancel it // immediately and proceed with this one. if( animatorHandler.getCurrentAnimator() != null ) { animatorHandler.getCurrentAnimator().cancel(); } // Calculate the starting and ending bounds for the zoomed-in image. // This step involves lots of math. Yay, math. final Rect startBounds = new Rect(); final Rect finalBounds = new Rect(); final Point globalOffset = new Point(); // The start bounds are the global visible rectangle of the thumbnail, // and the final bounds are the global visible rectangle of the container // view. Also set the container view's offset as the origin for the // bounds, since that's the origin for the positioning animation // properties (X, Y). thumbnailImage.getGlobalVisibleRect( startBounds ); containerView.getGlobalVisibleRect( finalBounds, globalOffset ); startBounds.offset( -globalOffset.x, -globalOffset.y ); finalBounds.offset( -globalOffset.x, -globalOffset.y ); // Adjust the start bounds to be the same aspect ratio as the final // bounds using the "center crop" technique. This prevents undesirable // stretching during the animation. Also calculate the start scaling // factor (the end scaling factor is always 1.0). float startScale; if( (float) finalBounds.width() / finalBounds.height() > (float) startBounds.width() / startBounds.height() ) { // Extend start bounds horizontally startScale = (float) startBounds.height() / finalBounds.height(); float startWidth = startScale * finalBounds.width(); float deltaWidth = (startWidth - startBounds.width()) / 2; startBounds.left -= deltaWidth; startBounds.right += deltaWidth; } else { // Extend start bounds vertically startScale = (float) startBounds.width() / finalBounds.width(); float startHeight = startScale * finalBounds.height(); float deltaHeight = (startHeight - startBounds.height()) / 2; startBounds.top -= deltaHeight; startBounds.bottom += deltaHeight; } // Hide the thumbnail and show the zoomed-in view. When the animation // begins, it will position the zoomed-in view in the place of the // thumbnail. thumbnailImage.setAlpha( 0f ); expandedImage.setVisibility( View.VISIBLE ); // Set the pivot point for SCALE_X and SCALE_Y transformations // to the top-left corner of the zoomed-in view (the default // is the center of the view). expandedImage.setPivotX( 0f ); expandedImage.setPivotY( 0f ); // Construct and run the parallel animation of the four translation and // scale properties (X, Y, SCALE_X, and SCALE_Y). AnimatorSet set = new AnimatorSet(); set .play( ObjectAnimator.ofFloat( expandedImage, View.X, startBounds.left, finalBounds.left ) ) .with( ObjectAnimator.ofFloat( expandedImage, View.Y, startBounds.top, finalBounds.top ) ) .with( ObjectAnimator.ofFloat( expandedImage, View.SCALE_X, startScale, 1f ) ) .with( ObjectAnimator.ofFloat( expandedImage, View.SCALE_Y, startScale, 1f ) ); set.setDuration( animationDuration ); set.setInterpolator( new DecelerateInterpolator() ); set.addListener( new AnimatorListenerAdapter() { @Override public void onAnimationEnd( final Animator animation ) { animatorHandler.setCurrentAnimator( null ); } @Override public void onAnimationCancel( final Animator animation ) { animatorHandler.setCurrentAnimator( null ); } } ); set.start(); animatorHandler.setCurrentAnimator( set ); // Upon clicking the zoomed-in image, it should zoom back down // to the original bounds and show the thumbnail instead of // the expanded image. final float startScaleFinal = startScale; expandedImage.setOnClickListener( new View.OnClickListener() { @Override public void onClick( final View view ) { if( animatorHandler.getCurrentAnimator() != null ) { animatorHandler.getCurrentAnimator().cancel(); } // Animate the four positioning/sizing properties in parallel, // back to their original values. AnimatorSet set = new AnimatorSet(); set.play( ObjectAnimator .ofFloat( expandedImage, View.X, startBounds.left ) ) .with( ObjectAnimator .ofFloat( expandedImage, View.Y, startBounds.top ) ) .with( ObjectAnimator .ofFloat( expandedImage, View.SCALE_X, startScaleFinal ) ) .with( ObjectAnimator .ofFloat( expandedImage, View.SCALE_Y, startScaleFinal ) ); set.setDuration( animationDuration ); set.setInterpolator( new DecelerateInterpolator() ); set.addListener( new AnimatorListenerAdapter() { @Override public void onAnimationEnd( final Animator animation ) { thumbnailImage.setAlpha( 1f ); expandedImage.setVisibility( View.GONE ); animatorHandler.setCurrentAnimator( null ); } @Override public void onAnimationCancel( final Animator animation ) { thumbnailImage.setAlpha( 1f ); expandedImage.setVisibility( View.GONE ); animatorHandler.setCurrentAnimator( null ); } } ); set.start(); animatorHandler.setCurrentAnimator( set ); } } ); } }