work on the reply layout

replace all layout param animations with better methods
make the expanded more fill the screen height
various refactorings for the thread layouts
multisite
Floens 8 years ago
parent 18d06e2f57
commit 1e59ac664f
  1. 4
      Clover/app/src/main/java/org/floens/chan/Chan.java
  2. 7
      Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java
  3. 4
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHistoryManager.java
  4. 2
      Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java
  5. 2
      Clover/app/src/main/java/org/floens/chan/core/model/orm/Loadable.java
  6. 52
      Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java
  7. 45
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  8. 18
      Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java
  9. 11
      Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java
  10. 14
      Clover/app/src/main/java/org/floens/chan/ui/animation/AnimationUtils.java
  11. 5
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  12. 197
      Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java
  13. 64
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  14. 63
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  15. 153
      Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java
  16. 6
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  17. 95
      Clover/app/src/main/res/layout/layout_reply_input.xml

@ -68,6 +68,10 @@ public class Chan extends Application implements UserAgentProvider {
return instance.graph;
}
public static <T> T inject(T instance) {
return Chan.instance.graph.inject(instance);
}
@Override
public void onCreate() {
super.onCreate();

@ -19,27 +19,34 @@ package org.floens.chan.chan;
import org.floens.chan.core.settings.ChanSettings;
@Deprecated
public class ChanUrls {
@Deprecated
public static String getCaptchaSiteKey() {
return "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc";
}
@Deprecated
public static String getBoardUrlDesktop(String board) {
return scheme() + "://boards.4chan.org/" + board + "/";
}
@Deprecated
public static String getThreadUrlDesktop(String board, int no) {
return scheme() + "://boards.4chan.org/" + board + "/thread/" + no;
}
@Deprecated
public static String getThreadUrlDesktop(String board, int no, int postNo) {
return scheme() + "://boards.4chan.org/" + board + "/thread/" + no + "#p" + postNo;
}
@Deprecated
public static String getCatalogUrlDesktop(String board) {
return scheme() + "://boards.4chan.org/" + board + "/catalog";
}
@Deprecated
private static String scheme() {
return ChanSettings.networkHttps.get() ? "https" : "http";
}

@ -42,10 +42,6 @@ public class DatabaseHistoryManager {
this.databaseLoadableManager = databaseLoadableManager;
}
public void add(History history) {
databaseManager.runTaskSync(addHistory(history));
}
public Callable<Void> load() {
return new Callable<Void>() {
@Override

@ -43,6 +43,7 @@ import org.floens.chan.ui.controller.SitesSetupController;
import org.floens.chan.ui.controller.ViewThreadController;
import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.ui.layout.FilterLayout;
import org.floens.chan.ui.layout.ReplyLayout;
import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.service.WatchNotifier;
import org.floens.chan.ui.view.MultiImageView;
@ -97,6 +98,7 @@ import dagger.Provides;
SiteSetupController.class,
SitesSetupController.class,
BoardSetupController.class,
ReplyLayout.class,
Chan4.class,
},

@ -167,7 +167,7 @@ public class Loadable implements SiteReference, BoardReference {
}
/**
* Compares the mode, board and no.
* Compares the mode, site, board and no.
*/
@Override
public boolean equals(Object object) {

@ -23,23 +23,21 @@ import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.manager.ReplyManager;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.orm.SavedReply;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.core.site.http.HttpCall;
import org.floens.chan.core.site.http.HttpCallManager;
import org.floens.chan.core.site.http.ReplyResponse;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.core.site.http.ReplyResponse;
import org.floens.chan.ui.captcha.CaptchaCallback;
import org.floens.chan.ui.captcha.CaptchaLayoutInterface;
import org.floens.chan.ui.helper.ImagePickDelegate;
import java.io.File;
import java.nio.charset.Charset;
@ -48,7 +46,6 @@ import java.util.regex.Pattern;
import javax.inject.Inject;
import static org.floens.chan.Chan.getGraph;
import static org.floens.chan.utils.AndroidUtils.getReadableFileSize;
import static org.floens.chan.utils.AndroidUtils.getRes;
import static org.floens.chan.utils.AndroidUtils.getString;
@ -65,20 +62,9 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
private ReplyPresenterCallback callback;
@Inject
ReplyManager replyManager;
@Inject
BoardManager boardManager;
@Inject
WatchManager watchManager;
@Inject
HttpCallManager httpCallManager;
@Inject
DatabaseManager databaseManager;
private ReplyManager replyManager;
private WatchManager watchManager;
private DatabaseManager databaseManager;
private boolean bound = false;
private Loadable loadable;
@ -92,9 +78,17 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
private boolean captchaInited;
private int selectedQuote = -1;
public ReplyPresenter(ReplyPresenterCallback callback) {
@Inject
public ReplyPresenter(ReplyManager replyManager,
WatchManager watchManager,
DatabaseManager databaseManager) {
this.replyManager = replyManager;
this.watchManager = watchManager;
this.databaseManager = databaseManager;
}
public void create(ReplyPresenterCallback callback) {
this.callback = callback;
getGraph().inject(this);
}
public void bindLoadable(Loadable loadable) {
@ -164,6 +158,7 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
public void onMoreClicked() {
moreOpen = !moreOpen;
callback.setExpanded(moreOpen);
callback.openNameOptions(moreOpen);
if (!loadable.isThreadMode()) {
callback.openSubject(moreOpen);
@ -176,6 +171,10 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
}
}
public boolean isExpanded() {
return moreOpen;
}
public void onAttachClicked() {
if (!pickingFile) {
if (previewOpen) {
@ -288,7 +287,7 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
String[] lines = post.comment.toString().split("\n+");
final Pattern quotePattern = Pattern.compile("^>>(>/[a-z0-9]+/)?\\d+.*$"); // matches for >>123, >>123 (OP), >>123 (You), >>>/fit/123
for (String line : lines) {
if(!quotePattern.matcher(line).matches()) { // do not include post no from quoted post
if (!quotePattern.matcher(line).matches()) { // do not include post no from quoted post
textToInsert += ">" + line + "\n";
}
}
@ -329,6 +328,7 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
previewOpen = false;
selectedQuote = -1;
callback.openMessage(false, true, "", false);
callback.setExpanded(false);
callback.openSubject(false);
callback.openNameOptions(false);
callback.openFileName(false);
@ -428,12 +428,12 @@ public class ReplyPresenter implements CaptchaCallback, ImagePickDelegate.ImageP
void openMessage(boolean open, boolean animate, String message, boolean autoHide);
void openMessageWebview(String rawMessage);
void onPosted();
void setCommentHint(String hint);
void setExpanded(boolean expanded);
void openNameOptions(boolean open);
void openSubject(boolean open);

@ -25,16 +25,15 @@ import org.floens.chan.chan.ChanLoader;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.exception.ChanLoaderException;
import org.floens.chan.core.manager.ReplyManager;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.orm.History;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.orm.Pin;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.model.PostLinkable;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.History;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.orm.Pin;
import org.floens.chan.core.model.orm.SavedReply;
import org.floens.chan.core.pool.ChanLoaderFactory;
import org.floens.chan.core.settings.ChanSettings;
@ -42,7 +41,6 @@ import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.DeleteResponse;
import org.floens.chan.core.site.http.HttpCall;
import org.floens.chan.core.site.http.HttpCallManager;
import org.floens.chan.ui.adapter.PostAdapter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCellInterface;
@ -59,7 +57,6 @@ import java.util.List;
import javax.inject.Inject;
import static org.floens.chan.Chan.getGraph;
import static org.floens.chan.utils.AndroidUtils.getString;
public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapter.PostAdapterCallback, PostCellInterface.PostCellCallback, ThreadStatusCell.Callback, ThreadListLayout.ThreadListLayoutPresenterCallback {
@ -79,22 +76,10 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
private static final int POST_OPTION_OPEN_BROWSER = 13;
private static final int POST_OPTION_FILTER_TRIPCODE = 14;
@Inject
WatchManager watchManager;
@Inject
DatabaseManager databaseManager;
@Inject
ReplyManager replyManager;
@Inject
HttpCallManager httpCallManager;
@Inject
ChanLoaderFactory chanLoaderFactory;
private ThreadPresenterCallback threadPresenterCallback;
private WatchManager watchManager;
private DatabaseManager databaseManager;
private ChanLoaderFactory chanLoaderFactory;
private Loadable loadable;
private ChanLoader chanLoader;
@ -103,10 +88,17 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
private PostsFilter.Order order = PostsFilter.Order.BUMP;
private boolean historyAdded = false;
public ThreadPresenter(ThreadPresenterCallback threadPresenterCallback) {
this.threadPresenterCallback = threadPresenterCallback;
@Inject
public ThreadPresenter(WatchManager watchManager,
DatabaseManager databaseManager,
ChanLoaderFactory chanLoaderFactory) {
this.watchManager = watchManager;
this.databaseManager = databaseManager;
this.chanLoaderFactory = chanLoaderFactory;
}
getGraph().inject(this);
public void create(ThreadPresenterCallback threadPresenterCallback) {
this.threadPresenterCallback = threadPresenterCallback;
}
public void bindLoadable(Loadable loadable) {
@ -116,6 +108,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
}
Pin pin = watchManager.findPinByLoadable(loadable);
// TODO this isn't true anymore, because all loadables come from one location.
if (pin != null) {
// Use the loadable from the pin.
// This way we can store the list position in the pin loadable,
@ -704,7 +697,7 @@ public class ThreadPresenter implements ChanLoader.ChanLoaderCallback, PostAdapt
history.loadable = loadable;
PostImage image = chanLoader.getThread().op.image;
history.thumbnailUrl = image == null ? "" : image.thumbnailUrl.toString();
databaseManager.getDatabaseHistoryManager().add(history);
databaseManager.runTask(databaseManager.getDatabaseHistoryManager().addHistory(history));
}
}

@ -17,9 +17,11 @@
*/
package org.floens.chan.core.site;
import android.support.v4.util.ArrayMap;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.Post;
import java.util.Map;
@ -48,4 +50,18 @@ public interface SiteEndpoints {
HttpUrl report(Post post);
HttpUrl login();
static Map<String, String> makeArgument(String key, String value) {
Map<String, String> map = new ArrayMap<>(1);
map.put(key, value);
return map;
}
static Map<String, String> makeArgument(String key1, String value1,
String key2, String value2) {
Map<String, String> map = new ArrayMap<>(2);
map.put(key1, value1);
map.put(key2, value2);
return map;
}
}

@ -14,6 +14,8 @@ import java.util.Map;
import okhttp3.HttpUrl;
import static org.floens.chan.core.site.SiteEndpoints.makeArgument;
public class FutabaChanReader implements ChanReader {
private final ChanParser chanParser;
@ -235,15 +237,14 @@ public class FutabaChanReader implements ChanReader {
if (countryCode != null && countryName != null) {
Map<String, String> arg = new HashMap<>(1);
arg.put("country_code", countryCode);
HttpUrl countryUrl = endpoints.icon(builder, "country", arg);
HttpUrl countryUrl = endpoints.icon(builder, "country",
makeArgument("country_code", countryCode));
builder.addHttpIcon(new PostHttpIcon(countryUrl, countryName));
}
if (trollCountryCode != null && countryName != null) {
Map<String, String> arg = new HashMap<>(1);
arg.put("troll_country_code", trollCountryCode);
HttpUrl countryUrl = endpoints.icon(builder, "troll_country", arg);
HttpUrl countryUrl = endpoints.icon(builder, "troll_country",
makeArgument("troll_country_code", trollCountryCode));
builder.addHttpIcon(new PostHttpIcon(countryUrl, countryName));
}

@ -35,10 +35,17 @@ public class AnimationUtils {
return (int) (a + (b - a) * x);
}
// a lot of these are deprecated, they animate the height with the layout params themselves,
// causing a measure loop for each frame. android just isn't designed for this, and it always
// lags. there are better ways to easily animate layouts, such as enabling the layoutAnimations
// flag.
@Deprecated
public static void setHeight(View view, boolean expand, boolean animated) {
setHeight(view, expand, animated, -1);
}
@Deprecated
public static void setHeight(View view, boolean expand, boolean animated, int knownWidth) {
if (animated) {
animateHeight(view, expand, knownWidth);
@ -49,14 +56,17 @@ public class AnimationUtils {
private static Map<View, ValueAnimator> layoutAnimations = new HashMap<>();
@Deprecated
public static int animateHeight(final View view, boolean expand) {
return animateHeight(view, expand, -1);
}
@Deprecated
public static int animateHeight(final View view, final boolean expand, int knownWidth) {
return animateHeight(view, expand, knownWidth, 300);
}
@Deprecated
public static int animateHeight(final View view, final boolean expand, int knownWidth, int duration) {
return animateHeight(view, expand, knownWidth, duration, null);
}
@ -68,6 +78,7 @@ public class AnimationUtils {
* You can call this even when a height animation is currently running, it will resolve any issues.<br>
* <b>This does cause some lag on complex views because requestLayout is called on each frame.</b>
*/
@Deprecated
public static int animateHeight(final View view, final boolean expand, int knownWidth, int duration, final LayoutAnimationProgress progressCallback) {
final int fromHeight;
int toHeight;
@ -89,6 +100,7 @@ public class AnimationUtils {
return toHeight;
}
@Deprecated
public static void animateLayout(final boolean vertical, final View view, final int from, final int to, int duration, final boolean wrapAfterwards, final LayoutAnimationProgress callback) {
ValueAnimator running = layoutAnimations.remove(view);
if (running != null) {
@ -153,7 +165,9 @@ public class AnimationUtils {
layoutAnimations.put(view, valueAnimator);
}
@Deprecated
public interface LayoutAnimationProgress {
@Deprecated
void onLayoutAnimationProgress(View view, boolean vertical, int from, int to, int value, float progress);
}

@ -69,7 +69,7 @@ public abstract class ThreadController extends Controller implements ThreadLayou
navigationItem.handlesToolbarInset = true;
threadLayout = (ThreadLayout) LayoutInflater.from(context).inflate(R.layout.layout_thread, null);
threadLayout.setCallback(this);
threadLayout.create(this);
swipeRefreshLayout = new SwipeRefreshLayout(context) {
@Override
@ -93,7 +93,7 @@ public abstract class ThreadController extends Controller implements ThreadLayou
public void onDestroy() {
super.onDestroy();
threadLayout.getPresenter().unbindLoadable();
threadLayout.destroy();
EventBus.getDefault().unregister(this);
}
@ -259,6 +259,7 @@ public abstract class ThreadController extends Controller implements ThreadLayou
} else {
navigationController.pushController(filtersController);
}
// TODO cleanup
Filter filter = new Filter();
filter.type = FilterType.TRIPCODE.flag;
filter.pattern = tripcode;

@ -17,6 +17,8 @@
*/
package org.floens.chan.ui.layout;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.text.Editable;
@ -25,7 +27,7 @@ import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.FrameLayout;
@ -37,8 +39,8 @@ import android.widget.Toast;
import org.floens.chan.R;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.presenter.ReplyPresenter;
import org.floens.chan.core.site.http.Reply;
import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.captcha.CaptchaCallback;
import org.floens.chan.ui.captcha.CaptchaLayout;
@ -50,29 +52,33 @@ import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.ui.view.SelectionListeningEditText;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.ui.animation.AnimationUtils;
import org.floens.chan.utils.ImageDecoder;
import java.io.File;
import javax.inject.Inject;
import static org.floens.chan.Chan.inject;
import static org.floens.chan.ui.theme.ThemeHelper.theme;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.getString;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
public class ReplyLayout extends LoadView implements View.OnClickListener, AnimationUtils.LayoutAnimationProgress, ReplyPresenter.ReplyPresenterCallback, TextWatcher, ImageDecoder.ImageDecoderCallback, SelectionListeningEditText.SelectionChangedListener {
private ReplyPresenter presenter;
public class ReplyLayout extends LoadView implements View.OnClickListener, ReplyPresenter.ReplyPresenterCallback, TextWatcher, ImageDecoder.ImageDecoderCallback, SelectionListeningEditText.SelectionChangedListener {
@Inject
ReplyPresenter presenter;
private ReplyLayoutCallback callback;
private boolean newCaptcha;
private View replyInputLayout;
private FrameLayout captchaContainer;
private ImageView captchaHardReset;
private CaptchaLayoutInterface authenticationLayout;
private boolean openingName;
private boolean blockSelectionChange = false;
// Reply views:
private View replyInputLayout;
private TextView message;
private EditText name;
private EditText subject;
@ -81,79 +87,97 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
private LinearLayout nameOptions;
private SelectionListeningEditText comment;
private TextView commentCounter;
private LinearLayout previewContainer;
private CheckBox spoiler;
private ImageView preview;
private TextView previewMessage;
private ImageView more;
private DropdownArrowDrawable moreDropdown;
private ImageView attach;
private ImageView more;
private ImageView submit;
private DropdownArrowDrawable moreDropdown;
// Captcha views:
private FrameLayout captchaContainer;
private ImageView captchaHardReset;
private Runnable closeMessageRunnable = new Runnable() {
@Override
public void run() {
AnimationUtils.animateHeight(message, false, getWidth());
message.setVisibility(View.GONE);
}
};
public ReplyLayout(Context context) {
super(context);
this(context, null);
}
public ReplyLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this(context, attrs, 0);
}
public ReplyLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
public ReplyLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setAnimateLayout(true, true);
presenter = new ReplyPresenter(this);
replyInputLayout = LayoutInflater.from(getContext()).inflate(R.layout.layout_reply_input, this, false);
message = (TextView) replyInputLayout.findViewById(R.id.message);
name = (EditText) replyInputLayout.findViewById(R.id.name);
subject = (EditText) replyInputLayout.findViewById(R.id.subject);
options = (EditText) replyInputLayout.findViewById(R.id.options);
fileName = (EditText) replyInputLayout.findViewById(R.id.file_name);
nameOptions = (LinearLayout) replyInputLayout.findViewById(R.id.name_options);
comment = (SelectionListeningEditText) replyInputLayout.findViewById(R.id.comment);
inject(this);
final LayoutInflater inflater = LayoutInflater.from(getContext());
// Inflate reply input
replyInputLayout = inflater.inflate(R.layout.layout_reply_input, this, false);
message = replyInputLayout.findViewById(R.id.message);
name = replyInputLayout.findViewById(R.id.name);
subject = replyInputLayout.findViewById(R.id.subject);
options = replyInputLayout.findViewById(R.id.options);
fileName = replyInputLayout.findViewById(R.id.file_name);
nameOptions = replyInputLayout.findViewById(R.id.name_options);
comment = replyInputLayout.findViewById(R.id.comment);
commentCounter = replyInputLayout.findViewById(R.id.comment_counter);
spoiler = replyInputLayout.findViewById(R.id.spoiler);
preview = replyInputLayout.findViewById(R.id.preview);
previewMessage = replyInputLayout.findViewById(R.id.preview_message);
attach = replyInputLayout.findViewById(R.id.attach);
more = replyInputLayout.findViewById(R.id.more);
submit = replyInputLayout.findViewById(R.id.submit);
// Setup reply layout views
comment.addTextChangedListener(this);
comment.setSelectionChangedListener(this);
commentCounter = (TextView) replyInputLayout.findViewById(R.id.comment_counter);
previewContainer = (LinearLayout) replyInputLayout.findViewById(R.id.preview_container);
spoiler = (CheckBox) replyInputLayout.findViewById(R.id.spoiler);
preview = (ImageView) replyInputLayout.findViewById(R.id.preview);
previewMessage = (TextView) replyInputLayout.findViewById(R.id.preview_message);
preview.setOnClickListener(this);
more = (ImageView) replyInputLayout.findViewById(R.id.more);
moreDropdown = new DropdownArrowDrawable(dp(16), dp(16), true, getAttrColor(getContext(), R.attr.dropdown_dark_color), getAttrColor(getContext(), R.attr.dropdown_dark_pressed_color));
moreDropdown = new DropdownArrowDrawable(dp(16), dp(16), true,
getAttrColor(getContext(), R.attr.dropdown_dark_color),
getAttrColor(getContext(), R.attr.dropdown_dark_pressed_color));
more.setImageDrawable(moreDropdown);
setRoundItemBackground(more);
more.setOnClickListener(this);
attach = (ImageView) replyInputLayout.findViewById(R.id.attach);
theme().imageDrawable.apply(attach);
setRoundItemBackground(attach);
attach.setOnClickListener(this);
submit = (ImageView) replyInputLayout.findViewById(R.id.submit);
theme().sendDrawable.apply(submit);
setRoundItemBackground(submit);
submit.setOnClickListener(this);
captchaContainer = (FrameLayout) LayoutInflater.from(getContext()).inflate(R.layout.layout_reply_captcha, this, false);
captchaHardReset = (ImageView) captchaContainer.findViewById(R.id.reset);
// Inflate captcha layout
captchaContainer = (FrameLayout) inflater.inflate(R.layout.layout_reply_captcha, this, false);
captchaHardReset = captchaContainer.findViewById(R.id.reset);
// Setup captcha layout views
captchaContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
theme().refreshDrawable.apply(captchaHardReset);
setRoundItemBackground(captchaHardReset);
captchaHardReset.setOnClickListener(this);
setView(replyInputLayout);
// Presenter
presenter.create(this);
}
public void setCallback(ReplyLayoutCallback callback) {
@ -177,29 +201,17 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
removeCallbacks(closeMessageRunnable);
}
@Override
public LayoutParams getLayoutParamsForView(View view) {
if (view == replyInputLayout || (view == captchaContainer && !newCaptcha)) {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
} else if (view == captchaContainer && newCaptcha) {
return new LayoutParams(LayoutParams.MATCH_PARENT, dp(300));
} else {
// Loadbar
return new LayoutParams(LayoutParams.MATCH_PARENT, dp(100));
}
}
@Override
public void onLayoutAnimationProgress(View view, boolean vertical, int from, int to, int value, float progress) {
if (view == nameOptions) {
moreDropdown.setRotation(openingName ? progress : 1f - progress);
}
}
public boolean onBack() {
return presenter.onBack();
}
private void setWrap(boolean wrap) {
setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT,
wrap ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT
));
}
@Override
public void onClick(View v) {
if (v == more) {
@ -215,6 +227,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
@ -222,20 +235,24 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
@Override
public void setPage(ReplyPresenter.Page page, boolean animate) {
setAnimateLayout(animate, true);
switch (page) {
case LOADING:
setView(null);
setWrap(true);
View progressBar = setView(null);
progressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, dp(100)));
break;
case INPUT:
setView(replyInputLayout);
setWrap(!presenter.isExpanded());
break;
case AUTHENTICATION:
setWrap(false);
if (authenticationLayout == null) {
if (newCaptcha) {
authenticationLayout = new CaptchaLayout(getContext());
} else {
authenticationLayout = (CaptchaLayoutInterface) LayoutInflater.from(getContext()).inflate(R.layout.layout_captcha_legacy, captchaContainer, false);
authenticationLayout = (CaptchaLayoutInterface) LayoutInflater.from(getContext())
.inflate(R.layout.layout_captcha_legacy, captchaContainer, false);
}
captchaContainer.addView((View) authenticationLayout, 0);
}
@ -294,25 +311,13 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
public void openMessage(boolean open, boolean animate, String text, boolean autoHide) {
removeCallbacks(closeMessageRunnable);
message.setText(text);
if (animate) {
AnimationUtils.animateHeight(message, open, getWidth());
} else {
message.setVisibility(open ? VISIBLE : GONE);
message.getLayoutParams().height = open ? ViewGroup.LayoutParams.WRAP_CONTENT : 0;
message.requestLayout();
}
message.setVisibility(open ? View.VISIBLE : View.GONE);
if (autoHide) {
postDelayed(closeMessageRunnable, 5000);
}
}
@Override
public void openMessageWebview(String rawMessage) {
// callback.
}
@Override
public void onPosted() {
Toast.makeText(getContext(), R.string.reply_success, Toast.LENGTH_SHORT).show();
@ -325,20 +330,34 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
comment.setHint(hint);
}
@Override
public void setExpanded(boolean expanded) {
setWrap(!expanded);
comment.setMaxLines(expanded ? 15 : 6);
ValueAnimator animator = ValueAnimator.ofFloat(expanded ? 0f : 1f, expanded ? 1f : 0f);
animator.setInterpolator(new DecelerateInterpolator(2f));
animator.setDuration(400);
animator.addUpdateListener(animation ->
moreDropdown.setRotation((float) animation.getAnimatedValue()));
animator.start();
}
@Override
public void openNameOptions(boolean open) {
openingName = open;
AnimationUtils.animateHeight(nameOptions, open, comment.getWidth(), 300, this);
nameOptions.setVisibility(open ? View.VISIBLE : View.GONE);
}
@Override
public void openSubject(boolean open) {
AnimationUtils.animateHeight(subject, open, comment.getWidth());
subject.setVisibility(open ? View.VISIBLE : View.GONE);
}
@Override
public void openFileName(boolean open) {
AnimationUtils.animateHeight(fileName, open, comment.getWidth());
fileName.setVisibility(open ? View.VISIBLE : View.GONE);
}
@Override
@ -346,6 +365,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
fileName.setText(name);
}
@SuppressLint("SetTextI18n")
@Override
public void updateCommentCount(int count, int maxCount, boolean over) {
commentCounter.setText(count + "/" + maxCount);
@ -354,13 +374,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
}
public void focusComment() {
comment.requestFocus();
comment.postDelayed(new Runnable() {
@Override
public void run() {
AndroidUtils.requestKeyboardFocus(comment);
}
}, 100);
comment.post(() -> AndroidUtils.requestViewAndKeyboardFocus(comment));
}
@Override
@ -372,9 +386,11 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
}
if (show) {
ImageDecoder.decodeFileOnBackgroundThread(previewFile, dp(100), dp(100), this);
ImageDecoder.decodeFileOnBackgroundThread(previewFile, dp(300), dp(200), this);
} else {
AnimationUtils.animateLayout(false, previewContainer, previewContainer.getWidth(), 0, 300, false, null);
spoiler.setVisibility(View.GONE);
preview.setVisibility(View.GONE);
previewMessage.setVisibility(View.GONE);
}
}
@ -386,7 +402,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
@Override
public void openSpoiler(boolean show, boolean checked) {
AnimationUtils.animateHeight(spoiler, show);
spoiler.setVisibility(show ? View.VISIBLE : View.GONE);
spoiler.setChecked(checked);
}
@ -394,7 +410,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
public void onImageBitmap(File file, Bitmap bitmap) {
if (bitmap != null) {
preview.setImageBitmap(bitmap);
AnimationUtils.animateLayout(false, previewContainer, 0, dp(100), 300, false, null);
preview.setVisibility(View.VISIBLE);
} else {
openPreviewMessage(true, getString(R.string.reply_no_preview));
}
@ -402,6 +418,7 @@ public class ReplyLayout extends LoadView implements View.OnClickListener, Anima
@Override
public void onFilePickLoading() {
// TODO
}
@Override

@ -70,7 +70,7 @@ import static org.floens.chan.utils.AndroidUtils.fixSnackbarText;
import static org.floens.chan.utils.AndroidUtils.getString;
/**
* Wrapper around ThreadListLayout, so that it cleanly manages between loadbar and listview.
* Wrapper around ThreadListLayout, so that it cleanly manages between a load bar and the list view.
*/
public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.ThreadPresenterCallback, PostPopupHelper.PostPopupHelperCallback, View.OnClickListener, ThreadListLayout.ThreadListLayoutCallback {
private enum Visible {
@ -82,9 +82,10 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
@Inject
DatabaseManager databaseManager;
private ThreadLayoutCallback callback;
@Inject
ThreadPresenter presenter;
private ThreadPresenter presenter;
private ThreadLayoutCallback callback;
private LoadView loadView;
private HidingFloatingActionButton replyButton;
@ -102,40 +103,45 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
private Snackbar newPostsNotification;
public ThreadLayout(Context context) {
super(context);
init();
this(context, null);
}
public ThreadLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
this(context, attrs, 0);
}
public ThreadLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
public ThreadLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
getGraph().inject(this);
}
public void setCallback(ThreadLayoutCallback callback) {
public void create(ThreadLayoutCallback callback) {
this.callback = callback;
presenter = new ThreadPresenter(this);
// View binding
loadView = findViewById(R.id.loadview);
replyButton = findViewById(R.id.reply_button);
loadView = (LoadView) findViewById(R.id.loadview);
replyButton = (HidingFloatingActionButton) findViewById(R.id.reply_button);
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
threadListLayout = (ThreadListLayout) LayoutInflater.from(getContext()).inflate(R.layout.layout_thread_list, this, false);
threadListLayout.setCallbacks(presenter, presenter, presenter, presenter, this);
// Inflate ThreadListLayout
threadListLayout = (ThreadListLayout) layoutInflater
.inflate(R.layout.layout_thread_list, this, false);
postPopupHelper = new PostPopupHelper(getContext(), presenter, this);
// Inflate error layout
errorLayout = (LinearLayout) layoutInflater
.inflate(R.layout.layout_thread_error, this, false);
errorText = errorLayout.findViewById(R.id.text);
errorRetryButton = errorLayout.findViewById(R.id.button);
errorLayout = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.layout_thread_error, this, false);
errorText = (TextView) errorLayout.findViewById(R.id.text);
// View setup
threadListLayout.setCallbacks(presenter, presenter, presenter, presenter, this);
postPopupHelper = new PostPopupHelper(getContext(), presenter, this);
errorText.setTypeface(AndroidUtils.ROBOTO_MEDIUM);
errorRetryButton = (Button) errorLayout.findViewById(R.id.button);
errorRetryButton.setOnClickListener(this);
// Setup
replyButtonEnabled = ChanSettings.enableReplyFab.get();
if (!replyButtonEnabled) {
AndroidUtils.removeFromParentView(replyButton);
@ -145,9 +151,15 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
theme().applyFabColor(replyButton);
}
presenter.create(this);
switchVisible(Visible.LOADING);
}
public void destroy() {
presenter.unbindLoadable();
}
@Override
public void onClick(View v) {
if (v == errorRetryButton) {
@ -377,8 +389,8 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
@Override
public void confirmPostDelete(final Post post) {
@SuppressLint("InflateParams")
final View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_post_delete, null);
@SuppressLint("InflateParams") final View view = LayoutInflater.from(getContext())
.inflate(R.layout.dialog_post_delete, null);
new AlertDialog.Builder(getContext())
.setTitle(R.string.delete_confirm)
.setView(view)
@ -386,7 +398,7 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
.setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
CheckBox checkBox = (CheckBox) view.findViewById(R.id.image_only);
CheckBox checkBox = view.findViewById(R.id.image_only);
presenter.deletePostConfirmed(post, checkBox.isChecked());
}
})
@ -531,10 +543,6 @@ public class ThreadLayout extends CoordinatorLayout implements ThreadPresenter.T
}
}
private void init() {
getGraph().inject(this);
}
@Override
public void presentRepliesController(Controller controller) {
callback.presentRepliesController(controller);

@ -17,6 +17,8 @@
*/
package org.floens.chan.ui.layout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -29,25 +31,27 @@ import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.core.model.ChanThread;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.model.Post;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.model.orm.Loadable;
import org.floens.chan.core.presenter.ReplyPresenter;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.adapter.PostAdapter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.animation.AnimationUtils;
import org.floens.chan.ui.cell.PostCell;
import org.floens.chan.ui.cell.PostCellInterface;
import org.floens.chan.ui.cell.ThreadStatusCell;
import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.ui.animation.AnimationUtils;
import java.util.Calendar;
import java.util.List;
@ -96,17 +100,20 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
protected void onFinishInflate() {
super.onFinishInflate();
reply = (ReplyLayout) findViewById(R.id.reply);
reply.setCallback(this);
// View binding
reply = findViewById(R.id.reply);
searchStatus = findViewById(R.id.search_status);
recyclerView = findViewById(R.id.recycler_view);
searchStatus = (TextView) findViewById(R.id.search_status);
// View setup
reply.setCallback(this);
searchStatus.setTypeface(ROBOTO_MEDIUM);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
}
public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback, PostCell.PostCellCallback postCellCallback,
ThreadStatusCell.Callback statusCellCallback, ThreadListLayoutPresenterCallback callback,
public void setCallbacks(PostAdapter.PostAdapterCallback postAdapterCallback,
PostCell.PostCellCallback postCellCallback,
ThreadStatusCell.Callback statusCellCallback,
ThreadListLayoutPresenterCallback callback,
ThreadListLayoutCallback threadListLayoutCallback) {
this.callback = callback;
this.threadListLayoutCallback = threadListLayoutCallback;
@ -136,12 +143,7 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
// As requested by the RecyclerView, make sure that the adapter isn't changed
// while in a layout pass. Postpone to the next frame.
mainHandler.post(new Runnable() {
@Override
public void run() {
ThreadListLayout.this.callback.onListScrolledToBottom();
}
});
mainHandler.post(() -> ThreadListLayout.this.callback.onListScrolledToBottom());
}
}
}
@ -261,7 +263,34 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
public void openReply(boolean open) {
if (showingThread != null && replyOpen != open) {
this.replyOpen = open;
int height = AnimationUtils.animateHeight(reply, replyOpen, getWidth(), 500);
reply.measure(
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
);
int height = reply.getMeasuredHeight();
final ViewPropertyAnimator viewPropertyAnimator = reply.animate();
viewPropertyAnimator.setListener(null);
viewPropertyAnimator.setInterpolator(new DecelerateInterpolator(2f));
viewPropertyAnimator.setDuration(600);
if (open) {
reply.setVisibility(View.VISIBLE);
reply.setTranslationY(-height);
viewPropertyAnimator.translationY(0f);
} else {
reply.setTranslationY(0f);
viewPropertyAnimator.translationY(-height);
viewPropertyAnimator.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
viewPropertyAnimator.setListener(null);
reply.setVisibility(View.GONE);
}
});
}
reply.onOpen(open);
if (open) {
recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerViewTopPadding + height, recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
@ -534,11 +563,13 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa
};
private void party() {
if (showingThread.loadable.site.id() == 0) {
Calendar calendar = Calendar.getInstance();
if (calendar.get(Calendar.MONTH) == Calendar.OCTOBER && calendar.get(Calendar.DAY_OF_MONTH) == 1) {
recyclerView.addItemDecoration(PARTY);
}
}
}
private void noParty() {
recyclerView.removeItemDecoration(PARTY);

@ -19,16 +19,19 @@ package org.floens.chan.ui.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewPropertyAnimator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import org.floens.chan.ui.animation.AnimationUtils;
import org.floens.chan.utils.AndroidUtils;
import java.util.ArrayList;
import java.util.List;
/**
* Container for a view with an ProgressBar. Toggles between the view and a
@ -36,11 +39,10 @@ import org.floens.chan.ui.animation.AnimationUtils;
*/
public class LoadView extends FrameLayout {
private int fadeDuration = 200;
private boolean animateLayout;
private boolean animateVertical;
private int layoutAnimationDuration = 500;
private Listener listener;
private AnimatorSet animatorSet = new AnimatorSet();
public LoadView(Context context) {
super(context);
}
@ -62,15 +64,6 @@ public class LoadView extends FrameLayout {
this.fadeDuration = fadeDuration;
}
public void setLayoutAnimationDuration(int layoutAnimationDuration) {
this.layoutAnimationDuration = layoutAnimationDuration;
}
public void setAnimateLayout(boolean animateLayout, boolean animateVertical) {
this.animateLayout = animateLayout;
this.animateVertical = animateVertical;
}
/**
* Set a listener that gives a call when a view gets removed
*
@ -99,37 +92,47 @@ public class LoadView extends FrameLayout {
*/
public View setView(View newView, boolean animate) {
if (newView == null) {
FrameLayout progressBar = new FrameLayout(getContext());
progressBar.addView(new ProgressBar(getContext()), new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER));
newView = progressBar;
newView = getViewForNull();
}
if (animate) {
// Fade all attached views out
// Fast forward possible pending animations (keeping the views attached.)
animatorSet.cancel();
animatorSet = new AnimatorSet();
// Fade all attached views out.
// If the animation is cancelled, the views are not removed. If the animation ends,
// the views are removed.
List<Animator> animators = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
final View child = getChildAt(i);
final ViewPropertyAnimator childAnimation = child.animate()
.setInterpolator(new LinearInterpolator())
.setDuration(fadeDuration)
.alpha(0f);
childAnimation.setListener(new AnimatorListenerAdapter() {
// We don't add a listener to remove the view we also animate in.
if (child == newView) {
continue;
}
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(child, View.ALPHA, 0f);
objectAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
// Canceled because it is being animated in again.
// Don't let this listener call removeView on the in animation.
childAnimation.setListener(null);
// If cancelled, don't remove the view, but re-run the animation.
animation.removeListener(this);
}
@Override
public void onAnimationEnd(Animator animation) {
// Animation ended without interruptions, remove listener for future animations.
childAnimation.setListener(null);
removeView(child);
if (listener != null) {
listener.onLoadViewRemoved(child);
removeAndCallListener(child);
}
});
animators.add(objectAnimator);
}
}).start();
if (newView.getParent() != this) {
if (newView.getParent() != null) {
AndroidUtils.removeFromParentView(newView);
}
addView(newView);
}
// Assume no running animations
@ -138,70 +141,50 @@ public class LoadView extends FrameLayout {
}
// Fade our new view in
newView.animate()
.setInterpolator(new LinearInterpolator())
.setDuration(fadeDuration)
.alpha(1f)
.start();
// Assume view already attached to this view (fading out)
if (newView.getParent() == null) {
addView(newView, getLayoutParamsForView(newView));
}
// Animate this view its size to the new view size if the width or height is WRAP_CONTENT
if (animateLayout && getChildCount() >= 2) {
final int currentSize = animateVertical ? getHeight() : getWidth();
int newSize = animateVertical ? newView.getHeight() : newView.getWidth();
if (newSize == 0) {
if (animateVertical) {
if (newView.getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
newView.measure(
View.MeasureSpec.makeMeasureSpec(getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED);
newSize = newView.getMeasuredHeight();
} else {
newSize = newView.getLayoutParams().height;
}
} else {
if (newView.getLayoutParams().width == LayoutParams.WRAP_CONTENT) {
newView.measure(
View.MeasureSpec.UNSPECIFIED,
View.MeasureSpec.makeMeasureSpec(getHeight(), View.MeasureSpec.EXACTLY));
newSize = newView.getMeasuredWidth();
} else {
newSize = newView.getLayoutParams().width;
}
}
}
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(newView, View.ALPHA, 1f);
animators.add(objectAnimator);
if (animateVertical) {
newSize += getPaddingTop() + getPaddingBottom();
animatorSet.setDuration(fadeDuration);
animatorSet.playTogether(animators);
animatorSet.start();
} else {
newSize += getPaddingLeft() + getPaddingRight();
}
// Fast forward possible pending animations (end, so also remove them).
animatorSet.end();
AnimationUtils.animateLayout(animateVertical, this, currentSize, newSize, layoutAnimationDuration, true, null);
}
} else {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.animate().cancel();
if (listener != null) {
listener.onLoadViewRemoved(child);
}
removeAndCallListener(child);
}
removeAllViews();
newView.animate().cancel();
animatorSet = new AnimatorSet();
newView.setAlpha(1f);
addView(newView, getLayoutParamsForView(newView));
addView(newView);
}
return newView;
}
public LayoutParams getLayoutParamsForView(View view) {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
protected View getViewForNull() {
// TODO: figure out why this is needed for the thread list layout view.
FrameLayout progressBarContainer = new FrameLayout(getContext());
progressBarContainer.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
View progressBar = new ProgressBar(getContext());
progressBar.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT, Gravity.CENTER));
progressBarContainer.addView(progressBar);
return progressBarContainer;
}
protected void removeAndCallListener(View child) {
removeView(child);
if (listener != null) {
listener.onLoadViewRemoved(child);
}
}
public interface Listener {

@ -69,6 +69,8 @@ public class AndroidUtils {
private static ConnectivityManager connectivityManager;
private static final Handler mainHandler = new Handler(Looper.getMainLooper());
public static void init() {
ROBOTO_MEDIUM = getTypeface("Roboto-Medium.ttf");
ROBOTO_MEDIUM_ITALIC = getTypeface("Roboto-MediumItalic.ttf");
@ -232,11 +234,11 @@ public class AndroidUtils {
* be run on the ui thread.
*/
public static void runOnUiThread(Runnable runnable) {
new Handler(Looper.getMainLooper()).post(runnable);
mainHandler.post(runnable);
}
public static void runOnUiThread(Runnable runnable, long delay) {
new Handler(Looper.getMainLooper()).postDelayed(runnable, delay);
mainHandler.postDelayed(runnable, delay);
}
public static void requestKeyboardFocus(Dialog dialog, final View view) {

@ -15,48 +15,54 @@ 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/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:baselineAligned="false"
android:minHeight="124dp"
android:orientation="vertical"
tools:ignore="Suspicious0dp,ContentDescription">
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="0dp"
android:padding="8dp"
android:textColor="#fff44336"
android:visibility="gone" />
android:orientation="horizontal"
tools:ignore="ContentDescription,RtlHardcoded,RtlSymmetry">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:baselineAligned="false"
android:orientation="horizontal">
android:minHeight="124dp"
android:orientation="horizontal"
tools:ignore="ContentDescription,RtlHardcoded,RtlSymmetry">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:animateLayoutChanges="true"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:paddingTop="8dp">
<TextView
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:textColor="#fff44336"
android:visibility="gone" />
<LinearLayout
android:id="@+id/name_options"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<EditText
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:hint="@string/reply_name"
android:inputType="textCapSentences|textAutoCorrect"
@ -66,7 +72,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<EditText
android:id="@+id/options"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/reply_options"
android:singleLine="true"
@ -77,22 +83,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<EditText
android:id="@+id/subject"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_height="wrap_content"
android:hint="@string/reply_subject"
android:inputType="textCapSentences|textAutoCorrect"
android:singleLine="true"
android:textSize="16sp"
android:visibility="gone" />
<EditText
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="0dp"
android:hint="@string/reply_file_name"
android:singleLine="true"
android:textSize="16sp"
android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -101,6 +98,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="top"
android:imeActionLabel="@string/reply_submit"
android:inputType="textMultiLine|textCapSentences|textAutoCorrect"
android:maxLines="6"
@ -120,45 +118,43 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/preview_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingLeft="8dp"
android:paddingTop="8dp"
android:visibility="gone">
<CheckBox
android:id="@+id/spoiler"
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/reply_spoiler_image"
android:textSize="14sp"
android:visibility="gone" />
<ImageView
android:id="@+id/preview"
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerInside" />
android:layout_width="match_parent"
android:layout_height="150dp"
android:scaleType="centerInside"
android:visibility="gone" />
<TextView
android:id="@+id/preview_message"
android:layout_width="100dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12sp" />
android:textSize="12sp"
android:visibility="gone" />
<EditText
android:id="@+id/file_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/reply_file_name"
android:singleLine="true"
android:textSize="16sp"
android:visibility="gone" />
</LinearLayout>
<LinearLayout
android:id="@+id/buttons"
android:layout_width="52dp"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
@ -168,11 +164,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_height="36dp"
android:padding="10dp" />
<Space
android:layout_width="36dp"
android:layout_height="0dp"
android:layout_weight="1" />
<ImageView
android:id="@+id/attach"
android:layout_width="36dp"
@ -189,4 +180,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
</LinearLayout>
</LinearLayout>
</ScrollView>

Loading…
Cancel
Save