diff --git a/app/src/main/java/org/xbmc/kore/ui/sections/remote/RemoteFragment.java b/app/src/main/java/org/xbmc/kore/ui/sections/remote/RemoteFragment.java index a5f6c1d..7e56db5 100644 --- a/app/src/main/java/org/xbmc/kore/ui/sections/remote/RemoteFragment.java +++ b/app/src/main/java/org/xbmc/kore/ui/sections/remote/RemoteFragment.java @@ -15,24 +15,14 @@ */ package org.xbmc.kore.ui.sections.remote; -import android.annotation.TargetApi; -import android.content.res.ColorStateList; -import android.content.res.Resources; import android.content.res.TypedArray; -import android.graphics.BitmapFactory; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -56,8 +46,8 @@ import org.xbmc.kore.jsonrpc.method.Player; import org.xbmc.kore.jsonrpc.type.GlobalType; import org.xbmc.kore.jsonrpc.type.ListType; import org.xbmc.kore.jsonrpc.type.PlayerType; +import org.xbmc.kore.ui.widgets.ControlPad; import org.xbmc.kore.utils.LogUtils; -import org.xbmc.kore.utils.RepeatListener; import org.xbmc.kore.utils.UIUtils; import org.xbmc.kore.utils.Utils; @@ -74,7 +64,8 @@ import butterknife.Optional; * Remote view */ public class RemoteFragment extends Fragment - implements HostConnectionObserver.PlayerEventsObserver { + implements HostConnectionObserver.PlayerEventsObserver, + ControlPad.OnPadButtonsListener { private static final String TAG = LogUtils.makeLogTag(RemoteFragment.class); /** @@ -102,9 +93,35 @@ public class RemoteFragment extends Fragment */ private String currentNowPlayingItemType = null; + private final Packet leftButtonPacket = + new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_LEFT, false, true, + true, (short)0, (byte)0); + private final Packet rightButtonPacket = + new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_RIGHT, false, true, + true, (short)0, (byte)0); + private final Packet upButtonPacket = + new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_UP, false, true, + true, (short)0, (byte)0); + private final Packet downButtonPacket = + new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_DOWN, false, true, + true, (short)0, (byte)0); + private final Packet selectButtonPack = + new PacketBUTTON(ButtonCodes.MAP_REMOTE, ButtonCodes.REMOTE_SELECT, false, true, + true, (short)0, (byte)0); + + private final ApiMethod downButtonAction = new Input.Down(); + private final ApiMethod leftButtonAction = new Input.Left(); + private final ApiMethod upButtonAction = new Input.Up(); + private final ApiMethod rightButtonAction = new Input.Right(); + private final ApiMethod selectButtonAction = new Input.Select(); + private final ApiMethod backButtonAction = new Input.Back(); + private final ApiMethod infoButtonAction = new Input.ExecuteAction(Input.ExecuteAction.INFO); + private final ApiMethod contextButtonAction = new Input.ExecuteAction(Input.ExecuteAction.CONTEXTMENU); + private final ApiMethod osdButtonAction = new Input.ExecuteAction(Input.ExecuteAction.OSD); + @InjectView(R.id.info_panel) RelativeLayout infoPanel; @InjectView(R.id.media_panel) RelativeLayout mediaPanel; - @InjectView(R.id.remote) RelativeLayout remotePanel; + @InjectView(R.id.remote) ControlPad controlPad; @InjectView(R.id.info_title) TextView infoTitle; @InjectView(R.id.info_message) TextView infoMessage; @@ -126,16 +143,6 @@ public class RemoteFragment extends Fragment @Optional @InjectView(R.id.weather) ImageButton weatherButton; @Optional @InjectView(R.id.system) ImageButton systemButton; - @InjectView(R.id.select) ImageView selectButton; - @InjectView(R.id.left) ImageView leftButton; - @InjectView(R.id.right) ImageView rightButton; - @InjectView(R.id.up) ImageView upButton; - @InjectView(R.id.down) ImageView downButton; - @InjectView(R.id.back) ImageView backButton; - @InjectView(R.id.info) ImageView infoButton; - @InjectView(R.id.context) ImageView contextButton; - @InjectView(R.id.osd) ImageView osdButton; - @InjectView(R.id.art) ImageView thumbnail; @InjectView(R.id.title) TextView nowPlayingTitle; @InjectView(R.id.details) TextView nowPlayingDetails; @@ -145,11 +152,6 @@ public class RemoteFragment extends Fragment @InjectView(R.id.rewind) ImageButton rewindButton; @InjectView(R.id.fast_forward) ImageButton fastForwardButton; - private Animation buttonInAnim; - private Animation buttonOutAnim; - // Touch listener that provides touch feedback - private View.OnTouchListener feedbackTouchListener; - // EventServer connection private EventServerConnection eventServerConnection = null; @@ -162,27 +164,8 @@ public class RemoteFragment extends Fragment hostManager = HostManager.getInstance(getActivity()); hostConnectionObserver = hostManager.getHostConnectionObserver(); - buttonInAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.button_in); - buttonOutAnim = AnimationUtils.loadAnimation(getActivity(), R.anim.button_out); - createEventServerConnection(); - - feedbackTouchListener = new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - buttonInAnim.setFillAfter(true); - v.startAnimation(buttonInAnim); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - v.startAnimation(buttonOutAnim); - break; - } - return false; - } - }; + eventServerConnection = createEventServerConnection(); } @Override @@ -190,43 +173,9 @@ public class RemoteFragment extends Fragment ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_remote, container, false); ButterKnife.inject(this, root); - if (hostManager.getHostInfo().getUseEventServer() && - (eventServerConnection != null)) { - // Setup d-pad to use EventServer - setupEventServerButton(leftButton, ButtonCodes.REMOTE_LEFT); - setupEventServerButton(rightButton, ButtonCodes.REMOTE_RIGHT); - setupEventServerButton(upButton, ButtonCodes.REMOTE_UP); - setupEventServerButton(downButton, ButtonCodes.REMOTE_DOWN); + controlPad.setOnPadButtonsListener(this); - setupEventServerButton(selectButton, ButtonCodes.REMOTE_SELECT); - } else { - // Otherwise, use json-rpc - setupRepeatButton(leftButton, new Input.Left()); - setupRepeatButton(rightButton, new Input.Right()); - setupRepeatButton(upButton, new Input.Up()); - setupRepeatButton(downButton, new Input.Down()); - - setupDefaultButton(selectButton, new Input.Select(), null); - } - - // Other buttons - setupDefaultButton(backButton, new Input.Back(), null); - setupDefaultButton(osdButton, new Input.ExecuteAction(Input.ExecuteAction.OSD), null); - setupDefaultButton(contextButton, new Input.ExecuteAction(Input.ExecuteAction.CONTEXTMENU), null); - - // Info button, v17 uses a different window to display codec info so check version number HostInfo hostInfo = hostManager.getHostInfo(); - if (hostInfo.getKodiVersionMajor() < 17) { - setupDefaultButton(infoButton, - new Input.ExecuteAction(Input.ExecuteAction.INFO), - new Input.ExecuteAction(Input.ExecuteAction.CODECINFO)); - } else { - setupDefaultButton(infoButton, - new Input.ExecuteAction(Input.ExecuteAction.INFO), - new Input.ExecuteAction(Input.ExecuteAction.PLAYERPROCESSINFO)); - } - - adjustRemoteButtons(); TypedArray styledAttributes = getActivity().getTheme().obtainStyledAttributes(new int[] { R.attr.iconFastForward, @@ -253,53 +202,9 @@ public class RemoteFragment extends Fragment buttons[i].setVisibility(shownItems.contains(String.valueOf(i)) ? View.VISIBLE : View.GONE); } -// // Pad main content view to account for bottom system bar -// UIUtils.setPaddingForSystemBars(getActivity(), root, false, false, true); - - // No clipping -// root.setClipToPadding(false); - return root; } - @TargetApi(21) - private void adjustRemoteButtons() { - Resources.Theme theme = getActivity().getTheme(); - TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] { - R.attr.remoteButtonColorFilter, - R.attr.contentBackgroundColor}); -// R.attr.remoteBackgroundColorFilter}); - Resources resources = getResources(); - int remoteButtonsColor = styledAttributes.getColor(styledAttributes.getIndex(0), resources.getColor(R.color.white)), - remoteBackgroundColor = styledAttributes.getColor(styledAttributes.getIndex(1), resources.getColor(R.color.dark_content_background_dim_70pct)); - styledAttributes.recycle(); - - leftButton.setColorFilter(remoteButtonsColor); - rightButton.setColorFilter(remoteButtonsColor); - upButton.setColorFilter(remoteButtonsColor); - downButton.setColorFilter(remoteButtonsColor); - - selectButton.setColorFilter(remoteButtonsColor); - backButton.setColorFilter(remoteButtonsColor); - infoButton.setColorFilter(remoteButtonsColor); - osdButton.setColorFilter(remoteButtonsColor); - contextButton.setColorFilter(remoteButtonsColor); - - - // On ICS the remote background isn't shown as the tinting isn't supported - //int backgroundResourceId = R.drawable.remote_background_square_black_alpha; - int backgroundResourceId = R.drawable.remote_background_square_black; - if (Utils.isLollipopOrLater()) { - remotePanel.setBackgroundTintList(ColorStateList.valueOf(remoteBackgroundColor)); - remotePanel.setBackgroundResource(backgroundResourceId); - } else if (Utils.isJellybeanOrLater()) { - BitmapDrawable background = new BitmapDrawable(getResources(), - BitmapFactory.decodeResource(getResources(), backgroundResourceId)); - background.setColorFilter(new PorterDuffColorFilter(remoteBackgroundColor, PorterDuff.Mode.SRC_IN)); - remotePanel.setBackground(background); - } - } - @Override public void onActivityCreated (Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -311,7 +216,7 @@ public class RemoteFragment extends Fragment super.onResume(); hostConnectionObserver.registerPlayerObserver(this, true); if (eventServerConnection == null) - createEventServerConnection(); + eventServerConnection = createEventServerConnection(); } @Override @@ -324,105 +229,29 @@ public class RemoteFragment extends Fragment } } - private void createEventServerConnection() { - eventServerConnection = new EventServerConnection( + /** + * Creates a new EventServerConnection if using the event server + * is enabled in the preferences. + * @return EventServerConnection or null if usage is disabled + */ + private EventServerConnection createEventServerConnection() { + if (! hostManager.getHostInfo().getUseEventServer()) { + return null; + } + + return new EventServerConnection( hostManager.getHostInfo(), new EventServerConnection.EventServerConnectionCallback() { @Override public void OnConnectResult(boolean success) { if (!success) { - LogUtils.LOGD(TAG, "Couldnt setup EventServer, disabling it"); + LogUtils.LOGD(TAG, "Couldn\'t setup EventServer, disabling it"); eventServerConnection = null; } } }); } - private void setupRepeatButton(View button, final ApiMethod action) { - button.setOnTouchListener(new RepeatListener(UIUtils.initialButtonRepeatInterval, UIUtils.buttonRepeatInterval, - new View.OnClickListener() { - @Override - public void onClick(View v) { - action.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); - } - }, buttonInAnim, buttonOutAnim, getActivity().getApplicationContext())); - } - - private void setupDefaultButton(View button, - final ApiMethod clickAction, - final ApiMethod longClickAction) { - // Set animation - button.setOnTouchListener(feedbackTouchListener); - if (clickAction != null) { - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - UIUtils.handleVibration(getActivity()); - clickAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); - } - }); - } - if (longClickAction != null) { - button.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - UIUtils.handleVibration(getActivity()); - longClickAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); - return true; - } - }); - } - } - - private void setupEventServerButton(View button, final String action) { - final Packet packet = - new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, false, true, true, (short)0, (byte)0); - button.setOnTouchListener(new RepeatListener(UIUtils.initialButtonRepeatInterval, UIUtils.buttonRepeatInterval, - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (eventServerConnection != null) - eventServerConnection.sendPacket(packet); - } - }, buttonInAnim, buttonOutAnim, getActivity().getApplicationContext())); - } - - -// private void setupEventServerButton(View button, final String action) { -// short amount = 0; -// byte axis = 0; -// final Packet packetDown = -// new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, true, true, false, amount, axis); -// final Packet packetUp = -// new PacketBUTTON(ButtonCodes.MAP_REMOTE, action, false, false, false, amount, axis); -// -// // Set animation and packet -// button.setOnTouchListener(new View.OnTouchListener() { -// @Override -// public boolean onTouch(View v, MotionEvent event) { -// switch (event.getAction()) { -// case MotionEvent.ACTION_DOWN: -// buttonInAnim.setFillAfter(true); -// v.startAnimation(buttonInAnim); -// eventServerConnection.sendPacket(packetDown); -// break; -// case MotionEvent.ACTION_UP: -// case MotionEvent.ACTION_CANCEL: -// v.startAnimation(buttonOutAnim); -// eventServerConnection.sendPacket(packetUp); -// break; -// } -// return false; -// } -// }); -// button.setOnClickListener(new View.OnClickListener() { -// @Override -// public void onClick(View v) { -// // Nothing to do -// } -// }); -// } - /** * Default callback for methods that don't return anything */ @@ -731,11 +560,92 @@ public class RemoteFragment extends Fragment } if (showRemote) { - remotePanel.setVisibility(View.VISIBLE); + controlPad.setVisibility(View.VISIBLE); buttonBarPanel.setVisibility(View.VISIBLE); } else { - remotePanel.setVisibility(View.GONE); + controlPad.setVisibility(View.GONE); buttonBarPanel.setVisibility(View.GONE); } } + + @Override + public void leftButtonClicked() { + if (eventServerConnection != null) { + eventServerConnection.sendPacket(leftButtonPacket); + } else { + leftButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + } + + @Override + public void rightButtonClicked() { + if (eventServerConnection != null) { + eventServerConnection.sendPacket(rightButtonPacket); + } else { + rightButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + } + + @Override + public void upButtonClicked() { + if (eventServerConnection != null) { + eventServerConnection.sendPacket(upButtonPacket); + } else { + upButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + } + + @Override + public void downButtonClicked() { + if (eventServerConnection != null) { + eventServerConnection.sendPacket(downButtonPacket); + } else { + downButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + } + + @Override + public void selectButtonClicked() { + if (eventServerConnection != null) { + eventServerConnection.sendPacket(selectButtonPack); + } else { + selectButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + } + + @Override + public void backButtonClicked() { + backButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + + @Override + public void infoButtonClicked() { + infoButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + + @Override + public boolean infoButtonLongClicked() { + ApiMethod action; + HostInfo hostInfo = hostManager.getHostInfo(); + + // Info button, v17 uses a different window to display codec info so check version number + if (hostInfo.getKodiVersionMajor() < 17) { + action = new Input.ExecuteAction(Input.ExecuteAction.CODECINFO); + } else { + action = new Input.ExecuteAction(Input.ExecuteAction.PLAYERPROCESSINFO); + } + action.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + + return true; + } + + @Override + public void contextButtonClicked() { + contextButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } + + @Override + public void osdButtonClicked() { + osdButtonAction.execute(hostManager.getConnection(), defaultActionCallback, callbackHandler); + } } diff --git a/app/src/main/java/org/xbmc/kore/ui/viewgroups/SquareGridLayout.java b/app/src/main/java/org/xbmc/kore/ui/viewgroups/SquareGridLayout.java new file mode 100644 index 0000000..4d51f1f --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/ui/viewgroups/SquareGridLayout.java @@ -0,0 +1,156 @@ +/* + * Copyright 2017 Martijn Brekhof. 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.ui.viewgroups; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.RelativeLayout; + +import org.xbmc.kore.R; + +/** + * The square grid layout creates a square layout that will fit inside + * the boundaries provided by the parent layout. Note that all cells + * will have the same size. + * + * The attribute columnCount is available to specify the amount of columns + * when using SquareGridLayout in a XML layout file. + */ +public class SquareGridLayout extends ViewGroup { + + private int columnCount = 1; + private int cellSize; + + public SquareGridLayout(Context context) { + this(context, null); + } + + public SquareGridLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SquareGridLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SquareGridLayout, 0, 0); + setColumnCount(a.getInt(R.styleable.SquareGridLayout_columnCount, 1)); + a.recycle(); + fixForRelativeLayout(); + } + + public void setColumnCount(int columnCount) { + if (columnCount < 1) throw new IllegalArgumentException("Column count must be 1 or more"); + this.columnCount = columnCount; + } + + /** + * Methods overridden to make sure we pass in the correct layout parameters for the child views + */ + @Override + protected LayoutParams generateLayoutParams(LayoutParams p) { + return new MarginLayoutParams(p); + } + + @Override + public LayoutParams generateLayoutParams(AttributeSet attrs) { + return new MarginLayoutParams(getContext(), attrs); + } + + @Override + protected LayoutParams generateDefaultLayoutParams() { + return new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, + MarginLayoutParams.WRAP_CONTENT); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + + int paddingWidth = getPaddingLeft() + getPaddingRight(); + int paddingHeight = getPaddingTop() + getPaddingBottom(); + + int size; + int padding; + if ((width - paddingWidth) < (height - paddingHeight)) { + size = width; + padding = size - paddingWidth; + } else { + size = height; + padding = size - paddingHeight; + } + + for (int y = 0; y < columnCount; y++) { + for (int x = 0; x < columnCount; x++) { + View child = getChildAt(y * size + x); + if (child != null) { + measureChildWithMargins(child, + MeasureSpec.makeMeasureSpec((padding + x) / columnCount, MeasureSpec.EXACTLY), + 0, + MeasureSpec.makeMeasureSpec((padding + y) / columnCount, MeasureSpec.EXACTLY), + 0); + } + } + } + + setMeasuredDimension(size, size); + cellSize = padding; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + // top left is used to position child views + left = getPaddingLeft(); + top = getPaddingTop(); + + for (int y = 0; y < columnCount; y++) { + for (int x = 0; x < columnCount; x++) { + View child = getChildAt(y * columnCount + x); + MarginLayoutParams childLayoutParams = (MarginLayoutParams) child.getLayoutParams(); + child.layout(left + (cellSize * x) / columnCount + childLayoutParams.leftMargin, + top + (cellSize * y) / columnCount + childLayoutParams.topMargin, + left + (cellSize * (x+1)) / columnCount - childLayoutParams.rightMargin, + top + (cellSize * (y+1)) / columnCount - childLayoutParams.bottomMargin + ); + } + } + } + + /** + * When used in a relative layout we need to set the layout parameters to + * the correct size manually. Otherwise the grid layout will be stretched. + */ + private void fixForRelativeLayout() { + getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + ViewGroup.LayoutParams pParams = getLayoutParams(); + + if (pParams instanceof RelativeLayout.LayoutParams) { + int size = Math.min(getWidth(), getHeight()); + pParams.width = size; + pParams.height = size; + setLayoutParams(pParams); + } + + getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/xbmc/kore/ui/widgets/ControlPad.java b/app/src/main/java/org/xbmc/kore/ui/widgets/ControlPad.java new file mode 100644 index 0000000..413dc7d --- /dev/null +++ b/app/src/main/java/org/xbmc/kore/ui/widgets/ControlPad.java @@ -0,0 +1,240 @@ +/* + * Copyright 2017 Martijn Brekhof. 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.ui.widgets; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.BitmapFactory; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.BitmapDrawable; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.ImageView; + +import org.xbmc.kore.R; +import org.xbmc.kore.ui.viewgroups.SquareGridLayout; +import org.xbmc.kore.utils.LogUtils; +import org.xbmc.kore.utils.RepeatListener; +import org.xbmc.kore.utils.Utils; + +import butterknife.ButterKnife; +import butterknife.InjectView; + +public class ControlPad extends SquareGridLayout + implements View.OnClickListener, View.OnLongClickListener { + private static final String TAG = LogUtils.makeLogTag(ControlPad.class); + + private static final int initialButtonRepeatInterval = 400; // ms + private static final int buttonRepeatInterval = 80; // ms + + public interface OnPadButtonsListener { + void leftButtonClicked(); + void rightButtonClicked(); + void upButtonClicked(); + void downButtonClicked(); + void selectButtonClicked(); + void backButtonClicked(); + void infoButtonClicked(); + boolean infoButtonLongClicked(); + void contextButtonClicked(); + void osdButtonClicked(); + } + + private OnPadButtonsListener onPadButtonsListener; + + @InjectView(R.id.select) ImageView selectButton; + @InjectView(R.id.left) ImageView leftButton; + @InjectView(R.id.right) ImageView rightButton; + @InjectView(R.id.up) ImageView upButton; + @InjectView(R.id.down) ImageView downButton; + @InjectView(R.id.back) ImageView backButton; + @InjectView(R.id.info) ImageView infoButton; + @InjectView(R.id.context) ImageView contextButton; + @InjectView(R.id.osd) ImageView osdButton; + + public ControlPad(Context context) { + super(context); + initializeView(context); + } + + public ControlPad(Context context, AttributeSet attrs) { + super(context, attrs); + initializeView(context); + } + + public ControlPad(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initializeView(context); + } + + @Override + public void setOnClickListener(@Nullable View.OnClickListener l) { + throw new Error("Use setOnPadButtonsListener(listener)"); + } + + @Override + public void setOnLongClickListener(@Nullable OnLongClickListener l) { + throw new Error("Use setOnPadButtonsListener(listener)"); + } + + public void setOnPadButtonsListener(OnPadButtonsListener onPadButtonsListener) { + this.onPadButtonsListener = onPadButtonsListener; + } + + private void initializeView(Context context) { + LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.remote_control_pad, this); + ButterKnife.inject(this, this); + + applyTheme(); + setupListeners(context); + } + + @Override + public void onClick(View v) { + if (onPadButtonsListener == null) + return; + + switch (v.getId()) { + case R.id.select: + onPadButtonsListener.selectButtonClicked(); + break; + case R.id.left: + onPadButtonsListener.leftButtonClicked(); + break; + case R.id.right: + onPadButtonsListener.rightButtonClicked(); + break; + case R.id.up: + onPadButtonsListener.upButtonClicked(); + break; + case R.id.down: + onPadButtonsListener.downButtonClicked(); + break; + case R.id.back: + onPadButtonsListener.backButtonClicked(); + break; + case R.id.info: + onPadButtonsListener.infoButtonClicked(); + break; + case R.id.context: + onPadButtonsListener.contextButtonClicked(); + break; + case R.id.osd: + onPadButtonsListener.osdButtonClicked(); + break; + default: + LogUtils.LOGD(TAG, "Unknown button "+v.getId()+" clicked"); + } + } + + @Override + public boolean onLongClick(View v) { + if ((onPadButtonsListener != null) && (v.getId() == R.id.info)) { + return onPadButtonsListener.infoButtonLongClicked(); + } + + return false; + } + + @TargetApi(21) + private void applyTheme() { + Resources.Theme theme = getContext().getTheme(); + TypedArray styledAttributes = theme.obtainStyledAttributes(new int[] { + R.attr.remoteButtonColorFilter, + R.attr.contentBackgroundColor}); + Resources resources = getResources(); + int remoteButtonsColor = styledAttributes.getColor(styledAttributes.getIndex(0), resources.getColor(R.color.white)), + remoteBackgroundColor = styledAttributes.getColor(styledAttributes.getIndex(1), resources.getColor(R.color.dark_content_background_dim_70pct)); + styledAttributes.recycle(); + + leftButton.setColorFilter(remoteButtonsColor); + rightButton.setColorFilter(remoteButtonsColor); + upButton.setColorFilter(remoteButtonsColor); + downButton.setColorFilter(remoteButtonsColor); + + selectButton.setColorFilter(remoteButtonsColor); + backButton.setColorFilter(remoteButtonsColor); + infoButton.setColorFilter(remoteButtonsColor); + osdButton.setColorFilter(remoteButtonsColor); + contextButton.setColorFilter(remoteButtonsColor); + + + // On ICS the remote background isn't shown as the tinting isn't supported + //int backgroundResourceId = R.drawable.remote_background_square_black_alpha; + int backgroundResourceId = R.drawable.remote_background_square_black; + if (Utils.isLollipopOrLater()) { + setBackgroundTintList(ColorStateList.valueOf(remoteBackgroundColor)); + setBackgroundResource(backgroundResourceId); + } else if (Utils.isJellybeanOrLater()) { + BitmapDrawable background = new BitmapDrawable(getResources(), + BitmapFactory.decodeResource(getResources(), backgroundResourceId)); + background.setColorFilter(new PorterDuffColorFilter(remoteBackgroundColor, PorterDuff.Mode.SRC_IN)); + setBackground(background); + } + } + + private void setupListeners(Context context) { + final Animation buttonInAnim = AnimationUtils.loadAnimation(context, R.anim.button_in); + final Animation buttonOutAnim = AnimationUtils.loadAnimation(context, R.anim.button_out); + + RepeatListener repeatListener = new RepeatListener(initialButtonRepeatInterval, + buttonRepeatInterval, this, + buttonInAnim, buttonOutAnim, getContext()); + + OnTouchListener feedbackTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + buttonInAnim.setFillAfter(true); + v.startAnimation(buttonInAnim); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + v.startAnimation(buttonOutAnim); + break; + } + return false; + } + }; + + leftButton.setOnTouchListener(repeatListener); + rightButton.setOnTouchListener(repeatListener); + upButton.setOnTouchListener(repeatListener); + downButton.setOnTouchListener(repeatListener); + setupButton(selectButton, feedbackTouchListener); + setupButton(backButton, feedbackTouchListener); + setupButton(infoButton, feedbackTouchListener); + setupButton(contextButton, feedbackTouchListener); + setupButton(osdButton, feedbackTouchListener); + } + + private void setupButton(View button, OnTouchListener feedbackTouchListener) { + button.setOnTouchListener(feedbackTouchListener); + button.setOnClickListener(this); + button.setOnLongClickListener(this); + } +} diff --git a/app/src/main/res/layout-land/fragment_remote.xml b/app/src/main/res/layout-land/fragment_remote.xml index 44a0e4e..598a1be 100644 --- a/app/src/main/res/layout-land/fragment_remote.xml +++ b/app/src/main/res/layout-land/fragment_remote.xml @@ -15,218 +15,19 @@ limitations under the License. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_remote.xml b/app/src/main/res/layout/fragment_remote.xml index d54991e..3b57e7d 100644 --- a/app/src/main/res/layout/fragment_remote.xml +++ b/app/src/main/res/layout/fragment_remote.xml @@ -18,93 +18,105 @@ + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> - - - + android:layout_alignParentLeft="true"> - + - - - - - - + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/remote_content_hmargin" + android:layout_marginRight="@dimen/remote_content_hmargin" + android:paddingTop="@dimen/remote_page_indicator_height"> - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + android:layout_below="@id/top_panel"> + + diff --git a/app/src/main/res/layout/remote_control_pad.xml b/app/src/main/res/layout/remote_control_pad.xml new file mode 100644 index 0000000..243e60c --- /dev/null +++ b/app/src/main/res/layout/remote_control_pad.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/remote_info_panel.xml b/app/src/main/res/layout/remote_info_panel.xml index fc0e867..c43ef76 100644 --- a/app/src/main/res/layout/remote_info_panel.xml +++ b/app/src/main/res/layout/remote_info_panel.xml @@ -20,7 +20,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/info_panel" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="@dimen/remote_poster_height" android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:paddingTop="@dimen/remote_page_indicator_height" diff --git a/app/src/main/res/values-land/styles.xml b/app/src/main/res/values-land/styles.xml new file mode 100644 index 0000000..a3f8f2a --- /dev/null +++ b/app/src/main/res/values-land/styles.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-sw320dp/dimens.xml b/app/src/main/res/values-sw320dp/dimens.xml deleted file mode 100644 index d9a03f4..0000000 --- a/app/src/main/res/values-sw320dp/dimens.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 58dp - 174dp - \ No newline at end of file diff --git a/app/src/main/res/values-sw360dp-land/styles.xml b/app/src/main/res/values-sw360dp-land/styles.xml new file mode 100644 index 0000000..f5543b7 --- /dev/null +++ b/app/src/main/res/values-sw360dp-land/styles.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-sw360dp/styles.xml b/app/src/main/res/values-sw360dp/styles.xml new file mode 100644 index 0000000..be53382 --- /dev/null +++ b/app/src/main/res/values-sw360dp/styles.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/attr.xml b/app/src/main/res/values/attr.xml index 8dd3f65..8f45760 100644 --- a/app/src/main/res/values/attr.xml +++ b/app/src/main/res/values/attr.xml @@ -38,20 +38,20 @@ - + - - + + - - - - + + + + @@ -113,4 +113,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index c0c877d..50cf31d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -60,6 +60,7 @@ 98dp 140dp + 164dp 112dp 160dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index c61338a..b2b08f8 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -331,5 +331,16 @@ + + + +