Work on site and new board setup screens.

Move to dagger 1
multisite
Floens 8 years ago
parent 2f31d19279
commit e7f0038bbd
  1. 21
      Clover/app/build.gradle
  2. BIN
      Clover/app/src/main/assets/icons/4chan.png
  3. 15
      Clover/app/src/main/java/org/floens/chan/Chan.java
  4. 4
      Clover/app/src/main/java/org/floens/chan/chan/ChanHelper.java
  5. 2
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseHelper.java
  6. 12
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseManager.java
  7. 29
      Clover/app/src/main/java/org/floens/chan/core/database/DatabaseSiteManager.java
  8. 86
      Clover/app/src/main/java/org/floens/chan/core/di/AppModule.java
  9. 131
      Clover/app/src/main/java/org/floens/chan/core/di/ChanGraph.java
  10. 8
      Clover/app/src/main/java/org/floens/chan/core/di/NetModule.java
  11. 29
      Clover/app/src/main/java/org/floens/chan/core/manager/SiteManager.java
  12. 6
      Clover/app/src/main/java/org/floens/chan/core/manager/WatchManager.java
  13. 37
      Clover/app/src/main/java/org/floens/chan/core/model/SiteModel.java
  14. 120
      Clover/app/src/main/java/org/floens/chan/core/presenter/SetupPresenter.java
  15. 2
      Clover/app/src/main/java/org/floens/chan/core/site/Site.java
  16. 72
      Clover/app/src/main/java/org/floens/chan/core/site/SiteIcon.java
  17. 9
      Clover/app/src/main/java/org/floens/chan/core/site/sites/chan4/Chan4.java
  18. 20
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  19. 28
      Clover/app/src/main/java/org/floens/chan/ui/animation/AnimationUtils.java
  20. 2
      Clover/app/src/main/java/org/floens/chan/ui/cell/PostCell.java
  21. 178
      Clover/app/src/main/java/org/floens/chan/ui/controller/BoardSetupController.java
  22. 2
      Clover/app/src/main/java/org/floens/chan/ui/controller/MainSettingsController.java
  23. 2
      Clover/app/src/main/java/org/floens/chan/ui/controller/PassSettingsController.java
  24. 357
      Clover/app/src/main/java/org/floens/chan/ui/controller/SiteSetupController.java
  25. 3
      Clover/app/src/main/java/org/floens/chan/ui/controller/ThemeSettingsController.java
  26. 2
      Clover/app/src/main/java/org/floens/chan/ui/layout/FilterLayout.java
  27. 2
      Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java
  28. 2
      Clover/app/src/main/java/org/floens/chan/ui/layout/ThreadListLayout.java
  29. 2
      Clover/app/src/main/java/org/floens/chan/ui/settings/SettingsController.java
  30. 15
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java
  31. 3
      Clover/app/src/main/java/org/floens/chan/ui/view/LoadView.java
  32. 2
      Clover/app/src/main/java/org/floens/chan/ui/view/ThumbnailView.java
  33. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png
  34. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_arrow_forward_black_24dp.png
  35. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_expand_less_black_24dp.png
  36. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_expand_more_black_24dp.png
  37. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png
  38. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_arrow_forward_black_24dp.png
  39. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_expand_less_black_24dp.png
  40. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_expand_more_black_24dp.png
  41. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png
  42. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_arrow_forward_black_24dp.png
  43. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_expand_less_black_24dp.png
  44. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_expand_more_black_24dp.png
  45. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_add_black_24dp.png
  46. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_arrow_forward_black_24dp.png
  47. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_expand_less_black_24dp.png
  48. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_expand_more_black_24dp.png
  49. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png
  50. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_arrow_forward_black_24dp.png
  51. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_expand_less_black_24dp.png
  52. BIN
      Clover/app/src/main/res/drawable-xxxhdpi/ic_expand_more_black_24dp.png
  53. 42
      Clover/app/src/main/res/layout/cell_saved_board.xml
  54. 41
      Clover/app/src/main/res/layout/cell_site.xml
  55. 83
      Clover/app/src/main/res/layout/controller_board_setup.xml
  56. 150
      Clover/app/src/main/res/layout/controller_site_setup.xml
  57. 2
      Clover/app/src/main/res/layout/layout_site_board_select.xml
  58. 12
      Clover/app/src/main/res/values/strings.xml
  59. 3
      Clover/build.gradle
  60. 1
      Clover/gradle.properties

@ -15,7 +15,7 @@ def getCommitHash = { ->
android {
compileSdkVersion 25
// update the travis config when changing this
buildToolsVersion '25.0.2'
buildToolsVersion '25.0.3'
defaultConfig {
minSdkVersion 15
@ -32,6 +32,11 @@ android {
targetCompatibility JavaVersion.VERSION_1_7
}
dexOptions {
preDexLibraries true
maxProcessCount 8
}
lintOptions {
abortOnError false
}
@ -122,6 +127,7 @@ dependencies {
compile 'com.android.support:support-annotations:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:customtabs:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'org.jsoup:jsoup:1.9.2'
compile 'com.j256.ormlite:ormlite-core:4.48'
@ -130,9 +136,14 @@ dependencies {
compile 'com.davemorrissey.labs:subsampling-scale-image-view:3.5.0'
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'de.greenrobot:eventbus:2.4.0'
compile 'com.google.dagger:dagger:2.2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.2'
compile 'org.nibor.autolink:autolink:0.6.0'
// Yes that's dagger 1, "deprecated".
// There's little wrong with it, except that it might be slow at runtime.
// We don't have a huge graph. The major downside of the newer dagger version
// is that it uses annotation processing. Annotation processors block incremental
// compiling, so our builds become way slower, and I rather have faster builds.
// Move to gradle 2 when incremental compiling supports annotation processors.
compile 'com.squareup.dagger:dagger:1.2.5'
compile 'com.squareup.dagger:dagger-compiler:1.2.5'
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

@ -23,11 +23,10 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.StrictMode;
import org.floens.chan.core.di.UserAgentProvider;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.di.AppModule;
import org.floens.chan.core.di.ChanGraph;
import org.floens.chan.core.di.DaggerChanGraph;
import org.floens.chan.core.di.NetModule;
import org.floens.chan.core.di.UserAgentProvider;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Time;
@ -36,6 +35,7 @@ import java.util.Locale;
import javax.inject.Inject;
import dagger.ObjectGraph;
import de.greenrobot.event.EventBus;
public class Chan extends Application implements UserAgentProvider {
@ -47,7 +47,7 @@ public class Chan extends Application implements UserAgentProvider {
private String userAgent;
private int activityForegroundCounter = 0;
protected ChanGraph graph;
protected ObjectGraph graph;
@Inject
DatabaseManager databaseManager;
@ -60,7 +60,7 @@ public class Chan extends Application implements UserAgentProvider {
return instance;
}
public static ChanGraph getGraph() {
public static ObjectGraph getGraph() {
return instance.graph;
}
@ -73,9 +73,8 @@ public class Chan extends Application implements UserAgentProvider {
AndroidUtils.init();
userAgent = createUserAgent();
graph = DaggerChanGraph.builder()
.appModule(new AppModule(this, this))
.build();
graph = ObjectGraph.create(new AppModule(this, this), new NetModule());
graph.inject(this);

@ -19,10 +19,8 @@ package org.floens.chan.chan;
import android.net.Uri;
import org.floens.chan.Chan;
import org.floens.chan.core.database.DatabaseLoadableManager;
import org.floens.chan.core.database.DatabaseManager;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.site.Site;
@ -43,7 +41,7 @@ public class ChanHelper {
if (parts.size() > 0) {
String rawBoard = parts.get(0);
DatabaseManager databaseManager = getGraph().getDatabaseManager();
DatabaseManager databaseManager = getGraph().get(DatabaseManager.class);
DatabaseLoadableManager loadableManager = databaseManager.getDatabaseLoadableManager();
Board board = site.board(rawBoard);
if (board != null) {

@ -31,6 +31,7 @@ import org.floens.chan.core.model.History;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Pin;
import org.floens.chan.core.model.SavedReply;
import org.floens.chan.core.model.SiteModel;
import org.floens.chan.core.model.ThreadHide;
import org.floens.chan.utils.Logger;
@ -52,6 +53,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
public Dao<ThreadHide, Integer> threadHideDao;
public Dao<History, Integer> historyDao;
public Dao<Filter, Integer> filterDao;
public Dao<SiteModel, Integer> siteDao;
private final Context context;

@ -73,6 +73,7 @@ public class DatabaseManager {
private final DatabaseSavedReplyManager databaseSavedReplyManager;
private final DatabaseFilterManager databaseFilterManager;
private final DatabaseBoardManager databaseBoardManager;
private final DatabaseSiteManager databaseSiteManager;
@Inject
public DatabaseManager(Context context) {
@ -85,6 +86,7 @@ public class DatabaseManager {
databaseSavedReplyManager = new DatabaseSavedReplyManager(this, helper);
databaseFilterManager = new DatabaseFilterManager(this, helper);
databaseBoardManager = new DatabaseBoardManager(this, helper);
databaseSiteManager = new DatabaseSiteManager(this, helper);
initialize();
EventBus.getDefault().register(this);
}
@ -113,6 +115,10 @@ public class DatabaseManager {
return databaseBoardManager;
}
public DatabaseSiteManager getDatabaseSiteManager() {
return databaseSiteManager;
}
// Called when the app changes foreground state
public void onEvent(Chan.ForegroundChangedMessage message) {
if (!message.inForeground) {
@ -122,8 +128,12 @@ public class DatabaseManager {
private void initialize() {
loadThreadHides();
runTaskSync(databaseHistoryManager.load());
// Loads data into fields.
runTaskSync(databaseSavedReplyManager.load());
// Only trims.
runTask(databaseHistoryManager.load());
}
/**

@ -0,0 +1,29 @@
/*
* 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.database;
public class DatabaseSiteManager {
private DatabaseManager databaseManager;
private DatabaseHelper databaseHelper;
public DatabaseSiteManager(DatabaseManager databaseManager, DatabaseHelper databaseHelper) {
this.databaseManager = databaseManager;
this.databaseHelper = databaseHelper;
}
}

@ -5,14 +5,98 @@ import android.content.Context;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import org.floens.chan.ChanApplication;
import org.floens.chan.chan.ChanLoader;
import org.floens.chan.chan.ChanParser;
import org.floens.chan.core.cache.FileCache;
import org.floens.chan.core.database.DatabaseManager;
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.manager.SiteManager;
import org.floens.chan.core.manager.WatchManager;
import org.floens.chan.core.net.BitmapLruImageCache;
import org.floens.chan.core.presenter.ImageViewerPresenter;
import org.floens.chan.core.presenter.ReplyPresenter;
import org.floens.chan.core.presenter.ThreadPresenter;
import org.floens.chan.core.receiver.WatchUpdateReceiver;
import org.floens.chan.core.saver.ImageSaveTask;
import org.floens.chan.core.site.http.HttpCallManager;
import org.floens.chan.core.site.sites.chan4.Chan4;
import org.floens.chan.core.site.sites.chan4.Chan4ReaderRequest;
import org.floens.chan.core.update.UpdateManager;
import org.floens.chan.ui.activity.BoardActivity;
import org.floens.chan.ui.adapter.DrawerAdapter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.controller.BoardEditController;
import org.floens.chan.ui.controller.BrowseController;
import org.floens.chan.ui.controller.DeveloperSettingsController;
import org.floens.chan.ui.controller.DrawerController;
import org.floens.chan.ui.controller.FiltersController;
import org.floens.chan.ui.controller.HistoryController;
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.ViewThreadController;
import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.ui.layout.FilterLayout;
import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.service.WatchNotifier;
import org.floens.chan.ui.view.MultiImageView;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
@Module(
injects = {
// Context.class, // ApplicationContext
ChanParser.class,
BoardManager.class,
DatabaseManager.class,
ReplyManager.class,
ImageLoader.class,
FileCache.class,
HttpCallManager.class,
ChanApplication.class,
MainSettingsController.class,
ReplyPresenter.class,
Chan4ReaderRequest.class,
ThreadLayout.class,
DeveloperSettingsController.class,
BoardActivity.class,
ThreadPresenter.class,
BoardEditController.class,
FilterEngine.class,
BrowseController.class,
FilterLayout.class,
HistoryController.class,
DrawerController.class,
DrawerAdapter.class,
WatchNotifier.class,
WatchUpdateReceiver.class,
ImagePickDelegate.class,
PassSettingsController.class,
FiltersController.class,
PostsFilter.class,
ChanLoader.class,
ImageViewerController.class,
ImageViewerPresenter.class,
MultiImageView.class,
ImageSaveTask.class,
ViewThreadController.class,
WatchManager.PinWatcher.class,
UpdateManager.class,
SiteManager.class,
Chan4.class,
},
complete = false,
includes = NetModule.class
)
public class AppModule {
private Context applicationContext;
private UserAgentProvider userAgentProvider;

@ -1,131 +0,0 @@
package org.floens.chan.core.di;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import org.floens.chan.Chan;
import org.floens.chan.chan.ChanLoader;
import org.floens.chan.chan.ChanParser;
import org.floens.chan.core.cache.FileCache;
import org.floens.chan.core.database.DatabaseManager;
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.manager.WatchManager;
import org.floens.chan.core.presenter.ImageViewerPresenter;
import org.floens.chan.core.presenter.ReplyPresenter;
import org.floens.chan.core.presenter.ThreadPresenter;
import org.floens.chan.core.receiver.WatchUpdateReceiver;
import org.floens.chan.core.saver.ImageSaveTask;
import org.floens.chan.core.site.http.HttpCallManager;
import org.floens.chan.core.site.sites.chan4.Chan4;
import org.floens.chan.core.site.sites.chan4.Chan4ReaderRequest;
import org.floens.chan.core.update.UpdateManager;
import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.adapter.DrawerAdapter;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.controller.BoardEditController;
import org.floens.chan.ui.controller.BrowseController;
import org.floens.chan.ui.controller.DeveloperSettingsController;
import org.floens.chan.ui.controller.DrawerController;
import org.floens.chan.ui.controller.FiltersController;
import org.floens.chan.ui.controller.HistoryController;
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.ViewThreadController;
import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.ui.layout.FilterLayout;
import org.floens.chan.ui.layout.ThreadLayout;
import org.floens.chan.ui.service.WatchNotifier;
import org.floens.chan.ui.view.MultiImageView;
import javax.inject.Singleton;
import dagger.Component;
@Component(modules = {
AppModule.class,
NetModule.class
})
@Singleton
/**
* Note: please avoid adding inject() statements for Sites.
*/
public interface ChanGraph {
ChanParser getChanParser();
BoardManager getBoardManager();
DatabaseManager getDatabaseManager();
ReplyManager getReplyManager();
RequestQueue getRequestQueue();
ImageLoader getImageLoader();
FileCache getFileCache();
HttpCallManager getHttpCallManager();
void inject(Chan chan);
void inject(MainSettingsController mainSettingsController);
void inject(ReplyPresenter replyPresenter);
void inject(Chan4ReaderRequest chanReaderRequest);
void inject(ThreadLayout threadLayout);
void inject(DeveloperSettingsController developerSettingsController);
void inject(StartActivity startActivity);
void inject(ThreadPresenter threadPresenter);
void inject(BoardEditController boardEditController);
void inject(FilterEngine filterEngine);
void inject(BrowseController browseController);
void inject(FilterLayout filterLayout);
void inject(HistoryController historyController);
void inject(DrawerController drawerController);
void inject(DrawerAdapter drawerAdapter);
void inject(WatchNotifier watchNotifier);
void inject(WatchUpdateReceiver watchUpdateReceiver);
void inject(ImagePickDelegate imagePickDelegate);
void inject(PassSettingsController passSettingsController);
void inject(FiltersController filtersController);
void inject(PostsFilter postsFilter);
void inject(ChanLoader chanLoader);
void inject(ImageViewerController imageViewerController);
void inject(ImageViewerPresenter imageViewerPresenter);
void inject(MultiImageView multiImageView);
void inject(ImageSaveTask imageSaveTask);
void inject(ViewThreadController viewThreadController);
void inject(WatchManager.PinWatcher pinWatcher);
void inject(UpdateManager updateManager);
void inject(Chan4 chan4);
}

@ -15,7 +15,13 @@ import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
@Module(
injects = {
RequestQueue.class,
FileCache.class
},
complete = false
)
public class NetModule {
private static final int VOLLEY_CACHE_SIZE = 10 * 1024 * 1024;
private static final long FILE_CACHE_DISK_SIZE = 50 * 1024 * 1024;

@ -0,0 +1,29 @@
/*
* 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.manager;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class SiteManager {
@Inject
public SiteManager() {
}
}

@ -113,6 +113,9 @@ public class WatchManager {
}
};
@Inject
ChanLoaderFactory chanLoaderFactory;
private final AlarmManager alarmManager;
private final PowerManager powerManager;
@ -651,9 +654,6 @@ public class WatchManager {
public class PinWatcher implements ChanLoader.ChanLoaderCallback {
private static final String TAG = "PinWatcher";
@Inject
ChanLoaderFactory chanLoaderFactory;
private final Pin pin;
private ChanLoader chanLoader;

@ -0,0 +1,37 @@
/*
* 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.model;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
/**
* Only for storing what sites we know of, for hardcoded sites the sites we have enabled,
* and for dynamic sites all settings for it.
*/
@DatabaseTable
public class SiteModel {
@DatabaseField(id = true, unique = true)
public int id;
@DatabaseField
public String name;
@DatabaseField
public String options;
}

@ -0,0 +1,120 @@
/*
* 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 android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import org.floens.chan.utils.AndroidUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
import static org.floens.chan.utils.AndroidUtils.getRes;
public class SetupPresenter {
private Callback callback;
private List<AddedSite> sites = new ArrayList<>();
public void create(Callback callback) {
this.callback = callback;
this.callback.setAddedSites(sites);
this.callback.setNextAllowed(!sites.isEmpty(), false);
}
public boolean mayExit() {
return false;
}
public void onUrlSubmitClicked(String url) {
callback.goToUrlSubmittedState();
AndroidUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
siteAdded(getTestSite());
}
}, 500);
// callback.showUrlHint("foo bar baz");
}
public void onNextClicked() {
if (!sites.isEmpty()) {
callback.moveToSavedBoards();
}
}
private void siteAdded(AddedSite site) {
sites.add(site);
callback.setAddedSites(sites);
callback.runSiteAddedAnimation(site);
callback.setNextAllowed(!sites.isEmpty(), true);
}
private int counter;
private AddedSite getTestSite() {
AddedSite site = new AddedSite();
site.id = counter++;
site.title = "4chan.org";
Bitmap bitmap;
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScaled = false;
bitmap = BitmapFactory.decodeStream(getAppContext().getAssets().open("icons/4chan.png"), null, opts);
} catch (IOException e) {
throw new RuntimeException(e);
}
BitmapDrawable drawable = new BitmapDrawable(getRes(), bitmap);
drawable = (BitmapDrawable) drawable.mutate();
drawable.getPaint().setFilterBitmap(false);
site.drawable = drawable;
return site;
}
public interface Callback {
void goToUrlSubmittedState();
void runSiteAddedAnimation(AddedSite site);
void setAddedSites(List<AddedSite> sites);
void setNextAllowed(boolean nextAllowed, boolean animate);
void showUrlHint(String text);
void moveToSavedBoards();
}
public static class AddedSite {
public int id;
public String title;
public Drawable drawable;
}
}

@ -111,6 +111,8 @@ public interface Site {
String name();
SiteIcon icon();
boolean feature(Feature feature);
boolean boardFeature(BoardFeature boardFeature, Board board);

@ -0,0 +1,72 @@
/*
* 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.site;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import java.io.IOException;
import okhttp3.HttpUrl;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
import static org.floens.chan.utils.AndroidUtils.getRes;
public class SiteIcon {
private String assetPath;
private HttpUrl url;
public static SiteIcon fromAssets(String path) {
SiteIcon siteIcon = new SiteIcon();
siteIcon.assetPath = path;
return siteIcon;
}
public static SiteIcon fromUrl(HttpUrl url) {
SiteIcon siteIcon = new SiteIcon();
siteIcon.url = url;
return siteIcon;
}
public void get(SiteIconResult result) {
if (assetPath != null) {
Bitmap bitmap;
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScaled = false;
bitmap = BitmapFactory.decodeStream(getAppContext().getAssets().open(assetPath), null, opts);
} catch (IOException e) {
throw new RuntimeException(e);
}
BitmapDrawable drawable = new BitmapDrawable(getRes(), bitmap);
drawable = (BitmapDrawable) drawable.mutate();
drawable.getPaint().setFilterBitmap(false);
result.onSiteIcon(this, drawable);
} else if (url != null) {
// TODO
}
}
public interface SiteIconResult {
void onSiteIcon(SiteIcon siteIcon, Drawable icon);
}
}

@ -28,6 +28,7 @@ import com.android.volley.VolleyError;
import org.floens.chan.chan.ChanLoaderRequest;
import org.floens.chan.chan.ChanLoaderRequestParams;
import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable;
import org.floens.chan.core.model.Post;
@ -36,6 +37,7 @@ import org.floens.chan.core.site.Boards;
import org.floens.chan.core.site.Site;
import org.floens.chan.core.site.SiteAuthentication;
import org.floens.chan.core.site.SiteEndpoints;
import org.floens.chan.core.site.SiteIcon;
import org.floens.chan.core.site.SiteRequestModifier;
import org.floens.chan.core.site.http.DeleteRequest;
import org.floens.chan.core.site.http.HttpCall;
@ -279,6 +281,11 @@ public class Chan4 implements Site {
return "4chan";
}
@Override
public SiteIcon icon() {
return SiteIcon.fromAssets("icons/4chan.png");
}
@Override
public boolean feature(Feature feature) {
switch (feature) {
@ -360,7 +367,7 @@ public class Chan4 implements Site {
@Override
public Board board(String code) {
List<Board> allBoards = getGraph().getBoardManager().getAllBoards();
List<Board> allBoards = getGraph().get(BoardManager.class).getAllBoards();
for (Board board : allBoards) {
if (board.code.equals(code)) {
return board;

@ -51,6 +51,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.SplitNavigationController;
import org.floens.chan.ui.controller.StyledToolbarNavigationController;
import org.floens.chan.ui.controller.ThreadSlideController;
@ -127,9 +128,15 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
adapter.setNdefPushMessageCallback(this, this);
}
// Startup from background or url
setupFromStateOrFreshLaunch(savedInstanceState);
versionHandler.run();
}
private void setupFromStateOrFreshLaunch(Bundle savedInstanceState) {
boolean loadDefault = true;
if (savedInstanceState != null) {
// Restore the activity state from the previously saved state.
ChanState chanState = savedInstanceState.getParcelable(STATE_KEY);
if (chanState == null) {
Logger.w(TAG, "savedInstanceState was not null, but no ChanState was found!");
@ -148,6 +155,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
}
} else {
final Uri data = getIntent().getData();
// Start from an url launch.
if (data != null) {
Loadable fromUri = ChanHelper.getLoadableFromStartUri(data);
if (fromUri != null) {
@ -171,11 +179,15 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
}
}
// 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) {
browseController.loadDefault();
if (boardManager.getSavedBoards().isEmpty()) {
mainNavigationController.pushController(new SiteSetupController(this), false);
} else {
browseController.loadDefault();
}
}
versionHandler.run();
}
private Pair<Loadable, Loadable> resolveChanState(ChanState state) {

@ -15,14 +15,17 @@
* 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;
package org.floens.chan.ui.animation;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.graphics.drawable.ColorDrawable;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
import java.util.HashMap;
import java.util.Map;
@ -153,4 +156,27 @@ public class AnimationUtils {
public interface LayoutAnimationProgress {
void onLayoutAnimationProgress(View view, boolean vertical, int from, int to, int value, float progress);
}
public static void animateTextColor(final TextView text, int to) {
ValueAnimator animation = ValueAnimator.ofObject(new ArgbEvaluator(), text.getCurrentTextColor(), to);
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
text.setTextColor((int) animation.getAnimatedValue());
}
});
animation.start();
}
public static void animateBackgroundColorDrawable(final View view, int newColor) {
int currentBackground = ((ColorDrawable) view.getBackground()).getColor();
ValueAnimator animation = ValueAnimator.ofObject(new ArgbEvaluator(), currentBackground, newColor);
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
view.setBackgroundColor((int) animation.getAnimatedValue());
}
});
animation.start();
}
}

@ -777,7 +777,7 @@ public class PostCell extends LinearLayout implements PostCellInterface {
}
private void request() {
request = getGraph().getImageLoader().get(url.toString(), this);
request = getGraph().get(ImageLoader.class).get(url.toString(), this);
}
private void cancel() {

@ -0,0 +1,178 @@
/*
* 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.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
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.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.ImageView;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
import static org.floens.chan.utils.AndroidUtils.getRes;
public class BoardSetupController extends Controller implements View.OnClickListener {
private AutoCompleteTextView code;
private RecyclerView savedBoardsRecycler;
private SavedBoardsAdapter adapter;
public BoardSetupController(Context context) {
super(context);
}
@Override
public void onCreate() {
super.onCreate();
view = inflateRes(R.layout.controller_board_setup);
navigationItem.setTitle(R.string.saved_boards_title);
code = (AutoCompleteTextView) view.findViewById(R.id.code);
savedBoardsRecycler = (RecyclerView) view.findViewById(R.id.boards_recycler);
savedBoardsRecycler.setLayoutManager(new LinearLayoutManager(context));
adapter = new SavedBoardsAdapter();
savedBoardsRecycler.setAdapter(adapter);
List<SavedBoard> savedBoards = new ArrayList<>();
for (int board = 0; board < 5; board++) {
savedBoards.add(new SavedBoard("foo - " + board, board));
}
adapter.setSavedBoards(savedBoards);
List<String> foo = new ArrayList<String>() {{
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("foo");
add("bar");
add("baz");
}};
ArrayAdapter<String> adapter = new ArrayAdapter<>(context, android.R.layout.simple_dropdown_item_1line, foo);
code.setAdapter(adapter);
code.setThreshold(1);
}
@Override
public void onClick(View v) {
}
private class SavedBoard {
private String title;
private int id;
public SavedBoard(String title, int id) {
this.title = title;
this.id = id;
}
}
private class SavedBoardsAdapter extends RecyclerView.Adapter<SavedBoardCell> {
private List<SavedBoard> savedBoards = new ArrayList<>();
public SavedBoardsAdapter() {
setHasStableIds(true);
}
private void setSavedBoards(List<SavedBoard> savedBoards) {
this.savedBoards.clear();
this.savedBoards.addAll(savedBoards);
notifyDataSetChanged();
}
@Override
public long getItemId(int position) {
return savedBoards.get(position).id;
}
@Override
public SavedBoardCell onCreateViewHolder(ViewGroup parent, int viewType) {
return new SavedBoardCell(LayoutInflater.from(context).inflate(R.layout.cell_saved_board, parent, false));
}
@Override
public void onBindViewHolder(SavedBoardCell holder, int position) {
SavedBoard savedBoard = savedBoards.get(position);
holder.setSavedBoard(savedBoard);
}
@Override
public int getItemCount() {
return savedBoards.size();
}
}
private class SavedBoardCell extends RecyclerView.ViewHolder {
private ImageView image;
private TextView text;
public SavedBoardCell(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.image);
text = (TextView) itemView.findViewById(R.id.text);
}
public void setSavedBoard(SavedBoard savedBoard) {
Bitmap bitmap;
try {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScaled = false;
bitmap = BitmapFactory.decodeStream(getAppContext().getAssets().open("icons/4chan.png"), null, opts);
} catch (IOException e) {
throw new RuntimeException(e);
}
BitmapDrawable drawable = new BitmapDrawable(getRes(), bitmap);
drawable = (BitmapDrawable) drawable.mutate();
drawable.getPaint().setFilterBitmap(false);
image.setImageDrawable(drawable);
text.setText(savedBoard.title);
}
}
}

@ -48,7 +48,7 @@ import org.floens.chan.ui.toolbar.ToolbarMenu;
import org.floens.chan.ui.toolbar.ToolbarMenuItem;
import org.floens.chan.ui.view.FloatingMenuItem;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils;
import org.floens.chan.ui.animation.AnimationUtils;
import java.util.ArrayList;
import java.util.Collections;

@ -37,7 +37,7 @@ import org.floens.chan.core.site.http.LoginRequest;
import org.floens.chan.core.site.http.LoginResponse;
import org.floens.chan.ui.view.CrossfadeView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils;
import org.floens.chan.ui.animation.AnimationUtils;
import javax.inject.Inject;

@ -0,0 +1,357 @@
/*
* 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.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.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.SetupPresenter;
import org.floens.chan.ui.animation.AnimationUtils;
import java.util.ArrayList;
import java.util.List;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor;
public class SiteSetupController extends StyledToolbarNavigationController implements View.OnClickListener, SetupPresenter.Callback {
private EditText url;
private View urlSubmit;
private View spinner;
private Button next;
private boolean blocked = false;
private SetupPresenter presenter;
private RecyclerView sitesRecyclerview;
private SitesAdapter sitesAdapter;
private List<SetupPresenter.AddedSite> sites = new ArrayList<>();
public SiteSetupController(Context context) {
super(context);
}
@Override
public void onCreate() {
super.onCreate();
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);
presenter = new SetupPresenter();
sitesAdapter = new SitesAdapter();
sitesRecyclerview.setAdapter(sitesAdapter);
presenter.create(this);
}
@Override
public void onClick(View v) {
if (blocked) return;
if (v == urlSubmit) {
presenter.onUrlSubmitClicked(url.getText().toString());
} else if (v == next) {
presenter.onNextClicked();
}
}
@Override
public boolean onBack() {
if (presenter.mayExit()) {
return super.onBack();
} else {
return true;
}
}
@Override
public void moveToSavedBoards() {
navigationController.pushController(new BoardSetupController(context), new FadeInTransition());
}
@Override
public void goToUrlSubmittedState() {
spinner.setVisibility(View.VISIBLE);
urlSubmit.setVisibility(View.INVISIBLE);
}
@Override
public void runSiteAddedAnimation(final SetupPresenter.AddedSite 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<SetupPresenter.AddedSite> 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(SetupPresenter.AddedSite 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.title);
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();
}
private class SitesAdapter extends RecyclerView.Adapter<SiteCell> {
private SetupPresenter.AddedSite 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) {
SetupPresenter.AddedSite 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;
public SiteCell(View itemView) {
super(itemView);
image = (ImageView) itemView.findViewById(R.id.image);
text = (TextView) itemView.findViewById(R.id.text);
}
private void setSite(SetupPresenter.AddedSite site) {
image.setImageDrawable(site.drawable);
text.setText(site.title);
}
}
}

@ -37,6 +37,7 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.chan.ChanParser;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.model.Board;
import org.floens.chan.core.model.Loadable;
@ -249,7 +250,7 @@ public class ThemeSettingsController extends Controller implements View.OnClickL
"http://example.com/" +
"<br>" +
"Phasellus consequat semper sodales. Donec dolor lectus, aliquet nec mollis vel, rutrum vel enim.");
Post post = getGraph().getChanParser().parse(theme, builder);
Post post = getGraph().get(ChanParser.class).parse(theme, builder);
LinearLayout linearLayout = new LinearLayout(themeContext);
linearLayout.setOrientation(LinearLayout.VERTICAL);

@ -239,7 +239,7 @@ public class FilterLayout extends LinearLayout implements View.OnClickListener {
final LinearLayout selectLayout = (LinearLayout) LayoutInflater.from(getContext())
.inflate(R.layout.layout_site_board_select, null);
final Spinner spinner = (Spinner) selectLayout.findViewById(R.id.spinner);
final Spinner spinner = (Spinner) selectLayout.findViewById(R.id.progress);
final List<? extends Site> allSites = Sites.ALL_SITES;

@ -50,7 +50,7 @@ import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.ui.view.LoadView;
import org.floens.chan.ui.view.SelectionListeningEditText;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils;
import org.floens.chan.ui.animation.AnimationUtils;
import org.floens.chan.utils.ImageDecoder;
import java.io.File;

@ -47,7 +47,7 @@ import org.floens.chan.ui.cell.ThreadStatusCell;
import org.floens.chan.ui.toolbar.Toolbar;
import org.floens.chan.ui.view.ThumbnailView;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils;
import org.floens.chan.ui.animation.AnimationUtils;
import java.util.Calendar;
import java.util.List;

@ -28,7 +28,7 @@ import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.AnimationUtils;
import org.floens.chan.ui.animation.AnimationUtils;
import java.util.ArrayList;
import java.util.List;

@ -263,10 +263,19 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
public void setArrowMenuProgress(float progress) {
arrowMenuDrawable.setProgress(progress);
}
public void setShowArrowMenu(boolean show) {
arrowMenuView.setVisibility(show ? VISIBLE : GONE);
}
public ArrowMenuDrawable getArrowMenuDrawable() {
return arrowMenuDrawable;
}
@ -291,6 +300,8 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
private void init() {
setOrientation(HORIZONTAL);
if (isInEditMode()) return;
FrameLayout leftButtonContainer = new FrameLayout(getContext());
addView(leftButtonContainer, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
@ -309,7 +320,9 @@ public class Toolbar extends LinearLayout implements View.OnClickListener {
addView(navigationItemContainer, new LayoutParams(0, LayoutParams.MATCH_PARENT, 1f));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(dp(4f));
if (getElevation() == 0f) {
setElevation(dp(4f));
}
}
}

@ -28,7 +28,7 @@ import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ProgressBar;
import org.floens.chan.utils.AnimationUtils;
import org.floens.chan.ui.animation.AnimationUtils;
/**
* Container for a view with an ProgressBar. Toggles between the view and a
@ -100,6 +100,7 @@ public class LoadView extends FrameLayout {
public View setView(View newView, boolean animate) {
if (newView == null) {
FrameLayout progressBar = new FrameLayout(getContext());
progressBar.setVisibility(View.GONE);
progressBar.addView(new ProgressBar(getContext()), new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER));
newView = progressBar;
}

@ -105,7 +105,7 @@ public class ThumbnailView extends View implements ImageLoader.ImageListener {
}
if (!TextUtils.isEmpty(url)) {
container = getGraph().getImageLoader().get(url, this, width, height);
container = getGraph().get(ImageLoader.class).get(url, this, width, height);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

@ -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/>.
-->
<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"/>
</LinearLayout>

@ -0,0 +1,41 @@
<?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="?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"
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" />
</LinearLayout>

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?><!--
Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/backcolor">
<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.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"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/code"/>
</android.support.constraint.ConstraintLayout>

@ -0,0 +1,150 @@
<?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"
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"/>
</android.support.constraint.ConstraintLayout>

@ -24,7 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:paddingRight="16dp">
<Spinner
android:id="@+id/spinner"
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

@ -44,6 +44,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="delete">Delete</string>
<string name="undo">Undo</string>
<string name="save">Save</string>
<string name="next">Next</string>
<string name="permission_app_settings">App settings</string>
<string name="permission_grant">Grant</string>
@ -163,6 +164,17 @@ 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="saved_boards_title">Saved 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>
<string name="board_edit">Board editor</string>
<string name="board_edit_header">Add, remove and reorder your boards here.\nThe topmost board will be loaded automatically.</string>
<string name="board_add">Add board</string>

@ -11,5 +11,8 @@ buildscript {
allprojects {
repositories {
jcenter()
maven {
url 'https://maven.google.com'
}
}
}

@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx2048M
Loading…
Cancel
Save