new setup flow

intro screen, sites setup screen, site setup screen, boards setup screen.
multisite
Floens 8 years ago
parent 49f628a4f1
commit 8a86b2d7c3
  1. 1
      Clover/app/build.gradle
  2. 6
      Clover/app/src/main/java/org/floens/chan/controller/Controller.java
  3. 12
      Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java
  4. 35
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseBoardManager.java
  5. 6
      Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java
  6. 10
      Clover/app/src/main/java/org/floens/chan/core/manager/BoardManager.java
  7. 4
      Clover/app/src/main/java/org/floens/chan/core/model/orm/Board.java
  8. 160
      Clover/app/src/main/java/org/floens/chan/core/presenter/BoardSetupPresenter.java
  9. 38
      Clover/app/src/main/java/org/floens/chan/core/presenter/SetupPresenter.java
  10. 100
      Clover/app/src/main/java/org/floens/chan/core/presenter/SiteSetupPresenter.java
  11. 109
      Clover/app/src/main/java/org/floens/chan/core/presenter/SitesSetupPresenter.java
  12. 2
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  13. 16
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  14. 225
      Clover/app/src/main/java/org/floens/chan/ui/controller/BoardSetupController.java
  15. 9
      Clover/app/src/main/java/org/floens/chan/ui/controller/BrowseController.java
  16. 5
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerNavigationController.java
  17. 52
      Clover/app/src/main/java/org/floens/chan/ui/controller/IntroController.java
  18. 4
      Clover/app/src/main/java/org/floens/chan/ui/controller/PopupController.java
  19. 82
      Clover/app/src/main/java/org/floens/chan/ui/controller/SetupController.java
  20. 358
      Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java
  21. 258
      Clover/app/src/main/java/org/floens/chan/ui/controller/SitesSetupController.java
  22. 5
      Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java
  23. 3
      Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java
  24. 50
      Clover/app/src/main/java/org/floens/chan/ui/helper/BoardHelper.java
  25. 9
      Clover/app/src/main/java/org/floens/chan/ui/helper/PostHelper.java
  26. 174
      Clover/app/src/main/java/org/floens/chan/ui/layout/BoardAddLayout.java
  27. 11
      Clover/app/src/main/java/org/floens/chan/ui/layout/SearchLayout.java
  28. 41
      Clover/app/src/main/java/org/floens/chan/ui/layout/SiteAddLayout.java
  29. 2
      Clover/app/src/main/java/org/floens/chan/ui/theme/Theme.java
  30. 46
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
  31. 10
      Clover/app/src/main/java/org/floens/chan/utils/AndroidUtils.java
  32. 71
      Clover/app/src/main/java/org/floens/chan/utils/BackgroundUtils.java
  33. BIN
      Clover/app/src/main/res/drawable-xxhdpi/logo.png
  34. 64
      Clover/app/src/main/res/layout/cell_board.xml
  35. 11
      Clover/app/src/main/res/layout/cell_board_edit.xml
  36. 58
      Clover/app/src/main/res/layout/cell_board_suggestion.xml
  37. 49
      Clover/app/src/main/res/layout/cell_saved_board.xml
  38. 56
      Clover/app/src/main/res/layout/cell_site.xml
  39. 80
      Clover/app/src/main/res/layout/controller_board_setup.xml
  40. 89
      Clover/app/src/main/res/layout/controller_intro.xml
  41. 34
      Clover/app/src/main/res/layout/controller_navigation_setup.xml
  42. 152
      Clover/app/src/main/res/layout/controller_site_setup.xml
  43. 44
      Clover/app/src/main/res/layout/controller_sites_setup.xml
  44. 42
      Clover/app/src/main/res/layout/layout_board_add.xml
  45. 42
      Clover/app/src/main/res/layout/layout_site_add.xml
  46. 24
      Clover/app/src/main/res/values/strings.xml

@ -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.

@ -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() {

@ -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);

@ -57,23 +57,34 @@ public class DatabaseBoardManager {
};
}
public Callable<List<Board>> getBoards(final Site site) {
public Callable<List<Board>> getSiteBoards(final Site site) {
return new Callable<List<Board>>() {
@Override
public List<Board> call() throws Exception {
List<Board> 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<Board> 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<List<Board>> getSiteSavedBoards(final Site site) {
return new Callable<List<Board>>() {
@Override
public List<Board> call() throws Exception {
List<Board> 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;
}
};

@ -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,

@ -92,6 +92,16 @@ public class BoardManager {
return savedBoards;
}
public List<Board> getSiteBoards(Site site) {
return databaseManager.runTaskSync(
databaseManager.getDatabaseBoardManager().getSiteBoards(site));
}
public List<Board> getSiteSavedBoards(Site site) {
return databaseManager.runTaskSync(
databaseManager.getDatabaseBoardManager().getSiteSavedBoards(site));
}
public void saveBoard(Board board) {
board.saved = true;

@ -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}.<br>
* {@link #id}, {@link #saved}, {@link #order} are skipped because these are user-set.

@ -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<Site> sites = new ArrayList<>();
@Inject
BoardManager boardManager;
private Site site;
private List<Board> savedBoards;
private Executor executor = Executors.newSingleThreadExecutor();
private BackgroundUtils.Cancelable suggestionCall;
private List<BoardSuggestion> suggestions = new ArrayList<>();
private Set<String> 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<BoardSuggestion> 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<BoardSuggestion> getSuggestionsForQuery(String query) {
List<BoardSuggestion> 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<List<BoardSuggestion>>() {
@Override
public List<BoardSuggestion> call() throws Exception {
List<BoardSuggestion> suggestions = new ArrayList<>();
if (site.boardsType() == Site.BoardsType.DYNAMIC) {
List<Board> siteBoards = boardManager.getSiteBoards(site);
List<Board> toSearch = new ArrayList<>();
for (Board siteBoard : siteBoards) {
if (!siteBoard.saved) {
toSearch.add(siteBoard);
}
}
List<Board> 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<List<BoardSuggestion>>() {
@Override
public void onResult(List<BoardSuggestion> 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<BoardSuggestion> suggestions) {
this.suggestions = suggestions;
for (BoardSuggestion suggestion : this.suggestions) {
suggestion.checked = selectedSuggestions.contains(suggestion.board.code);
}
}
public interface Callback {
void showAddDialog();
void setSavedBoards(List<Board> 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;
}
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.presenter;
public class SetupPresenter {
private Callback callback;
public void create(Callback callback) {
this.callback = callback;
callback.moveToIntro();
}
public void startClicked() {
callback.moveToSiteSetup();
}
public interface Callback {
void moveToIntro();
void moveToSiteSetup();
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Site> 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<Site> sites);
void setNextAllowed(boolean nextAllowed, boolean animate);
void showUrlHint(String text);
void moveToSavedBoards();
void setBoardCount(int boardCount);
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Site> 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<Site> sites);
void setNextAllowed(boolean nextAllowed, boolean animate);
void openSiteConfiguration(Site site);
}
}

@ -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);

@ -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<Loadable, Loadable> resolveChanState(ChanState state) {
DatabaseLoadableManager loadableManager = databaseManager.getDatabaseLoadableManager();

@ -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<Board> 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<BoardSetupPresenter.BoardSuggestion> 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<Board> savedBoards) {
savedAdapter.setSavedBoards(savedBoards);
}
@Override
public void finish() {
navigationController.popController();
}
private class SavedBoardsAdapter extends RecyclerView.Adapter<SavedBoardCell> {
@ -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);
}
}
}

@ -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<Board> 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;
}
}

@ -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);
}

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;
import android.view.View;
import android.widget.Button;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
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();
}
}
}

@ -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();
}
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.controller;
import android.content.Context;
import android.view.animation.DecelerateInterpolator;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.controller.transition.FadeInTransition;
import org.floens.chan.core.presenter.SetupPresenter;
import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.ui.toolbar.Toolbar;
public class SetupController extends ToolbarNavigationController implements SetupPresenter.Callback {
private SetupPresenter presenter;
public SetupController(Context context) {
super(context);
}
@Override
public void onCreate() {
super.onCreate();
view = inflateRes(R.layout.controller_navigation_setup);
container = view.findViewById(R.id.container);
toolbar = view.findViewById(R.id.toolbar);
toolbar.setArrowMenuIconShown(false);
toolbar.setBackgroundColor(ThemeHelper.getInstance().getTheme().primaryColor.color);
toolbar.setCallback(new Toolbar.SimpleToolbarCallback() {
@Override
public void onMenuOrBackClicked(boolean isArrow) {
}
});
toolbar.setAlpha(0f);
requireSpaceForToolbar = false;
presenter = new SetupPresenter();
presenter.create(this);
}
public SetupPresenter getPresenter() {
return presenter;
}
@Override
public void moveToIntro() {
replaceController(new IntroController(context), false);
}
@Override
public void moveToSiteSetup() {
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();
}
}

@ -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<Site> 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<Site> 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<SiteCell> {
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);
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Site> 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<Site> 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<SiteCell> {
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);
}
}
}
}

@ -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);

@ -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);

@ -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<Board> search(List<Board> from, final String query) {
List<Pair<Board, Integer>> 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<Pair<Board, Integer>>() {
@Override
public int compare(Pair<Board, Integer> o1, Pair<Board, Integer> o2) {
return o2.second - o1.second;
}
});
List<Board> result = new ArrayList<>(ratios.size());
for (Pair<Board, Integer> 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;
}
}

@ -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();

@ -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 <http://www.gnu.org/licenses/>.
*/
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<SuggestionCell> {
@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);
}
}
}
}

@ -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 {

@ -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());
}
}

@ -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;
}

@ -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);

@ -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)

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <T> Cancelable runWithExecutor(Executor executor, final Callable<T> background,
final BackgroundResult<T> 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<T> {
void onResult(T result);
}
public interface Cancelable {
void cancel();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/backcolor"
android:orientation="horizontal"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingTop="8dp"
android:singleLine="true"
android:textColor="?text_color_primary"
android:textSize="14sp"
tools:text="Technology" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingBottom="8dp"
android:textColor="?text_color_secondary"
android:textSize="12sp"
tools:text="Description is here" />
</LinearLayout>
<ImageView
android:id="@+id/reorder"
android:layout_width="48dp"
android:layout_height="match_parent"
android:padding="8dp"
android:scaleType="centerInside"
tools:src="@drawable/ic_reorder_black_24dp" />
</LinearLayout>

@ -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 <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -32,7 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingRight="8dp"
android:scaleType="center"
android:src="@drawable/ic_reorder_black_24dp"
tools:ignore="ContentDescription"/>
tools:ignore="ContentDescription" />
<LinearLayout
android:layout_width="0dp"
@ -50,7 +49,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingTop="8dp"
android:singleLine="true"
android:textColor="?text_color_primary"
android:textSize="14sp"/>
android:textSize="14sp"
tools:text="Technology" />
<TextView
android:id="@+id/description"
@ -61,7 +61,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textColor="?text_color_secondary"
android:textSize="12sp"/>
android:textSize="12sp"
tools:text="Description is here" />
</LinearLayout>

@ -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 <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="?attr/backcolor"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/text"
<LinearLayout
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?text_color_primary"
android:textSize="14sp"/>
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingTop="8dp"
android:singleLine="true"
android:textColor="?text_color_primary"
android:textSize="14sp"
tools:text="Technology" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingBottom="8dp"
android:textColor="?text_color_secondary"
android:textSize="12sp"
tools:text="Description is here" />
</LinearLayout>
<ImageView
android:id="@+id/check"
android:layout_width="56dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:scaleType="center"
tools:ignore="ContentDescription" />
</LinearLayout>

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="?attr/backcolor"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:scaleType="fitCenter"/>
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?text_color_primary"
android:textSize="14sp"/>
<ImageView
android:id="@+id/reorder"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="8dp"
android:scaleType="centerInside"/>
</LinearLayout>

@ -16,26 +16,62 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_height="match_parent"
android:padding="8dp"
android:scaleType="fitCenter" />
android:scaleType="fitCenter"
tools:src="@drawable/ic_help_outline_black_24dp" />
<TextView
android:id="@+id/text"
<LinearLayout
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="?text_color_primary"
android:textSize="14sp" />
android:orientation="vertical">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="8dp"
android:singleLine="true"
android:textColor="?text_color_primary"
android:textSize="14sp"
tools:text="4chan" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:paddingBottom="8dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:textColor="?text_color_secondary"
android:textSize="12sp"
tools:text="Description of this site" />
</LinearLayout>
<ImageView
android:id="@+id/settings"
android:layout_width="56dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_marginRight="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:scaleType="center"
tools:src="@drawable/ic_settings_black_24dp" />
</LinearLayout>

@ -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 <http://www.gnu.org/licenses/>.
-->
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/backcolor"
android:clipChildren="false">
<TextView
android:id="@+id/introduction"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/saved_boards_introduction"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/code_hint"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:text="@string/saved_boards_code"
app:layout_constraintBottom_toBottomOf="@+id/code"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/introduction"/>
<AutoCompleteTextView
android:id="@+id/code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:ems="10"
android:hint="@string/saved_boards_code_hint"
android:inputType="textPersonName"
android:textSize="14sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/code_hint"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/introduction"/>
android:background="?attr/backcolor">
<android.support.v7.widget.RecyclerView
android:id="@+id/boards_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/code"/>
android:paddingBottom="80dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="8dp"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:layout_margin="16dp"
android:src="@drawable/ic_add_white_24dp" />
</android.support.constraint.ConstraintLayout>
</android.support.design.widget.CoordinatorLayout>

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

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.floens.chan.ui.view.TouchBlockingLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?backcolor"
android:orientation="vertical">
<org.floens.chan.ui.toolbar.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/toolbar_height" />
<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</org.floens.chan.ui.view.TouchBlockingLinearLayout>

@ -1,150 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="?attr/backcolor"
tools:ignore="ContentDescription,RtlHardcoded">
<TextView
android:id="@+id/introduction"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="@string/setup_introduction"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<EditText
android:id="@+id/site_url"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:hint="@string/setup_site_url_hint"
android:inputType="textUri"
android:textSize="14sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/site_url_label"
app:layout_constraintRight_toLeftOf="@+id/site_url_submit"
app:layout_constraintTop_toBottomOf="@+id/introduction"/>
<ImageButton
android:id="@+id/site_url_submit"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="7dp"
android:layout_marginRight="7dp"
android:layout_marginTop="8dp"
android:background="?selectableItemBackground"
app:layout_constraintBottom_toBottomOf="@+id/site_url"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/introduction"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/ic_add_black_24dp"/>
<ProgressBar
android:id="@+id/progress"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:padding="8dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/site_url"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/introduction"/>
<TextView
android:id="@+id/site_url_label"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="0dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="@string/setup_site_url"
app:layout_constraintBottom_toBottomOf="@+id/site_url"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/introduction"
app:layout_constraintVertical_bias="0.0"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/sites_recycler"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="0dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:layout_marginTop="0dp"
android:padding="8dp"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
app:layout_constraintBottom_toTopOf="@+id/next_button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider"/>
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:layout_marginTop="8dp"
android:background="?attr/divider_color"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/site_url"/>
<Button
android:id="@+id/next_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:background="@android:color/white"
android:elevation="12dp"
android:foreground="?selectableItemBackground"
android:text="@string/next"
android:textColor="?text_color_hint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="?attr/backcolor"
tools:ignore="ContentDescription,RtlHardcoded">
<android.support.v7.widget.RecyclerView
android:id="@+id/sites_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:paddingBottom="72dp"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:layout_margin="16dp"
android:src="@drawable/ic_add_white_24dp" />
</android.support.design.widget.CoordinatorLayout>

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.floens.chan.ui.layout.BoardAddLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="10000dp"
android:minWidth="10000dp"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp">
<org.floens.chan.ui.layout.SearchLayout
android:id="@+id/search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:paddingTop="8dp" />
<android.support.v7.widget.RecyclerView
android:id="@+id/suggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical" />
</org.floens.chan.ui.layout.BoardAddLayout>

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<org.floens.chan.ui.layout.SiteAddLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<android.support.design.widget.TextInputEditText
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/setup_sites_url"
android:inputType="textUri" />
</android.support.design.widget.TextInputLayout>
</org.floens.chan.ui.layout.SiteAddLayout>

@ -164,13 +164,25 @@ Re-enable this permission in the app settings if you permanently disabled it."</
<string name="thread_up_down_hint">Scroll to top/bottom</string>
<string name="thread_pin_hint">Bookmark this thread</string>
<string name="setup_title">Add sites</string>
<string name="setup_introduction">"Welcome to Clover. Clover can browse multiple imageboards.\n
Start by entering a URL of a site."</string>
<string name="setup_site_url">Site url</string>
<string name="setup_site_url_hint">http://</string>
<string name="intro_title">Clover</string>
<string name="intro_content">Browse your favorite imageboards</string>
<string name="intro_start">Get started</string>
<string name="saved_boards_title">Saved boards</string>
<string name="setup_sites_title">Your sites</string>
<string name="setup_sites_add_title">Add site</string>
<string name="setup_sites_url">Site url</string>
<string name="setup_sites_url_hint">http://</string>
<string name="setup_sites_site_description">%s added</string>
<string name="setup_site_title">Configure %s</string>
<string name="setup_site_group_general">General</string>
<string name="setup_site_boards">Setup boards</string>
<string name="setup_site_boards_description">%s added</string>
<string name="setup_board_title">Configure boards of %s</string>
<string name="setup_board_add">Add board</string>
<string name="saved_boards_title">Your boards</string>
<string name="saved_boards_introduction">"Change your saved boards. These will appear in a list at the top."</string>
<string name="saved_boards_code">Board code</string>
<string name="saved_boards_code_hint">lit</string>

Loading…
Cancel
Save