diff --git a/Clover/app/src/main/java/org/floens/chan/Chan.java b/Clover/app/src/main/java/org/floens/chan/Chan.java index 20f9fb86..2efc85de 100644 --- a/Clover/app/src/main/java/org/floens/chan/Chan.java +++ b/Clover/app/src/main/java/org/floens/chan/Chan.java @@ -68,6 +68,10 @@ public class Chan extends Application implements UserAgentProvider { return instance.graph; } + public static T inject(T instance) { + return Chan.instance.graph.inject(instance); + } + @Override public void onCreate() { super.onCreate(); diff --git a/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java b/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java index bfd6eea1..e7379e71 100644 --- a/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java +++ b/Clover/app/src/main/java/org/floens/chan/chan/ChanUrls.java @@ -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"; } diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHistoryManager.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHistoryManager.java index 137877de..2f04b9d2 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHistoryManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHistoryManager.java @@ -42,10 +42,6 @@ public class DatabaseHistoryManager { this.databaseLoadableManager = databaseLoadableManager; } - public void add(History history) { - databaseManager.runTaskSync(addHistory(history)); - } - public Callable load() { return new Callable() { @Override diff --git a/Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java b/Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java index 4e253ae2..2fd634bb 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java +++ b/Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java @@ -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, }, diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/orm/Loadable.java b/Clover/app/src/main/java/org/floens/chan/core/model/orm/Loadable.java index 15679d53..f0214d90 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/orm/Loadable.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/orm/Loadable.java @@ -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) { diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java index f24ad897..70773e62 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java @@ -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); diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java index 7662f107..f09f236f 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java @@ -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)); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java b/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java index 64c58592..ac7b7976 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/SiteEndpoints.java @@ -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 makeArgument(String key, String value) { + Map map = new ArrayMap<>(1); + map.put(key, value); + return map; + } + + static Map makeArgument(String key1, String value1, + String key2, String value2) { + Map map = new ArrayMap<>(2); + map.put(key1, value1); + map.put(key2, value2); + return map; + } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java b/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java index 0120179c..b45776bd 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java +++ b/Clover/app/src/main/java/org/floens/chan/core/site/common/FutabaChanReader.java @@ -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 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 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)); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/animation/AnimationUtils.java b/Clover/app/src/main/java/org/floens/chan/ui/animation/AnimationUtils.java index e33c3a14..c516a675 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/animation/AnimationUtils.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/animation/AnimationUtils.java @@ -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 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.
* This does cause some lag on complex views because requestLayout is called on each frame. */ + @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); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java index 629cb9ef..36c94cca 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java @@ -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; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java index ccdb0799..42fd27c1 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java @@ -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 diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java index 18fc826f..2f9c76b6 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java @@ -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); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java index 8452bd00..32a524e5 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java @@ -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,9 +563,11 @@ public class ThreadListLayout extends FrameLayout implements ReplyLayout.ReplyLa }; private void party() { - Calendar calendar = Calendar.getInstance(); - if (calendar.get(Calendar.MONTH) == Calendar.OCTOBER && calendar.get(Calendar.DAY_OF_MONTH) == 1) { - recyclerView.addItemDecoration(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); + } } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java b/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java index 2a042c50..176688dd 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java @@ -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 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); } - }).start(); + }); + animators.add(objectAnimator); + } + + 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)); - } + ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(newView, View.ALPHA, 1f); + animators.add(objectAnimator); - // 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; - } - } - } - - if (animateVertical) { - newSize += getPaddingTop() + getPaddingBottom(); - } else { - newSize += getPaddingLeft() + getPaddingRight(); - } - - AnimationUtils.animateLayout(animateVertical, this, currentSize, newSize, layoutAnimationDuration, true, null); - } + animatorSet.setDuration(fadeDuration); + animatorSet.playTogether(animators); + animatorSet.start(); } else { + // Fast forward possible pending animations (end, so also remove them). + animatorSet.end(); + 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 { diff --git a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java index 48c4fd36..a37b82be 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java @@ -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) { diff --git a/Clover/app/src/main/res/layout/layout_reply_input.xml b/Clover/app/src/main/res/layout/layout_reply_input.xml index 836e8c2b..b7d76f1b 100644 --- a/Clover/app/src/main/res/layout/layout_reply_input.xml +++ b/Clover/app/src/main/res/layout/layout_reply_input.xml @@ -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 . --> - - - + android:orientation="horizontal" + tools:ignore="ContentDescription,RtlHardcoded,RtlSymmetry"> + android:minHeight="124dp" + android:orientation="horizontal" + tools:ignore="ContentDescription,RtlHardcoded,RtlSymmetry"> + + . . - - @@ -101,6 +98,7 @@ along with this program. If not, see . 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 . - - - - + android:layout_width="match_parent" + android:layout_height="150dp" + android:scaleType="centerInside" + android:visibility="gone" /> + + + android:hint="@string/reply_file_name" + android:singleLine="true" + android:textSize="16sp" + android:visibility="gone" /> @@ -168,11 +164,6 @@ along with this program. If not, see . android:layout_height="36dp" android:padding="10dp" /> - - . - +