Merge pull request #518 from Floens/rework-setup-flow

Rework setup flow
crowdin
Florens 7 years ago committed by GitHub
commit b82944cf7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseBoardManager.java
  2. 42
      Clover/app/src/main/java/org/floens/chan/core/presenter/BoardSetupPresenter.java
  3. 38
      Clover/app/src/main/java/org/floens/chan/core/presenter/SetupPresenter.java
  4. 25
      Clover/app/src/main/java/org/floens/chan/core/presenter/SitesSetupPresenter.java
  5. 6
      Clover/app/src/main/java/org/floens/chan/core/presenter/ThreadPresenter.java
  6. 9
      Clover/app/src/main/java/org/floens/chan/core/repository/BoardRepository.java
  7. 11
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  8. 34
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  9. 51
      Clover/app/src/main/java/org/floens/chan/ui/controller/IntroController.java
  10. 84
      Clover/app/src/main/java/org/floens/chan/ui/controller/SetupController.java
  11. 49
      Clover/app/src/main/java/org/floens/chan/ui/controller/SitesSetupController.java
  12. 4
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThreadController.java
  13. 6
      Clover/app/src/main/java/org/floens/chan/ui/drawable/DropdownArrowDrawable.java
  14. 44
      Clover/app/src/main/java/org/floens/chan/ui/helper/HintPopup.java
  15. 2
      Clover/app/src/main/java/org/floens/chan/ui/layout/BoardAddLayout.java
  16. 1
      Clover/app/src/main/java/org/floens/chan/ui/layout/SiteAddLayout.java
  17. 22
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadLayout.java
  18. 29
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/ToolbarContainer.java
  19. BIN
      Clover/app/src/main/res/drawable-xxhdpi/logo.png
  20. 90
      Clover/app/src/main/res/layout/controller_intro.xml
  21. 61
      Clover/app/src/main/res/layout/layout_empty_setup.xml
  22. 1
      Clover/app/src/main/res/layout/popup_hint.xml
  23. 1
      Clover/app/src/main/res/layout/popup_hint_top.xml
  24. 20
      Clover/app/src/main/res/values/strings.xml

@ -89,7 +89,7 @@ public class DatabaseBoardManager {
}; };
} }
public Callable<Void> createAll(final Site site, final List<Board> boards) { public Callable<Boolean> createAll(final Site site, final List<Board> boards) {
return () -> { return () -> {
long start = Time.startTiming(); long start = Time.startTiming();
@ -146,7 +146,7 @@ public class DatabaseBoardManager {
Time.endTiming("createAll boards " + Time.endTiming("createAll boards " +
toCreate.size() + ", " + toUpdate.size(), start); toCreate.size() + ", " + toUpdate.size(), start);
return null; return !toCreate.isEmpty() || !toUpdate.isEmpty();
}; };
} }

@ -20,6 +20,7 @@ package org.floens.chan.core.presenter;
import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.repository.BoardRepository;
import org.floens.chan.core.site.Site; import org.floens.chan.core.site.Site;
import org.floens.chan.ui.helper.BoardHelper; import org.floens.chan.ui.helper.BoardHelper;
import org.floens.chan.utils.BackgroundUtils; import org.floens.chan.utils.BackgroundUtils;
@ -29,12 +30,14 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import javax.inject.Inject; import javax.inject.Inject;
public class BoardSetupPresenter { public class BoardSetupPresenter implements Observer {
private BoardManager boardManager; private BoardManager boardManager;
private Callback callback; private Callback callback;
@ -44,12 +47,16 @@ public class BoardSetupPresenter {
private List<Board> savedBoards; private List<Board> savedBoards;
private BoardRepository.SitesBoards allBoardsObservable;
private Executor executor = Executors.newSingleThreadExecutor(); private Executor executor = Executors.newSingleThreadExecutor();
private BackgroundUtils.Cancelable suggestionCall; private BackgroundUtils.Cancelable suggestionCall;
private List<BoardSuggestion> suggestions = new ArrayList<>(); private List<BoardSuggestion> suggestions = new ArrayList<>();
private List<String> selectedSuggestions = new LinkedList<>(); private List<String> selectedSuggestions = new LinkedList<>();
private String suggestionsQuery = null;
@Inject @Inject
public BoardSetupPresenter(BoardManager boardManager) { public BoardSetupPresenter(BoardManager boardManager) {
this.boardManager = boardManager; this.boardManager = boardManager;
@ -61,10 +68,25 @@ public class BoardSetupPresenter {
savedBoards = boardManager.getSiteSavedBoards(site); savedBoards = boardManager.getSiteSavedBoards(site);
callback.setSavedBoards(savedBoards); callback.setSavedBoards(savedBoards);
allBoardsObservable = boardManager.getAllBoardsObservable();
allBoardsObservable.addObserver(this);
} }
public void destroy() { public void destroy() {
boardManager.updateBoardOrders(savedBoards); boardManager.updateBoardOrders(savedBoards);
allBoardsObservable.deleteObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o == allBoardsObservable) {
if (addCallback != null) {
// Update the boards shown in the query.
queryBoardsWithQueryAndShowInAddDialog();
}
}
} }
public void addClicked() { public void addClicked() {
@ -74,13 +96,14 @@ public class BoardSetupPresenter {
public void bindAddDialog(AddCallback addCallback) { public void bindAddDialog(AddCallback addCallback) {
this.addCallback = addCallback; this.addCallback = addCallback;
searchEntered(null); queryBoardsWithQueryAndShowInAddDialog();
} }
public void unbindAddDialog() { public void unbindAddDialog() {
this.addCallback = null; this.addCallback = null;
suggestions.clear(); suggestions.clear();
selectedSuggestions.clear(); selectedSuggestions.clear();
suggestionsQuery = null;
} }
public void onSelectAllClicked() { public void onSelectAllClicked() {
@ -88,7 +111,7 @@ public class BoardSetupPresenter {
suggestion.checked = true; suggestion.checked = true;
selectedSuggestions.add(suggestion.getCode()); selectedSuggestions.add(suggestion.getCode());
} }
addCallback.updateSuggestions(); addCallback.suggestionsWereChanged();
} }
public void onSuggestionClicked(BoardSuggestion suggestion) { public void onSuggestionClicked(BoardSuggestion suggestion) {
@ -165,12 +188,17 @@ public class BoardSetupPresenter {
} }
public void searchEntered(String userQuery) { public void searchEntered(String userQuery) {
suggestionsQuery = userQuery;
queryBoardsWithQueryAndShowInAddDialog();
}
private void queryBoardsWithQueryAndShowInAddDialog() {
if (suggestionCall != null) { if (suggestionCall != null) {
suggestionCall.cancel(); suggestionCall.cancel();
} }
final String query = userQuery == null ? null : final String query = suggestionsQuery == null ? null :
userQuery.replace("/", "").replace("\\", ""); suggestionsQuery.replace("/", "").replace("\\", "");
suggestionCall = BackgroundUtils.runWithExecutor(executor, () -> { suggestionCall = BackgroundUtils.runWithExecutor(executor, () -> {
List<BoardSuggestion> suggestions = new ArrayList<>(); List<BoardSuggestion> suggestions = new ArrayList<>();
if (site.boardsType().canList) { if (site.boardsType().canList) {
@ -210,7 +238,7 @@ public class BoardSetupPresenter {
updateSuggestions(result); updateSuggestions(result);
if (addCallback != null) { if (addCallback != null) {
addCallback.updateSuggestions(); addCallback.suggestionsWereChanged();
} }
}); });
} }
@ -240,7 +268,7 @@ public class BoardSetupPresenter {
} }
public interface AddCallback { public interface AddCallback {
void updateSuggestions(); void suggestionsWereChanged();
} }
public static class BoardSuggestion { public static class BoardSuggestion {

@ -1,38 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.presenter;
public class SetupPresenter {
private Callback callback;
public void create(Callback callback) {
this.callback = callback;
callback.moveToIntro();
}
public void startClicked() {
callback.moveToSiteSetup();
}
public interface Callback {
void moveToIntro();
void moveToSiteSetup();
}
}

@ -59,10 +59,8 @@ public class SitesSetupPresenter implements Observer {
updateSitesInUi(); updateSitesInUi();
this.callback.setNextAllowed(!sitesShown.isEmpty()); if (sites.getAll().isEmpty()) {
callback.showHint();
if (sitesShown.isEmpty()) {
callback.presentIntro();
} }
} }
@ -90,12 +88,6 @@ public class SitesSetupPresenter implements Observer {
updateSitesInUi(); updateSitesInUi();
} }
public void onIntroDismissed() {
if (sitesShown.isEmpty()) {
callback.showHint();
}
}
public void bindAddDialog(AddCallback addCallback) { public void bindAddDialog(AddCallback addCallback) {
this.addCallback = addCallback; this.addCallback = addCallback;
} }
@ -104,10 +96,6 @@ public class SitesSetupPresenter implements Observer {
this.addCallback = null; this.addCallback = null;
} }
public boolean mayExit() {
return sitesShown.size() > 0;
}
public void onShowDialogClicked() { public void onShowDialogClicked() {
callback.showAddDialog(); callback.showAddDialog();
} }
@ -131,16 +119,11 @@ public class SitesSetupPresenter implements Observer {
}); });
} }
public void onDoneClicked() {
}
private void siteAdded(Site site) { private void siteAdded(Site site) {
sitesShown.add(site); sitesShown.add(site);
saveOrder(); saveOrder();
updateSitesInUi(); updateSitesInUi();
callback.setNextAllowed(!sitesShown.isEmpty());
} }
public void onSiteCellSettingsClicked(Site site) { public void onSiteCellSettingsClicked(Site site) {
@ -172,14 +155,10 @@ public class SitesSetupPresenter implements Observer {
public interface Callback { public interface Callback {
void setSites(List<SiteBoardCount> sites); void setSites(List<SiteBoardCount> sites);
void presentIntro();
void showHint(); void showHint();
void showAddDialog(); void showAddDialog();
void setNextAllowed(boolean nextAllowed);
void openSiteConfiguration(Site site); void openSiteConfiguration(Site site);
} }

@ -102,6 +102,10 @@ public class ThreadPresenter implements ChanThreadLoader.ChanLoaderCallback, Pos
this.threadPresenterCallback = threadPresenterCallback; this.threadPresenterCallback = threadPresenterCallback;
} }
public void showNoContent() {
threadPresenterCallback.showEmpty();
}
public void bindLoadable(Loadable loadable) { public void bindLoadable(Loadable loadable) {
if (!loadable.equals(this.loadable)) { if (!loadable.equals(this.loadable)) {
if (chanLoader != null) { if (chanLoader != null) {
@ -751,6 +755,8 @@ public class ThreadPresenter implements ChanThreadLoader.ChanLoaderCallback, Pos
void showLoading(); void showLoading();
void showEmpty();
void showPostInfo(String info); void showPostInfo(String info);
void showPostLinkables(Post post); void showPostLinkables(Post post);

@ -23,6 +23,7 @@ import org.floens.chan.core.database.DatabaseBoardManager;
import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.model.orm.Board;
import org.floens.chan.core.site.Site; import org.floens.chan.core.site.Site;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Time; import org.floens.chan.utils.Time;
import java.util.ArrayList; import java.util.ArrayList;
@ -35,6 +36,8 @@ import javax.inject.Singleton;
@Singleton @Singleton
public class BoardRepository implements Observer { public class BoardRepository implements Observer {
private static final String TAG = "BoardRepository";
private final DatabaseManager databaseManager; private final DatabaseManager databaseManager;
private final DatabaseBoardManager databaseBoardManager; private final DatabaseBoardManager databaseBoardManager;
@ -68,7 +71,11 @@ public class BoardRepository implements Observer {
} }
public void updateAvailableBoardsForSite(Site site, List<Board> availableBoards) { public void updateAvailableBoardsForSite(Site site, List<Board> availableBoards) {
databaseManager.runTask(databaseBoardManager.createAll(site, availableBoards)); boolean changed = databaseManager.runTask(databaseBoardManager.createAll(site, availableBoards));
Logger.d(TAG, "updateAvailableBoardsForSite changed = " + changed);
if (changed) {
updateObservablesAsync();
}
} }
public Board getFromCode(Site site, String code) { public Board getFromCode(Site site, String code) {

@ -50,7 +50,6 @@ import org.floens.chan.core.site.SiteService;
import org.floens.chan.ui.controller.BrowseController; import org.floens.chan.ui.controller.BrowseController;
import org.floens.chan.ui.controller.DoubleNavigationController; import org.floens.chan.ui.controller.DoubleNavigationController;
import org.floens.chan.ui.controller.DrawerController; import org.floens.chan.ui.controller.DrawerController;
import org.floens.chan.ui.controller.SitesSetupController;
import org.floens.chan.ui.controller.SplitNavigationController; import org.floens.chan.ui.controller.SplitNavigationController;
import org.floens.chan.ui.controller.StyledToolbarNavigationController; import org.floens.chan.ui.controller.StyledToolbarNavigationController;
import org.floens.chan.ui.controller.ThreadSlideController; import org.floens.chan.ui.controller.ThreadSlideController;
@ -158,15 +157,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
private void restoreFresh() { private void restoreFresh() {
if (!siteService.areSitesSetup()) { if (!siteService.areSitesSetup()) {
SitesSetupController setupController = new SitesSetupController(this); browseController.showSitesNotSetup();
if (drawerController.childControllers.get(0) instanceof DoubleNavigationController) {
DoubleNavigationController doubleNavigationController =
(DoubleNavigationController) drawerController.childControllers.get(0);
doubleNavigationController.pushController(setupController, false);
} else {
mainNavigationController.pushController(setupController, false);
}
} else { } else {
browseController.loadWithDefaultBoard(); browseController.loadWithDefaultBoard();
} }

@ -35,6 +35,7 @@ import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.site.Site; import org.floens.chan.core.site.Site;
import org.floens.chan.ui.adapter.PostsFilter; import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.helper.BoardHelper; import org.floens.chan.ui.helper.BoardHelper;
import org.floens.chan.ui.helper.HintPopup;
import org.floens.chan.ui.layout.BrowseBoardsFloatingMenu; import org.floens.chan.ui.layout.BrowseBoardsFloatingMenu;
import org.floens.chan.ui.layout.ThreadLayout; import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.toolbar.NavigationItem; import org.floens.chan.ui.toolbar.NavigationItem;
@ -92,6 +93,15 @@ public class BrowseController extends ThreadController implements
presenter.destroy(); presenter.destroy();
} }
@Override
public void showSitesNotSetup() {
super.showSitesNotSetup();
HintPopup hint = HintPopup.show(context, getToolbar(), R.string.thread_empty_setup_hint);
hint.alignLeft();
hint.wiggle();
}
public void setBoard(Board board) { public void setBoard(Board board) {
presenter.setBoard(board); presenter.setBoard(board);
} }
@ -109,30 +119,6 @@ public class BrowseController extends ThreadController implements
// Toolbar menu // Toolbar menu
navigation.hasBack = false; navigation.hasBack = false;
/*search = menu.addItem(new ToolbarMenuItem(context, this, SEARCH_ID, R.drawable.ic_search_white_24dp));
refresh = menu.addItem(new ToolbarMenuItem(context, this, REFRESH_ID, R.drawable.ic_refresh_white_24dp));
// Toolbar overflow
overflow = menu.createOverflow(this);
List<FloatingMenuItem> items = new ArrayList<>();
if (!ChanSettings.enableReplyFab.get()) {
items.add(new FloatingMenuItem(REPLY_ID, R.string.action_reply));
}
viewModeMenuItem = new FloatingMenuItem(VIEW_MODE_ID, postViewMode == ChanSettings.PostViewMode.LIST ?
R.string.action_switch_catalog : R.string.action_switch_board);
items.add(viewModeMenuItem);
archive = new FloatingMenuItem(ARCHIVE_ID, R.string.thread_view_archive);
items.add(archive);
archive.setEnabled(false);
items.add(new FloatingMenuItem(ORDER_ID, R.string.action_order));
items.add(new FloatingMenuItem(OPEN_BROWSER_ID, R.string.action_open_browser));
items.add(new FloatingMenuItem(SHARE_ID, R.string.action_share));
overflow.setSubMenu(new FloatingMenu(context, overflow.getView(), items));*/
NavigationItem.MenuOverflowBuilder overflowBuilder = navigation.buildMenu() NavigationItem.MenuOverflowBuilder overflowBuilder = navigation.buildMenu()
.withItem(R.drawable.ic_search_white_24dp, this::searchClicked) .withItem(R.drawable.ic_search_white_24dp, this::searchClicked)
.withItem(R.drawable.ic_refresh_white_24dp, this::reloadClicked) .withItem(R.drawable.ic_refresh_white_24dp, this::reloadClicked)

@ -1,51 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;
import android.view.View;
import android.widget.Button;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
public class IntroController extends Controller implements View.OnClickListener {
private Button start;
public IntroController(Context context) {
super(context);
}
@Override
public void onCreate() {
super.onCreate();
view = inflateRes(R.layout.controller_intro);
start = view.findViewById(R.id.start);
start.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (v == start) {
((SitesSetupController) presentedByController).onIntroDismissed();
stopPresenting();
}
}
}

@ -1,84 +0,0 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;
import android.view.animation.DecelerateInterpolator;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.controller.transition.FadeInTransition;
import org.floens.chan.core.presenter.SetupPresenter;
import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.ui.toolbar.Toolbar;
public class SetupController extends ToolbarNavigationController implements SetupPresenter.Callback {
private SetupPresenter presenter;
public SetupController(Context context) {
super(context);
}
@Override
public void onCreate() {
super.onCreate();
view = inflateRes(R.layout.controller_navigation_setup);
container = view.findViewById(R.id.container);
toolbar = view.findViewById(R.id.toolbar);
toolbar.setArrowMenuIconShown(false);
toolbar.setBackgroundColor(ThemeHelper.getInstance().getTheme().primaryColor.color);
toolbar.setCallback(new Toolbar.SimpleToolbarCallback() {
@Override
public void onMenuOrBackClicked(boolean isArrow) {
}
});
toolbar.setAlpha(0f);
requireSpaceForToolbar = false;
presenter = new SetupPresenter();
presenter.create(this);
}
public SetupPresenter getPresenter() {
return presenter;
}
@Override
public void moveToIntro() {
replaceController(new IntroController(context), false);
}
@Override
public void moveToSiteSetup() {
SitesSetupController sitesSetupController = new SitesSetupController(context);
sitesSetupController.showDoneCheckmark();
replaceController(sitesSetupController, true);
}
private void replaceController(Controller to, boolean showToolbar) {
if (blockingInput || toolbar.isTransitioning()) return;
boolean animated = getTop() != null;
transition(getTop(), to, true, animated ? new FadeInTransition() : null);
toolbar.animate().alpha(showToolbar ? 1f : 0f)
.setInterpolator(new DecelerateInterpolator(2f)).start();
}
}

@ -68,6 +68,8 @@ public class SitesSetupController extends StyledToolbarNavigationController impl
private RecyclerView sitesRecyclerview; private RecyclerView sitesRecyclerview;
private FloatingActionButton addButton; private FloatingActionButton addButton;
private HintPopup addBoardsHint;
private SitesAdapter sitesAdapter; private SitesAdapter sitesAdapter;
private ItemTouchHelper itemTouchHelper; private ItemTouchHelper itemTouchHelper;
private List<SiteBoardCount> sites = new ArrayList<>(); private List<SiteBoardCount> sites = new ArrayList<>();
@ -143,16 +145,6 @@ public class SitesSetupController extends StyledToolbarNavigationController impl
presenter.destroy(); presenter.destroy();
} }
public void showDoneCheckmark() {
navigation.swipeable = false;
navigation.buildMenu()
.withItem(R.drawable.ic_done_white_24dp, (item) -> presenter.onDoneClicked())
.build();
// doneMenuItem.getView().setAlpha(0f);
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v == addButton) { if (v == addButton) {
@ -160,28 +152,11 @@ public class SitesSetupController extends StyledToolbarNavigationController impl
} }
} }
@Override
public boolean onBack() {
if (presenter.mayExit()) {
return super.onBack();
} else {
return true;
}
}
@Override
public void presentIntro() {
presentController(new IntroController(context), false);
}
public void onIntroDismissed() {
presenter.onIntroDismissed();
}
@Override @Override
public void showHint() { public void showHint() {
String s = context.getString(R.string.setup_sites_add_hint); String s = context.getString(R.string.setup_sites_add_hint);
HintPopup popup = new HintPopup(context, addButton, s, 0, 0, true); HintPopup popup = new HintPopup(context, addButton, s, 0, 0, true);
popup.wiggle();
popup.show(); popup.show();
} }
@ -226,16 +201,6 @@ public class SitesSetupController extends StyledToolbarNavigationController impl
crossfadeView.toggle(!sites.isEmpty(), true); crossfadeView.toggle(!sites.isEmpty(), true);
} }
@Override
public void setNextAllowed(boolean nextAllowed) {
// if (doneMenuItem != null) {
// doneMenuItem.getView().animate().alpha(nextAllowed ? 1f : 0f).start();
// }
if (!nextAllowed) {
navigation.swipeable = false;
}
}
private void onSiteCellSettingsClicked(Site site) { private void onSiteCellSettingsClicked(Site site) {
presenter.onSiteCellSettingsClicked(site); presenter.onSiteCellSettingsClicked(site);
} }
@ -267,6 +232,14 @@ public class SitesSetupController extends StyledToolbarNavigationController impl
String boardsString = context.getResources().getQuantityString(R.plurals.board, boards, boards); String boardsString = context.getResources().getQuantityString(R.plurals.board, boards, boards);
String descriptionText = context.getString(R.string.setup_sites_site_description, boardsString); String descriptionText = context.getString(R.string.setup_sites_site_description, boardsString);
holder.description.setText(descriptionText); holder.description.setText(descriptionText);
if (boards == 0) {
if (addBoardsHint != null) {
addBoardsHint.dismiss();
}
addBoardsHint = HintPopup.show(context, holder.settings, R.string.setup_sites_add_boards_hint);
addBoardsHint.wiggle();
}
} }
@Override @Override

@ -103,6 +103,10 @@ public abstract class ThreadController extends Controller implements
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
} }
public void showSitesNotSetup() {
threadLayout.getPresenter().showNoContent();
}
public abstract void openPin(Pin pin); public abstract void openPin(Pin pin);
/* /*

@ -23,6 +23,7 @@ import android.graphics.Paint;
import android.graphics.Path; import android.graphics.Path;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
public class DropdownArrowDrawable extends Drawable { public class DropdownArrowDrawable extends Drawable {
private Paint paint = new Paint(); private Paint paint = new Paint();
@ -45,7 +46,7 @@ public class DropdownArrowDrawable extends Drawable {
} }
@Override @Override
public void draw(Canvas canvas) { public void draw(@NonNull Canvas canvas) {
path.rewind(); path.rewind();
path.moveTo(0, height / 4); path.moveTo(0, height / 4);
path.lineTo(width, height / 4); path.lineTo(width, height / 4);
@ -84,7 +85,9 @@ public class DropdownArrowDrawable extends Drawable {
} }
int color = pressed ? pressedColor : this.color; int color = pressed ? pressedColor : this.color;
if (color != paint.getColor()) { if (color != paint.getColor()) {
int prevAlpha = paint.getAlpha();
paint.setColor(color); paint.setColor(color);
paint.setAlpha(prevAlpha);
invalidateSelf(); invalidateSelf();
return true; return true;
} else { } else {
@ -100,6 +103,7 @@ public class DropdownArrowDrawable extends Drawable {
@Override @Override
public void setAlpha(int alpha) { public void setAlpha(int alpha) {
paint.setAlpha(alpha); paint.setAlpha(alpha);
invalidateSelf();
} }
@Override @Override

@ -17,10 +17,12 @@
*/ */
package org.floens.chan.ui.helper; package org.floens.chan.ui.helper;
import android.animation.TimeInterpolator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -51,15 +53,18 @@ public class HintPopup {
private TextView textView; private TextView textView;
private PopupWindow popupWindow; private PopupWindow popupWindow;
private LinearLayout popupView; private ViewGroup popupView;
private final View anchor; private final View anchor;
private String text; private String text;
private final int offsetX; private final int offsetX;
private final int offsetY; private final int offsetY;
private final boolean top; private final boolean top;
private boolean dismissed; private boolean dismissed;
private boolean rightAligned = true;
private boolean wiggle = false;
public HintPopup(Context context, final View anchor, final String text, final int offsetX, final int offsetY, final boolean top) { public HintPopup(Context context, final View anchor, final String text,
final int offsetX, final int offsetY, final boolean top) {
this.anchor = anchor; this.anchor = anchor;
this.text = text; this.text = text;
this.offsetX = offsetX; this.offsetX = offsetX;
@ -71,7 +76,7 @@ public class HintPopup {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
private void createView(Context context) { private void createView(Context context) {
popupView = (LinearLayout) LayoutInflater.from(context) popupView = (ViewGroup) LayoutInflater.from(context)
.inflate(top ? R.layout.popup_hint_top : R.layout.popup_hint, null); .inflate(top ? R.layout.popup_hint_top : R.layout.popup_hint, null);
textView = popupView.findViewById(R.id.text); textView = popupView.findViewById(R.id.text);
@ -91,15 +96,46 @@ public class HintPopup {
if (!dismissed) { if (!dismissed) {
popupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); popupView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
// TODO: cleanup // TODO: cleanup
int xoff = -popupView.getMeasuredWidth() + anchor.getWidth() + offsetX - dp(2); int xoff;
if (rightAligned) {
xoff = -popupView.getMeasuredWidth() + anchor.getWidth() + offsetX - dp(2);
} else {
xoff = -popupView.getMeasuredWidth() + offsetX - dp(2);
}
int yoff = -dp(25) + offsetY + (top ? -anchor.getHeight() - dp(30) : 0); int yoff = -dp(25) + offsetY + (top ? -anchor.getHeight() - dp(30) : 0);
popupWindow.showAsDropDown(anchor, xoff, yoff); popupWindow.showAsDropDown(anchor, xoff, yoff);
if (wiggle) {
TimeInterpolator wiggleInterpolator = input ->
(float) Math.sin(60 * input * 2.0 * Math.PI);
popupView.animate()
.translationY(dp(2))
.setInterpolator(wiggleInterpolator)
.setDuration(60000)
.start();
}
if (!rightAligned) {
View arrow = popupView.findViewById(R.id.arrow);
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) arrow.getLayoutParams();
lp.gravity = Gravity.LEFT;
arrow.setLayoutParams(lp);
}
} }
}, 400); }, 400);
// popupView.postDelayed(popupWindow::dismiss, 5000); // popupView.postDelayed(popupWindow::dismiss, 5000);
} }
public void alignLeft() {
rightAligned = false;
}
public void wiggle() {
wiggle = true;
}
public void dismiss() { public void dismiss() {
popupWindow.dismiss(); popupWindow.dismiss();
dismissed = true; dismissed = true;

@ -110,7 +110,7 @@ public class BoardAddLayout extends LinearLayout implements SearchLayout.SearchL
} }
@Override @Override
public void updateSuggestions() { public void suggestionsWereChanged() {
suggestionsAdapter.notifyDataSetChanged(); suggestionsAdapter.notifyDataSetChanged();
} }

@ -35,6 +35,7 @@ public class SiteAddLayout extends LinearLayout implements SitesSetupPresenter.A
urlContainer = findViewById(R.id.url_container); urlContainer = findViewById(R.id.url_container);
url = findViewById(R.id.url); url = findViewById(R.id.url);
url.setHint(R.string.setup_sites_url_hint);
} }
public void setDialog(Dialog dialog) { public void setDialog(Dialog dialog) {

@ -70,7 +70,8 @@ import static org.floens.chan.utils.AndroidUtils.fixSnackbarText;
import static org.floens.chan.utils.AndroidUtils.getString; import static org.floens.chan.utils.AndroidUtils.getString;
/** /**
* Wrapper around ThreadListLayout, so that it cleanly manages between a load bar and the list view. * Wrapper around ThreadListLayout, so that it cleanly manages between a loading state
* and the recycler view.
*/ */
public class ThreadLayout extends CoordinatorLayout implements public class ThreadLayout extends CoordinatorLayout implements
ThreadPresenter.ThreadPresenterCallback, ThreadPresenter.ThreadPresenterCallback,
@ -78,9 +79,10 @@ public class ThreadLayout extends CoordinatorLayout implements
View.OnClickListener, View.OnClickListener,
ThreadListLayout.ThreadListLayoutCallback { ThreadListLayout.ThreadListLayoutCallback {
private enum Visible { private enum Visible {
EMPTY,
LOADING, LOADING,
THREAD, THREAD,
ERROR; ERROR
} }
@Inject @Inject
@ -138,6 +140,9 @@ public class ThreadLayout extends CoordinatorLayout implements
errorText = errorLayout.findViewById(R.id.text); errorText = errorLayout.findViewById(R.id.text);
errorRetryButton = errorLayout.findViewById(R.id.button); errorRetryButton = errorLayout.findViewById(R.id.button);
// Inflate empty layout
// View setup // View setup
threadListLayout.setCallbacks(presenter, presenter, presenter, presenter, this); threadListLayout.setCallbacks(presenter, presenter, presenter, presenter, this);
postPopupHelper = new PostPopupHelper(getContext(), presenter, this); postPopupHelper = new PostPopupHelper(getContext(), presenter, this);
@ -251,6 +256,11 @@ public class ThreadLayout extends CoordinatorLayout implements
switchVisible(Visible.LOADING); switchVisible(Visible.LOADING);
} }
@Override
public void showEmpty() {
switchVisible(Visible.EMPTY);
}
public void showPostInfo(String info) { public void showPostInfo(String info) {
new AlertDialog.Builder(getContext()) new AlertDialog.Builder(getContext())
.setTitle(R.string.post_info_title) .setTitle(R.string.post_info_title)
@ -538,6 +548,9 @@ public class ThreadLayout extends CoordinatorLayout implements
this.visible = visible; this.visible = visible;
switch (visible) { switch (visible) {
case EMPTY:
loadView.setView(inflateEmptyView());
break;
case LOADING: case LOADING:
View view = loadView.setView(null); View view = loadView.setView(null);
// TODO: cleanup // TODO: cleanup
@ -559,6 +572,11 @@ public class ThreadLayout extends CoordinatorLayout implements
} }
} }
@SuppressLint("InflateParams")
private View inflateEmptyView() {
return LayoutInflater.from(getContext()).inflate(R.layout.layout_empty_setup, null);
}
@Override @Override
public void presentRepliesController(Controller controller) { public void presentRepliesController(Controller controller) {
callback.presentRepliesController(controller); callback.presentRepliesController(controller);

@ -27,7 +27,8 @@ import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -48,6 +49,7 @@ import org.floens.chan.utils.AndroidUtils;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static android.text.TextUtils.isEmpty;
import static org.floens.chan.utils.AndroidUtils.dp; import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor; import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.removeFromParentView; import static org.floens.chan.utils.AndroidUtils.removeFromParentView;
@ -153,7 +155,7 @@ public class ToolbarContainer extends FrameLayout {
titleView.setText(item.title); titleView.setText(item.title);
} }
if (!TextUtils.isEmpty(item.subtitle)) { if (!isEmpty(item.subtitle)) {
TextView subtitleView = view.findViewById(R.id.subtitle); TextView subtitleView = view.findViewById(R.id.subtitle);
if (subtitleView != null) { if (subtitleView != null) {
subtitleView.setText(item.subtitle); subtitleView.setText(item.subtitle);
@ -459,16 +461,33 @@ public class ToolbarContainer extends FrameLayout {
int arrowColor = getAttrColor(getContext(), R.attr.dropdown_light_color); int arrowColor = getAttrColor(getContext(), R.attr.dropdown_light_color);
int arrowPressedColor = getAttrColor( int arrowPressedColor = getAttrColor(
getContext(), R.attr.dropdown_light_pressed_color); getContext(), R.attr.dropdown_light_pressed_color);
Drawable drawable = new DropdownArrowDrawable( final Drawable arrowDrawable = new DropdownArrowDrawable(
dp(12), dp(12), true, arrowColor, arrowPressedColor); dp(12), dp(12), true, arrowColor, arrowPressedColor);
titleView.setCompoundDrawablesWithIntrinsicBounds( titleView.setCompoundDrawablesWithIntrinsicBounds(
null, null, drawable, null); null, null, arrowDrawable, null);
titleView.setOnClickListener(v -> item.middleMenu.show(titleView)); titleView.setOnClickListener(v -> item.middleMenu.show(titleView));
// Hide the dropdown arrow if there is no text.
titleView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
arrowDrawable.setAlpha(isEmpty(s) ? 0 : 255);
}
});
arrowDrawable.setAlpha(isEmpty(item.title) ? 0 : 255);
} }
// Possible subtitle. // Possible subtitle.
TextView subtitleView = menu.findViewById(R.id.subtitle); TextView subtitleView = menu.findViewById(R.id.subtitle);
if (!TextUtils.isEmpty(item.subtitle)) { if (!isEmpty(item.subtitle)) {
ViewGroup.LayoutParams titleParams = titleView.getLayoutParams(); ViewGroup.LayoutParams titleParams = titleView.getLayoutParams();
titleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; titleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
titleView.setLayoutParams(titleParams); titleView.setLayoutParams(titleParams);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?backcolor"
android:orientation="vertical">
<ImageView
android:id="@+id/logo"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="16dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_weight="1"
android:gravity="center"
android:src="@drawable/logo"
app:layout_constraintBottom_toTopOf="@+id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintGuide_percent="0.45" />
<TextView
android:id="@+id/title"
style="@style/TextAppearance.AppCompat.Display1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/intro_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline" />
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/intro_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<Button
android:id="@+id/start"
style="@style/Widget.AppCompat.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:foreground="?selectableItemBackground"
android:text="@string/intro_start"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/header"
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/thread_empty_setup_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guideline" />
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<TextView
android:id="@+id/body"
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/thread_empty_setup_body"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/header" />
<TextView
android:id="@+id/feature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:text="@string/thread_empty_setup_feature"
android:textSize="50dp"
app:layout_constraintBottom_toTopOf="@+id/header"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="SpUsage" />
</android.support.constraint.ConstraintLayout>

@ -27,6 +27,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_height="15dp" android:layout_height="15dp"
android:layout_gravity="right" android:layout_gravity="right"
android:layout_marginRight="14dp" android:layout_marginRight="14dp"
android:layout_marginLeft="14dp"
android:background="@drawable/background_hint_arrow" /> android:background="@drawable/background_hint_arrow" />
<FrameLayout <FrameLayout

@ -42,6 +42,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:layout_height="15dp" android:layout_height="15dp"
android:layout_gravity="right" android:layout_gravity="right"
android:layout_marginRight="14dp" android:layout_marginRight="14dp"
android:layout_marginLeft="14dp"
android:background="@drawable/background_hint_arrow" android:background="@drawable/background_hint_arrow"
android:rotation="180" /> android:rotation="180" />

@ -172,18 +172,20 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="thread_pin_hint">Bookmark this thread</string> <string name="thread_pin_hint">Bookmark this thread</string>
<string name="thread_view_archive">Archive</string> <string name="thread_view_archive">Archive</string>
<string name="intro_title">Clover</string> <string name="thread_empty_setup_feature">&#128564;</string>
<string name="intro_content">Browse your favorite imageboards</string> <string name="thread_empty_setup_title">Nothing to show</string>
<string name="intro_start">Get started</string> <string name="thread_empty_setup_body">Add a site to begin browsing</string>
<string name="thread_empty_setup_hint">Add a site here</string>
<string name="setup_sites_title">Your sites</string> <string name="setup_sites_title">Setup sites</string>
<string name="setup_sites_empty">No sites added</string> <string name="setup_sites_empty">No sites added</string>
<string name="setup_sites_add_hint">Add sites</string> <string name="setup_sites_add_hint">Add a site here</string>
<string name="setup_sites_add_title">Add site</string> <string name="setup_sites_add_title">Add site</string>
<string name="setup_sites_description">Enter the url of the site you want to browse</string> <string name="setup_sites_description">Add a site by its url or name</string>
<string name="setup_sites_url">Site url</string> <string name="setup_sites_url">URL or name</string>
<string name="setup_sites_url_hint">http://</string> <string name="setup_sites_url_hint">http://example.com</string>
<string name="setup_sites_site_description">%s added</string> <string name="setup_sites_site_description">%s added</string>
<string name="setup_sites_add_boards_hint">Tap to add boards</string>
<string name="setup_site_title">Configure %s</string> <string name="setup_site_title">Configure %s</string>
<string name="setup_site_group_general">General</string> <string name="setup_site_group_general">General</string>
@ -369,7 +371,7 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="image_save_failed">Saving image failed</string> <string name="image_save_failed">Saving image failed</string>
<string name="image_save_directory_error">Cannot make save directory</string> <string name="image_save_directory_error">Cannot make save directory</string>
<string name="drawer_sites">Sites</string> <string name="drawer_sites">Setup sites</string>
<string name="drawer_history">History</string> <string name="drawer_history">History</string>
<!-- Main settings --> <!-- Main settings -->

Loading…
Cancel
Save