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 00000000..15514f64 Binary files /dev/null and b/Clover/app/src/main/res/drawable-xxhdpi/logo.png differ 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 @@ + + + + + + + + + + + +