mirror of https://github.com/kurisufriend/Clover
commit
0be0e8a68a
@ -0,0 +1,36 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.core.cache; |
||||
|
||||
import android.content.Context; |
||||
import android.net.Uri; |
||||
import android.support.v4.content.FileProvider; |
||||
|
||||
import org.floens.chan.utils.AndroidUtils; |
||||
|
||||
import java.io.File; |
||||
|
||||
public class FileCacheProvider { |
||||
// Same as the one defined in the manifest.
|
||||
private static final String AUTHORITY = "org.floens.chan.fileprovider"; |
||||
|
||||
public static Uri getUriForFile(File file) { |
||||
Context applicationContext = AndroidUtils.getAppContext(); |
||||
return FileProvider.getUriForFile(applicationContext, AUTHORITY, file); |
||||
} |
||||
} |
@ -0,0 +1,530 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.toolbar; |
||||
|
||||
import android.animation.Animator; |
||||
import android.animation.AnimatorListenerAdapter; |
||||
import android.animation.AnimatorSet; |
||||
import android.animation.ObjectAnimator; |
||||
import android.animation.ValueAnimator; |
||||
import android.annotation.SuppressLint; |
||||
import android.content.Context; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.text.TextUtils; |
||||
import android.util.AttributeSet; |
||||
import android.view.Gravity; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.view.animation.DecelerateInterpolator; |
||||
import android.view.animation.LinearInterpolator; |
||||
import android.widget.FrameLayout; |
||||
import android.widget.LinearLayout; |
||||
import android.widget.TextView; |
||||
|
||||
import org.floens.chan.R; |
||||
import org.floens.chan.ui.drawable.ArrowMenuDrawable; |
||||
import org.floens.chan.ui.drawable.DropdownArrowDrawable; |
||||
import org.floens.chan.ui.layout.SearchLayout; |
||||
import org.floens.chan.utils.AndroidUtils; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import static org.floens.chan.utils.AndroidUtils.dp; |
||||
import static org.floens.chan.utils.AndroidUtils.getAttrColor; |
||||
import static org.floens.chan.utils.AndroidUtils.removeFromParentView; |
||||
|
||||
/** |
||||
* The container for the views created by the toolbar for the navigation items. |
||||
* <p> |
||||
* It will strictly only transition between two views. If a new view is set |
||||
* and a transition is in progress, it is stopped before adding the new view. |
||||
* <p> |
||||
* For normal animations the previousView is the view that is animated away from, and the |
||||
* currentView is the view where is animated to. The previousView is removed and cleared if the |
||||
* animation finished. |
||||
* <p> |
||||
* Transitions are user-controlled animations that can be cancelled of finished. For that the |
||||
* currentView describes the view that was originally there, and the transitionView is the view |
||||
* what is possibly transitioned to. |
||||
* <p> |
||||
* This is also the class that is responsible for the orientation and animation of the arrow-menu |
||||
* drawable. |
||||
*/ |
||||
public class ToolbarContainer extends FrameLayout { |
||||
private Callback callback; |
||||
|
||||
private ArrowMenuDrawable arrowMenu; |
||||
|
||||
@Nullable |
||||
private ItemView previousView; |
||||
|
||||
@Nullable |
||||
private ItemView currentView; |
||||
|
||||
@Nullable |
||||
private ItemView transitionView; |
||||
|
||||
@Nullable |
||||
private ToolbarPresenter.TransitionAnimationStyle transitionAnimationStyle; |
||||
|
||||
private Map<View, Animator> animatorSet = new HashMap<>(); |
||||
|
||||
public ToolbarContainer(Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
public ToolbarContainer(Context context, AttributeSet attrs) { |
||||
this(context, attrs, 0); |
||||
} |
||||
|
||||
public ToolbarContainer(Context context, AttributeSet attrs, int defStyle) { |
||||
super(context, attrs, defStyle); |
||||
} |
||||
|
||||
public void setCallback(Callback callback) { |
||||
this.callback = callback; |
||||
} |
||||
|
||||
public void setArrowMenu(ArrowMenuDrawable arrowMenu) { |
||||
this.arrowMenu = arrowMenu; |
||||
} |
||||
|
||||
public void set(NavigationItem item, ToolbarPresenter.AnimationStyle animation) { |
||||
if (transitionView != null) { |
||||
throw new IllegalStateException("Currently in transition mode"); |
||||
} |
||||
|
||||
endAnimations(); |
||||
|
||||
ItemView itemView = new ItemView(item); |
||||
|
||||
previousView = currentView; |
||||
currentView = itemView; |
||||
|
||||
addView(itemView.view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); |
||||
|
||||
if (getChildCount() > 2) { |
||||
throw new IllegalArgumentException("More than 2 child views attached"); |
||||
} |
||||
|
||||
// Can't run the animation if there is no previous view
|
||||
// Otherwise just show it without an animation.
|
||||
if (animation != ToolbarPresenter.AnimationStyle.NONE && previousView != null) { |
||||
setAnimation(itemView, previousView, animation); |
||||
} else { |
||||
if (previousView != null) { |
||||
removeItem(previousView); |
||||
previousView = null; |
||||
} |
||||
} |
||||
|
||||
if (animation == ToolbarPresenter.AnimationStyle.NONE) { |
||||
setArrowProgress(1f, !currentView.item.hasArrow()); |
||||
} |
||||
|
||||
itemView.attach(); |
||||
} |
||||
|
||||
public void update(NavigationItem item, boolean current) { |
||||
// TODO
|
||||
View view = viewForItem(item); |
||||
if (view != null) { |
||||
TextView titleView = view.findViewById(R.id.title); |
||||
if (titleView != null) { |
||||
titleView.setText(item.title); |
||||
} |
||||
|
||||
if (!TextUtils.isEmpty(item.subtitle)) { |
||||
TextView subtitleView = view.findViewById(R.id.subtitle); |
||||
if (subtitleView != null) { |
||||
subtitleView.setText(item.subtitle); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public View viewForItem(NavigationItem item) { |
||||
ItemView itemView = itemViewForItem(item); |
||||
if (itemView == null) { |
||||
return null; |
||||
} |
||||
return itemView.view; |
||||
} |
||||
|
||||
private ItemView itemViewForItem(NavigationItem item) { |
||||
if (currentView != null && item == currentView.item) { |
||||
return currentView; |
||||
} else if (previousView != null && item == previousView.item) { |
||||
return previousView; |
||||
} else if (transitionView != null && item == transitionView.item) { |
||||
return transitionView; |
||||
} else { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
public boolean isTransitioning() { |
||||
return transitionView != null || previousView != null; |
||||
} |
||||
|
||||
public void startTransition( |
||||
NavigationItem item, ToolbarPresenter.TransitionAnimationStyle style) { |
||||
if (transitionView != null) { |
||||
throw new IllegalStateException("Already in transition mode"); |
||||
} |
||||
|
||||
endAnimations(); |
||||
|
||||
ItemView itemView = new ItemView(item); |
||||
|
||||
transitionView = itemView; |
||||
transitionAnimationStyle = style; |
||||
addView(itemView.view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); |
||||
|
||||
if (getChildCount() > 2) { |
||||
throw new IllegalArgumentException("More than 2 child views attached"); |
||||
} |
||||
|
||||
itemView.attach(); |
||||
} |
||||
|
||||
public void stopTransition(boolean didComplete) { |
||||
if (transitionView == null) { |
||||
throw new IllegalStateException("Not in transition mode"); |
||||
} |
||||
|
||||
if (didComplete) { |
||||
removeItem(currentView); |
||||
currentView = transitionView; |
||||
transitionView = null; |
||||
} else { |
||||
removeItem(transitionView); |
||||
transitionView = null; |
||||
} |
||||
|
||||
if (getChildCount() != 1) { |
||||
throw new IllegalStateException("Not 1 view attached"); |
||||
} |
||||
} |
||||
|
||||
public void setTransitionProgress(float progress) { |
||||
if (transitionView == null) { |
||||
throw new IllegalStateException("Not in transition mode"); |
||||
} |
||||
|
||||
transitionProgressAnimation(progress, transitionAnimationStyle); |
||||
} |
||||
|
||||
private void endAnimations() { |
||||
if (previousView != null) { |
||||
endAnimation(previousView.view); |
||||
if (previousView != null) { |
||||
throw new IllegalStateException("Animation end did not remove view"); |
||||
} |
||||
} |
||||
|
||||
if (currentView != null) { |
||||
endAnimation(currentView.view); |
||||
} |
||||
} |
||||
|
||||
private void endAnimation(View view) { |
||||
Animator a = animatorSet.remove(view); |
||||
if (a != null) { |
||||
a.end(); |
||||
} |
||||
} |
||||
|
||||
private void setAnimation(ItemView view, ItemView previousView, |
||||
ToolbarPresenter.AnimationStyle animationStyle) { |
||||
if (animationStyle == ToolbarPresenter.AnimationStyle.PUSH || |
||||
animationStyle == ToolbarPresenter.AnimationStyle.POP) { |
||||
final boolean pushing = animationStyle == ToolbarPresenter.AnimationStyle.PUSH; |
||||
|
||||
// Previous animation
|
||||
ValueAnimator previousAnimation = getShortAnimator(); |
||||
previousAnimation.addUpdateListener(a -> { |
||||
float value = (float) a.getAnimatedValue(); |
||||
setPreviousAnimationProgress(previousView.view, pushing, value); |
||||
}); |
||||
previousAnimation.addListener(new AnimatorListenerAdapter() { |
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
animatorSet.remove(previousView.view); |
||||
removeItem(previousView); |
||||
ToolbarContainer.this.previousView = null; |
||||
} |
||||
}); |
||||
if (!pushing) previousAnimation.setStartDelay(100); |
||||
animatorSet.put(previousView.view, previousAnimation); |
||||
|
||||
post(previousAnimation::start); |
||||
|
||||
// Current animation + arrow
|
||||
view.view.setAlpha(0f); |
||||
ValueAnimator animation = getShortAnimator(); |
||||
animation.addUpdateListener(a -> { |
||||
float value = (float) a.getAnimatedValue(); |
||||
setAnimationProgress(view.view, pushing, value); |
||||
|
||||
if (previousView.item.hasArrow() != currentView.item.hasArrow()) { |
||||
setArrowProgress(value, !currentView.item.hasArrow()); |
||||
} |
||||
}); |
||||
animation.addListener(new AnimatorListenerAdapter() { |
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
animatorSet.remove(view.view); |
||||
} |
||||
}); |
||||
if (!pushing) animation.setStartDelay(100); |
||||
animatorSet.put(view.view, animation); |
||||
|
||||
post(animation::start); |
||||
} else if (animationStyle == ToolbarPresenter.AnimationStyle.FADE) { |
||||
// Previous animation
|
||||
ValueAnimator previousAnimation = |
||||
ObjectAnimator.ofFloat(previousView.view, View.ALPHA, 1f, 0f); |
||||
previousAnimation.setDuration(300); |
||||
previousAnimation.setInterpolator(new LinearInterpolator()); |
||||
previousAnimation.addListener(new AnimatorListenerAdapter() { |
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
animatorSet.remove(previousView.view); |
||||
removeItem(previousView); |
||||
ToolbarContainer.this.previousView = null; |
||||
} |
||||
}); |
||||
animatorSet.put(previousView.view, previousAnimation); |
||||
|
||||
post(previousAnimation::start); |
||||
|
||||
// Current animation + arrow
|
||||
view.view.setAlpha(0f); |
||||
ValueAnimator animation = ObjectAnimator.ofFloat(view.view, View.ALPHA, 0f, 1f); |
||||
animation.setDuration(300); |
||||
animation.setInterpolator(new LinearInterpolator()); |
||||
animation.addListener(new AnimatorListenerAdapter() { |
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
animatorSet.remove(view.view); |
||||
} |
||||
}); |
||||
// A different animator for the arrow because that one needs the deceleration
|
||||
// interpolator.
|
||||
ValueAnimator arrow = ValueAnimator.ofFloat(0f, 1f); |
||||
arrow.setDuration(300); |
||||
arrow.setInterpolator(new DecelerateInterpolator(2f)); |
||||
arrow.addUpdateListener(a -> { |
||||
float value = (float) a.getAnimatedValue(); |
||||
if (previousView.item.hasArrow() != currentView.item.hasArrow()) { |
||||
setArrowProgress(value, !currentView.item.hasArrow()); |
||||
} |
||||
}); |
||||
|
||||
AnimatorSet animationAndArrow = new AnimatorSet(); |
||||
animationAndArrow.playTogether(animation, arrow); |
||||
animationAndArrow.addListener(new AnimatorListenerAdapter() { |
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
animatorSet.remove(view.view); |
||||
} |
||||
}); |
||||
|
||||
animatorSet.put(view.view, animationAndArrow); |
||||
|
||||
post(animationAndArrow::start); |
||||
} |
||||
} |
||||
|
||||
private void setPreviousAnimationProgress(View view, boolean pushing, float progress) { |
||||
final int offset = dp(16); |
||||
view.setTranslationY((pushing ? -offset : offset) * progress); |
||||
view.setAlpha(1f - progress); |
||||
} |
||||
|
||||
private void setAnimationProgress(View view, boolean pushing, float progress) { |
||||
final int offset = dp(16); |
||||
view.setTranslationY((pushing ? offset : -offset) * (1f - progress)); |
||||
view.setAlpha(progress); |
||||
} |
||||
|
||||
private void setArrowProgress(float progress, boolean reverse) { |
||||
if (reverse) { |
||||
progress = 1f - progress; |
||||
} |
||||
progress = Math.max(0f, Math.min(1f, progress)); |
||||
|
||||
arrowMenu.setProgress(progress); |
||||
} |
||||
|
||||
private void transitionProgressAnimation( |
||||
float progress, ToolbarPresenter.TransitionAnimationStyle style) { |
||||
progress = Math.max(0f, Math.min(1f, progress)); |
||||
|
||||
final int offset = dp(16); |
||||
|
||||
boolean pushing = style == ToolbarPresenter.TransitionAnimationStyle.PUSH; |
||||
|
||||
transitionView.view.setTranslationY((pushing ? offset : -offset) * (1f - progress)); |
||||
transitionView.view.setAlpha(progress); |
||||
|
||||
currentView.view.setTranslationY((pushing ? -offset : offset) * progress); |
||||
currentView.view.setAlpha(1f - progress); |
||||
|
||||
if (transitionView.item.hasArrow() != currentView.item.hasArrow()) { |
||||
setArrowProgress(progress, !transitionView.item.hasArrow()); |
||||
} |
||||
} |
||||
|
||||
private ValueAnimator getShortAnimator() { |
||||
final ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f); |
||||
animator.setDuration(300); |
||||
animator.setInterpolator(new DecelerateInterpolator(2f)); |
||||
return animator; |
||||
} |
||||
|
||||
private void removeItem(ItemView item) { |
||||
item.remove(); |
||||
removeView(item.view); |
||||
} |
||||
|
||||
private class ItemView { |
||||
final View view; |
||||
final NavigationItem item; |
||||
|
||||
@Nullable |
||||
private ToolbarMenuView menuView; |
||||
|
||||
public ItemView(NavigationItem item) { |
||||
this.view = createNavigationItemView(item); |
||||
this.item = item; |
||||
} |
||||
|
||||
public void attach() { |
||||
if (item.menu != null) { |
||||
menuView.attach(item.menu); |
||||
} |
||||
} |
||||
|
||||
public void remove() { |
||||
if (menuView != null) { |
||||
menuView.detach(); |
||||
} |
||||
} |
||||
|
||||
private LinearLayout createNavigationItemView(final NavigationItem item) { |
||||
if (item.search) { |
||||
return createSearchLayout(item); |
||||
} else { |
||||
return createNavigationLayout(item); |
||||
} |
||||
} |
||||
|
||||
@NonNull |
||||
private LinearLayout createNavigationLayout(NavigationItem item) { |
||||
@SuppressLint("InflateParams") |
||||
LinearLayout menu = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.toolbar_menu, null); |
||||
menu.setGravity(Gravity.CENTER_VERTICAL); |
||||
|
||||
FrameLayout titleContainer = menu.findViewById(R.id.title_container); |
||||
|
||||
// Title
|
||||
final TextView titleView = menu.findViewById(R.id.title); |
||||
titleView.setTypeface(AndroidUtils.ROBOTO_MEDIUM); |
||||
titleView.setText(item.title); |
||||
titleView.setTextColor(0xffffffff); |
||||
|
||||
// Middle title with arrow and callback
|
||||
if (item.middleMenu != null) { |
||||
int arrowColor = getAttrColor(getContext(), R.attr.dropdown_light_color); |
||||
int arrowPressedColor = getAttrColor( |
||||
getContext(), R.attr.dropdown_light_pressed_color); |
||||
Drawable drawable = new DropdownArrowDrawable( |
||||
dp(12), dp(12), true, arrowColor, arrowPressedColor); |
||||
titleView.setCompoundDrawablesWithIntrinsicBounds( |
||||
null, null, drawable, null); |
||||
titleView.setOnClickListener(v -> item.middleMenu.show(titleView)); |
||||
} |
||||
|
||||
// Possible subtitle.
|
||||
TextView subtitleView = menu.findViewById(R.id.subtitle); |
||||
if (!TextUtils.isEmpty(item.subtitle)) { |
||||
ViewGroup.LayoutParams titleParams = titleView.getLayoutParams(); |
||||
titleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; |
||||
titleView.setLayoutParams(titleParams); |
||||
subtitleView.setText(item.subtitle); |
||||
subtitleView.setTextColor(0xffffffff); |
||||
titleView.setPadding(titleView.getPaddingLeft(), dp(5f), |
||||
titleView.getPaddingRight(), titleView.getPaddingBottom()); |
||||
} else { |
||||
titleContainer.removeView(subtitleView); |
||||
} |
||||
|
||||
// Possible view shown at the right side.
|
||||
if (item.rightView != null) { |
||||
removeFromParentView(item.rightView); |
||||
item.rightView.setPadding(0, 0, dp(16), 0); |
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( |
||||
LinearLayout.LayoutParams.WRAP_CONTENT, |
||||
LinearLayout.LayoutParams.MATCH_PARENT); |
||||
menu.addView(item.rightView, lp); |
||||
} |
||||
|
||||
// Possible menu with items.
|
||||
if (item.menu != null) { |
||||
menuView = new ToolbarMenuView(getContext()); |
||||
|
||||
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( |
||||
LinearLayout.LayoutParams.WRAP_CONTENT, |
||||
LinearLayout.LayoutParams.MATCH_PARENT); |
||||
menu.addView(this.menuView, lp); |
||||
} |
||||
|
||||
return menu; |
||||
} |
||||
|
||||
@NonNull |
||||
private LinearLayout createSearchLayout(NavigationItem item) { |
||||
SearchLayout searchLayout = new SearchLayout(getContext()); |
||||
|
||||
searchLayout.setCallback(input -> { |
||||
callback.searchInput(input); |
||||
}); |
||||
|
||||
if (item.searchText != null) { |
||||
searchLayout.setText(item.searchText); |
||||
} |
||||
|
||||
searchLayout.setHint(callback.searchHint(item)); |
||||
searchLayout.setPadding(dp(16), searchLayout.getPaddingTop(), searchLayout.getPaddingRight(), searchLayout.getPaddingBottom()); |
||||
|
||||
return searchLayout; |
||||
} |
||||
} |
||||
|
||||
public interface Callback { |
||||
void searchInput(String input); |
||||
|
||||
String searchHint(NavigationItem item); |
||||
} |
||||
} |
@ -0,0 +1,64 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.toolbar; |
||||
|
||||
import static org.floens.chan.utils.AndroidUtils.getString; |
||||
|
||||
/** |
||||
* An item for a submenu of a ToolbarMenuItem. Most common as subitem for the overflow button. |
||||
* Add with NavigationItem MenuBuilder. |
||||
*/ |
||||
public class ToolbarMenuSubItem { |
||||
public int id; |
||||
public String text; |
||||
public boolean enabled = true; |
||||
public ClickCallback clicked; |
||||
|
||||
public ToolbarMenuSubItem(int id, int text, ClickCallback clicked) { |
||||
this(id, getString(text), clicked); |
||||
} |
||||
|
||||
public ToolbarMenuSubItem(int id, int text, boolean enabled) { |
||||
this(id, getString(text), enabled, null); |
||||
} |
||||
|
||||
public ToolbarMenuSubItem(int id, String text, ClickCallback clicked) { |
||||
this(id, text, true, clicked); |
||||
} |
||||
|
||||
public ToolbarMenuSubItem(String text, ClickCallback clicked) { |
||||
this(-1, text, true, clicked); |
||||
} |
||||
|
||||
public ToolbarMenuSubItem(int id, String text, boolean enabled, ClickCallback clicked) { |
||||
this.id = id; |
||||
this.text = text; |
||||
this.enabled = enabled; |
||||
this.clicked = clicked; |
||||
} |
||||
|
||||
public void performClick() { |
||||
if (clicked != null) { |
||||
clicked.clicked(this); |
||||
} |
||||
} |
||||
|
||||
public interface ClickCallback { |
||||
void clicked(ToolbarMenuSubItem subItem); |
||||
} |
||||
} |
@ -0,0 +1,100 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.toolbar; |
||||
|
||||
import android.content.Context; |
||||
import android.util.AttributeSet; |
||||
import android.view.Gravity; |
||||
import android.view.View; |
||||
import android.widget.ImageView; |
||||
import android.widget.LinearLayout; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import static org.floens.chan.utils.AndroidUtils.dp; |
||||
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground; |
||||
|
||||
/** |
||||
* The container view for the list of ToolbarMenuItems, a list of ImageViews. |
||||
*/ |
||||
public class ToolbarMenuView extends LinearLayout { |
||||
private ToolbarMenu menu; |
||||
|
||||
public ToolbarMenuView(Context context) { |
||||
this(context, null); |
||||
} |
||||
|
||||
public ToolbarMenuView(Context context, AttributeSet attrs) { |
||||
this(context, attrs, 0); |
||||
} |
||||
|
||||
public ToolbarMenuView(Context context, AttributeSet attrs, int defStyle) { |
||||
super(context, attrs, defStyle); |
||||
|
||||
setOrientation(HORIZONTAL); |
||||
setGravity(Gravity.CENTER_VERTICAL); |
||||
} |
||||
|
||||
public void attach(ToolbarMenu menu) { |
||||
this.menu = menu; |
||||
|
||||
setupMenuViews(); |
||||
} |
||||
|
||||
public void detach() { |
||||
for (ToolbarMenuItem item : menu.items) { |
||||
item.detach(); |
||||
} |
||||
} |
||||
|
||||
private void setupMenuViews() { |
||||
removeAllViews(); |
||||
|
||||
Collections.sort(menu.items, (a, b) -> a.order - b.order); |
||||
|
||||
for (ToolbarMenuItem item : menu.items) { |
||||
ImageView imageView = new ImageView(getContext()); |
||||
|
||||
imageView.setOnClickListener((v) -> { |
||||
handleClick(item); |
||||
}); |
||||
imageView.setFocusable(true); |
||||
imageView.setScaleType(ImageView.ScaleType.CENTER); |
||||
|
||||
imageView.setVisibility(item.visible ? View.VISIBLE : View.GONE); |
||||
|
||||
if (item.overflowStyle) { |
||||
imageView.setLayoutParams(new LayoutParams(dp(44), dp(56))); |
||||
imageView.setPadding(dp(8), 0, dp(16), 0); |
||||
} else { |
||||
imageView.setLayoutParams(new LinearLayout.LayoutParams(dp(50), dp(56))); |
||||
} |
||||
|
||||
imageView.setImageDrawable(item.drawable); |
||||
setRoundItemBackground(imageView); |
||||
|
||||
addView(imageView); |
||||
|
||||
item.attach(imageView); |
||||
} |
||||
} |
||||
|
||||
private void handleClick(ToolbarMenuItem item) { |
||||
item.performClick(); |
||||
} |
||||
} |
@ -0,0 +1,159 @@ |
||||
/* |
||||
* Clover - 4chan browser https://github.com/Floens/Clover/
|
||||
* Copyright (C) 2014 Floens |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/ |
||||
package org.floens.chan.ui.toolbar; |
||||
|
||||
public class ToolbarPresenter { |
||||
public enum AnimationStyle { |
||||
NONE, |
||||
PUSH, |
||||
POP, |
||||
FADE |
||||
} |
||||
|
||||
public enum TransitionAnimationStyle { |
||||
PUSH, |
||||
POP |
||||
} |
||||
|
||||
private Callback callback; |
||||
|
||||
private NavigationItem item; |
||||
private NavigationItem search; |
||||
private NavigationItem transition; |
||||
|
||||
void create(Callback callback) { |
||||
this.callback = callback; |
||||
} |
||||
|
||||
void set(NavigationItem newItem, AnimationStyle animation) { |
||||
cancelTransitionIfNeeded(); |
||||
if (closeSearchIfNeeded()) { |
||||
animation = AnimationStyle.FADE; |
||||
} |
||||
|
||||
item = newItem; |
||||
|
||||
callback.showForNavigationItem(item, animation); |
||||
} |
||||
|
||||
void update(NavigationItem updatedItem) { |
||||
callback.updateViewForItem(updatedItem, updatedItem == item); |
||||
} |
||||
|
||||
void startTransition(NavigationItem newItem, TransitionAnimationStyle animation) { |
||||
cancelTransitionIfNeeded(); |
||||
if (closeSearchIfNeeded()) { |
||||
callback.showForNavigationItem(item, AnimationStyle.NONE); |
||||
} |
||||
|
||||
transition = newItem; |
||||
|
||||
callback.containerStartTransition(transition, animation); |
||||
} |
||||
|
||||
void stopTransition(boolean didComplete) { |
||||
if (transition == null) { |
||||
return; |
||||
} |
||||
|
||||
callback.containerStopTransition(didComplete); |
||||
|
||||
if (didComplete) { |
||||
item = transition; |
||||
callback.showForNavigationItem(item, AnimationStyle.NONE); |
||||
} |
||||
transition = null; |
||||
} |
||||
|
||||
void setTransitionProgress(float progress) { |
||||
if (transition == null) { |
||||
return; |
||||
} |
||||
|
||||
callback.containerSetTransitionProgress(progress); |
||||
} |
||||
|
||||
void openSearch() { |
||||
if (search != null) { |
||||
return; |
||||
} |
||||
|
||||
cancelTransitionIfNeeded(); |
||||
|
||||
search = new NavigationItem(); |
||||
search.search = true; |
||||
callback.showForNavigationItem(search, AnimationStyle.FADE); |
||||
|
||||
callback.onSearchVisibilityChanged(item, true); |
||||
} |
||||
|
||||
boolean closeSearch() { |
||||
if (search == null) { |
||||
return false; |
||||
} |
||||
|
||||
search = null; |
||||
set(item, AnimationStyle.FADE); |
||||
|
||||
callback.onSearchVisibilityChanged(item, false); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private void cancelTransitionIfNeeded() { |
||||
if (transition != null) { |
||||
callback.containerStopTransition(false); |
||||
transition = null; |
||||
} |
||||
} |
||||
|
||||
private boolean closeSearchIfNeeded() { |
||||
// Cancel search
|
||||
if (search != null) { |
||||
search = null; |
||||
callback.onSearchVisibilityChanged(item, false); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
void searchInput(String input) { |
||||
if (search == null) { |
||||
return; |
||||
} |
||||
|
||||
search.searchText = input; |
||||
callback.onSearchInput(item, search.searchText); |
||||
} |
||||
|
||||
interface Callback { |
||||
void showForNavigationItem(NavigationItem item, AnimationStyle animation); |
||||
|
||||
void containerStartTransition(NavigationItem item, TransitionAnimationStyle animation); |
||||
|
||||
void containerStopTransition(boolean didComplete); |
||||
|
||||
void containerSetTransitionProgress(float progress); |
||||
|
||||
void onSearchVisibilityChanged(NavigationItem item, boolean visible); |
||||
|
||||
void onSearchInput(NavigationItem item, String input); |
||||
|
||||
void updateViewForItem(NavigationItem item, boolean current); |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<paths> |
||||
|
||||
<!-- Corresponds to the file path chosen in the NetModule.getCacheDir method --> |
||||
<cache-path |
||||
name="fc" |
||||
path="filecache/" /> |
||||
<external-cache-path |
||||
name="exfc" |
||||
path="filecache/" /> |
||||
|
||||
</paths> |
Loading…
Reference in new issue