/* * Copyright 2015 Synced Synapse. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xbmc.kore.utils; import android.animation.Animator; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.content.res.TypedArray; import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.ViewAnimationUtils; import android.view.WindowManager; import android.widget.GridLayout; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import org.xbmc.kore.R; import org.xbmc.kore.Settings; import org.xbmc.kore.host.HostInfo; import org.xbmc.kore.host.HostManager; import org.xbmc.kore.jsonrpc.type.GlobalType; import org.xbmc.kore.jsonrpc.type.VideoType; import org.xbmc.kore.ui.RemoteActivity; import java.util.ArrayList; import java.util.List; /** * General UI Utils */ public class UIUtils { public static final float IMAGE_RESIZE_FACTOR = 1.0f; public static final int initialButtonRepeatInterval = 400; // ms public static final int buttonRepeatInterval = 80; // ms /** * Formats time based on seconds * @param seconds seconds * @return Formated string */ public static String formatTime(int seconds) { return formatTime(seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60); } /** * Formats time */ public static String formatTime(GlobalType.Time time) { return formatTime(time.hours, time.minutes, time.seconds); } /** * Formats time */ public static String formatTime(int hours, int minutes, int seconds) { if (hours > 0) { return String.format("%d:%02d:%02d", hours, minutes, seconds); } else { return String.format("%1d:%02d",minutes, seconds); } } /** * Loads an image into an imageview * @param hostManager Hostmanager connected to the host * @param imageUrl XBMC url of the image to load * @param imageView Image view to load into * @param imageWidth Width of the image, for caching purposes * @param imageHeight Height of the image, for caching purposes */ public static void loadImageIntoImageview(HostManager hostManager, String imageUrl, ImageView imageView, int imageWidth, int imageHeight) { // if (TextUtils.isEmpty(imageUrl)) { // imageView.setImageResource(R.drawable.delete_ic_action_picture); // return; // } if ((imageWidth) > 0 && (imageHeight > 0)) { hostManager.getPicasso() .load(hostManager.getHostInfo().getImageUrl(imageUrl)) .resize(imageWidth, imageHeight) .centerCrop() .into(imageView); } else { hostManager.getPicasso() .load(hostManager.getHostInfo().getImageUrl(imageUrl)) .fit() .centerCrop() .into(imageView); } } private static TypedArray characterAvatarColors = null; private static int avatarColorsIdx = 0; // private static Random randomGenerator = new Random(); /** * Loads an image into an imageview, presenting an alternate charater avatar if empty * @param hostManager Hostmanager connected to the host * @param imageUrl XBMC url of the image to load * @param stringAvatar Character avatar too present if image is null * @param imageView Image view to load into * @param imageWidth Width of the image, for caching purposes * @param imageHeight Height of the image, for caching purposes */ public static void loadImageWithCharacterAvatar( Context context, HostManager hostManager, String imageUrl, String stringAvatar, ImageView imageView, int imageWidth, int imageHeight) { CharacterDrawable avatarDrawable = getCharacterAvatar(context, stringAvatar); if (TextUtils.isEmpty(imageUrl)) { imageView.setImageDrawable(avatarDrawable); return; } if ((imageWidth) > 0 && (imageHeight > 0)) { hostManager.getPicasso() .load(hostManager.getHostInfo().getImageUrl(imageUrl)) .placeholder(avatarDrawable) .resize(imageWidth, imageHeight) .centerCrop() .into(imageView); } else { hostManager.getPicasso() .load(hostManager.getHostInfo().getImageUrl(imageUrl)) .fit() .centerCrop() .into(imageView); } } /** * Returns a CharacterDrawable that is suitable to use as an avatar * @param context Context * @param str String to use to create the avatar * @return Character avatar to use in a image view */ public static CharacterDrawable getCharacterAvatar(Context context, String str) { // Load character avatar if (characterAvatarColors == null) { characterAvatarColors = context .getResources() .obtainTypedArray(R.array.character_avatar_colors); } char charAvatar = TextUtils.isEmpty(str) ? ' ' : str.charAt(0); avatarColorsIdx = TextUtils.isEmpty(str) ? 0 : Math.max(Character.getNumericValue(str.charAt(0)) + Character.getNumericValue(str.charAt(str.length() - 1)) + str.length(), 0) % characterAvatarColors.length(); int color = characterAvatarColors.getColor(avatarColorsIdx, 0xff000000); // avatarColorsIdx = randomGenerator.nextInt(characterAvatarColors.length()); return new CharacterDrawable(charAvatar, color); } /** * Sets play/pause button icon on a ImageView, based on speed * @param context Activity * @param view ImageView/ImageButton * @param speed Current player speed */ public static void setPlayPauseButtonIcon(Context context, ImageView view, int speed) { int resAttrId = (speed == 1) ? R.attr.iconPause : R.attr.iconPlay; int defaultResourceId = (speed == 1) ? R.drawable.ic_pause_white_24dp : R.drawable.ic_play_arrow_white_24dp; TypedArray styledAttributes = context.obtainStyledAttributes(new int[]{resAttrId}); view.setImageResource(styledAttributes.getResourceId(0, defaultResourceId)); styledAttributes.recycle(); } /** * Fills the standard cast info list, consisting of a {@link android.widget.GridLayout} * with actor images and a Textview with the name and the role of the additional cast. * The number of actor presented on the {@link android.widget.GridLayout} is controlled * through the global setting, and only actors with images are presented. * The rest are presented in the additionalCastView TextView * * @param context Activity * @param castList Cast list * @param castListView GridLayout on which too show actors that have images * @param additionalCastTitleView View with additional cast title * @param additionalCastView Additional cast */ public static void setupCastInfo(final Context context, List castList, GridLayout castListView, TextView additionalCastTitleView, TextView additionalCastView) { HostManager hostManager = HostManager.getInstance(context); Resources resources = context.getResources(); DisplayMetrics displayMetrics = new DisplayMetrics(); WindowManager windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); windowManager.getDefaultDisplay().getMetrics(displayMetrics); View.OnClickListener castListClickListener = new View.OnClickListener() { @Override public void onClick(View v) { Utils.openImdbForPerson(context, (String)v.getTag()); } }; castListView.removeAllViews(); int numColumns = castListView.getColumnCount(); int layoutMarginPx = 2 * resources.getDimensionPixelSize(R.dimen.remote_content_hmargin); int imageMarginPx = 2 * resources.getDimensionPixelSize(R.dimen.image_grid_margin); int imageWidth = (displayMetrics.widthPixels - layoutMarginPx - numColumns * imageMarginPx) / numColumns; int imageHeight = (int)(imageWidth * 1.2); List noPicturesCastList = new ArrayList(); int maxCastPictures = Settings.DEFAULT_MAX_CAST_PICTURES; int currentPictureNumber = 0; for (int i = 0; i < castList.size(); i++) { VideoType.Cast actor = castList.get(i); if (((maxCastPictures == -1) || (currentPictureNumber < maxCastPictures)) && (actor.thumbnail != null)) { // Present the picture currentPictureNumber++; View castView = LayoutInflater.from(context).inflate(R.layout.grid_item_cast, castListView, false); ImageView castPicture = (ImageView) castView.findViewById(R.id.picture); TextView castName = (TextView) castView.findViewById(R.id.name); TextView castRole = (TextView) castView.findViewById(R.id.role); castView.getLayoutParams().width = imageWidth; castView.getLayoutParams().height = (int) (imageHeight * 1.2); castView.setTag(actor.name); castView.setOnClickListener(castListClickListener); castName.setText(actor.name); castRole.setText(actor.role); UIUtils.loadImageWithCharacterAvatar(context, hostManager, actor.thumbnail, actor.name, castPicture, imageWidth, imageHeight); castListView.addView(castView); } else { noPicturesCastList.add(actor); } } // Additional cast if (noPicturesCastList.size() > 0) { additionalCastTitleView.setVisibility(View.VISIBLE); additionalCastView.setVisibility(View.VISIBLE); StringBuilder castListText = new StringBuilder(); boolean first = true; for (VideoType.Cast cast : noPicturesCastList) { if (!first) castListText.append("\n"); first = false; if (!TextUtils.isEmpty(cast.role)) { castListText.append(String.format(context.getString(R.string.cast_list_text), cast.name, cast.role)); } else { castListText.append(cast.name); } } additionalCastView.setText(castListText); } else { additionalCastTitleView.setVisibility(View.GONE); additionalCastView.setVisibility(View.GONE); } } /** * Simple wrapper to {@link NetUtils#sendWolMagicPacket(String, String, int)} * that sends a WoL magic packet in a new thread * * @param context Context * @param hostInfo Host to send WoL */ public static void sendWolAsync(Context context, final HostInfo hostInfo) { if (hostInfo == null) return; // Send WoL magic packet on a new thread new Thread(new Runnable() { @Override public void run() { NetUtils.sendWolMagicPacket(hostInfo.getMacAddress(), hostInfo.getAddress(), hostInfo.getWolPort()); } }).start(); Toast.makeText(context, R.string.wol_sent, Toast.LENGTH_SHORT).show(); } // /** // * Sets the default {@link android.support.v4.widget.SwipeRefreshLayout} color scheme // * @param swipeRefreshLayout layout // */ // public static void setSwipeRefreshLayoutColorScheme(SwipeRefreshLayout swipeRefreshLayout) { // Resources.Theme theme = swipeRefreshLayout.getContext().getTheme(); // TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] { // R.attr.refreshColor1, // R.attr.refreshColor2, // R.attr.refreshColor3, // R.attr.refreshColor4, // }); // // swipeRefreshLayout.setColorScheme(styledAttributes.getResourceId(0, android.R.color.holo_blue_dark), // styledAttributes.getResourceId(1, android.R.color.holo_purple), // styledAttributes.getResourceId(2, android.R.color.holo_red_dark), // styledAttributes.getResourceId(3, android.R.color.holo_green_dark)); // styledAttributes.recycle(); // } // /** // * Sets a views padding top/bottom to account for the system bars // * (Top status and action bar, bottom nav bar, right nav bar if in ladscape mode) // * // * @param context Context // * @param view View to pad // * @param padTop Whether to set views paddingTop // * @param padRight Whether to set views paddingRight (for nav bar in landscape mode) // * @param padBottom Whether to set views paddingBottom // */ // public static void setPaddingForSystemBars(Activity context, View view, // boolean padTop, boolean padRight, boolean padBottom) { // //if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; // SystemBarTintManager tintManager = new SystemBarTintManager(context); // SystemBarTintManager.SystemBarConfig config = tintManager.getConfig(); // // view.setPadding(view.getPaddingLeft(), // padTop ? config.getPixelInsetTop(true) : view.getPaddingTop(), // padRight? config.getPixelInsetRight() : view.getPaddingRight(), // padBottom ? config.getPixelInsetBottom() : view.getPaddingBottom()); // } /** * Returns a theme resource Id given the value stored in Shared Preferences * @param prefThemeValue Shared Preferences value for the theme * @return Android resource id of the theme */ public static int getThemeResourceId(String prefThemeValue) { switch (Integer.valueOf(prefThemeValue)) { case 0: return R.style.NightTheme; case 1: return R.style.DayTheme; case 2: return R.style.MistTheme; case 3: return R.style.SolarizedLightTheme; case 4: return R.style.SolarizedDarkTheme; default: return R.style.NightTheme; } } /** * Launches the remote activity, performing a circular reveal animation if * Lollipop or later * * @param context Context * @param centerX Center X of the animation * @param centerY Center Y of the animation * @param exitTransitionView View to reveal. Should occupy the whole screen and * be invisible before calling this */ @TargetApi(21) public static void switchToRemoteWithAnimation(final Context context, int centerX, int centerY, final View exitTransitionView) { final Intent launchIntent = new Intent(context, RemoteActivity.class); if (Utils.isLollipopOrLater()) { // Show the animation int endRadius = Math.max(exitTransitionView.getHeight(), exitTransitionView.getWidth()); Animator exitAnim = ViewAnimationUtils.createCircularReveal(exitTransitionView, centerX, centerY, 0, endRadius); exitAnim.setDuration(200); exitAnim.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) {} @Override public void onAnimationEnd(Animator animation) { // Launch remote activity context.startActivity(launchIntent); } @Override public void onAnimationCancel(Animator animation) {} @Override public void onAnimationRepeat(Animator animation) {} }); exitTransitionView.setVisibility(View.VISIBLE); exitAnim.start(); } else { // No animation show, just launch the remote context.startActivity(launchIntent); } } }