Initial commit

This commit is contained in:
inorichi 2015-09-24 17:27:43 +02:00
commit b69510e972
57 changed files with 1965 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.gradle
/local.properties
/.idea/workspace.xml
.DS_Store
/build
.idea/
*iml
*.iml
*/build

19
.travis.yml Normal file
View File

@ -0,0 +1,19 @@
language: android
android:
components:
- platform-tools
- tools
# The BuildTools version used by your project
- build-tools-23.0.1
- android-23
- extra-android-m2repository
- extra-google-m2repository
- extra-android-support
- extra-google-google_play_services
before_script:
- chmod +x gradlew
#Build, and run tests
script: "./gradlew build testDebug"
sudo: false

21
README.md Normal file
View File

@ -0,0 +1,21 @@
Mangafeed is a Manga reader for Android that tries to have the same features as Manga Watcher, but being Open Source.
## License
Copyright 2015 Javier Tomás
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## Disclaimer
The developer of this application does not have any affiliation with the content providers available.

4
app/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/build
*iml
*.iml
.idea

85
app/build.gradle Normal file
View File

@ -0,0 +1,85 @@
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
publishNonDefault true
defaultConfig {
applicationId "eu.kanade.mangafeed"
minSdkVersion 16
targetSdkVersion 23
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'LICENSE.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE'
}
lintOptions {
abortOnError false
}
}
dependencies {
final SUPPORT_LIBRARY_VERSION = '23.0.1'
final DAGGER_VERSION = '2.0.1'
final HAMCREST_VERSION = '1.3'
final MOCKITO_VERSION = '1.10.19'
compile fileTree(dir: 'libs', include: ['*.jar'])
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta1'
compile 'com.squareup.okhttp:okhttp-urlconnection:2.4.0'
compile 'com.squareup.okhttp:okhttp:2.4.0'
compile 'com.pushtorefresh.storio:sqlite:1.4.0'
compile 'com.pushtorefresh.storio:sqlite-annotations:1.4.0'
compile 'de.greenrobot:eventbus:2.4.0'
compile 'com.github.bumptech.glide:glide:3.6.1'
compile 'de.hdodenhof:circleimageview:1.3.0'
compile 'io.reactivex:rxandroid:1.0.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.jakewharton.timber:timber:3.1.0'
compile 'uk.co.ribot:easyadapter:1.5.0@aar'
compile "com.google.dagger:dagger:$DAGGER_VERSION"
apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
provided 'org.glassfish:javax.annotation:10.0-b28'
compile('com.mikepenz:materialdrawer:4.3.0@aar') {
transitive = true
}
testCompile 'junit:junit:4.12'
testCompile "org.hamcrest:hamcrest-core:$HAMCREST_VERSION"
testCompile "org.hamcrest:hamcrest-library:$HAMCREST_VERSION"
testCompile "org.hamcrest:hamcrest-integration:$HAMCREST_VERSION"
testCompile "org.mockito:mockito-core:$MOCKITO_VERSION"
testCompile('org.robolectric:robolectric:3.0') {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
}
androidTestApt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
}

17
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/hitherejoe/Android Studio.app/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.mangafeed" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="eu.kanade.mangafeed.ui.activity.MainActivity"
android:label="@string/label_main"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name="eu.kanade.mangafeed.data.SyncService$SyncOnConnectionAvailable"
android:enabled="false">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -0,0 +1,34 @@
package eu.kanade.mangafeed;
import android.app.Application;
import android.content.Context;
import timber.log.Timber;
public class App extends Application {
AppComponent mApplicationComponent;
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) Timber.plant(new Timber.DebugTree());
mApplicationComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
public static App get(Context context) {
return (App) context.getApplicationContext();
}
public AppComponent getComponent() {
return mApplicationComponent;
}
// Needed to replace the component with a test specific one
public void setComponent(AppComponent applicationComponent) {
mApplicationComponent = applicationComponent;
}
}

View File

@ -0,0 +1,23 @@
package eu.kanade.mangafeed;
import android.app.Application;
import javax.inject.Singleton;
import dagger.Component;
import eu.kanade.mangafeed.data.DataModule;
import eu.kanade.mangafeed.ui.activity.MainActivity;
@Singleton
@Component(
modules = {
AppModule.class,
DataModule.class
}
)
public interface AppComponent {
void inject(MainActivity mainActivity);
Application application();
}

View File

@ -0,0 +1,28 @@
package eu.kanade.mangafeed;
import android.app.Application;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
/**
* Provide application-level dependencies. Mainly singleton object that can be injected from
* anywhere in the app.
*/
@Module
public class AppModule {
protected final Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
@Provides
@Singleton
Application provideApplication() {
return mApplication;
}
}

View File

@ -0,0 +1,38 @@
package eu.kanade.mangafeed.data;
import android.app.Application;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import eu.kanade.mangafeed.data.helpers.PreferencesHelper;
import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
import rx.Scheduler;
import rx.schedulers.Schedulers;
/**
* Provide dependencies to the DataManager, mainly Helper classes and Retrofit services.
*/
@Module
public class DataModule {
@Provides
@Singleton
PreferencesHelper providePreferencesHelper(Application app) {
return new PreferencesHelper(app);
}
@Provides
@Singleton
DatabaseHelper provideDatabaseHelper(Application app) {
return new DatabaseHelper(app);
}
@Provides
@Singleton
Scheduler provideSubscribeScheduler() {
return Schedulers.io();
}
}

View File

@ -0,0 +1,198 @@
package eu.kanade.mangafeed.data.entities;
/**
* Created by len on 23/09/2015.
*/
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
import eu.kanade.mangafeed.data.tables.MangasTable;
@StorIOSQLiteType(table = MangasTable.TABLE)
public class Manga {
@Nullable
@StorIOSQLiteColumn(name = MangasTable.COLUMN_ID, key = true)
Long id;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_SOURCE)
int source;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_URL)
String url;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_ARTIST)
String artist;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_AUTHOR)
String author;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_DESCRIPTION)
String description;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_GENRE)
String genre;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_TITLE)
String title;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_STATUS)
String status;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_THUMBNAIL_URL)
String thumbnail_url;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_RANK)
int rank;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_LAST_UPDATE)
long last_update;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_INITIALIZED)
boolean initialized;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_VIEWER)
int viewer;
@NonNull
@StorIOSQLiteColumn(name = MangasTable.COLUMN_CHAPTER_ORDER)
int chapter_order;
Manga() {}
@Nullable
public Long id() {
return id;
}
@NonNull
public int source() {
return source;
}
@NonNull
public String url() {
return url;
}
@NonNull
public String artist() {
return artist;
}
@NonNull
public String author() {
return author;
}
@NonNull
public String description() {
return description;
}
@NonNull
public String genre() {
return genre;
}
@NonNull
public String title() {
return title;
}
@NonNull
public String status() {
return status;
}
@NonNull
public String thumbnail_url() {
return thumbnail_url;
}
@NonNull
public int rank() {
return rank;
}
@NonNull
public long last_update() {
return last_update;
}
@NonNull
public boolean nitialized() {
return initialized;
}
@NonNull
public int viewer() {
return viewer;
}
@NonNull
public int chapter_order() {
return chapter_order;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Manga manga = (Manga) o;
if (source != manga.source) return false;
if (rank != manga.rank) return false;
if (last_update != manga.last_update) return false;
if (initialized != manga.initialized) return false;
if (viewer != manga.viewer) return false;
if (chapter_order != manga.chapter_order) return false;
if (id != null ? !id.equals(manga.id) : manga.id != null) return false;
if (!url.equals(manga.url)) return false;
if (!artist.equals(manga.artist)) return false;
if (!author.equals(manga.author)) return false;
if (!description.equals(manga.description)) return false;
if (!genre.equals(manga.genre)) return false;
if (!title.equals(manga.title)) return false;
if (!status.equals(manga.status)) return false;
return thumbnail_url.equals(manga.thumbnail_url);
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + source;
result = 31 * result + url.hashCode();
result = 31 * result + artist.hashCode();
result = 31 * result + author.hashCode();
result = 31 * result + description.hashCode();
result = 31 * result + genre.hashCode();
result = 31 * result + title.hashCode();
result = 31 * result + status.hashCode();
result = 31 * result + thumbnail_url.hashCode();
result = 31 * result + rank;
result = 31 * result + (int) (last_update ^ (last_update >>> 32));
result = 31 * result + (initialized ? 1 : 0);
result = 31 * result + viewer;
result = 31 * result + chapter_order;
return result;
}
}

View File

@ -0,0 +1,43 @@
package eu.kanade.mangafeed.data.helpers;
import android.content.Context;
import com.pushtorefresh.storio.sqlite.StorIOSQLite;
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite;
import com.pushtorefresh.storio.sqlite.queries.Query;
import java.util.List;
import eu.kanade.mangafeed.data.entities.Manga;
import eu.kanade.mangafeed.data.tables.MangasTable;
import rx.Observable;
/**
* Created by len on 23/09/2015.
*/
public class DatabaseHelper {
private StorIOSQLite db;
public DatabaseHelper(Context context) {
db = DefaultStorIOSQLite.builder()
.sqliteOpenHelper(new DbOpenHelper(context))
.build();
}
public StorIOSQLite getStorIODb() {
return db;
}
public Observable<List<Manga>> getMangas() {
return db.get()
.listOfObjects(Manga.class)
.withQuery(Query.builder()
.table(MangasTable.TABLE)
.build())
.prepare()
.createObservable();
}
}

View File

@ -0,0 +1,31 @@
package eu.kanade.mangafeed.data.helpers;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import eu.kanade.mangafeed.data.tables.MangasTable;
/**
* Created by len on 23/09/2015.
*/
public class DbOpenHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "mangafeed.db";
public static final int DATABASE_VERSION = 1;
public DbOpenHelper(@NonNull Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(@NonNull SQLiteDatabase db) {
db.execSQL(MangasTable.getCreateTableQuery());
}
@Override
public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) {
// no impl
}
}

View File

@ -0,0 +1,21 @@
package eu.kanade.mangafeed.data.helpers;
import android.content.Context;
import android.content.SharedPreferences;
public class PreferencesHelper {
private static SharedPreferences mPref;
public static final String PREF_FILE_NAME = "android_boilerplate_pref_file";
public PreferencesHelper(Context context) {
mPref = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
}
public void clear() {
mPref.edit().clear().apply();
}
}

View File

@ -0,0 +1,18 @@
package eu.kanade.mangafeed.data.tables;
import android.support.annotation.NonNull;
/**
* Created by len on 23/09/2015.
*/
public class CategoriesTable {
@NonNull
public static final String TABLE = "categories";
@NonNull
public static final String COLUMN_ID = "_id";
@NonNull
public static final String COLUMN_NAME = "name";
}

View File

@ -0,0 +1,30 @@
package eu.kanade.mangafeed.data.tables;
import android.support.annotation.NonNull;
/**
* Created by len on 23/09/2015.
*/
public class ChaptersTable {
@NonNull
public static final String TABLE = "chapters";
@NonNull
public static final String COLUMN_ID = "_id";
@NonNull
public static final String COLUMN_MANGA_ID = "manga_id";
@NonNull
public static final String COLUMN_URL = "url";
@NonNull
public static final String COLUMN_NAME = "name";
@NonNull
public static final String COLUMN_READ = "read";
@NonNull
public static final String COLUMN_DATE_FETCH = "date_fetch";
}

View File

@ -0,0 +1,18 @@
package eu.kanade.mangafeed.data.tables;
import android.support.annotation.NonNull;
/**
* Created by len on 23/09/2015.
*/
public class MangasCategoriesTable {
@NonNull
public static final String TABLE = "mangas_categories";
@NonNull
public static final String COLUMN_MANGA_ID = "_manga_id";
@NonNull
public static final String COLUMN_CATEGORY_ID = "_category_id";
}

View File

@ -0,0 +1,85 @@
package eu.kanade.mangafeed.data.tables;
import android.support.annotation.NonNull;
/**
* Created by len on 23/09/2015.
*/
public class MangasTable {
@NonNull
public static final String TABLE = "mangas";
@NonNull
public static final String COLUMN_ID = "_id";
@NonNull
public static final String COLUMN_SOURCE = "source";
@NonNull
public static final String COLUMN_URL = "url";
@NonNull
public static final String COLUMN_ARTIST = "artist";
@NonNull
public static final String COLUMN_AUTHOR = "author" ;
@NonNull
public static final String COLUMN_DESCRIPTION = "description";
@NonNull
public static final String COLUMN_GENRE = "genre";
@NonNull
public static final String COLUMN_TITLE = "title";
@NonNull
public static final String COLUMN_STATUS = "status";
@NonNull
public static final String COLUMN_THUMBNAIL_URL = "thumbnail_url";
@NonNull
public static final String COLUMN_RANK = "rank";
@NonNull
public static final String COLUMN_LAST_UPDATE = "last_update";
@NonNull
public static final String COLUMN_INITIALIZED = "initialized";
@NonNull
public static final String COLUMN_VIEWER = "viewer";
@NonNull
public static final String COLUMN_CHAPTER_ORDER = "chapter_order";
// This is just class with Meta Data, we don't need instances
private MangasTable() {
throw new IllegalStateException("No instances please");
}
// Better than static final field -> allows VM to unload useless String
// Because you need this string only once per application life on the device
@NonNull
public static String getCreateTableQuery() {
return "CREATE TABLE " + TABLE + "("
+ COLUMN_ID + " INTEGER NOT NULL PRIMARY KEY, "
+ COLUMN_SOURCE + " INTEGER NOT NULL, "
+ COLUMN_URL + " TEXT NOT NULL, "
+ COLUMN_ARTIST + " TEXT NOT NULL, "
+ COLUMN_AUTHOR + " TEXT NOT NULL, "
+ COLUMN_DESCRIPTION + " TEXT NOT NULL, "
+ COLUMN_GENRE + " TEXT NOT NULL, "
+ COLUMN_TITLE + " TEXT NOT NULL, "
+ COLUMN_STATUS + " TEXT NOT NULL, "
+ COLUMN_THUMBNAIL_URL + " TEXT NOT NULL, "
+ COLUMN_RANK + " INTEGER NOT NULL, "
+ COLUMN_LAST_UPDATE + " LONG NOT NULL, "
+ COLUMN_INITIALIZED + " BOOLEAN NOT NULL, "
+ COLUMN_VIEWER + " INTEGER NOT NULL, "
+ COLUMN_CHAPTER_ORDER + " INTEGER NOT NULL"
+ ");";
}
}

View File

@ -0,0 +1,32 @@
package eu.kanade.mangafeed.ui.activity;
import android.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import eu.kanade.mangafeed.App;
import eu.kanade.mangafeed.AppComponent;
public class BaseActivity extends AppCompatActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
FragmentManager fm = getFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
finish();
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}
protected AppComponent applicationComponent() {
return App.get(this).getComponent();
}
}

View File

@ -0,0 +1,85 @@
package eu.kanade.mangafeed.ui.activity;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ProgressBar;
import javax.inject.Inject;
import eu.kanade.mangafeed.R;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
import rx.subscriptions.CompositeSubscription;
import timber.log.Timber;
import uk.co.ribot.easyadapter.EasyRecyclerAdapter;
public class MainActivity extends BaseActivity {
@Bind(R.id.recycler_characters)
RecyclerView mCharactersRecycler;
@Bind(R.id.toolbar)
Toolbar mToolbar;
@Bind(R.id.progress_indicator)
ProgressBar mProgressBar;
@Bind(R.id.swipe_container)
SwipeRefreshLayout mSwipeRefresh;
@Inject DatabaseHelper mDb;
private CompositeSubscription mSubscriptions;
private EasyRecyclerAdapter<Character> mEasyRecycleAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
applicationComponent().inject(this);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mSubscriptions = new CompositeSubscription();
//mDataManager = App.get(this).getComponent().dataManager();
setupToolbar();
setupRecyclerView();
}
@Override
protected void onDestroy() {
super.onDestroy();
mSubscriptions.unsubscribe();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_github:
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void setupToolbar() {
setSupportActionBar(mToolbar);
}
private void setupRecyclerView() {
mCharactersRecycler.setLayoutManager(new LinearLayoutManager(this));
mCharactersRecycler.setAdapter(mEasyRecycleAdapter);
mSwipeRefresh.setColorSchemeResources(R.color.primary);
}
}

View File

@ -0,0 +1,37 @@
package eu.kanade.mangafeed.ui.adapter;
import android.content.Intent;
import android.net.Uri;
import android.view.View;
import android.widget.TextView;
import uk.co.ribot.easyadapter.ItemViewHolder;
import uk.co.ribot.easyadapter.PositionInfo;
import uk.co.ribot.easyadapter.annotations.LayoutId;
import uk.co.ribot.easyadapter.annotations.ViewId;
@LayoutId(eu.kanade.mangafeed.R.layout.item_detail)
public class DetailHolder extends ItemViewHolder<String> {
@ViewId(eu.kanade.mangafeed.R.id.text_detail)
TextView mDetailText;
public DetailHolder(View view) {
super(view);
}
@Override
public void onSetValues(String item, PositionInfo positionInfo) {
mDetailText.setText(item);
}
@Override
public void onSetListeners() {
mDetailText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getContext().startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getItem())));
}
});
}
}

View File

@ -0,0 +1,33 @@
package eu.kanade.mangafeed.util;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import timber.log.Timber;
public class AndroidComponentUtil {
public static void toggleComponent(Context context, Class componentClass, boolean enable) {
Timber.i((enable ? "Enabling " : "Disabling ") + componentClass.getSimpleName());
ComponentName componentName = new ComponentName(context, componentClass);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(componentName,
enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}
public static boolean isServiceRunning(Context context, Class serviceClass) {
ActivityManager manager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,13 @@
package eu.kanade.mangafeed.util;
import android.content.Context;
import android.net.ConnectivityManager;
public class DataUtils {
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return connectivityManager.getActiveNetworkInfo() != null;
}
}

View File

@ -0,0 +1,39 @@
package eu.kanade.mangafeed.util;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.support.annotation.StringRes;
import android.support.v7.app.AlertDialog;
import eu.kanade.mangafeed.R;
public class DialogFactory {
public static Dialog createSimpleOkErrorDialog(Context context, String title, String message) {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message)
.setNeutralButton(R.string.dialog_action_ok, null);
return alertDialog.create();
}
public static Dialog createSimpleErrorDialog(Context context) {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(context)
.setTitle(context.getString(R.string.dialog_error_title))
.setMessage(context.getString(R.string.dialog_general_error_Message))
.setNeutralButton(R.string.dialog_action_ok, null);
return alertDialog.create();
}
public static ProgressDialog createProgressDialog(Context context, String message) {
ProgressDialog progressDialog = new ProgressDialog(context);
progressDialog.setMessage(message);
return progressDialog;
}
public static ProgressDialog createProgressDialog(Context context, @StringRes int messageResoruce) {
return createProgressDialog(context, context.getString(messageResoruce));
}
}

View File

@ -0,0 +1,27 @@
package eu.kanade.mangafeed.util;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import retrofit.HttpException;
public class NetworkUtil {
/**
* Returns true if the Throwable is an instance of RetrofitError with an
* http status code equals to the given one.
*/
public static boolean isHttpStatusCode(Throwable throwable, int statusCode) {
return throwable instanceof HttpException
&& ((HttpException) throwable).code() == statusCode;
}
public static boolean isNetworkConnected(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnectedOrConnecting();
}
}

View File

@ -0,0 +1,18 @@
package eu.kanade.mangafeed.util;
import android.content.Context;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.view.ViewGroup;
import eu.kanade.mangafeed.R;
public class SnackbarFactory {
public static Snackbar createSnackbar(Context context, View view, String message) {
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_SHORT);
ViewGroup group = (ViewGroup) snackbar.getView();
group.setBackgroundColor(context.getResources().getColor(R.color.primary));
return snackbar;
}
}

View File

@ -0,0 +1,13 @@
package eu.kanade.mangafeed.util;
import android.content.Context;
import android.util.DisplayMetrics;
public class ViewUtils {
public static float convertPixelsToDp(float px, Context context){
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return px / (metrics.densityDpi / 160f);
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/line_grey">
<item android:drawable="@color/white"/>
</ripple>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/super_light_grey" android:state_pressed="true"/>
<item android:drawable="@color/super_light_grey" android:state_focused="true"/>
<item android:drawable="@color/white"/>
</selector>

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="180dp"
android:layout_gravity="top"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_collapsing"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:layout_collapseMode="pin"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_light_grey"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/text_lorem_ipsum"
android:textColor="@color/black_87pc"
android:textSize="@dimen/text_body"/>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:minHeight="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary"
app:layout_scrollFlags="scroll|enterAlways"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
<android.support.design.widget.TabLayout
android:id="@+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary"
app:tabGravity="center"
app:tabIndicatorColor="@color/accent"
app:tabIndicatorHeight="3dp"
app:tabMode="scrollable"
app:tabSelectedTextColor="@color/white"
app:tabTextColor="@color/primary_light"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/pager_character_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/app_bar"
android:background="@color/white"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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="@color/bg_light_grey">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_characters"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:clipToPadding="false"/>
</android.support.v4.widget.SwipeRefreshLayout>
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:id="@+id/progress_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
</RelativeLayout>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"
android:background="@color/primary"
android:theme="@style/AppTheme.ActionBar"/>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<TextView
android:id="@+id/text_no_data"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="2"
android:ellipsize="end"
android:text="@string/text_no_data"
android:textColor="@color/black_87pc"
android:visibility="gone"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="4dp"
android:paddingBottom="8dp"
android:clipToPadding="false"/>
</FrameLayout>

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
card_view:cardCornerRadius="2dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<RelativeLayout
android:id="@+id/container_character"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/touchable_background_white">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/image_character"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"/>
<TextView
android:id="@+id/text_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/image_character"
android:layout_alignTop="@id/image_character"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="4dp"
android:textColor="@color/black_87pc"
android:textStyle="bold"/>
<TextView
android:id="@+id/text_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/text_name"
android:layout_alignLeft="@id/text_name"
android:layout_marginBottom="24dp"
android:layout_marginRight="16dp"
android:maxLines="2"
android:ellipsize="end"
android:textColor="@color/black_87pc"/>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/container_character"
android:background="@color/white">
<View
android:id="@+id/seperator_line"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_alignParentTop="true"
android:background="@color/light_grey"/>
<TextView
style="@style/CardButtonTextStyle"
android:id="@+id/text_view"
android:layout_below="@+id/seperator_line"
android:background="@drawable/touchable_background_white"
android:textColor="@color/primary_text"
android:text="@string/text_view"/>
<TextView
style="@style/CardButtonTextStyle"
android:id="@+id/text_tab"
android:layout_toRightOf="@+id/text_view"
android:layout_alignBottom="@+id/text_view"
android:background="@drawable/touchable_background_white"
android:textColor="@color/primary"
android:text="@string/text_collections"/>
</RelativeLayout>
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:maxLines="2"
android:background="@drawable/touchable_background_white"
android:ellipsize="end"
android:textColor="@color/black_87pc"
android:textSize="@dimen/text_large_body"/>

View File

@ -0,0 +1,6 @@
<menu 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" tools:context=".MainActivity">
<item android:id="@+id/action_github" android:title="@string/action_github"
android:orderInCategory="100" app:showAsAction="never" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#607D8B</color>
<color name="primary_dark">#455A64</color>
<color name="primary_light">#CFD8DC</color>
<color name="accent">#009688</color>
<color name="primary_text">#212121</color>
<color name="secondary_text">#727272</color>
<color name="icons">#FFFFFF</color>
<color name="divider">#B6B6B6</color>
<color name="white">#FFFFFF</color>
<color name="super_light_grey">#FAFAFA</color>
<color name="line_grey">#D7D7D7</color>
<color name="light_grey">#D4D4D4</color>
<color name="bg_light_grey">#E9E9E9</color>
<color name="black_87pc">#DD000000</color>
</resources>

View File

@ -0,0 +1,13 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="text_headline">24sp</dimen>
<dimen name="text_large_title">22sp</dimen>
<dimen name="text_title">20sp</dimen>
<dimen name="text_large_body">18sp</dimen>
<dimen name="text_body">16sp</dimen>
<dimen name="text_small_body">14sp</dimen>
</resources>

View File

@ -0,0 +1,57 @@
<resources>
<string name="app_name">AndroidBoilerPlate</string>
<!-- Activity labels -->
<string name="label_main">Boilerplate</string>
<!-- Main Activity -->
<string name="action_github">View on GitHub</string>
<string name="text_view">View</string>
<string name="text_collections">Collections</string>
<string name="text_no_description">No description available</string>
<string name="text_films_description">%d films</string>
<!-- Character Activity -->
<string name="text_lorem_ipsum">
Don\'t underestimate the Force. The Force is strong with this one. I have you now. He is here. \n \n
I suggest you try it again, Luke. This time, let go your conscious self and act on instinct. The more you tighten your grip, Tarkin, the more star systems will slip through your fingers. I\'m trying not to, kid. Red Five standing by.\n \n
I care. So, what do you think of her, Han? Obi-Wan is here. The Force is with him. I\'m surprised you had the courage to take the responsibility yourself. Red Five standing by. I need your help, Luke. She needs your help. I\'m getting too old for this sort of thing.\n \n
What!? All right. Well, take care of yourself, Han. I guess that\'s what you\'re best at, ain\'t it? I don\'t know what you\'re talking about. I am a member of the Imperial Senate on a diplomatic mission to Alderaan, Red Five standing by.\n \n
I suggest you try it again, Luke. This time, let go your conscious self and act on instinct. I want to come with you to Alderaan. There\'s nothing for me here now. I want to learn the ways of the Force and be a Jedi, like my father before me. She must have hidden the plans in the escape pod. Send a detachment down to retrieve them, and see to it personally, Commander. There\'ll be no one to stop us this time! Obi-Wan is here. The Force is with him.\n \n
Hey, Luke! May the Force be with you. Alderaan? I\'m not going to Alderaan. I\'ve got to go home. It\'s late, I\'m in for it as it is. Obi-Wan is here. The Force is with him. Don\'t be too proud of this technological terror you\'ve constructed. The ability to destroy a planet is insignificant next to the power of the Force. She must have hidden the plans in the escape pod. Send a detachment down to retrieve them, and see to it personally, Commander. There\'ll be no one to stop us this time!\n \n
</string>
<!-- Detail Fragment -->
<string-array name="detail_fragment_titles">
<item>Films</item>
<item>Vehicles</item>
<item>Species</item>
<item>Starships</item>
</string-array>
<string name="text_no_data">No data to display</string>
<!-- Dialogs -->
<string name="dialog_action_ok">OK</string>
<string name="dialog_action_delete">Delete</string>
<string name="dialog_action_cancel">Cancel</string>
<string name="dialog_error_title">Oops</string>
<string name="dialog_general_error_Message">There was an error making the request</string>
<string name="dialog_error_no_connection">Sorry, you need a connection to do that!</string>
<!-- Arrays -->
<integer-array name="characters">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</integer-array>
</resources>

View File

@ -0,0 +1,79 @@
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
<item name="alertDialogTheme">@style/AlertDialogStyle</item>
<item name="android:itemTextAppearance">@style/OptionsMenuTextColor</item>
<item name="android:textColorPrimary">@color/black_87pc</item>
<item name="android:textColor">@color/black_87pc</item>
<item name="colorControlNormal">@color/white</item>
</style>
<style name="AppTheme.NoActionBar" parent="AppTheme">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>
<style name="AppTheme.ActionBar" parent="AppTheme">
<item name="android:textColorPrimary">@color/white</item>
<item name="drawerArrowStyle">@style/HamburgerIconStyle</item>
<item name="android:itemTextAppearance">@style/OptionsMenuTextColor</item>
</style>
<style name="AlertDialogStyle" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:windowTitleStyle">@style/DialogTitleText</item>
<item name="colorAccent">@color/primary</item>
</style>
<style name="DialogTitleText">
<item name="android:textColor">@color/black_87pc</item>
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Title</item>
</style>
<style name="HamburgerIconStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="color">@color/icons</item>
</style>
<style name="OptionsMenuTextColor" parent="@android:style/TextAppearance.Widget.IconMenu.Item">
<item name="android:textColor">@android:color/black</item>
</style>
<style name="TitleTextStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">@color/primary</item>
<item name="android:textSize">@dimen/text_body</item>
<item name="android:textStyle">bold</item>
</style>
<style name="CardButtonTextStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:padding">16dp</item>
<item name="android:textSize">@dimen/text_small_body</item>
<item name="android:textStyle">bold</item>
<item name="android:background">@drawable/touchable_background_white</item>
</style>
<style name="ErrorTextStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginLeft">4dp</item>
<item name="android:layout_marginRight">4dp</item>
<item name="android:textColor">@android:color/holo_red_light</item>
<item name="android:textSize">@dimen/text_small_body</item>
<item name="android:visibility">invisible</item>
</style>
<style name="EmptyListTextStyle">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_centerInParent">true</item>
<item name="android:textColor">@color/primary_text</item>
<item name="android:textSize">@dimen/text_small_body</item>
<item name="android:visibility">gone</item>
</style>
</resources>

View File

@ -0,0 +1,70 @@
package eu.kanade.mangafeed;
import android.database.Cursor;
import eu.kanade.mangafeed.data.local.DatabaseHelper;
import eu.kanade.mangafeed.data.local.Db;
import eu.kanade.mangafeed.data.local.PreferencesHelper;
import eu.kanade.mangafeed.data.model.Character;
import eu.kanade.mangafeed.data.remote.AndroidBoilerplateService;
import eu.kanade.mangafeed.util.DefaultConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.List;
import rx.Observable;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = DefaultConfig.EMULATE_SDK, manifest = DefaultConfig.MANIFEST)
public class DataManagerTest {
private DataManager mDataManager;
private AndroidBoilerplateService mMockAndroidBoilerplateService;
private DatabaseHelper mDatabaseHelper;
@Before
public void setUp() {
mMockAndroidBoilerplateService = mock(AndroidBoilerplateService.class);
mDatabaseHelper = new DatabaseHelper(RuntimeEnvironment.application);
mDatabaseHelper.clearTables().subscribe();
mDataManager = new DataManager(mMockAndroidBoilerplateService,
mDatabaseHelper,
mock(Bus.class),
new PreferencesHelper(RuntimeEnvironment.application),
Schedulers.immediate());
}
@Test
public void shouldSyncCharacters() throws Exception {
int[] ids = new int[]{ 10034, 14050, 10435, 35093 };
List<Character> characters = MockModelsUtil.createListOfMockCharacters(4);
for (int i = 0; i < ids.length; i++) {
when(mMockAndroidBoilerplateService.getCharacter(ids[i]))
.thenReturn(Observable.just(characters.get(i)));
}
TestSubscriber<Character> result = new TestSubscriber<>();
mDataManager.syncCharacters(ids).subscribe(result);
result.assertNoErrors();
result.assertReceivedOnNext(characters);
Cursor cursor = mDatabaseHelper.getBriteDb()
.query("SELECT * FROM " + Db.CharacterTable.TABLE_NAME);
assertEquals(4, cursor.getCount());
cursor.close();
}
}

View File

@ -0,0 +1,65 @@
package eu.kanade.mangafeed;
import android.database.Cursor;
import eu.kanade.mangafeed.data.local.DatabaseHelper;
import eu.kanade.mangafeed.data.local.Db;
import eu.kanade.mangafeed.data.model.Character;
import eu.kanade.mangafeed.util.DefaultConfig;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import java.util.Collections;
import java.util.List;
import rx.observers.TestSubscriber;
import static junit.framework.Assert.assertEquals;
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = DefaultConfig.EMULATE_SDK, manifest = DefaultConfig.MANIFEST)
public class DatabaseHelperTest {
private DatabaseHelper mDatabaseHelper;
@Before
public void setUp() {
mDatabaseHelper = new DatabaseHelper(RuntimeEnvironment.application);
mDatabaseHelper.clearTables().subscribe();
}
@Test
public void shouldSetCharacters() throws Exception {
List<Character> characters = MockModelsUtil.createListOfMockCharacters(5);
TestSubscriber<Character> result = new TestSubscriber<>();
mDatabaseHelper.setCharacters(characters).subscribe(result);
result.assertNoErrors();
result.assertReceivedOnNext(characters);
Cursor cursor = mDatabaseHelper.getBriteDb()
.query("SELECT * FROM " + Db.CharacterTable.TABLE_NAME);
assertEquals(5, cursor.getCount());
for (Character character : characters) {
cursor.moveToNext();
assertEquals(character, Db.CharacterTable.parseCursor(cursor));
}
}
@Test
public void shouldGetCharacters() throws Exception {
List<Character> characters = MockModelsUtil.createListOfMockCharacters(5);
mDatabaseHelper.setCharacters(characters).subscribe();
TestSubscriber<List<Character>> result = new TestSubscriber<>();
mDatabaseHelper.getCharacters().subscribe(result);
result.assertNoErrors();
result.assertReceivedOnNext(Collections.singletonList(characters));
}
}

View File

@ -0,0 +1,7 @@
package eu.kanade.mangafeed.util;
public class DefaultConfig {
//The api level that Roboelectric will use to run the unit tests
public static final int EMULATE_SDK = 21;
public static final String MANIFEST = "./src/main/AndroidManifest.xml";
}

20
build.gradle Normal file
View File

@ -0,0 +1,20 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.7'
classpath 'me.tatarka:gradle-retrolambda:3.2.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}

18
gradle.properties Normal file
View File

@ -0,0 +1,18 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip

164
gradlew vendored Normal file
View File

@ -0,0 +1,164 @@
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

90
gradlew.bat vendored Normal file
View File

@ -0,0 +1,90 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
settings.gradle Normal file
View File

@ -0,0 +1 @@
include ':app'