From 8a86b2d7c30bf3cac0d65913d827a10c7aab27da Mon Sep 17 00:00:00 2001 From: Floens Date: Fri, 22 Dec 2017 23:26:35 +0100 Subject: [PATCH] new setup flow intro screen, sites setup screen, site setup screen, boards setup screen. --- Clover/app/build.gradle | 1 + .../floens/chan/controller/Controller.java | 6 +- .../chan/controller/NavigationController.java | 12 +- .../core/database/DatabaseBoardManager.java | 35 +- .../org/floens/chan/core/di/AppModule.java | 6 +- .../chan/core/manager/BoardManager.java | 10 + .../org/floens/chan/core/model/orm/Board.java | 4 - .../core/presenter/BoardSetupPresenter.java | 160 ++++++-- .../chan/core/presenter/SetupPresenter.java | 38 ++ .../core/presenter/SiteSetupPresenter.java | 100 +---- .../core/presenter/SitesSetupPresenter.java | 109 ++++++ .../chan/core/settings/ChanSettings.java | 2 +- .../chan/ui/activity/StartActivity.java | 16 +- .../ui/controller/BoardSetupController.java | 225 +++++------ .../chan/ui/controller/BrowseController.java | 9 +- .../ImageViewerNavigationController.java | 5 +- .../chan/ui/controller/IntroController.java | 52 +++ .../chan/ui/controller/PopupController.java | 4 +- .../chan/ui/controller/SetupController.java | 82 ++++ .../ui/controller/SiteSetupController.java | 358 +++--------------- .../ui/controller/SitesSetupController.java | 258 +++++++++++++ .../StyledToolbarNavigationController.java | 5 +- .../ToolbarNavigationController.java | 3 +- .../floens/chan/ui/helper/BoardHelper.java | 50 ++- .../org/floens/chan/ui/helper/PostHelper.java | 9 - .../floens/chan/ui/layout/BoardAddLayout.java | 174 +++++++++ .../floens/chan/ui/layout/SearchLayout.java | 11 +- .../floens/chan/ui/layout/SiteAddLayout.java | 41 ++ .../java/org/floens/chan/ui/theme/Theme.java | 2 +- .../org/floens/chan/ui/toolbar/Toolbar.java | 46 ++- .../org/floens/chan/utils/AndroidUtils.java | 10 + .../floens/chan/utils/BackgroundUtils.java | 71 ++++ .../app/src/main/res/drawable-xxhdpi/logo.png | Bin 0 -> 46679 bytes Clover/app/src/main/res/layout/cell_board.xml | 64 ++++ .../src/main/res/layout/cell_board_edit.xml | 11 +- .../main/res/layout/cell_board_suggestion.xml | 58 ++- .../src/main/res/layout/cell_saved_board.xml | 49 --- Clover/app/src/main/res/layout/cell_site.xml | 56 ++- .../res/layout/controller_board_setup.xml | 80 +--- .../src/main/res/layout/controller_intro.xml | 89 +++++ .../layout/controller_navigation_setup.xml | 34 ++ .../main/res/layout/controller_site_setup.xml | 152 +------- .../res/layout/controller_sites_setup.xml | 44 +++ .../src/main/res/layout/layout_board_add.xml | 42 ++ .../src/main/res/layout/layout_site_add.xml | 42 ++ Clover/app/src/main/res/values/strings.xml | 24 +- 46 files changed, 1731 insertions(+), 928 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/core/presenter/SetupPresenter.java create mode 100644 Clover/app/src/main/java/org/floens/chan/core/presenter/SitesSetupPresenter.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/controller/IntroController.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/controller/SetupController.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/controller/SitesSetupController.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/layout/BoardAddLayout.java create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/layout/SiteAddLayout.java create mode 100644 Clover/app/src/main/java/org/floens/chan/utils/BackgroundUtils.java create mode 100644 Clover/app/src/main/res/drawable-xxhdpi/logo.png create mode 100644 Clover/app/src/main/res/layout/cell_board.xml delete mode 100644 Clover/app/src/main/res/layout/cell_saved_board.xml create mode 100644 Clover/app/src/main/res/layout/controller_intro.xml create mode 100644 Clover/app/src/main/res/layout/controller_navigation_setup.xml create mode 100644 Clover/app/src/main/res/layout/controller_sites_setup.xml create mode 100644 Clover/app/src/main/res/layout/layout_board_add.xml create mode 100644 Clover/app/src/main/res/layout/layout_site_add.xml diff --git a/Clover/app/build.gradle b/Clover/app/build.gradle index aa71b322..15e4d6de 100644 --- a/Clover/app/build.gradle +++ b/Clover/app/build.gradle @@ -144,6 +144,7 @@ dependencies { implementation 'de.greenrobot:eventbus:2.4.0' implementation 'org.nibor.autolink:autolink:0.6.0' implementation 'com.google.code.gson:gson:2.8.1' + implementation 'me.xdrop:fuzzywuzzy:1.1.9' // Yes that's dagger 1, "deprecated". // There's little wrong with it, except that it might be slow at runtime. diff --git a/Clover/app/src/main/java/org/floens/chan/controller/Controller.java b/Clover/app/src/main/java/org/floens/chan/controller/Controller.java index d8154a5c..1965a060 100644 --- a/Clover/app/src/main/java/org/floens/chan/controller/Controller.java +++ b/Clover/app/src/main/java/org/floens/chan/controller/Controller.java @@ -56,7 +56,7 @@ public abstract class Controller { /** * Controller that this controller is presented by. */ - public Controller presentingByController; + public Controller presentedByController; /** * Controller that this controller is presenting. @@ -192,7 +192,7 @@ public abstract class Controller { public void presentController(Controller controller, boolean animated) { ViewGroup contentView = ((StartActivity) context).getContentView(); presentingThisController = controller; - controller.presentingByController = this; + controller.presentedByController = this; controller.onCreate(); controller.attachToView(contentView, true); @@ -227,7 +227,7 @@ public abstract class Controller { } ((StartActivity) context).removeController(this); - presentingByController.presentingThisController = null; + presentedByController.presentingThisController = null; } private void finishPresenting() { diff --git a/Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java b/Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java index 92b91bde..1b461bb6 100644 --- a/Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java +++ b/Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java @@ -19,13 +19,13 @@ package org.floens.chan.controller; import android.content.Context; import android.view.KeyEvent; +import android.view.ViewGroup; import org.floens.chan.controller.transition.PopControllerTransition; import org.floens.chan.controller.transition.PushControllerTransition; -import org.floens.chan.controller.ui.NavigationControllerContainerLayout; public abstract class NavigationController extends Controller { - protected NavigationControllerContainerLayout container; + protected ViewGroup container; protected ControllerTransition controllerTransition; protected boolean blockingInput = false; @@ -51,9 +51,6 @@ public abstract class NavigationController extends Controller { throw new IllegalArgumentException("Cannot animate push when from is null"); } - to.navigationController = this; - to.previousSiblingController = from; - transition(from, to, true, controllerTransition); return true; @@ -120,6 +117,11 @@ public abstract class NavigationController extends Controller { throw new IllegalArgumentException("Cannot pop with no controllers left"); } + if (to != null) { + to.navigationController = this; + to.previousSiblingController = from; + } + if (pushing && to != null) { addChildController(to); to.attachToParentView(container); diff --git a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseBoardManager.java b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseBoardManager.java index e6f6c534..ae2eb73d 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseBoardManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/database/DatabaseBoardManager.java @@ -57,23 +57,34 @@ public class DatabaseBoardManager { }; } - public Callable> getBoards(final Site site) { + public Callable> getSiteBoards(final Site site) { return new Callable>() { @Override public List call() throws Exception { - List boards = null; - try { - boards = helper.boardsDao.queryBuilder() - .where().eq("site", site.id()) - .query(); - for (int i = 0; i < boards.size(); i++) { - Board board = boards.get(i); - board.site = site; - } - } catch (SQLException e) { - Logger.e(TAG, "Error getting boards from db", e); + List boards = helper.boardsDao.queryBuilder() + .where().eq("site", site.id()) + .query(); + for (int i = 0; i < boards.size(); i++) { + Board board = boards.get(i); + board.site = site; } + return boards; + } + }; + } + public Callable> getSiteSavedBoards(final Site site) { + return new Callable>() { + @Override + public List call() throws Exception { + List boards = helper.boardsDao.queryBuilder() + .where().eq("site", site.id()) + .and().eq("saved", true) + .query(); + for (int i = 0; i < boards.size(); i++) { + Board board = boards.get(i); + board.site = site; + } return boards; } }; 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 9738082e..f19f9862 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 @@ -14,7 +14,7 @@ import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.FilterEngine; import org.floens.chan.core.manager.ReplyManager; import org.floens.chan.core.presenter.BoardSetupPresenter; -import org.floens.chan.core.presenter.SiteSetupPresenter; +import org.floens.chan.core.presenter.SitesSetupPresenter; import org.floens.chan.core.site.SiteManager; import org.floens.chan.core.manager.WatchManager; import org.floens.chan.core.net.BitmapLruImageCache; @@ -41,6 +41,7 @@ import org.floens.chan.ui.controller.ImageViewerController; import org.floens.chan.ui.controller.MainSettingsController; import org.floens.chan.ui.controller.PassSettingsController; import org.floens.chan.ui.controller.SiteSetupController; +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; @@ -95,9 +96,10 @@ import dagger.Provides; WatchManager.PinWatcher.class, UpdateManager.class, SiteManager.class, - SiteSetupPresenter.class, + SitesSetupPresenter.class, BoardSetupPresenter.class, SiteSetupController.class, + SitesSetupController.class, BoardSetupController.class, Chan4.class, diff --git a/Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java b/Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java index 34afbc72..7faac521 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java +++ b/Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java @@ -92,6 +92,16 @@ public class BoardManager { return savedBoards; } + public List getSiteBoards(Site site) { + return databaseManager.runTaskSync( + databaseManager.getDatabaseBoardManager().getSiteBoards(site)); + } + + public List getSiteSavedBoards(Site site) { + return databaseManager.runTaskSync( + databaseManager.getDatabaseBoardManager().getSiteSavedBoards(site)); + } + public void saveBoard(Board board) { board.saved = true; diff --git a/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java b/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java index 35c6a212..d648fca6 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java +++ b/Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java @@ -167,10 +167,6 @@ public class Board implements SiteReference { return site; } - public String getName() { - return "/" + code + "/ \u2013 " + name; - } - /** * Updates the board with data from {@code o}.
* {@link #id}, {@link #saved}, {@link #order} are skipped because these are user-set. diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/BoardSetupPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/BoardSetupPresenter.java index ff851a7a..beb909a2 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/BoardSetupPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/BoardSetupPresenter.java @@ -21,46 +21,81 @@ package org.floens.chan.core.presenter; import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.site.Site; -import org.floens.chan.core.site.Sites; +import org.floens.chan.ui.helper.BoardHelper; +import org.floens.chan.utils.BackgroundUtils; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import javax.inject.Inject; -import static org.floens.chan.Chan.getGraph; - public class BoardSetupPresenter { + private BoardManager boardManager; + private Callback callback; + private AddCallback addCallback; - private final List sites = new ArrayList<>(); - - @Inject - BoardManager boardManager; + private Site site; private List savedBoards; + private Executor executor = Executors.newSingleThreadExecutor(); + private BackgroundUtils.Cancelable suggestionCall; + + private List suggestions = new ArrayList<>(); + private Set selectedSuggestions = new HashSet<>(); + @Inject - public BoardSetupPresenter() { - getGraph().inject(this); + public BoardSetupPresenter(BoardManager boardManager) { + this.boardManager = boardManager; } - public void create(Callback callback) { + public void create(Callback callback, Site site) { this.callback = callback; + this.site = site; - sites.addAll(Sites.allSites()); - - loadSavedBoards(); + updateSavedBoards(); callback.setSavedBoards(savedBoards); } - public void addFromSuggestion(BoardSuggestion suggestion) { - Board board = Board.fromSiteNameCode(suggestion.site, suggestion.key, suggestion.key); + public void setAddCallback(AddCallback addCallback) { + this.addCallback = addCallback; + suggestions.clear(); + selectedSuggestions.clear(); + } - boardManager.saveBoard(board); + public void addClicked() { + callback.showAddDialog(); + } - loadSavedBoards(); - callback.setSavedBoards(savedBoards); + public void onSuggestionClicked(BoardSuggestion suggestion) { + suggestion.checked = !suggestion.checked; + String code = suggestion.board.code; + if (suggestion.checked) { + selectedSuggestions.add(code); + } else { + selectedSuggestions.remove(code); + } + addCallback.updateSuggestions(); + } + + public List getSuggestions() { + return suggestions; + } + + public void onAddDialogPositiveClicked() { + for (BoardSuggestion suggestion : suggestions) { + if (suggestion.checked) { + boardManager.saveBoard(suggestion.board); + updateSavedBoards(); + callback.setSavedBoards(savedBoards); + } + } } public void move(int from, int to) { @@ -75,35 +110,98 @@ public class BoardSetupPresenter { boardManager.unsaveBoard(board); - loadSavedBoards(); + updateSavedBoards(); callback.setSavedBoards(savedBoards); } - public List getSuggestionsForQuery(String query) { - List suggestions = new ArrayList<>(); + public void done() { + callback.finish(); + } - for (Site site : sites) { - suggestions.add(new BoardSuggestion(query, site)); + public void searchEntered(final String query) { + if (suggestionCall != null) { + suggestionCall.cancel(); } - return suggestions; + suggestionCall = BackgroundUtils.runWithExecutor(executor, new Callable>() { + @Override + public List call() throws Exception { + List suggestions = new ArrayList<>(); + if (site.boardsType() == Site.BoardsType.DYNAMIC) { + List siteBoards = boardManager.getSiteBoards(site); + List toSearch = new ArrayList<>(); + for (Board siteBoard : siteBoards) { + if (!siteBoard.saved) { + toSearch.add(siteBoard); + } + } + List search = BoardHelper.search(toSearch, query); + + for (Board board : search) { + BoardSuggestion suggestion = new BoardSuggestion(board); + suggestions.add(suggestion); + } + } else { + // TODO + suggestions.add(new BoardSuggestion(null)); + } + + return suggestions; + } + }, new BackgroundUtils.BackgroundResult>() { + @Override + public void onResult(List result) { + updateSuggestions(result); + + if (addCallback != null) { + addCallback.updateSuggestions(); + } + } + }); + } + + private void updateSavedBoards() { + savedBoards = new ArrayList<>(boardManager.getSiteSavedBoards(site)); } - private void loadSavedBoards() { - savedBoards = new ArrayList<>(boardManager.getSavedBoards()); + private void updateSuggestions(List suggestions) { + this.suggestions = suggestions; + for (BoardSuggestion suggestion : this.suggestions) { + suggestion.checked = selectedSuggestions.contains(suggestion.board.code); + } } public interface Callback { + void showAddDialog(); + void setSavedBoards(List savedBoards); + + void finish(); + } + + public interface AddCallback { + void updateSuggestions(); } public static class BoardSuggestion { - public final String key; - public final Site site; + public final Board board; + + private boolean checked = false; + + public BoardSuggestion(Board board) { + this.board = board; + } + + public String getName() { + return BoardHelper.getName(board); + } + + public String getDescription() { + return BoardHelper.getDescription(board); + } - public BoardSuggestion(String key, Site site) { - this.key = key; - this.site = site; + public boolean isChecked() { + return checked; } } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/SetupPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/SetupPresenter.java new file mode 100644 index 00000000..26ea662d --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/SetupPresenter.java @@ -0,0 +1,38 @@ +/* + * 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 . + */ +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(); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/SiteSetupPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/SiteSetupPresenter.java index cec53648..54f35194 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/SiteSetupPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/SiteSetupPresenter.java @@ -1,106 +1,34 @@ -/* - * 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 . - */ package org.floens.chan.core.presenter; - +import org.floens.chan.core.database.DatabaseManager; import org.floens.chan.core.site.Site; -import org.floens.chan.core.site.SiteManager; -import org.floens.chan.core.site.Sites; - -import java.util.ArrayList; -import java.util.List; import javax.inject.Inject; -import static org.floens.chan.Chan.getGraph; - public class SiteSetupPresenter { - @Inject - SiteManager siteManager; - private Callback callback; - - private List sites = new ArrayList<>(); + private Site site; + private DatabaseManager databaseManager; @Inject - public SiteSetupPresenter() { - getGraph().inject(this); + public SiteSetupPresenter(DatabaseManager databaseManager) { + this.databaseManager = databaseManager; } - public void create(Callback callback) { + public void create(Callback callback, Site site) { this.callback = callback; - - sites.addAll(Sites.allSites()); - - this.callback.setAddedSites(sites); - - this.callback.setNextAllowed(!sites.isEmpty(), false); - } - - public boolean mayExit() { - return false; + this.site = site; } - public void onUrlSubmitClicked(String url) { - callback.goToUrlSubmittedState(); - - siteManager.addSite(url, new SiteManager.SiteAddCallback() { - @Override - public void onSiteAdded(Site site) { - siteAdded(site); - } - - @Override - public void onSiteAddFailed(String message) { - callback.showUrlHint(message); - } - }); + public void show() { + callback.setBoardCount( + databaseManager.runTaskSync( + databaseManager.getDatabaseBoardManager().getSiteSavedBoards(site) + ).size() + ); } - public void onNextClicked() { - if (!sites.isEmpty()) { - callback.moveToSavedBoards(); - } - } - - private void siteAdded(Site site) { - sites.clear(); - sites.addAll(Sites.allSites()); - - callback.setAddedSites(sites); - callback.runSiteAddedAnimation(site); - - callback.setNextAllowed(!sites.isEmpty(), true); - } - - private int counter; - public interface Callback { - void goToUrlSubmittedState(); - - void runSiteAddedAnimation(Site site); - - void setAddedSites(List sites); - - void setNextAllowed(boolean nextAllowed, boolean animate); - - void showUrlHint(String text); - - void moveToSavedBoards(); + void setBoardCount(int boardCount); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/SitesSetupPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/SitesSetupPresenter.java new file mode 100644 index 00000000..dc08dd63 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/SitesSetupPresenter.java @@ -0,0 +1,109 @@ +/* + * 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 . + */ +package org.floens.chan.core.presenter; + + +import org.floens.chan.core.manager.BoardManager; +import org.floens.chan.core.site.Site; +import org.floens.chan.core.site.SiteManager; +import org.floens.chan.core.site.Sites; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +public class SitesSetupPresenter { + private SiteManager siteManager; + private BoardManager boardManager; + + private Callback callback; + + private List sites = new ArrayList<>(); + + @Inject + public SitesSetupPresenter(SiteManager siteManager, BoardManager boardManager) { + this.siteManager = siteManager; + this.boardManager = boardManager; + } + + public void create(Callback callback) { + this.callback = callback; + + sites.addAll(Sites.allSites()); + + this.callback.setAddedSites(sites); + + this.callback.setNextAllowed(!sites.isEmpty(), false); + } + + public boolean mayExit() { + return false; + } + + public void onShowDialogClicked() { + callback.showAddDialog(); + } + + public void onAddClicked(String url) { + siteManager.addSite(url, new SiteManager.SiteAddCallback() { + @Override + public void onSiteAdded(Site site) { + siteAdded(site); + } + + @Override + public void onSiteAddFailed(String message) { + // TODO + } + }); + } + + public void onDoneClicked() { + callback.dismiss(); + } + + public int getSiteBoardCount(Site site) { + return boardManager.getSiteSavedBoards(site).size(); + } + + private void siteAdded(Site site) { + sites.clear(); + sites.addAll(Sites.allSites()); + + callback.setAddedSites(sites); + + callback.setNextAllowed(!sites.isEmpty(), true); + } + + public void onSiteCellSettingsClicked(Site site) { + callback.openSiteConfiguration(site); + } + + public interface Callback { + void showAddDialog(); + + void dismiss(); + + void setAddedSites(List sites); + + void setNextAllowed(boolean nextAllowed, boolean animate); + + void openSiteConfiguration(Site site); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index 8a2543e4..70e4db7a 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -161,7 +161,7 @@ public class ChanSettings { static { SharedPreferences p = AndroidUtils.getPreferences(); - theme = new StringSetting(p, "preference_theme", "light"); + theme = new StringSetting(p, "preference_theme", "yotsuba"); layoutMode = new OptionsSetting<>(p, "preference_layout_mode", LayoutMode.values(), LayoutMode.AUTO); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index 829d0815..cbaed4c6 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java @@ -20,7 +20,6 @@ package org.floens.chan.ui.activity; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; -import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NfcAdapter; import android.nfc.NfcEvent; @@ -35,7 +34,6 @@ import android.view.ViewGroup; import org.floens.chan.Chan; import org.floens.chan.R; -import org.floens.chan.chan.ChanHelper; import org.floens.chan.controller.Controller; import org.floens.chan.controller.NavigationController; import org.floens.chan.core.database.DatabaseLoadableManager; @@ -51,7 +49,7 @@ import org.floens.chan.core.site.Sites; import org.floens.chan.ui.controller.BrowseController; import org.floens.chan.ui.controller.DoubleNavigationController; import org.floens.chan.ui.controller.DrawerController; -import org.floens.chan.ui.controller.SiteSetupController; +import org.floens.chan.ui.controller.SetupController; import org.floens.chan.ui.controller.SplitNavigationController; import org.floens.chan.ui.controller.StyledToolbarNavigationController; import org.floens.chan.ui.controller.ThreadSlideController; @@ -135,7 +133,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat private void setupFromStateOrFreshLaunch(Bundle savedInstanceState) { boolean loadDefault = true; - if (savedInstanceState != null) { + /*if (savedInstanceState != null) { // Restore the activity state from the previously saved state. ChanState chanState = savedInstanceState.getParcelable(STATE_KEY); if (chanState == null) { @@ -177,19 +175,23 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat .show(); } } - } + }*/ // Not from a state or from an url, launch the setup controller if no boards are setup up yet, // otherwise load the default saved board. if (loadDefault) { - if (boardManager.getSavedBoards().isEmpty()) { - mainNavigationController.pushController(new SiteSetupController(this), false); + if (true || boardManager.getSavedBoards().isEmpty()) { + setupWithNoBoards(); } else { browseController.loadDefault(); } } } + private void setupWithNoBoards() { + mainNavigationController.presentController(new SetupController(this), false); + } + private Pair resolveChanState(ChanState state) { DatabaseLoadableManager loadableManager = databaseManager.getDatabaseLoadableManager(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardSetupController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardSetupController.java index 6e92acf2..74599672 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardSetupController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BoardSetupController.java @@ -18,9 +18,13 @@ package org.floens.chan.ui.controller; +import android.annotation.SuppressLint; import android.content.Context; +import android.content.DialogInterface; import android.graphics.drawable.Drawable; +import android.support.design.widget.FloatingActionButton; import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; @@ -28,11 +32,8 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AutoCompleteTextView; -import android.widget.BaseAdapter; -import android.widget.Filter; -import android.widget.Filterable; +import android.view.Window; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; @@ -40,61 +41,31 @@ import org.floens.chan.R; import org.floens.chan.controller.Controller; import org.floens.chan.core.model.orm.Board; import org.floens.chan.core.presenter.BoardSetupPresenter; -import org.floens.chan.core.site.SiteIcon; +import org.floens.chan.core.site.Site; +import org.floens.chan.ui.helper.BoardHelper; +import org.floens.chan.ui.layout.BoardAddLayout; -import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import static org.floens.chan.Chan.getGraph; -import static org.floens.chan.ui.helper.PostHelper.formatBoardCodeAndName; +import static org.floens.chan.ui.theme.ThemeHelper.theme; import static org.floens.chan.utils.AndroidUtils.getAttrColor; -public class BoardSetupController extends Controller implements View.OnClickListener, AdapterView.OnItemClickListener, BoardSetupPresenter.Callback { +public class BoardSetupController extends Controller implements View.OnClickListener, BoardSetupPresenter.Callback { + private static final int DONE_ID = 1; + @Inject BoardSetupPresenter presenter; - private AutoCompleteTextView code; private RecyclerView savedBoardsRecycler; - - private SuggestBoardsAdapter suggestAdapter; + private FloatingActionButton add; private SavedBoardsAdapter savedAdapter; private ItemTouchHelper itemTouchHelper; - public BoardSetupController(Context context) { - super(context); - } - - @Override - public void onCreate() { - super.onCreate(); - - getGraph().inject(this); - - view = inflateRes(R.layout.controller_board_setup); - navigationItem.setTitle(R.string.saved_boards_title); - navigationItem.swipeable = false; - - code = (AutoCompleteTextView) view.findViewById(R.id.code); - code.setOnItemClickListener(this); - savedBoardsRecycler = (RecyclerView) view.findViewById(R.id.boards_recycler); - savedBoardsRecycler.setLayoutManager(new LinearLayoutManager(context)); - - savedAdapter = new SavedBoardsAdapter(); - savedBoardsRecycler.setAdapter(savedAdapter); - - itemTouchHelper = new ItemTouchHelper(touchHelperCallback); - itemTouchHelper.attachToRecyclerView(savedBoardsRecycler); - - suggestAdapter = new SuggestBoardsAdapter(); - - code.setAdapter(suggestAdapter); - code.setThreshold(1); - - presenter.create(this); - } + private Site site; private ItemTouchHelper.SimpleCallback touchHelperCallback = new ItemTouchHelper.SimpleCallback( ItemTouchHelper.UP | ItemTouchHelper.DOWN, @@ -118,88 +89,88 @@ public class BoardSetupController extends Controller implements View.OnClickList } }; - @Override - public void setSavedBoards(List savedBoards) { - savedAdapter.setSavedBoards(savedBoards); - } - - @Override - public void onClick(View v) { + public BoardSetupController(Context context) { + super(context); } @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - BoardSetupPresenter.BoardSuggestion suggestion = suggestAdapter.getSuggestion(position); - - presenter.addFromSuggestion(suggestion); - } - - private class SuggestBoardsAdapter extends BaseAdapter implements Filterable { - private List suggestions = new ArrayList<>(); - - @Override - public int getCount() { - return suggestions.size(); - } + public void onCreate() { + super.onCreate(); - @Override - public String getItem(int position) { - return getSuggestion(position).key; - } + getGraph().inject(this); - public BoardSetupPresenter.BoardSuggestion getSuggestion(int position) { - return suggestions.get(position); - } + // Inflate + view = inflateRes(R.layout.controller_board_setup); - @Override - public long getItemId(int position) { - return position; - } + // Navigation + navigationItem.title = context.getString(R.string.setup_board_title, site.name()); - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view = LayoutInflater.from(context).inflate(R.layout.cell_board_suggestion, parent, false); + // View binding + savedBoardsRecycler = view.findViewById(R.id.boards_recycler); + add = view.findViewById(R.id.add); - final ImageView image = (ImageView) view.findViewById(R.id.image); - TextView text = (TextView) view.findViewById(R.id.text); + // Adapters + savedAdapter = new SavedBoardsAdapter(); - BoardSetupPresenter.BoardSuggestion suggestion = getSuggestion(position); + // View setup + savedBoardsRecycler.setLayoutManager(new LinearLayoutManager(context)); + savedBoardsRecycler.setAdapter(savedAdapter); + itemTouchHelper = new ItemTouchHelper(touchHelperCallback); + itemTouchHelper.attachToRecyclerView(savedBoardsRecycler); + add.setOnClickListener(this); + theme().applyFabColor(add); - final SiteIcon icon = suggestion.site.icon(); - icon.get(new SiteIcon.SiteIconResult() { - @Override - public void onSiteIcon(SiteIcon siteIcon, Drawable icon) { - // TODO: don't if recycled - image.setImageDrawable(icon); - } - }); + // Presenter + presenter.create(this, site); + } - text.setText(suggestion.site.name() + " \u2013 /" + suggestion.key + "/"); + public void setSite(Site site) { + this.site = site; + } - return view; + @Override + public void onClick(View v) { + if (v == add) { + presenter.addClicked(); } + } - @Override - public Filter getFilter() { - return new Filter() { - @Override - protected FilterResults performFiltering(CharSequence constraint) { - // Invoked on a worker thread, do not use. - return null; - } + @Override + public void showAddDialog() { + @SuppressLint("InflateParams") final BoardAddLayout boardAddLayout = + (BoardAddLayout) LayoutInflater.from(context) + .inflate(R.layout.layout_board_add, null); + + boardAddLayout.setPresenter(presenter); + + AlertDialog dialog = new AlertDialog.Builder(context) + .setView(boardAddLayout) +// .setTitle(R.string.setup_board_add) + .setPositiveButton(R.string.add, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + boardAddLayout.onPositiveClicked(); + } + }) + .setNegativeButton(R.string.cancel, null) + .create(); - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - suggestions.clear(); + boardAddLayout.setDialog(dialog); - if (constraint != null) { - suggestions.addAll(presenter.getSuggestionsForQuery(constraint.toString())); - } + Window window = dialog.getWindow(); + assert window != null; + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + dialog.show(); + } - notifyDataSetChanged(); - } - }; - } + @Override + public void setSavedBoards(List savedBoards) { + savedAdapter.setSavedBoards(savedBoards); + } + + @Override + public void finish() { + navigationController.popController(); } private class SavedBoardsAdapter extends RecyclerView.Adapter { @@ -221,13 +192,16 @@ public class BoardSetupController extends Controller implements View.OnClickList @Override public SavedBoardCell onCreateViewHolder(ViewGroup parent, int viewType) { - return new SavedBoardCell(LayoutInflater.from(context).inflate(R.layout.cell_saved_board, parent, false)); + return new SavedBoardCell( + LayoutInflater.from(context) + .inflate(R.layout.cell_board, parent, false)); } @Override public void onBindViewHolder(SavedBoardCell holder, int position) { Board savedBoard = savedBoards.get(position); - holder.setSavedBoard(savedBoard); + holder.text.setText(BoardHelper.getName(savedBoard)); + holder.description.setText(BoardHelper.getDescription(savedBoard)); } @Override @@ -237,23 +211,23 @@ public class BoardSetupController extends Controller implements View.OnClickList } private class SavedBoardCell extends RecyclerView.ViewHolder { - private ImageView image; private TextView text; - private SiteIcon siteIcon; + private TextView description; private ImageView reorder; public SavedBoardCell(View itemView) { super(itemView); - image = (ImageView) itemView.findViewById(R.id.image); - text = (TextView) itemView.findViewById(R.id.text); - reorder = (ImageView) itemView.findViewById(R.id.reorder); + text = itemView.findViewById(R.id.text); + description = itemView.findViewById(R.id.description); + reorder = itemView.findViewById(R.id.reorder); Drawable drawable = DrawableCompat.wrap(context.getResources().getDrawable(R.drawable.ic_reorder_black_24dp)).mutate(); DrawableCompat.setTint(drawable, getAttrColor(context, R.attr.text_color_hint)); reorder.setImageDrawable(drawable); - reorder.setOnTouchListener(new View.OnTouchListener() { + View.OnTouchListener l = new View.OnTouchListener() { + @SuppressLint("ClickableViewAccessibility") @Override public boolean onTouch(View v, MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { @@ -261,21 +235,8 @@ public class BoardSetupController extends Controller implements View.OnClickList } return false; } - }); - } - - public void setSavedBoard(Board savedBoard) { - siteIcon = savedBoard.site.icon(); - siteIcon.get(new SiteIcon.SiteIconResult() { - @Override - public void onSiteIcon(SiteIcon siteIcon, Drawable icon) { - if (SavedBoardCell.this.siteIcon == siteIcon) { - image.setImageDrawable(icon); - } - } - }); - - text.setText(formatBoardCodeAndName(savedBoard)); + }; + reorder.setOnTouchListener(l); } } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java index e75204ff..c2185121 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java @@ -39,6 +39,7 @@ import org.floens.chan.core.model.orm.Pin; import org.floens.chan.core.presenter.ThreadPresenter; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.adapter.PostsFilter; +import org.floens.chan.ui.helper.BoardHelper; import org.floens.chan.ui.layout.ThreadLayout; import org.floens.chan.ui.toolbar.ToolbarMenu; import org.floens.chan.ui.toolbar.ToolbarMenuItem; @@ -329,8 +330,8 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte public void loadBoard(Board board) { Loadable loadable = databaseManager.getDatabaseLoadableManager().get(Loadable.forCatalog(board)); - loadable.title = board.getName(); - navigationItem.title = board.getName(); + loadable.title = BoardHelper.getName(board); + navigationItem.title = BoardHelper.getName(board); ThreadPresenter presenter = threadLayout.getPresenter(); presenter.unbindLoadable(); @@ -353,7 +354,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte private void loadBoards() { List boards = boardManager.getSavedBoards(); - boolean wasEmpty = boardItems == null; + boolean wasEmpty = boardItems == null || boardItems.isEmpty(); boardItems = new ArrayList<>(); for (Board board : boards) { @@ -373,7 +374,7 @@ public class BrowseController extends ThreadController implements ToolbarMenuIte public Board board; public FloatingMenuItemBoard(Board board) { - super(board.id, board.getName()); + super(board.id, BoardHelper.getName(board)); this.board = board; } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java index 109e3f0b..6af88890 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java @@ -40,8 +40,9 @@ public class ImageViewerNavigationController extends ToolbarNavigationController view = inflateRes(R.layout.controller_navigation_image_viewer); container = (NavigationControllerContainerLayout) view.findViewById(R.id.container); - container.setNavigationController(this); - container.setSwipeEnabled(false); + NavigationControllerContainerLayout nav = (NavigationControllerContainerLayout) container; + nav.setNavigationController(this); + nav.setSwipeEnabled(false); toolbar = (Toolbar) view.findViewById(R.id.toolbar); toolbar.setCallback(this); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/IntroController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/IntroController.java new file mode 100644 index 00000000..0e4be484 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/IntroController.java @@ -0,0 +1,52 @@ +/* + * 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 . + */ +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; +import org.floens.chan.core.presenter.SetupPresenter; + +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) { + SetupPresenter presenter = ((SetupController) navigationController).getPresenter(); + presenter.startClicked(); + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/PopupController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/PopupController.java index 86e10f5e..7873929b 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/PopupController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/PopupController.java @@ -55,8 +55,8 @@ public class PopupController extends Controller implements View.OnClickListener } public void dismiss() { - if (presentingByController instanceof DoubleNavigationController) { - ((SplitNavigationController) presentingByController).popAll(); + if (presentedByController instanceof DoubleNavigationController) { + ((SplitNavigationController) presentedByController).popAll(); } } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/SetupController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/SetupController.java new file mode 100644 index 00000000..4b33d91a --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/SetupController.java @@ -0,0 +1,82 @@ +/* + * 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 . + */ +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() { + replaceController(new SitesSetupController(context), 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(); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java index a1807457..0309d8ad 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java @@ -17,62 +17,26 @@ */ package org.floens.chan.ui.controller; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ArgbEvaluator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.annotation.TargetApi; import android.content.Context; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.support.v4.view.animation.PathInterpolatorCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroupOverlay; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.TextView; import org.floens.chan.R; -import org.floens.chan.controller.transition.FadeInTransition; import org.floens.chan.core.presenter.SiteSetupPresenter; import org.floens.chan.core.site.Site; -import org.floens.chan.core.site.SiteIcon; -import org.floens.chan.ui.animation.AnimationUtils; - -import java.util.ArrayList; -import java.util.List; +import org.floens.chan.ui.settings.LinkSettingView; +import org.floens.chan.ui.settings.SettingsController; +import org.floens.chan.ui.settings.SettingsGroup; import javax.inject.Inject; import static org.floens.chan.Chan.getGraph; -import static org.floens.chan.utils.AndroidUtils.dp; -import static org.floens.chan.utils.AndroidUtils.getAttrColor; -public class SiteSetupController extends StyledToolbarNavigationController implements View.OnClickListener, SiteSetupPresenter.Callback { +public class SiteSetupController extends SettingsController implements SiteSetupPresenter.Callback { @Inject SiteSetupPresenter presenter; - private EditText url; - private View urlSubmit; - private View spinner; - private Button next; - - private boolean blocked = false; - - private RecyclerView sitesRecyclerview; - private SitesAdapter sitesAdapter; - private List sites = new ArrayList<>(); + private Site site; + private LinkSettingView boardsLink; public SiteSetupController(Context context) { super(context); @@ -84,292 +48,58 @@ public class SiteSetupController extends StyledToolbarNavigationController imple getGraph().inject(this); - view = inflateRes(R.layout.controller_site_setup); - navigationItem.setTitle(R.string.setup_title); - - url = (EditText) view.findViewById(R.id.site_url); - urlSubmit = view.findViewById(R.id.site_url_submit); - urlSubmit.setOnClickListener(this); - spinner = view.findViewById(R.id.progress); - sitesRecyclerview = (RecyclerView) view.findViewById(R.id.sites_recycler); - sitesRecyclerview.setLayoutManager(new LinearLayoutManager(context)); - next = (Button) view.findViewById(R.id.next_button); - next.setOnClickListener(this); - - sitesAdapter = new SitesAdapter(); - sitesRecyclerview.setAdapter(sitesAdapter); - - presenter.create(this); - } + // Navigation + navigationItem.setTitle(R.string.settings_screen); + navigationItem.title = context.getString(R.string.setup_site_title, site.name()); - @Override - public void onClick(View v) { - if (blocked) return; + // View binding + view = inflateRes(R.layout.settings_layout); + content = view.findViewById(R.id.scrollview_content); - if (v == urlSubmit) { - presenter.onUrlSubmitClicked(url.getText().toString()); - } else if (v == next) { - presenter.onNextClicked(); -// navigationController.popController(false); - } - } + // Preferences + populatePreferences(); + buildPreferences(); - @Override - public boolean onBack() { - if (presenter.mayExit()) { - return super.onBack(); - } else { - return true; - } + // Presenter + presenter.create(this, site); } - @Override - public void moveToSavedBoards() { - navigationController.pushController(new BoardSetupController(context), new FadeInTransition()); + public void setSite(Site site) { + this.site = site; } @Override - public void goToUrlSubmittedState() { - spinner.setVisibility(View.VISIBLE); - urlSubmit.setVisibility(View.INVISIBLE); + public void onShow() { + super.onShow(); + presenter.show(); } @Override - public void runSiteAddedAnimation(Site site) { - spinner.setVisibility(View.INVISIBLE); - urlSubmit.setVisibility(View.VISIBLE); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - runSiteAddedAnimationInternal(site); - } else { - resetUrl(); - } - } - - @Override - public void showUrlHint(String text) { - spinner.setVisibility(View.INVISIBLE); - urlSubmit.setVisibility(View.VISIBLE); - - url.setError(text, null); - } - - @Override - public void setAddedSites(List sites) { - this.sites.clear(); - this.sites.addAll(sites); - sitesAdapter.notifyDataSetChanged(); - } - - @Override - public void setNextAllowed(boolean nextAllowed, boolean animate) { - next.setEnabled(nextAllowed); - int newBackground = getAttrColor(context, nextAllowed ? R.attr.colorAccent : R.attr.backcolor); - int newTextColor = nextAllowed ? Color.WHITE : getAttrColor(context, R.attr.text_color_hint); - if (animate) { - AnimationUtils.animateTextColor(next, newTextColor); - AnimationUtils.animateBackgroundColorDrawable(next, newBackground); - } else { - next.setBackgroundColor(newBackground); - next.setTextColor(newTextColor); - } - } - - private void resetUrl() { - url.setText(""); - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) - private void runSiteAddedAnimationInternal(Site site) { - blocked = true; - sitesAdapter.invisibleSiteOnBind = site; - - SiteCell siteCell = new SiteCell(LayoutInflater.from(context).inflate(R.layout.cell_site, null)); - siteCell.setSite(site); - final View siteCellView = siteCell.itemView; - final View siteCellIcon = siteCell.image; - siteCellView.measure(View.MeasureSpec.makeMeasureSpec(url.getMeasuredWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); - siteCellView.layout(0, 0, siteCellView.getMeasuredWidth(), siteCellView.getMeasuredHeight()); - - final ViewGroupOverlay overlay = view.getOverlay(); - - overlay.add(siteCellView); - - int[] urlWinLoc = new int[2]; - url.getLocationInWindow(urlWinLoc); - int[] recWinLoc = new int[2]; - sitesRecyclerview.getLocationInWindow(recWinLoc); - recWinLoc[0] += sitesRecyclerview.getPaddingLeft(); - recWinLoc[1] += sitesRecyclerview.getPaddingTop() + siteCellView.getMeasuredHeight() * (sites.size() - 1); - int[] viewWinLoc = new int[2]; - view.getLocationInWindow(viewWinLoc); - - String urlText = url.getText().toString(); - int indexOf = urlText.indexOf(site.name()); - int offsetLeft = 0; - if (indexOf > 0) { - Paint paint = new Paint(); - paint.setTextSize(url.getTextSize()); - offsetLeft = (int) paint.measureText(urlText.substring(0, indexOf)); - } - - final int staX = urlWinLoc[0] - viewWinLoc[0] - dp(48) + dp(4) + offsetLeft; - final int staY = urlWinLoc[1] - viewWinLoc[1] - dp(4); - final int desX = recWinLoc[0] - viewWinLoc[0]; - final int desY = recWinLoc[1] - viewWinLoc[1]; - - siteCellView.setTranslationX(staX); - siteCellView.setTranslationY(staY); - - final int textColor = url.getCurrentTextColor(); - final int textHintColor = url.getCurrentHintTextColor(); - - ValueAnimator textAlpha = ValueAnimator.ofObject(new ArgbEvaluator(), - textColor, textColor & 0xffffff); - textAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - url.setTextColor((int) animation.getAnimatedValue()); - } - }); - textAlpha.setDuration(300); - textAlpha.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - resetUrl(); - } - }); - - url.setCursorVisible(false); - url.setHintTextColor(0); - - ValueAnimator alpha = ObjectAnimator.ofFloat(siteCellView, View.ALPHA, 0f, 1f); - alpha.setDuration(300); - - ValueAnimator iconAlpha = ValueAnimator.ofFloat(0f, 1f); - iconAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - siteCellIcon.setAlpha((float) animation.getAnimatedValue()); - } - }); - iconAlpha.setDuration(400); - iconAlpha.setStartDelay(500); - siteCellIcon.setAlpha(0f); - - ValueAnimator horizontal = ValueAnimator.ofFloat(0f, 1f); - horizontal.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - siteCellView.setTranslationX(staX + (desX - staX) * value); - } - }); - horizontal.setDuration(600); - horizontal.setStartDelay(400); - - ValueAnimator vertical = ValueAnimator.ofFloat(0f, 1f); - vertical.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - siteCellView.setTranslationY(staY + (desY - staY) * value); - } - }); - - Path path = new Path(); - - float jump = 300f / Math.abs(desY - staY); - path.cubicTo(0.5f, 0f - jump, 0.75f, 1.0f, 1f, 1f); - vertical.setInterpolator(PathInterpolatorCompat.create(path)); - vertical.setDuration(600); - vertical.setStartDelay(400); - - ValueAnimator hintAlpha = ValueAnimator.ofObject(new ArgbEvaluator(), - textHintColor & 0xffffff, textHintColor); - hintAlpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - url.setHintTextColor((int) animation.getAnimatedValue()); - } - }); - hintAlpha.setDuration(300); - hintAlpha.setStartDelay(1000); - - AnimatorSet set = new AnimatorSet(); - set.playTogether(alpha, textAlpha, iconAlpha, horizontal, vertical, hintAlpha); - set.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - blocked = false; - overlay.remove(siteCellView); - sitesAdapter.invisibleSiteOnBind = null; - for (int i = 0; i < sitesRecyclerview.getChildCount(); i++) { - sitesRecyclerview.getChildAt(i).setVisibility(View.VISIBLE); - } - url.setTextColor(textColor); - url.setHintTextColor(textHintColor); - url.setCursorVisible(true); - } - }); - set.start(); + public void setBoardCount(int boardCount) { + String boardsString = context.getResources().getQuantityString( + R.plurals.board, boardCount, boardCount); + String descriptionText = context.getString( + R.string.setup_site_boards_description, boardsString); + boardsLink.setDescription(descriptionText); } - private class SitesAdapter extends RecyclerView.Adapter { - private Site invisibleSiteOnBind; - - public SitesAdapter() { - setHasStableIds(true); - } - - @Override - public long getItemId(int position) { - return sites.get(position).id(); - } - - @Override - public SiteCell onCreateViewHolder(ViewGroup parent, int viewType) { - return new SiteCell(LayoutInflater.from(context).inflate(R.layout.cell_site, parent, false)); - } - - @Override - public void onBindViewHolder(SiteCell holder, int position) { - Site site = sites.get(position); - holder.setSite(site); - if (site == invisibleSiteOnBind) { - holder.itemView.setVisibility(View.INVISIBLE); - } - } - - @Override - public int getItemCount() { - return sites.size(); - } - } - - private class SiteCell extends RecyclerView.ViewHolder { - private ImageView image; - private TextView text; - private SiteIcon siteIcon; - - public SiteCell(View itemView) { - super(itemView); - image = (ImageView) itemView.findViewById(R.id.image); - text = (TextView) itemView.findViewById(R.id.text); - } - - private void setSite(Site site) { - siteIcon = site.icon(); - siteIcon.get(new SiteIcon.SiteIconResult() { - @Override - public void onSiteIcon(SiteIcon siteIcon, Drawable icon) { - if (SiteCell.this.siteIcon == siteIcon) { - image.setImageDrawable(icon); + private void populatePreferences() { + SettingsGroup general = new SettingsGroup(R.string.setup_site_group_general); + + boardsLink = new LinkSettingView( + this, + context.getString(R.string.setup_site_boards), + "", + new View.OnClickListener() { + @Override + public void onClick(View v) { + BoardSetupController boardSetupController = new BoardSetupController(context); + boardSetupController.setSite(site); + navigationController.pushController(boardSetupController); } - } - }); + }); + general.add(boardsLink); - text.setText(site.name()); - } + groups.add(general); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/SitesSetupController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/SitesSetupController.java new file mode 100644 index 00000000..0f98558a --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/SitesSetupController.java @@ -0,0 +1,258 @@ +/* + * 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 . + */ +package org.floens.chan.ui.controller; + + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.drawable.Drawable; +import android.support.design.widget.FloatingActionButton; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.floens.chan.R; +import org.floens.chan.core.presenter.SitesSetupPresenter; +import org.floens.chan.core.site.Site; +import org.floens.chan.core.site.SiteIcon; +import org.floens.chan.ui.layout.SiteAddLayout; +import org.floens.chan.ui.toolbar.ToolbarMenu; +import org.floens.chan.ui.toolbar.ToolbarMenuItem; +import org.floens.chan.ui.view.FloatingMenuItem; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import static org.floens.chan.Chan.getGraph; +import static org.floens.chan.ui.theme.ThemeHelper.theme; +import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground; + +public class SitesSetupController extends StyledToolbarNavigationController implements SitesSetupPresenter.Callback, ToolbarMenuItem.ToolbarMenuItemCallback, View.OnClickListener { + private static final int DONE_ID = 1; + + @Inject + SitesSetupPresenter presenter; + + private ToolbarMenuItem doneMenuItem; + + private RecyclerView sitesRecyclerview; + private FloatingActionButton addButton; + + private SitesAdapter sitesAdapter; + private List sites = new ArrayList<>(); + + public SitesSetupController(Context context) { + super(context); + } + + @Override + public void onCreate() { + super.onCreate(); + + getGraph().inject(this); + + // Inflate + view = inflateRes(R.layout.controller_sites_setup); + + // Navigation + navigationItem.setTitle(R.string.setup_sites_title); + navigationItem.swipeable = false; + navigationItem.menu = new ToolbarMenu(context); + doneMenuItem = navigationItem.menu.addItem( + new ToolbarMenuItem(context, this, DONE_ID, 0, R.drawable.ic_done_white_24dp)); + doneMenuItem.getView().setAlpha(0f); + + // View binding + sitesRecyclerview = view.findViewById(R.id.sites_recycler); + addButton = view.findViewById(R.id.add); + + // Adapters + sitesAdapter = new SitesAdapter(); + + // View setup + sitesRecyclerview.setLayoutManager(new LinearLayoutManager(context)); + sitesRecyclerview.setAdapter(sitesAdapter); + addButton.setOnClickListener(this); + theme().applyFabColor(addButton); + + // Presenter + presenter.create(this); + } + + @Override + public void onMenuItemClicked(ToolbarMenuItem item) { + if ((Integer) item.getId() == DONE_ID) { + presenter.onDoneClicked(); + } + } + + @Override + public void onSubMenuItemClicked(ToolbarMenuItem parent, FloatingMenuItem item) { + } + + @Override + public void onClick(View v) { + if (v == addButton) { + presenter.onShowDialogClicked(); + } + } + + @Override + public boolean onBack() { + if (presenter.mayExit()) { + return super.onBack(); + } else { + return true; + } + } + + @Override + public void showAddDialog() { + @SuppressLint("InflateParams") final SiteAddLayout dialogView = + (SiteAddLayout) LayoutInflater.from(context) + .inflate(R.layout.layout_site_add, null); + + dialogView.setPresenter(presenter); + + new AlertDialog.Builder(context) + .setView(dialogView) + .setTitle(R.string.setup_sites_add_title) + .setPositiveButton(R.string.add, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialogView.onPositiveClicked(); + } + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + + @Override + public void dismiss() { + navigationController.stopPresenting(); + } + + @Override + public void openSiteConfiguration(Site site) { + SiteSetupController c = new SiteSetupController(context); + c.setSite(site); + navigationController.pushController(c); + } + + @Override + public void setAddedSites(List sites) { + this.sites.clear(); + this.sites.addAll(sites); + sitesAdapter.notifyDataSetChanged(); + } + + @Override + public void setNextAllowed(boolean nextAllowed, boolean animate) { + doneMenuItem.getView().animate().alpha(nextAllowed ? 1f : 0f).start(); + } + + private void onSiteCellSettingsClicked(Site site) { + presenter.onSiteCellSettingsClicked(site); + } + + private class SitesAdapter extends RecyclerView.Adapter { + public SitesAdapter() { + setHasStableIds(true); + } + + @Override + public long getItemId(int position) { + return sites.get(position).id(); + } + + @Override + public SiteCell onCreateViewHolder(ViewGroup parent, int viewType) { + return new SiteCell(LayoutInflater.from(context).inflate(R.layout.cell_site, parent, false)); + } + + @Override + public void onBindViewHolder(SiteCell holder, int position) { + Site site = sites.get(position); + holder.setSite(site); + holder.setSiteIcon(site); + holder.text.setText(site.name()); + + int boards = presenter.getSiteBoardCount(site); + String boardsString = context.getResources().getQuantityString(R.plurals.board, boards, boards); + String descriptionText = context.getString(R.string.setup_sites_site_description, boardsString); + holder.description.setText(descriptionText); + } + + @Override + public int getItemCount() { + return sites.size(); + } + } + + private class SiteCell extends RecyclerView.ViewHolder implements View.OnClickListener { + private ImageView image; + private TextView text; + private TextView description; + private SiteIcon siteIcon; + private ImageView settings; + + private Site site; + + public SiteCell(View itemView) { + super(itemView); + image = itemView.findViewById(R.id.image); + text = itemView.findViewById(R.id.text); + description = itemView.findViewById(R.id.description); + settings = itemView.findViewById(R.id.settings); + setRoundItemBackground(settings); + theme().settingsDrawable.apply(settings); + settings.setOnClickListener(this); + } + + private void setSite(Site site) { + this.site = site; + } + + private void setSiteIcon(Site site) { + siteIcon = site.icon(); + siteIcon.get(new SiteIcon.SiteIconResult() { + @Override + public void onSiteIcon(SiteIcon siteIcon, Drawable icon) { + if (SiteCell.this.siteIcon == siteIcon) { + image.setImageDrawable(icon); + } + } + }); + } + + @Override + public void onClick(View v) { + if (v == settings) { + onSiteCellSettingsClicked(site); + } + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java index 7da1965c..9e4d05d7 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java @@ -38,8 +38,9 @@ public class StyledToolbarNavigationController extends ToolbarNavigationControll view = inflateRes(R.layout.controller_navigation_toolbar); container = (NavigationControllerContainerLayout) view.findViewById(R.id.container); - container.setNavigationController(this); - container.setSwipeEnabled(ChanSettings.controllerSwipeable.get()); + NavigationControllerContainerLayout nav = (NavigationControllerContainerLayout) container; + nav.setNavigationController(this); + nav.setSwipeEnabled(ChanSettings.controllerSwipeable.get()); toolbar = (Toolbar) view.findViewById(R.id.toolbar); toolbar.setBackgroundColor(ThemeHelper.getInstance().getTheme().primaryColor.color); toolbar.setCallback(this); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java index 9ac14bcd..4bfe30db 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java @@ -29,6 +29,7 @@ import org.floens.chan.ui.toolbar.Toolbar; public abstract class ToolbarNavigationController extends NavigationController implements Toolbar.ToolbarCallback { protected Toolbar toolbar; + protected boolean requireSpaceForToolbar = true; public ToolbarNavigationController(Context context) { super(context); @@ -123,7 +124,7 @@ public abstract class ToolbarNavigationController extends NavigationController i } protected void updateToolbarCollapse(Controller controller, boolean animate) { - if (!controller.navigationItem.handlesToolbarInset) { + if (requireSpaceForToolbar && !controller.navigationItem.handlesToolbarInset) { FrameLayout.LayoutParams toViewParams = (FrameLayout.LayoutParams) controller.view.getLayoutParams(); toViewParams.topMargin = toolbar.getToolbarHeight(); controller.view.setLayoutParams(toViewParams); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/BoardHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/BoardHelper.java index daa5f29d..23b1e1e1 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/BoardHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/BoardHelper.java @@ -17,15 +17,63 @@ */ package org.floens.chan.ui.helper; +import android.util.Pair; + import org.floens.chan.core.model.orm.Board; +import org.floens.chan.utils.Logger; import org.jsoup.parser.Parser; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import me.xdrop.fuzzywuzzy.FuzzySearch; + public class BoardHelper { + private static final String TAG = "BoardHelper"; + public static String getName(Board board) { - return "/" + board.code + "/ " + board.name; + return "/" + board.code + "/ \u2013 " + board.name; } public static String getDescription(Board board) { return board.description == null ? null : Parser.unescapeEntities(board.description, false); } + + public static List search(List from, final String query) { + List> ratios = new ArrayList<>(); + for (Board board : from) { + int ratio = getTokenSortRatio(board, query); + + if (ratio > 2) { + ratios.add(new Pair<>(board, ratio)); + } + } + + Collections.sort(ratios, new Comparator>() { + @Override + public int compare(Pair o1, Pair o2) { + return o2.second - o1.second; + } + }); + + List result = new ArrayList<>(ratios.size()); + for (Pair ratio : ratios) { + result.add(ratio.first); + } + return result; + } + + private static int getTokenSortRatio(Board board, String query) { + int code = FuzzySearch.ratio(board.code, query); + int name = FuzzySearch.ratio(board.name, query); + int description = FuzzySearch.weightedRatio(String.valueOf(getDescription(board)), query); + + Logger.d(TAG, board.code + " = code = " + code + ", name = " + name + ", desc = " + description); + + return code * 4 + + name * 5 + + Math.max(0, description - 30) * 8; + } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java index 376da0d5..1531eca7 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java @@ -26,7 +26,6 @@ import android.text.style.ImageSpan; import org.floens.chan.R; 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.utils.AndroidUtils; @@ -87,14 +86,6 @@ public class PostHelper { } } - public static String formatSiteAndBoardName(Board board) { - return board.site.name() + " \u2013 /" + board.name + "/"; - } - - public static String formatBoardCodeAndName(Board board) { - return "/" + board.code + "/ \u2013 " + board.name; - } - private static SimpleDateFormat dateFormat = new SimpleDateFormat("LL/dd/yy(EEE)HH:mm:ss", Locale.US); private static Date tmpDate = new Date(); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/BoardAddLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/BoardAddLayout.java new file mode 100644 index 00000000..4e4e9f9c --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/BoardAddLayout.java @@ -0,0 +1,174 @@ +/* + * 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 . + */ +package org.floens.chan.ui.layout; + +import android.content.Context; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.floens.chan.R; +import org.floens.chan.core.presenter.BoardSetupPresenter; + +import static org.floens.chan.ui.theme.ThemeHelper.theme; +import static org.floens.chan.utils.AndroidUtils.getAttrColor; +import static org.floens.chan.utils.AndroidUtils.getString; + +public class BoardAddLayout extends LinearLayout implements SearchLayout.SearchLayoutCallback, BoardSetupPresenter.AddCallback { + private BoardSetupPresenter presenter; + + private SuggestionsAdapter suggestionsAdapter; + + private SearchLayout search; + private RecyclerView suggestionsRecycler; + + private AlertDialog dialog; + + public BoardAddLayout(Context context) { + this(context, null); + } + + public BoardAddLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BoardAddLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + // View binding + search = findViewById(R.id.search); + suggestionsRecycler = findViewById(R.id.suggestions); + + // Adapters + suggestionsAdapter = new SuggestionsAdapter(); + + // View setup + search.setCallback(this); + search.setHint(getString(R.string.search_hint)); + search.setTextColor(getAttrColor(getContext(), R.attr.text_color_primary)); + search.setHintColor(getAttrColor(getContext(), R.attr.text_color_hint)); + search.setClearButtonImage(R.drawable.ic_clear_black_24dp); + search.openKeyboard(); + suggestionsRecycler.setLayoutManager(new LinearLayoutManager(getContext())); + suggestionsRecycler.setAdapter(suggestionsAdapter); + + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + presenter.setAddCallback(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + presenter.setAddCallback(null); + } + + @Override + public void onSearchEntered(String entered) { + presenter.searchEntered(entered); + } + + @Override + public void updateSuggestions() { + suggestionsAdapter.notifyDataSetChanged(); + } + + public void setPresenter(BoardSetupPresenter presenter) { + this.presenter = presenter; + } + + public void setDialog(AlertDialog dialog) { + this.dialog = dialog; + } + + private void onSuggestionClicked(BoardSetupPresenter.BoardSuggestion suggestion) { + presenter.onSuggestionClicked(suggestion); + } + + public void onPositiveClicked() { + presenter.onAddDialogPositiveClicked(); + } + + private class SuggestionsAdapter extends RecyclerView.Adapter { + @Override + public int getItemCount() { + return presenter.getSuggestions().size(); + } + + @Override + public SuggestionCell onCreateViewHolder(ViewGroup parent, int viewType) { + return new SuggestionCell( + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.cell_board_suggestion, parent, false)); + } + + @Override + public void onBindViewHolder(SuggestionCell holder, int position) { + BoardSetupPresenter.BoardSuggestion boardSuggestion = presenter.getSuggestions().get(position); + holder.setSuggestion(boardSuggestion); + holder.text.setText(boardSuggestion.getName()); + holder.description.setText(boardSuggestion.getDescription()); + holder.check.setVisibility(boardSuggestion.isChecked() ? View.VISIBLE : View.INVISIBLE); + } + } + + private class SuggestionCell extends RecyclerView.ViewHolder implements OnClickListener { + private TextView text; + private TextView description; + private ImageView check; + + private BoardSetupPresenter.BoardSuggestion suggestion; + + public SuggestionCell(View itemView) { + super(itemView); + + text = itemView.findViewById(R.id.text); + description = itemView.findViewById(R.id.description); + check = itemView.findViewById(R.id.check); + theme().doneDrawable.apply(check); + + itemView.setOnClickListener(this); + } + + public void setSuggestion(BoardSetupPresenter.BoardSuggestion suggestion) { + this.suggestion = suggestion; + } + + @Override + public void onClick(View v) { + if (v == itemView) { + onSuggestionClicked(suggestion); + } + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java index ee712a55..5c7672aa 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java @@ -121,18 +121,21 @@ public class SearchLayout extends LinearLayout { searchView.setText(text); } + public String getText() { + return searchView.getText().toString(); + } + public void setHint(String hint) { searchView.setHint(hint); } public void openKeyboard() { - searchView.postDelayed(new Runnable() { + searchView.post(new Runnable() { @Override public void run() { - searchView.requestFocus(); - AndroidUtils.requestKeyboardFocus(searchView); + AndroidUtils.requestViewAndKeyboardFocus(searchView); } - }, 100); + }); } public interface SearchLayoutCallback { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/SiteAddLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/SiteAddLayout.java new file mode 100644 index 00000000..0d2cb715 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/SiteAddLayout.java @@ -0,0 +1,41 @@ +package org.floens.chan.ui.layout; + +import android.content.Context; +import android.support.constraint.ConstraintLayout; +import android.util.AttributeSet; +import android.widget.EditText; + +import org.floens.chan.R; +import org.floens.chan.core.presenter.SitesSetupPresenter; + +public class SiteAddLayout extends ConstraintLayout { + private EditText url; + private SitesSetupPresenter presenter; + + public SiteAddLayout(Context context) { + this(context, null); + } + + public SiteAddLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SiteAddLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + url = findViewById(R.id.url); + } + + public void setPresenter(SitesSetupPresenter presenter) { + this.presenter = presenter; + } + + public void onPositiveClicked() { + presenter.onAddClicked(url.getText().toString()); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java b/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java index cec1de21..b7223539 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java @@ -168,7 +168,7 @@ public class Theme { public Drawable makeDrawable(Context context) { //noinspection deprecation - Drawable d = context.getResources().getDrawable(drawable); + Drawable d = context.getResources().getDrawable(drawable).mutate(); d.setAlpha(intAlpha); return d; } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java index 25478b43..f96c4d60 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java @@ -97,17 +97,15 @@ public class Toolbar extends LinearLayout implements View.OnClickListener { private LinearLayout toView; public Toolbar(Context context) { - super(context); - init(); + this(context, null); } public Toolbar(Context context, AttributeSet attrs) { - super(context, attrs); - init(); + this(context, attrs, 0); } - public Toolbar(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + public Toolbar(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); init(); } @@ -187,6 +185,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener { setNavigationItemInternal(animate, pushing, item); } + public void setArrowMenuIconShown(boolean show) { + arrowMenuView.setVisibility(show ? View.VISIBLE : View.GONE); + } + public boolean isTransitioning() { return transitioning; } @@ -379,7 +381,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener { if (animate) { toView.setAlpha(0f); - ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f); + final ValueAnimator animator = ObjectAnimator.ofFloat(0f, 1f); animator.setDuration(300); animator.setInterpolator(new DecelerateInterpolator(2f)); animator.addListener(new AnimatorListenerAdapter() { @@ -397,7 +399,16 @@ public class Toolbar extends LinearLayout implements View.OnClickListener { if (!pushing) { animator.setStartDelay(100); } - animator.start(); + + // hack to avoid the animation jumping when the current frame has a lot to do, + // which is often because setting a new navigationitem is almost always done + // when setting a new controller. + post(new Runnable() { + @Override + public void run() { + animator.start(); + } + }); } else { arrowMenuDrawable.setProgress(toItem.hasBack || toItem.search ? 1f : 0f); finishTransition(true); @@ -538,6 +549,25 @@ public class Toolbar extends LinearLayout implements View.OnClickListener { void onSearchEntered(NavigationItem item, String entered); } + public static class SimpleToolbarCallback implements ToolbarCallback { + @Override + public void onMenuOrBackClicked(boolean isArrow) { + } + + @Override + public void onSearchVisibilityChanged(NavigationItem item, boolean visible) { + } + + @Override + public String getSearchHint(NavigationItem item) { + return null; + } + + @Override + public void onSearchEntered(NavigationItem item, String entered) { + } + } + public interface ToolbarCollapseCallback { void onCollapseTranslation(float offset); 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 a6b7c86a..48c4fd36 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 @@ -259,6 +259,16 @@ public class AndroidUtils { inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + public static void requestViewAndKeyboardFocus(View view) { + view.setFocusable(false); + view.setFocusableInTouchMode(true); + if (view.requestFocus()) { + InputMethodManager inputManager = + (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } + } + public static String getReadableFileSize(long bytes, boolean si) { long unit = si ? 1000 : 1024; if (bytes < unit) diff --git a/Clover/app/src/main/java/org/floens/chan/utils/BackgroundUtils.java b/Clover/app/src/main/java/org/floens/chan/utils/BackgroundUtils.java new file mode 100644 index 00000000..6062d117 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/utils/BackgroundUtils.java @@ -0,0 +1,71 @@ +/* + * 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 . + */ +package org.floens.chan.utils; + +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; + +public class BackgroundUtils { + public static Cancelable runWithExecutor(Executor executor, final Callable background, + final BackgroundResult result) { + final AtomicBoolean cancelled = new AtomicBoolean(false); + Cancelable cancelable = new Cancelable() { + @Override + public void cancel() { + cancelled.set(true); + } + }; + + executor.execute(new Runnable() { + @Override + public void run() { + if (!cancelled.get()) { + try { + final T res = background.call(); + AndroidUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + if (!cancelled.get()) { + result.onResult(res); + } + } + }); + } catch (final Exception e) { + AndroidUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + throw new RuntimeException(e); + } + }); + } + } + } + }); + + return cancelable; + } + + public interface BackgroundResult { + void onResult(T result); + } + + public interface Cancelable { + void cancel(); + } +} diff --git a/Clover/app/src/main/res/drawable-xxhdpi/logo.png b/Clover/app/src/main/res/drawable-xxhdpi/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..15514f64ffb9f9b803f3cb27b0a07734415ed579 GIT binary patch literal 46679 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 literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/layout/cell_board.xml b/Clover/app/src/main/res/layout/cell_board.xml new file mode 100644 index 00000000..2c7e3977 --- /dev/null +++ b/Clover/app/src/main/res/layout/cell_board.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + diff --git a/Clover/app/src/main/res/layout/cell_board_edit.xml b/Clover/app/src/main/res/layout/cell_board_edit.xml index 09ab76f4..bee95b43 100644 --- a/Clover/app/src/main/res/layout/cell_board_edit.xml +++ b/Clover/app/src/main/res/layout/cell_board_edit.xml @@ -15,8 +15,7 @@ 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:paddingRight="8dp" android:scaleType="center" android:src="@drawable/ic_reorder_black_24dp" - tools:ignore="ContentDescription"/> + tools:ignore="ContentDescription" /> . android:paddingTop="8dp" android:singleLine="true" android:textColor="?text_color_primary" - android:textSize="14sp"/> + android:textSize="14sp" + tools:text="Technology" /> . android:paddingLeft="16dp" android:paddingRight="16dp" android:textColor="?text_color_secondary" - android:textSize="12sp"/> + android:textSize="12sp" + tools:text="Description is here" /> diff --git a/Clover/app/src/main/res/layout/cell_board_suggestion.xml b/Clover/app/src/main/res/layout/cell_board_suggestion.xml index 82aa841a..94b8edd2 100644 --- a/Clover/app/src/main/res/layout/cell_board_suggestion.xml +++ b/Clover/app/src/main/res/layout/cell_board_suggestion.xml @@ -15,28 +15,50 @@ 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="vertical"> + + + + + + + + diff --git a/Clover/app/src/main/res/layout/cell_saved_board.xml b/Clover/app/src/main/res/layout/cell_saved_board.xml deleted file mode 100644 index c32e3a41..00000000 --- a/Clover/app/src/main/res/layout/cell_saved_board.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - diff --git a/Clover/app/src/main/res/layout/cell_site.xml b/Clover/app/src/main/res/layout/cell_site.xml index 1bf154f4..510f59b0 100644 --- a/Clover/app/src/main/res/layout/cell_site.xml +++ b/Clover/app/src/main/res/layout/cell_site.xml @@ -16,26 +16,62 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . --> + android:scaleType="fitCenter" + tools:src="@drawable/ic_help_outline_black_24dp" /> - + android:orientation="vertical"> + + + + + + + + diff --git a/Clover/app/src/main/res/layout/controller_board_setup.xml b/Clover/app/src/main/res/layout/controller_board_setup.xml index cb5deac5..8aa51b9c 100644 --- a/Clover/app/src/main/res/layout/controller_board_setup.xml +++ b/Clover/app/src/main/res/layout/controller_board_setup.xml @@ -15,72 +15,30 @@ 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:background="?attr/backcolor"> + android:paddingBottom="80dp" + android:paddingLeft="8dp" + android:paddingRight="8dp" + android:paddingTop="8dp" + android:scrollbarStyle="outsideOverlay" + android:scrollbars="vertical" /> + + - + diff --git a/Clover/app/src/main/res/layout/controller_intro.xml b/Clover/app/src/main/res/layout/controller_intro.xml new file mode 100644 index 00000000..6414c619 --- /dev/null +++ b/Clover/app/src/main/res/layout/controller_intro.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + +