├── .gitignore ├── README.md ├── app ├── .gitignore ├── app.iml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── rx │ │ └── demo │ │ ├── DemoApplication.java │ │ ├── dao │ │ ├── ImageDao.java │ │ └── RxDao.java │ │ ├── di │ │ ├── DataModule.java │ │ ├── UiModule.java │ │ └── annotation │ │ │ ├── HistoryViewBus.java │ │ │ └── ImageViewBus.java │ │ ├── model │ │ ├── Cursor.java │ │ ├── ImageRequest.java │ │ ├── ImageResponse.java │ │ ├── Page.java │ │ ├── ResponseData.java │ │ └── Result.java │ │ ├── rest │ │ └── ImagesApi.java │ │ ├── ui │ │ ├── activity │ │ │ ├── BaseActivity.java │ │ │ └── SearchActivity.java │ │ ├── animation │ │ │ ├── AnimationFactory.java │ │ │ └── FlipAnimation.java │ │ └── view │ │ │ ├── HistoryView.java │ │ │ ├── ImageCardView.java │ │ │ ├── SearchView.java │ │ │ ├── SquareDraweeView.java │ │ │ └── presenter │ │ │ ├── IViewPresenter.java │ │ │ └── ImageSearchPresenter.java │ │ └── util │ │ └── SubscriptionManager.java │ └── res │ ├── anim │ ├── fade_in.xml │ ├── fade_out.xml │ ├── slide_in_from_left.xml │ ├── slide_in_from_right.xml │ ├── slide_out_to_left.xml │ └── slide_out_to_right.xml │ ├── drawable-xhdpi │ └── history.png │ ├── drawable │ ├── grey_with_border.xml │ ├── white_view_with_border_clicked.xml │ └── white_with_border.xml │ ├── layout │ ├── activity_layout.xml │ ├── history_row.xml │ ├── history_view.xml │ ├── image_card.xml │ ├── row_view.xml │ └── search_view.xml │ ├── menu │ └── menu_main.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── attrs_civ.xml │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── import-summary.txt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # ideal config file 31 | *.iml 32 | .idea/ 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Sneak Peak of next version: 3 | https://github.com/digitalbuddha/DAOFramework 4 | 5 | 6 | RxJava Showcase Project 7 | ============= 8 | 9 | Sample Setup with Retrofit, RxJava and Dagger. 10 | 11 | 12 | Slides from NYCJava talk on RxJava: 13 | http://www.slideshare.net/nakhimovich/intro-to-functional-programming-with-rxjava 14 | 15 | Change Log 16 | 17 | 4/19/2015 - Renamed Commander to DAO 18 | 19 | 4/22/2015 - Fixed issue with images not loading after closing keyboard, sped up image load time 20 | 21 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Android template 3 | # Built application files 4 | *.apk 5 | *.ap_ 6 | 7 | # Files for the Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | 17 | # Gradle files 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | android { 4 | compileSdkVersion 19 5 | buildToolsVersion "19.1.0" 6 | 7 | defaultConfig { 8 | applicationId "com.example.dagger.activitygraphs" 9 | minSdkVersion 15 10 | targetSdkVersion 17 11 | } 12 | compileOptions { 13 | sourceCompatibility JavaVersion.VERSION_1_8 14 | targetCompatibility JavaVersion.VERSION_1_8 15 | } 16 | 17 | buildTypes { 18 | release { 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 20 | } 21 | } 22 | packagingOptions { 23 | exclude 'META-INF/ASL2.0' 24 | exclude 'META-INF/DEPENDENCIES.txt' 25 | exclude 'META-INF/LICENSE.txt' 26 | exclude 'META-INF/NOTICE.txt' 27 | exclude 'META-INF/NOTICE' 28 | exclude 'META-INF/LICENSE' 29 | exclude 'META-INF/DEPENDENCIES' 30 | exclude 'META-INF/notice.txt' 31 | exclude 'META-INF/license.txt' 32 | exclude 'META-INF/dependencies.txt' 33 | exclude 'META-INF/LGPL2.1' 34 | } 35 | } 36 | 37 | 38 | dependencies 39 | { 40 | provided 'com.squareup.dagger:dagger-compiler:1.2.1' 41 | compile 'com.squareup.dagger:dagger:1.2.1' 42 | compile 'com.squareup.retrofit:retrofit:1.6.1' 43 | compile 'io.reactivex:rxandroid:0.24.0' 44 | compile 'com.android.support:support-v13:20.+' 45 | compile 'com.android.support:appcompat-v7:20.0.0' 46 | compile 'com.squareup.picasso:picasso:2.5.0' 47 | compile 'com.jakewharton:butterknife:6.1.0' 48 | compile 'com.facebook.fresco:fresco:0.2.0+' 49 | compile 'frankiesardo:icepick:3+' 50 | provided 'frankiesardo:icepick-processor:3+' 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/DemoApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.rx.demo; 17 | 18 | import android.app.Application; 19 | 20 | import com.facebook.drawee.backends.pipeline.Fresco; 21 | import com.rx.demo.di.DataModule; 22 | 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | import dagger.ObjectGraph; 27 | 28 | public class DemoApplication extends Application { 29 | public ObjectGraph getApplicationGraph() { 30 | return applicationGraph; 31 | } 32 | 33 | ObjectGraph applicationGraph; 34 | 35 | @Override 36 | public void onCreate() { 37 | super.onCreate(); 38 | applicationGraph = ObjectGraph.create(getModules().toArray()); 39 | Fresco.initialize(this); 40 | 41 | } 42 | 43 | protected List getModules() { 44 | return Arrays.asList(new DataModule()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/dao/ImageDao.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.dao; 2 | 3 | import com.rx.demo.model.ImageRequest; 4 | import com.rx.demo.model.ImageResponse; 5 | import com.rx.demo.model.Page; 6 | import com.rx.demo.model.Result; 7 | import com.rx.demo.rest.ImagesApi; 8 | 9 | import javax.inject.Inject; 10 | import javax.inject.Singleton; 11 | 12 | import rx.Observable; 13 | import rx.schedulers.Schedulers; 14 | 15 | @Singleton 16 | public class ImageDao extends RxDao { 17 | @Inject 18 | ImagesApi api; 19 | 20 | @Override 21 | public ImageResponse load(ImageRequest request) throws Exception { 22 | return api.getPage(request.getSearchTerm(), request.getOffset()); 23 | } 24 | 25 | public Observable fetchImageResults(ImageRequest request) { 26 | return load(request) 27 | .flatMap(imageResponse -> Observable.from(imageResponse.getResponseData().getCursor().getPages())) 28 | .concatMap((Page page) -> getPage(request.getSearchTerm(), page)) 29 | .concatMap(this::streamOfImages); 30 | } 31 | 32 | private Observable getPage(String term, Page page) { 33 | return load(new ImageRequest(term, page.getStart())); 34 | } 35 | 36 | /** 37 | * creates observable that emits stream of images from a single image response 38 | * 39 | * @param imageResponse 40 | * @return Observable that emit individual image results 41 | */ 42 | private Observable streamOfImages(ImageResponse imageResponse) { 43 | return Observable.from(imageResponse.getResponseData().getResults()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/dao/RxDao.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.dao; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import rx.Observable; 10 | import rx.subjects.PublishSubject; 11 | 12 | import static rx.Observable.create; 13 | import static rx.Observable.just; 14 | 15 | 16 | //T = request type, V = response type 17 | public abstract class RxDao { 18 | private final Map cachedResponses; 19 | protected Map> inFlightRequests = new HashMap<>(); 20 | protected PublishSubject onNextObservable = PublishSubject.create(); 21 | protected PublishSubject updateObservable = PublishSubject.create(); 22 | private Gson gson = new Gson(); 23 | 24 | public RxDao() { 25 | cachedResponses = Collections.synchronizedMap(new HashMap<>()); 26 | inFlightRequests = Collections.synchronizedMap(new HashMap<>()); 27 | } 28 | 29 | public Observable fresh(final T request) { 30 | return isInFlightNetwork(request) ? inFlightResponse(request) : response(request); 31 | } 32 | 33 | public Observable cached(final T request) { 34 | return just(getCachedValue(request)); 35 | } 36 | 37 | public Observable all(final T request) { 38 | return fresh(request).startWith(cached(request)); 39 | } 40 | 41 | public Observable get(final T request) { 42 | V cachedValue = getCachedValue(request); 43 | return cachedValue == null ? fresh(request) : cached(request); 44 | } 45 | 46 | boolean isInFlightNetwork(T request) { 47 | return request != null && inFlightRequests.containsKey(json(request)); 48 | } 49 | 50 | public abstract V load(T request) throws Exception; 51 | 52 | private void loadResponse(final T request) throws Exception { 53 | V result = load(request); 54 | cachedResponses.put(json(request), result); 55 | } 56 | 57 | private String json(T request) { 58 | return gson.toJson(request); 59 | } 60 | 61 | protected Observable response(final T request) { 62 | final Observable response = create(subscriber -> { 63 | try { 64 | subscriber.onStart(); 65 | loadResponse(request); 66 | subscriber.onNext(getCachedValue(request)); 67 | subscriber.onCompleted(); 68 | } catch (Exception e) { 69 | subscriber.onError(e); 70 | } 71 | }); 72 | return registerResponse(request, response); 73 | } 74 | 75 | Observable inFlightResponse(T request) { 76 | return inFlightRequests.get(json(request)); 77 | } 78 | 79 | private V getCachedValue(T request) { 80 | V v = cachedResponses.get(json(request)); 81 | if (v != null) { 82 | onNextObservable.onNext(v); 83 | } 84 | //TODO: make a defensive copy of the cached value in case V is mutable 85 | return v; 86 | } 87 | 88 | protected Observable registerResponse(final T request, final Observable response) { 89 | return response 90 | .doOnSubscribe(() -> inFlightRequests.put(json(request), response)) 91 | .doOnCompleted(() -> inFlightRequests.remove(json(request))).doOnNext(updateObservable::onNext); 92 | } 93 | 94 | 95 | public Observable onNextObservable() { 96 | return onNextObservable.asObservable(); 97 | } 98 | 99 | public Observable onUpdateObservable() { 100 | return updateObservable.asObservable(); 101 | } 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/di/DataModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.rx.demo.di; 17 | 18 | import com.rx.demo.dao.ImageDao; 19 | import com.rx.demo.rest.ImagesApi; 20 | 21 | import javax.inject.Singleton; 22 | 23 | import dagger.Module; 24 | import dagger.Provides; 25 | import retrofit.RestAdapter; 26 | 27 | @Module(library = true, 28 | injects = {ImageDao.class}) 29 | public class DataModule { 30 | 31 | public DataModule() {} 32 | 33 | @Provides 34 | @Singleton 35 | RestAdapter provideRestAdapter() { 36 | return new RestAdapter.Builder() 37 | .setEndpoint("https://ajax.googleapis.com/ajax/services/search") 38 | .build(); 39 | } 40 | 41 | @Provides 42 | @Singleton 43 | ImagesApi provideImageApi(RestAdapter restAdapter) { 44 | return restAdapter.create(ImagesApi.class); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/di/UiModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.rx.demo.di; 17 | 18 | import android.os.Handler; 19 | import android.view.LayoutInflater; 20 | 21 | import com.rx.demo.di.annotation.HistoryViewBus; 22 | import com.rx.demo.di.annotation.ImageViewBus; 23 | import com.rx.demo.model.Result; 24 | import com.rx.demo.ui.activity.SearchActivity; 25 | import com.rx.demo.ui.view.HistoryView; 26 | import com.rx.demo.ui.view.ImageCardView; 27 | import com.rx.demo.ui.view.SearchView; 28 | 29 | import java.util.Queue; 30 | import java.util.concurrent.ConcurrentLinkedQueue; 31 | 32 | import javax.inject.Singleton; 33 | 34 | import dagger.Module; 35 | import dagger.Provides; 36 | import rx.subjects.PublishSubject; 37 | 38 | @Module( 39 | injects = {SearchActivity.class, 40 | SearchView.class, 41 | HistoryView.class, 42 | ImageCardView.class}, 43 | addsTo = DataModule.class, 44 | library = true) 45 | public class UiModule { 46 | private final SearchActivity activity; 47 | 48 | public UiModule(SearchActivity activity) { 49 | this.activity = activity; 50 | } 51 | 52 | @Provides 53 | @Singleton 54 | SearchActivity provideActivity() { 55 | return activity; 56 | } 57 | 58 | @Provides 59 | @Singleton 60 | LayoutInflater provideLayoutInflater() { 61 | return activity.getLayoutInflater(); 62 | } 63 | 64 | @Provides 65 | @Singleton 66 | Handler provideHandler() { 67 | return new Handler(); 68 | } 69 | 70 | @Provides 71 | @Singleton 72 | @ImageViewBus 73 | PublishSubject provideSearchViewBus() { 74 | return PublishSubject.create(); 75 | } 76 | 77 | @Provides 78 | @Singleton 79 | @HistoryViewBus 80 | PublishSubject provideHistoryBus() { 81 | return PublishSubject.create(); 82 | } 83 | 84 | 85 | @Provides 86 | @Singleton 87 | Queue providesImageQueue() { 88 | return new ConcurrentLinkedQueue<>(); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/di/annotation/HistoryViewBus.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.di.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | 5 | import javax.inject.Qualifier; 6 | 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Created by Nakhimovich on 4/10/15. 11 | */ 12 | @Qualifier 13 | @Retention(RUNTIME) 14 | public @interface HistoryViewBus { 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/di/annotation/ImageViewBus.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.di.annotation; 2 | 3 | import java.lang.annotation.Retention; 4 | 5 | import javax.inject.Qualifier; 6 | 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Created by Nakhimovich on 4/11/15. 11 | */ 12 | @Qualifier 13 | @Retention(RUNTIME) 14 | public @interface ImageViewBus { 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/Cursor.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rx.demo.model; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class Cursor { 8 | 9 | private String resultCount; 10 | private List pages = new ArrayList(); 11 | private String estimatedResultCount; 12 | private Integer currentPageIndex; 13 | private String moreResultsUrl; 14 | private String searchResultTime; 15 | 16 | /** 17 | * 18 | * @return 19 | * The resultCount 20 | */ 21 | public String getResultCount() { 22 | return resultCount; 23 | } 24 | 25 | /** 26 | * 27 | * @param resultCount 28 | * The resultCount 29 | */ 30 | public void setResultCount(String resultCount) { 31 | this.resultCount = resultCount; 32 | } 33 | 34 | /** 35 | * 36 | * @return 37 | * The pages 38 | */ 39 | public List getPages() { 40 | return pages; 41 | } 42 | 43 | /** 44 | * 45 | * @param pages 46 | * The pages 47 | */ 48 | public void setPages(List pages) { 49 | this.pages = pages; 50 | } 51 | 52 | /** 53 | * 54 | * @return 55 | * The estimatedResultCount 56 | */ 57 | public String getEstimatedResultCount() { 58 | return estimatedResultCount; 59 | } 60 | 61 | /** 62 | * 63 | * @param estimatedResultCount 64 | * The estimatedResultCount 65 | */ 66 | public void setEstimatedResultCount(String estimatedResultCount) { 67 | this.estimatedResultCount = estimatedResultCount; 68 | } 69 | 70 | /** 71 | * 72 | * @return 73 | * The currentPageIndex 74 | */ 75 | public Integer getCurrentPageIndex() { 76 | return currentPageIndex; 77 | } 78 | 79 | /** 80 | * 81 | * @param currentPageIndex 82 | * The currentPageIndex 83 | */ 84 | public void setCurrentPageIndex(Integer currentPageIndex) { 85 | this.currentPageIndex = currentPageIndex; 86 | } 87 | 88 | /** 89 | * 90 | * @return 91 | * The moreResultsUrl 92 | */ 93 | public String getMoreResultsUrl() { 94 | return moreResultsUrl; 95 | } 96 | 97 | /** 98 | * 99 | * @param moreResultsUrl 100 | * The moreResultsUrl 101 | */ 102 | public void setMoreResultsUrl(String moreResultsUrl) { 103 | this.moreResultsUrl = moreResultsUrl; 104 | } 105 | 106 | /** 107 | * 108 | * @return 109 | * The searchResultTime 110 | */ 111 | public String getSearchResultTime() { 112 | return searchResultTime; 113 | } 114 | 115 | /** 116 | * 117 | * @param searchResultTime 118 | * The searchResultTime 119 | */ 120 | public void setSearchResultTime(String searchResultTime) { 121 | this.searchResultTime = searchResultTime; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/ImageRequest.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.model; 2 | 3 | 4 | public class ImageRequest { 5 | private final String searchTerm; 6 | private final String offset; 7 | 8 | public ImageRequest(String searchTerm, String offset) { 9 | this.searchTerm = searchTerm; 10 | this.offset = offset; 11 | } 12 | 13 | public ImageRequest(String searchTerm) { 14 | this.searchTerm = searchTerm; 15 | offset = null; 16 | } 17 | 18 | 19 | public String getSearchTerm() { 20 | return searchTerm; 21 | } 22 | 23 | public String getOffset() { 24 | return offset; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/ImageResponse.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rx.demo.model; 3 | 4 | public class ImageResponse { 5 | 6 | private ResponseData responseData; 7 | private Object responseDetails; 8 | private Integer responseStatus; 9 | 10 | /** 11 | * 12 | * @return 13 | * The responseData 14 | */ 15 | public ResponseData getResponseData() { 16 | return responseData; 17 | } 18 | 19 | /** 20 | * 21 | * @param responseData 22 | * The responseData 23 | */ 24 | public void setResponseData(ResponseData responseData) { 25 | this.responseData = responseData; 26 | } 27 | 28 | /** 29 | * 30 | * @return 31 | * The responseDetails 32 | */ 33 | public Object getResponseDetails() { 34 | return responseDetails; 35 | } 36 | 37 | /** 38 | * 39 | * @param responseDetails 40 | * The responseDetails 41 | */ 42 | public void setResponseDetails(Object responseDetails) { 43 | this.responseDetails = responseDetails; 44 | } 45 | 46 | /** 47 | * 48 | * @return 49 | * The responseStatus 50 | */ 51 | public Integer getResponseStatus() { 52 | return responseStatus; 53 | } 54 | 55 | /** 56 | * 57 | * @param responseStatus 58 | * The responseStatus 59 | */ 60 | public void setResponseStatus(Integer responseStatus) { 61 | this.responseStatus = responseStatus; 62 | } 63 | 64 | @Override 65 | public boolean equals(Object o) { 66 | if (this == o) return true; 67 | if (!(o instanceof ImageResponse)) return false; 68 | 69 | ImageResponse that = (ImageResponse) o; 70 | 71 | if (!responseData.equals(that.responseData)) return false; 72 | if (!responseDetails.equals(that.responseDetails)) return false; 73 | if (!responseStatus.equals(that.responseStatus)) return false; 74 | 75 | return true; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/Page.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rx.demo.model; 3 | 4 | public class Page { 5 | 6 | private String start; 7 | private Integer label; 8 | 9 | /** 10 | * 11 | * @return 12 | * The start 13 | */ 14 | public String getStart() { 15 | return start; 16 | } 17 | 18 | /** 19 | * 20 | * @param start 21 | * The start 22 | */ 23 | public void setStart(String start) { 24 | this.start = start; 25 | } 26 | 27 | /** 28 | * 29 | * @return 30 | * The label 31 | */ 32 | public Integer getLabel() { 33 | return label; 34 | } 35 | 36 | /** 37 | * 38 | * @param label 39 | * The label 40 | */ 41 | public void setLabel(Integer label) { 42 | this.label = label; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/ResponseData.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rx.demo.model; 3 | 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class ResponseData { 9 | 10 | private ArrayList results = new ArrayList(); 11 | private Cursor cursor; 12 | private Map additionalProperties = new HashMap(); 13 | 14 | /** 15 | * 16 | * @return 17 | * The results 18 | */ 19 | public ArrayList getResults() { 20 | return results; 21 | } 22 | 23 | /** 24 | * 25 | * @param results 26 | * The results 27 | */ 28 | public void setResults(ArrayList results) { 29 | this.results = results; 30 | } 31 | 32 | /** 33 | * 34 | * @return 35 | * The cursor 36 | */ 37 | public Cursor getCursor() { 38 | return cursor; 39 | } 40 | 41 | /** 42 | * 43 | * @param cursor 44 | * The cursor 45 | */ 46 | public void setCursor(Cursor cursor) { 47 | this.cursor = cursor; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/model/Result.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rx.demo.model; 3 | 4 | public class Result { 5 | 6 | private String GsearchResultClass; 7 | private String width; 8 | private String height; 9 | private String imageId; 10 | private String tbWidth; 11 | private String tbHeight; 12 | private String unescapedUrl; 13 | private String url; 14 | private String visibleUrl; 15 | private String title; 16 | private String titleNoFormatting; 17 | private String originalContextUrl; 18 | private String content; 19 | private String contentNoFormatting; 20 | private String tbUrl; 21 | 22 | /** 23 | * 24 | * @return 25 | * The GsearchResultClass 26 | */ 27 | public String getGsearchResultClass() { 28 | return GsearchResultClass; 29 | } 30 | 31 | /** 32 | * 33 | * @param GsearchResultClass 34 | * The GsearchResultClass 35 | */ 36 | public void setGsearchResultClass(String GsearchResultClass) { 37 | this.GsearchResultClass = GsearchResultClass; 38 | } 39 | 40 | /** 41 | * 42 | * @return 43 | * The width 44 | */ 45 | public String getWidth() { 46 | return width; 47 | } 48 | 49 | /** 50 | * 51 | * @param width 52 | * The width 53 | */ 54 | public void setWidth(String width) { 55 | this.width = width; 56 | } 57 | 58 | /** 59 | * 60 | * @return 61 | * The height 62 | */ 63 | public String getHeight() { 64 | return height; 65 | } 66 | 67 | /** 68 | * 69 | * @param height 70 | * The height 71 | */ 72 | public void setHeight(String height) { 73 | this.height = height; 74 | } 75 | 76 | /** 77 | * 78 | * @return 79 | * The imageId 80 | */ 81 | public String getImageId() { 82 | return imageId; 83 | } 84 | 85 | /** 86 | * 87 | * @param imageId 88 | * The imageId 89 | */ 90 | public void setImageId(String imageId) { 91 | this.imageId = imageId; 92 | } 93 | 94 | /** 95 | * 96 | * @return 97 | * The tbWidth 98 | */ 99 | public String getTbWidth() { 100 | return tbWidth; 101 | } 102 | 103 | /** 104 | * 105 | * @param tbWidth 106 | * The tbWidth 107 | */ 108 | public void setTbWidth(String tbWidth) { 109 | this.tbWidth = tbWidth; 110 | } 111 | 112 | /** 113 | * 114 | * @return 115 | * The tbHeight 116 | */ 117 | public String getTbHeight() { 118 | return tbHeight; 119 | } 120 | 121 | /** 122 | * 123 | * @param tbHeight 124 | * The tbHeight 125 | */ 126 | public void setTbHeight(String tbHeight) { 127 | this.tbHeight = tbHeight; 128 | } 129 | 130 | /** 131 | * 132 | * @return 133 | * The unescapedUrl 134 | */ 135 | public String getUnescapedUrl() { 136 | return unescapedUrl; 137 | } 138 | 139 | /** 140 | * 141 | * @param unescapedUrl 142 | * The unescapedUrl 143 | */ 144 | public void setUnescapedUrl(String unescapedUrl) { 145 | this.unescapedUrl = unescapedUrl; 146 | } 147 | 148 | /** 149 | * 150 | * @return 151 | * The url 152 | */ 153 | public String getUrl() { 154 | return url; 155 | } 156 | 157 | /** 158 | * 159 | * @param url 160 | * The url 161 | */ 162 | public void setUrl(String url) { 163 | this.url = url; 164 | } 165 | 166 | /** 167 | * 168 | * @return 169 | * The visibleUrl 170 | */ 171 | public String getVisibleUrl() { 172 | return visibleUrl; 173 | } 174 | 175 | /** 176 | * 177 | * @param visibleUrl 178 | * The visibleUrl 179 | */ 180 | public void setVisibleUrl(String visibleUrl) { 181 | this.visibleUrl = visibleUrl; 182 | } 183 | 184 | /** 185 | * 186 | * @return 187 | * The title 188 | */ 189 | public String getTitle() { 190 | return title; 191 | } 192 | 193 | /** 194 | * 195 | * @param title 196 | * The title 197 | */ 198 | public void setTitle(String title) { 199 | this.title = title; 200 | } 201 | 202 | /** 203 | * 204 | * @return 205 | * The titleNoFormatting 206 | */ 207 | public String getTitleNoFormatting() { 208 | return titleNoFormatting; 209 | } 210 | 211 | /** 212 | * 213 | * @param titleNoFormatting 214 | * The titleNoFormatting 215 | */ 216 | public void setTitleNoFormatting(String titleNoFormatting) { 217 | this.titleNoFormatting = titleNoFormatting; 218 | } 219 | 220 | /** 221 | * 222 | * @return 223 | * The originalContextUrl 224 | */ 225 | public String getOriginalContextUrl() { 226 | return originalContextUrl; 227 | } 228 | 229 | /** 230 | * 231 | * @param originalContextUrl 232 | * The originalContextUrl 233 | */ 234 | public void setOriginalContextUrl(String originalContextUrl) { 235 | this.originalContextUrl = originalContextUrl; 236 | } 237 | 238 | /** 239 | * 240 | * @return 241 | * The content 242 | */ 243 | public String getContent() { 244 | return content; 245 | } 246 | 247 | /** 248 | * 249 | * @param content 250 | * The content 251 | */ 252 | public void setContent(String content) { 253 | this.content = content; 254 | } 255 | 256 | /** 257 | * 258 | * @return 259 | * The contentNoFormatting 260 | */ 261 | public String getContentNoFormatting() { 262 | return contentNoFormatting; 263 | } 264 | 265 | /** 266 | * 267 | * @param contentNoFormatting 268 | * The contentNoFormatting 269 | */ 270 | public void setContentNoFormatting(String contentNoFormatting) { 271 | this.contentNoFormatting = contentNoFormatting; 272 | } 273 | 274 | /** 275 | * 276 | * @return 277 | * The tbUrl 278 | */ 279 | public String getTbUrl() { 280 | return tbUrl; 281 | } 282 | 283 | /** 284 | * 285 | * @param tbUrl 286 | * The tbUrl 287 | */ 288 | public void setTbUrl(String tbUrl) { 289 | this.tbUrl = tbUrl; 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/rest/ImagesApi.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.rest; 2 | 3 | import com.rx.demo.model.ImageResponse; 4 | 5 | import retrofit.http.GET; 6 | import retrofit.http.Query; 7 | 8 | /** 9 | * Created by MikeN on 8/16/14. 10 | */ 11 | public interface ImagesApi { 12 | @GET("/images?v=1.0&imgsz=medium") 13 | ImageResponse getPage(@Query("q") String searchTerm, @Query("start") String offset); 14 | @GET("/images?v=1.0&imgsz=medium") 15 | ImageResponse getPage(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.rx.demo.ui.activity; 17 | 18 | import android.app.Activity; 19 | import android.os.Bundle; 20 | import android.view.View; 21 | 22 | import com.rx.demo.DemoApplication; 23 | import com.rx.demo.util.SubscriptionManager; 24 | 25 | import java.util.List; 26 | 27 | import javax.inject.Inject; 28 | 29 | import dagger.ObjectGraph; 30 | 31 | /** 32 | * Base activity which sets up a per-activity object graph and performs injection. 33 | */ 34 | public abstract class BaseActivity extends Activity { 35 | @Inject 36 | SubscriptionManager subscriptionManager; 37 | ObjectGraph activityGraph; 38 | 39 | /** 40 | * creates a activity scoped object graph which adds to global graph 41 | * @param savedInstanceState 42 | */ 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate (savedInstanceState); 46 | activityGraph = ((DemoApplication)getApplication()).getApplicationGraph().plus(getModules().toArray()); 47 | activityGraph.inject(this); 48 | } 49 | 50 | protected abstract List getModules(); 51 | 52 | @Override 53 | protected void onSaveInstanceState(Bundle outState) { 54 | super.onSaveInstanceState(outState); 55 | } 56 | 57 | @Override 58 | protected void onDestroy() { 59 | activityGraph = null; 60 | subscriptionManager.unsubscribeAll(); 61 | super.onDestroy(); 62 | } 63 | 64 | 65 | 66 | public void inject(View view) { 67 | activityGraph.inject(view); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/activity/SearchActivity.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.activity; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.view.Window; 6 | import android.widget.ViewFlipper; 7 | 8 | import com.digitalbuddha.rx.demo.R; 9 | import com.rx.demo.di.UiModule; 10 | import com.rx.demo.ui.animation.AnimationFactory; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | import butterknife.ButterKnife; 16 | import butterknife.InjectView; 17 | import butterknife.OnClick; 18 | import icepick.Icepick; 19 | import icepick.Icicle; 20 | 21 | import static butterknife.ButterKnife.findById; 22 | 23 | 24 | public class SearchActivity extends BaseActivity { 25 | 26 | public static final int SEARCH_VIEW_POSITION = 0; 27 | 28 | @InjectView(R.id.container) 29 | ViewFlipper flipper; 30 | 31 | @InjectView(R.id.history) 32 | View historyButton; 33 | 34 | @Icicle 35 | int selectedPosition = 0; 36 | 37 | /** 38 | * inflate search and history view and add the to container 39 | * set click listener for history button 40 | * 41 | * @param savedInstanceState 42 | */ 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | Icepick.restoreInstanceState(this, savedInstanceState); 47 | //Remove title bar 48 | this.requestWindowFeature(Window.FEATURE_NO_TITLE); 49 | setContentView(R.layout.activity_layout); 50 | 51 | ButterKnife.inject(this); 52 | flipper = findById(this, R.id.container); 53 | getLayoutInflater().inflate(R.layout.search_view, flipper); 54 | getLayoutInflater().inflate(R.layout.history_view, flipper); 55 | showView(selectedPosition); 56 | } 57 | 58 | @OnClick(R.id.history) 59 | public void historyButtonClicked() { 60 | flipViews(); 61 | } 62 | 63 | @Override 64 | protected void onSaveInstanceState(Bundle outState) { 65 | super.onSaveInstanceState(outState); 66 | selectedPosition = flipper.getDisplayedChild(); 67 | Icepick.saveInstanceState(this, outState); 68 | } 69 | 70 | protected List getModules() { 71 | return Arrays.asList(new UiModule(this)); 72 | } 73 | 74 | public void showView(int index) { 75 | flipper.setDisplayedChild(index); 76 | syncHistoryButton(); 77 | } 78 | 79 | public void flipViews() { 80 | AnimationFactory.flipTransition(flipper, AnimationFactory.FlipDirection.LEFT_RIGHT); 81 | syncHistoryButton(); 82 | } 83 | 84 | public void syncHistoryButton() { 85 | historyButton.setVisibility(flipper.getDisplayedChild() == SEARCH_VIEW_POSITION ? View.VISIBLE : View.INVISIBLE); 86 | } 87 | 88 | @Override 89 | public void onBackPressed() { 90 | if (flipper.getDisplayedChild() != SEARCH_VIEW_POSITION) { 91 | historyButton.setVisibility(View.VISIBLE); 92 | AnimationFactory.flipTransition(flipper, AnimationFactory.FlipDirection.LEFT_RIGHT); 93 | } else { 94 | super.onBackPressed(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/animation/AnimationFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Ephraim Tekle genzeb@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 8 | * following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies or substantial 11 | * portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 14 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 15 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | * 19 | * @author Ephraim A. Tekle 20 | * 21 | */ 22 | package com.rx.demo.ui.animation; 23 | 24 | import android.view.View; 25 | import android.view.animation.AccelerateInterpolator; 26 | import android.view.animation.AlphaAnimation; 27 | import android.view.animation.Animation; 28 | import android.view.animation.AnimationSet; 29 | import android.view.animation.DecelerateInterpolator; 30 | import android.view.animation.Interpolator; 31 | import android.view.animation.TranslateAnimation; 32 | import android.view.animation.Animation.AnimationListener; 33 | import android.widget.ViewAnimator; 34 | 35 | /** 36 | * This class contains methods for creating {@link android.view.animation.Animation} objects for some of the most common animation, including a 3D flip animation, {@link FlipAnimation}. 37 | * Furthermore, utility methods are provided for initiating fade-in-then-out and flip animations. 38 | * 39 | * @author Ephraim A. Tekle 40 | * 41 | */ 42 | public class AnimationFactory { 43 | 44 | private static final int DEFAULT_FLIP_TRANSITION_DURATION = 500; 45 | 46 | /** 47 | * The {@code FlipDirection} enumeration defines the most typical flip view transitions: left-to-right and right-to-left. {@code FlipDirection} is used during the creation of {@link FlipAnimation} animations. 48 | * 49 | * @author Ephraim A. Tekle 50 | * 51 | */ 52 | public static enum FlipDirection { 53 | LEFT_RIGHT, 54 | RIGHT_LEFT, 55 | TOP_BOTTOM, 56 | BOTTOM_TOP; 57 | 58 | public float getStartDegreeForFirstView() { 59 | return 0; 60 | } 61 | 62 | public float getStartDegreeForSecondView() { 63 | switch(this) { 64 | case LEFT_RIGHT: 65 | case TOP_BOTTOM: 66 | return -90; 67 | case RIGHT_LEFT: 68 | case BOTTOM_TOP: 69 | return 90; 70 | default: 71 | return 0; 72 | } 73 | } 74 | 75 | public float getEndDegreeForFirstView() { 76 | switch(this) { 77 | case LEFT_RIGHT: 78 | case TOP_BOTTOM: 79 | return 90; 80 | case RIGHT_LEFT: 81 | case BOTTOM_TOP: 82 | return -90; 83 | default: 84 | return 0; 85 | } 86 | } 87 | 88 | public float getEndDegreeForSecondView() { 89 | return 0; 90 | } 91 | 92 | public FlipDirection theOtherDirection() { 93 | switch(this) { 94 | case LEFT_RIGHT: 95 | return RIGHT_LEFT; 96 | case TOP_BOTTOM: 97 | return BOTTOM_TOP; 98 | case RIGHT_LEFT: 99 | return LEFT_RIGHT; 100 | case BOTTOM_TOP: 101 | return TOP_BOTTOM; 102 | default: 103 | return null; 104 | } 105 | } 106 | }; 107 | 108 | 109 | /** 110 | * Create a pair of {@link FlipAnimation} that can be used to flip 3D transition from {@code fromView} to {@code toView}. A typical use case is with {@link android.widget.ViewAnimator} as an out and in transition. 111 | * 112 | * NOTE: Avoid using this method. Instead, use {@link #flipTransition}. 113 | * 114 | * @param fromView the view transition away from 115 | * @param toView the view transition to 116 | * @param dir the flip direction 117 | * @param duration the transition duration in milliseconds 118 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 119 | * @return 120 | */ 121 | public static Animation[] flipAnimation(final View fromView, final View toView, FlipDirection dir, long duration, Interpolator interpolator) { 122 | Animation[] result = new Animation[2]; 123 | float centerX; 124 | float centerY; 125 | 126 | centerX = fromView.getWidth() / 2.0f; 127 | centerY = fromView.getHeight() / 2.0f; 128 | 129 | FlipAnimation outFlip= new FlipAnimation(dir.getStartDegreeForFirstView(), dir.getEndDegreeForFirstView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_DOWN); 130 | outFlip.setDuration(duration); 131 | outFlip.setFillAfter(true); 132 | outFlip.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 133 | 134 | if (dir == FlipDirection.BOTTOM_TOP || dir == FlipDirection.TOP_BOTTOM) 135 | outFlip.setDirection(FlipAnimation.ROTATION_X); 136 | else 137 | outFlip.setDirection(FlipAnimation.ROTATION_Y); 138 | 139 | AnimationSet outAnimation = new AnimationSet(true); 140 | outAnimation.addAnimation(outFlip); 141 | result[0] = outAnimation; 142 | 143 | // Uncomment the following if toView has its layout established (not the case if using ViewFlipper and on first show) 144 | //centerX = toView.getWidth() / 2.0f; 145 | //centerY = toView.getHeight() / 2.0f; 146 | 147 | FlipAnimation inFlip = new FlipAnimation(dir.getStartDegreeForSecondView(), dir.getEndDegreeForSecondView(), centerX, centerY, FlipAnimation.SCALE_DEFAULT, FlipAnimation.ScaleUpDownEnum.SCALE_UP); 148 | inFlip.setDuration(duration); 149 | inFlip.setFillAfter(true); 150 | inFlip.setInterpolator(interpolator == null ? new AccelerateInterpolator() : interpolator); 151 | inFlip.setStartOffset(duration); 152 | 153 | if (dir == FlipDirection.BOTTOM_TOP || dir == FlipDirection.TOP_BOTTOM) 154 | inFlip.setDirection(FlipAnimation.ROTATION_X); 155 | else 156 | inFlip.setDirection(FlipAnimation.ROTATION_Y); 157 | 158 | AnimationSet inAnimation = new AnimationSet(true); 159 | inAnimation.addAnimation(inFlip); 160 | result[1] = inAnimation; 161 | 162 | return result; 163 | 164 | } 165 | 166 | /** 167 | * Flip to the next view of the {@code ViewAnimator}'s subviews. A call to this method will initiate a {@link FlipAnimation} to show the next View. 168 | * If the currently visible view is the last view, flip direction will be reversed for this transition. 169 | * 170 | * @param viewAnimator the {@code ViewAnimator} 171 | * @param dir the direction of flip 172 | */ 173 | public static void flipTransition(final ViewAnimator viewAnimator, FlipDirection dir) { 174 | flipTransition(viewAnimator, dir, DEFAULT_FLIP_TRANSITION_DURATION); 175 | } 176 | 177 | /** 178 | * Flip to the next view of the {@code ViewAnimator}'s subviews. A call to this method will initiate a {@link FlipAnimation} to show the next View. 179 | * If the currently visible view is the last view, flip direction will be reversed for this transition. 180 | * 181 | * @param viewAnimator the {@code ViewAnimator} 182 | * @param dir the direction of flip 183 | * @param duration the transition duration in milliseconds 184 | */ 185 | public static void flipTransition(final ViewAnimator viewAnimator, FlipDirection dir, long duration) { 186 | 187 | final View fromView = viewAnimator.getCurrentView(); 188 | final int currentIndex = viewAnimator.getDisplayedChild(); 189 | final int nextIndex = (currentIndex + 1)%viewAnimator.getChildCount(); 190 | 191 | final View toView = viewAnimator.getChildAt(nextIndex); 192 | 193 | Animation[] animc = AnimationFactory.flipAnimation(fromView, toView, (nextIndex < currentIndex?dir.theOtherDirection():dir), duration, null); 194 | 195 | viewAnimator.setOutAnimation(animc[0]); 196 | viewAnimator.setInAnimation(animc[1]); 197 | 198 | viewAnimator.showNext(); 199 | } 200 | 201 | ////////////// 202 | 203 | 204 | /** 205 | * Slide animations to enter a view from left. 206 | * 207 | * @param duration the animation duration in milliseconds 208 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 209 | * @return a slide transition animation 210 | */ 211 | public static Animation inFromLeftAnimation(long duration, Interpolator interpolator) { 212 | Animation inFromLeft = new TranslateAnimation( 213 | Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 214 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 215 | ); 216 | inFromLeft.setDuration(duration); 217 | inFromLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); //AccelerateInterpolator 218 | return inFromLeft; 219 | } 220 | 221 | /** 222 | * Slide animations to hide a view by sliding it to the right 223 | * 224 | * @param duration the animation duration in milliseconds 225 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 226 | * @return a slide transition animation 227 | */ 228 | public static Animation outToRightAnimation(long duration, Interpolator interpolator) { 229 | Animation outtoRight = new TranslateAnimation( 230 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, +1.0f, 231 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 232 | ); 233 | outtoRight.setDuration(duration); 234 | outtoRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 235 | return outtoRight; 236 | } 237 | 238 | /** 239 | * Slide animations to enter a view from right. 240 | * 241 | * @param duration the animation duration in milliseconds 242 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 243 | * @return a slide transition animation 244 | */ 245 | public static Animation inFromRightAnimation(long duration, Interpolator interpolator) { 246 | 247 | Animation inFromRight = new TranslateAnimation( 248 | Animation.RELATIVE_TO_PARENT, +1.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 249 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 250 | ); 251 | inFromRight.setDuration(duration); 252 | inFromRight.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 253 | return inFromRight; 254 | } 255 | 256 | /** 257 | * Slide animations to hide a view by sliding it to the left. 258 | * 259 | * @param duration the animation duration in milliseconds 260 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 261 | * @return a slide transition animation 262 | */ 263 | public static Animation outToLeftAnimation(long duration, Interpolator interpolator) { 264 | Animation outtoLeft = new TranslateAnimation( 265 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f, 266 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f 267 | ); 268 | outtoLeft.setDuration(duration); 269 | outtoLeft.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 270 | return outtoLeft; 271 | } 272 | 273 | /** 274 | * Slide animations to enter a view from top. 275 | * 276 | * @param duration the animation duration in milliseconds 277 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 278 | * @return a slide transition animation 279 | */ 280 | public static Animation inFromTopAnimation(long duration, Interpolator interpolator) { 281 | Animation infromtop = new TranslateAnimation( 282 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 283 | Animation.RELATIVE_TO_PARENT, -1.0f, Animation.RELATIVE_TO_PARENT, 0.0f 284 | ); 285 | infromtop.setDuration(duration); 286 | infromtop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 287 | return infromtop; 288 | } 289 | 290 | /** 291 | * Slide animations to hide a view by sliding it to the top 292 | * 293 | * @param duration the animation duration in milliseconds 294 | * @param interpolator the interpolator to use (pass {@code null} to use the {@link android.view.animation.AccelerateInterpolator} interpolator) 295 | * @return a slide transition animation 296 | */ 297 | public static Animation outToTopAnimation(long duration, Interpolator interpolator) { 298 | Animation outtotop = new TranslateAnimation( 299 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, 300 | Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, -1.0f 301 | ); 302 | outtotop.setDuration(duration); 303 | outtotop.setInterpolator(interpolator==null?new AccelerateInterpolator():interpolator); 304 | return outtotop; 305 | } 306 | 307 | /** 308 | * A fade animation that will fade the subject in by changing alpha from 0 to 1. 309 | * 310 | * @param duration the animation duration in milliseconds 311 | * @param delay how long to wait before starting the animation, in milliseconds 312 | * @return a fade animation 313 | * @see #fadeInAnimation(android.view.View, long) 314 | */ 315 | public static Animation fadeInAnimation(long duration, long delay) { 316 | 317 | Animation fadeIn = new AlphaAnimation(0, 1); 318 | fadeIn.setInterpolator(new DecelerateInterpolator()); 319 | fadeIn.setDuration(duration); 320 | fadeIn.setStartOffset(delay); 321 | 322 | return fadeIn; 323 | } 324 | 325 | /** 326 | * A fade animation that will fade the subject out by changing alpha from 1 to 0. 327 | * 328 | * @param duration the animation duration in milliseconds 329 | * @param delay how long to wait before starting the animation, in milliseconds 330 | * @return a fade animation 331 | * @see #fadeOutAnimation(android.view.View, long) 332 | */ 333 | public static Animation fadeOutAnimation(long duration, long delay) { 334 | 335 | Animation fadeOut = new AlphaAnimation(1, 0); 336 | fadeOut.setInterpolator(new AccelerateInterpolator()); 337 | fadeOut.setStartOffset(delay); 338 | fadeOut.setDuration(duration); 339 | 340 | return fadeOut; 341 | } 342 | 343 | /** 344 | * A fade animation that will ensure the View starts and ends with the correct visibility 345 | * @param view the View to be faded in 346 | * @param duration the animation duration in milliseconds 347 | * @return a fade animation that will set the visibility of the view at the start and end of animation 348 | */ 349 | public static Animation fadeInAnimation(long duration, final View view) { 350 | Animation animation = fadeInAnimation(500, 0); 351 | 352 | animation.setAnimationListener(new AnimationListener() { 353 | @Override 354 | public void onAnimationEnd(Animation animation) { 355 | view.setVisibility(View.VISIBLE); 356 | } 357 | 358 | @Override 359 | public void onAnimationRepeat(Animation animation) { 360 | } 361 | 362 | @Override 363 | public void onAnimationStart(Animation animation) { 364 | view.setVisibility(View.GONE); 365 | } 366 | }); 367 | 368 | return animation; 369 | } 370 | 371 | /** 372 | * A fade animation that will ensure the View starts and ends with the correct visibility 373 | * @param view the View to be faded out 374 | * @param duration the animation duration in milliseconds 375 | * @return a fade animation that will set the visibility of the view at the start and end of animation 376 | */ 377 | public static Animation fadeOutAnimation(long duration, final View view) { 378 | 379 | Animation animation = fadeOutAnimation(500, 0); 380 | 381 | animation.setAnimationListener(new AnimationListener() { 382 | @Override 383 | public void onAnimationEnd(Animation animation) { 384 | view.setVisibility(View.GONE); 385 | } 386 | 387 | @Override 388 | public void onAnimationRepeat(Animation animation) { 389 | } 390 | 391 | @Override 392 | public void onAnimationStart(Animation animation) { 393 | view.setVisibility(View.VISIBLE); 394 | } 395 | }); 396 | 397 | return animation; 398 | 399 | } 400 | 401 | /** 402 | * Creates a pair of animation that will fade in, delay, then fade out 403 | * @param duration the animation duration in milliseconds 404 | * @param delay how long to wait after fading in the subject and before starting the fade out 405 | * @return a fade in then out animations 406 | */ 407 | public static Animation[] fadeInThenOutAnimation(long duration, long delay) { 408 | return new Animation[] {fadeInAnimation(duration,0), fadeOutAnimation(duration, duration+delay)}; 409 | } 410 | 411 | /** 412 | * Fades the view in. Animation starts right away. 413 | * @param v the view to be faded in 414 | */ 415 | public static void fadeOut(View v) { 416 | if (v==null) return; 417 | v.startAnimation(fadeOutAnimation(500, v)); 418 | } 419 | 420 | /** 421 | * Fades the view out. Animation starts right away. 422 | * @param v the view to be faded out 423 | */ 424 | public static void fadeIn(View v) { 425 | if (v==null) return; 426 | 427 | v.startAnimation(fadeInAnimation(500, v)); 428 | } 429 | 430 | /** 431 | * Fades the view in, delays the specified amount of time, then fades the view out 432 | * @param v the view to be faded in then out 433 | * @param delay how long the view will be visible for 434 | */ 435 | public static void fadeInThenOut(final View v, long delay) { 436 | if (v==null) return; 437 | 438 | v.setVisibility(View.VISIBLE); 439 | AnimationSet animation = new AnimationSet(true); 440 | Animation[] fadeInOut = fadeInThenOutAnimation(500,delay); 441 | animation.addAnimation(fadeInOut[0]); 442 | animation.addAnimation(fadeInOut[1]); 443 | animation.setAnimationListener(new AnimationListener() { 444 | @Override 445 | public void onAnimationEnd(Animation animation) { 446 | v.setVisibility(View.GONE); 447 | } 448 | @Override 449 | public void onAnimationRepeat(Animation animation) { 450 | } 451 | @Override 452 | public void onAnimationStart(Animation animation) { 453 | v.setVisibility(View.VISIBLE); 454 | } 455 | }); 456 | 457 | v.startAnimation(animation); 458 | } 459 | 460 | } 461 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/animation/FlipAnimation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2012 Ephraim Tekle genzeb@gmail.com 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 6 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 8 | * following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in all copies or substantial 11 | * portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 14 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 15 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 16 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | * 19 | * @author Ephraim A. Tekle 20 | * 21 | */ 22 | package com.rx.demo.ui.animation; 23 | 24 | import android.view.animation.Animation; 25 | import android.graphics.Camera; 26 | import android.graphics.Matrix; 27 | import android.view.animation.Transformation; 28 | 29 | /** 30 | * This class extends Animation to support a 3D flip view transition animation. Two instances of this class is 31 | * required: one for the "from" view and another for the "to" view. 32 | * 33 | * NOTE: use {@link AnimationFactory} to use this class. 34 | * 35 | * @author Ephraim A. Tekle 36 | * 37 | */ 38 | public class FlipAnimation extends Animation { 39 | public static final int ROTATION_X = 0; 40 | public static final int ROTATION_Y = 1; 41 | private final float mFromDegrees; 42 | private final float mToDegrees; 43 | private final float mCenterX; 44 | private final float mCenterY; 45 | private Camera mCamera; 46 | private int mDirection; 47 | 48 | private final ScaleUpDownEnum scaleType; 49 | 50 | /** 51 | * How much to scale up/down. The default scale of 75% of full size seems optimal based on testing. Feel free to experiment away, however. 52 | */ 53 | public static final float SCALE_DEFAULT = 0.75f; 54 | 55 | private float scale; 56 | 57 | /** 58 | * Constructs a new {@code FlipAnimation} object.Two {@code FlipAnimation} objects are needed for a complete transition b/n two views. 59 | * 60 | * @param fromDegrees the start angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees. 61 | * @param toDegrees the end angle in degrees for a rotation along the y-axis, i.e. in-and-out of the screen, i.e. 3D flip. This should really be multiple of 90 degrees. 62 | * @param centerX the x-axis value of the center of rotation 63 | * @param centerY the y-axis value of the center of rotation 64 | * @param scale to get a 3D effect, the transition views need to be zoomed (scaled). This value must be b/n (0,1) or else the default scale {@link #SCALE_DEFAULT} is used. 65 | * @param scaleType flip view transition is broken down into two: the zoom-out of the "from" view and the zoom-in of the "to" view. This parameter is used to determine which is being done. See {@link com.rx.demo.ui.animation.FlipAnimation.ScaleUpDownEnum}. 66 | */ 67 | public FlipAnimation(float fromDegrees, float toDegrees, float centerX, float centerY, float scale, ScaleUpDownEnum scaleType) { 68 | mFromDegrees = fromDegrees; 69 | mToDegrees = toDegrees; 70 | mCenterX = centerX; 71 | mCenterY = centerY; 72 | this.scale = (scale<=0||scale>=1)?SCALE_DEFAULT:scale; 73 | this.scaleType = scaleType==null?ScaleUpDownEnum.SCALE_CYCLE:scaleType; 74 | mDirection = ROTATION_Y; 75 | } 76 | 77 | @Override 78 | public void initialize(int width, int height, int parentWidth, int parentHeight) { 79 | super.initialize(width, height, parentWidth, parentHeight); 80 | mCamera = new Camera(); 81 | } 82 | 83 | @Override 84 | protected void applyTransformation(float interpolatedTime, Transformation t) { 85 | final float fromDegrees = mFromDegrees; 86 | float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); 87 | 88 | final float centerX = mCenterX; 89 | final float centerY = mCenterY; 90 | final Camera camera = mCamera; 91 | 92 | final Matrix matrix = t.getMatrix(); 93 | 94 | camera.save(); 95 | 96 | if (mDirection == ROTATION_X) 97 | camera.rotateX(degrees); 98 | else 99 | camera.rotateY(degrees); 100 | 101 | camera.getMatrix(matrix); 102 | camera.restore(); 103 | 104 | matrix.preTranslate(-centerX, -centerY); 105 | matrix.postTranslate(centerX, centerY); 106 | 107 | matrix.preScale(scaleType.getScale(scale, interpolatedTime), scaleType.getScale(scale, interpolatedTime), centerX, centerY); 108 | 109 | } 110 | 111 | /** 112 | * Get the current direction, it can be {@link #ROTATION_X} or {#ROTATION_Y} 113 | * @param direction 114 | */ 115 | public void setDirection(int direction) { 116 | mDirection = direction; 117 | } 118 | 119 | /** 120 | * This enumeration is used to determine the zoom (or scale) behavior of a {@link com.rx.demo.ui.animation.FlipAnimation}. 121 | * 122 | * @author Ephraim A. Tekle 123 | * 124 | */ 125 | public static enum ScaleUpDownEnum { 126 | /** 127 | * The view will be scaled up from the scale value until it's at 100% zoom level (i.e. no zoom). 128 | */ 129 | SCALE_UP, 130 | /** 131 | * The view will be scaled down starting at no zoom (100% zoom level) until it's at a specified zoom level. 132 | */ 133 | SCALE_DOWN, 134 | /** 135 | * The view will cycle through a zoom down and then zoom up. 136 | */ 137 | SCALE_CYCLE, 138 | /** 139 | * No zoom effect is applied. 140 | */ 141 | SCALE_NONE; 142 | 143 | /** 144 | * The intermittent zoom level given the current or desired maximum zoom level for the specified iteration 145 | * 146 | * @param max the maximum desired or current zoom level 147 | * @param iter the iteration (from 0..1). 148 | * @return the current zoom level 149 | */ 150 | public float getScale(float max, float iter) { 151 | switch(this) { 152 | case SCALE_UP: 153 | return max + (1-max)*iter; 154 | 155 | case SCALE_DOWN: 156 | return 1 - (1-max)*iter; 157 | 158 | case SCALE_CYCLE: { 159 | final boolean halfWay = (iter > 0.5); 160 | 161 | if (halfWay) { 162 | return max + (1-max)*(iter-0.5f)*2; 163 | } else { 164 | return 1 - (1-max)*(iter*2); 165 | } 166 | } 167 | 168 | default: 169 | return 1; 170 | } 171 | } 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/HistoryView.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view; 2 | 3 | import android.content.Context; 4 | import android.os.Parcelable; 5 | import android.util.AttributeSet; 6 | import android.widget.ArrayAdapter; 7 | import android.widget.ListView; 8 | 9 | import com.digitalbuddha.rx.demo.R; 10 | import com.rx.demo.ui.activity.BaseActivity; 11 | import com.rx.demo.ui.activity.SearchActivity; 12 | import com.rx.demo.ui.view.presenter.ImageSearchPresenter; 13 | 14 | import java.util.ArrayList; 15 | 16 | import javax.inject.Inject; 17 | 18 | import icepick.Icepick; 19 | import icepick.Icicle; 20 | 21 | public class HistoryView extends ListView { 22 | @Inject 23 | ImageSearchPresenter presenter; 24 | 25 | @Inject 26 | SearchActivity activity; 27 | 28 | @Icicle 29 | ArrayList history; 30 | 31 | private ArrayAdapter adapter; 32 | 33 | public HistoryView(Context context) { 34 | this(context, null); 35 | } 36 | 37 | public HistoryView(Context context, AttributeSet attrs) { 38 | this(context, attrs, 0); 39 | } 40 | 41 | public HistoryView(Context context, AttributeSet attrs, int defStyle) { 42 | super(context, attrs, defStyle); 43 | ((BaseActivity) context).inject(this); 44 | } 45 | 46 | 47 | @Override 48 | protected void onFinishInflate() { 49 | history=new ArrayList<>(); 50 | presenter.getHistoryViewBus() 51 | .subscribe(this::addToHistory); 52 | 53 | 54 | adapter = new ArrayAdapter<>(getContext(), 55 | R.layout.history_row, R.id.list_content, history); 56 | setAdapter(adapter); 57 | 58 | setOnItemClickListener((adapterView, view, i, l) -> { 59 | presenter.changeSearchTerm(history.get(i)); 60 | activity.flipViews(); 61 | }); 62 | } 63 | 64 | 65 | private void addToHistory(String s) { 66 | 67 | if (history.isEmpty() || !history.get(history.size() - 1).equals(s)) 68 | history.add(s); 69 | adapter.notifyDataSetChanged(); 70 | } 71 | 72 | @Override 73 | public Parcelable onSaveInstanceState() { 74 | return Icepick.saveInstanceState(this, super.onSaveInstanceState()); 75 | } 76 | 77 | @Override 78 | public void onRestoreInstanceState(Parcelable state) { 79 | super.onRestoreInstanceState(Icepick.restoreInstanceState(this, state)); 80 | adapter = new ArrayAdapter<>(getContext(), 81 | R.layout.history_row, R.id.list_content, history); 82 | setAdapter(adapter); 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/ImageCardView.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.util.AttributeSet; 6 | import android.widget.LinearLayout; 7 | 8 | import com.digitalbuddha.rx.demo.R; 9 | import com.rx.demo.model.Result; 10 | import com.rx.demo.ui.activity.BaseActivity; 11 | 12 | import java.util.Queue; 13 | 14 | import javax.inject.Inject; 15 | 16 | public class ImageCardView extends LinearLayout { 17 | @Inject 18 | Queue que; 19 | 20 | public ImageCardView(Context context) { 21 | this(context, null); 22 | } 23 | 24 | public ImageCardView(Context context, AttributeSet attrs) { 25 | this(context, attrs, 0); 26 | } 27 | 28 | public ImageCardView(Context context, AttributeSet attrs, int defStyle) { 29 | super(context, attrs, defStyle); 30 | ((BaseActivity) context).inject(this); 31 | } 32 | 33 | 34 | @Override 35 | protected void onFinishInflate() { 36 | super.onFinishInflate(); 37 | bindImageData(); 38 | 39 | } 40 | 41 | public void bindImageData() { 42 | Result image = que.remove(); 43 | if (image != null) { 44 | Uri uri = Uri.parse(image.getUnescapedUrl()); 45 | SquareDraweeView draweeView = (SquareDraweeView) findViewById(R.id.avatar); 46 | draweeView.setImageURI(uri); 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/SearchView.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.ViewGroup; 6 | import android.widget.EditText; 7 | import android.widget.LinearLayout; 8 | import android.widget.ScrollView; 9 | 10 | import com.digitalbuddha.rx.demo.R; 11 | import com.rx.demo.ui.activity.BaseActivity; 12 | import com.rx.demo.ui.view.presenter.ImageSearchPresenter; 13 | 14 | import javax.inject.Inject; 15 | 16 | import butterknife.ButterKnife; 17 | import butterknife.InjectView; 18 | 19 | public class SearchView extends ScrollView { 20 | @InjectView(R.id.searchBox) 21 | EditText search; 22 | @InjectView(R.id.cards) 23 | public 24 | LinearLayout cardsLayout; 25 | @Inject 26 | ImageSearchPresenter presenter; 27 | 28 | 29 | public SearchView(Context context) { 30 | this(context, null); 31 | } 32 | 33 | public SearchView(Context context, AttributeSet attrs) { 34 | this(context, attrs, 0); 35 | } 36 | 37 | public SearchView(Context context, AttributeSet attrs, int defStyle) { 38 | super(context, attrs, defStyle); 39 | ((BaseActivity) context).inject(this); 40 | } 41 | 42 | 43 | @Override 44 | protected void onFinishInflate() { 45 | super.onFinishInflate(); 46 | ButterKnife.inject(this); 47 | presenter.takeView(this); 48 | } 49 | 50 | 51 | @Override 52 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 53 | super.onSizeChanged(w, h, oldw, oldh); 54 | presenter.drawNewRowIfNeeded(); 55 | } 56 | 57 | /** 58 | * Inflate a new row 59 | * Bind data to row 60 | * Add row to container 61 | *

62 | * 63 | */ 64 | public void addRow() { 65 | ViewGroup row = getLastResultsRow(); 66 | while (row.getChildCount() < 3) { 67 | inflate(getContext(), R.layout.image_card, row); 68 | } 69 | presenter.drawNewRowIfNeeded(); 70 | } 71 | 72 | /** 73 | * if less than 3 images in last row return it 74 | * else return a newly added linear layout 75 | * 76 | * @return ViewGroup with 0-2 images 77 | */ 78 | private ViewGroup getLastResultsRow() { 79 | ViewGroup bottomRow = (ViewGroup) cardsLayout.getChildAt(cardsLayout.getChildCount() - 1); 80 | if (bottomRow == null || bottomRow.getChildCount() == 3) { 81 | inflate(getContext(), R.layout.row_view, cardsLayout); 82 | bottomRow = (ViewGroup) cardsLayout.getChildAt(cardsLayout.getChildCount() - 1); 83 | } 84 | return bottomRow; 85 | } 86 | 87 | 88 | /** 89 | * detach from presenter 90 | */ 91 | @Override 92 | protected void onDetachedFromWindow() { 93 | super.onDetachedFromWindow(); 94 | presenter.dropView(); 95 | } 96 | 97 | public EditText getSearchView() { 98 | return getSearch(); 99 | } 100 | 101 | public EditText getSearch() { 102 | return search; 103 | } 104 | 105 | public void updateSearchView(String searchTerm) { 106 | search.setText(searchTerm); 107 | presenter.clearResults(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/SquareDraweeView.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import com.facebook.drawee.view.SimpleDraweeView; 7 | 8 | public class SquareDraweeView extends SimpleDraweeView { 9 | 10 | 11 | public SquareDraweeView(Context context) { 12 | this(context, null); 13 | } 14 | 15 | public SquareDraweeView(Context context, AttributeSet attrs) { 16 | this(context, attrs, 0); 17 | } 18 | 19 | public SquareDraweeView(Context context, AttributeSet attrs, int defStyle) { 20 | super(context, attrs, defStyle); 21 | } 22 | 23 | /** 24 | * width is defined by weight attribute, set height to same to make square images 25 | * @param widthSpec 26 | * @param heightSpec 27 | */ 28 | @Override 29 | public void onMeasure(int widthSpec, int heightSpec) { 30 | super.onMeasure(widthSpec, heightSpec); 31 | setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); 32 | } 33 | 34 | @Override 35 | protected void onFinishInflate() { 36 | super.onFinishInflate(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/presenter/IViewPresenter.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view.presenter; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by Nakhimovich on 4/10/15. 7 | */ 8 | public interface IViewPresenter { 9 | void takeView(View view); 10 | 11 | void dropView(); 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/ui/view/presenter/ImageSearchPresenter.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.ui.view.presenter; 2 | 3 | import android.graphics.Rect; 4 | import android.util.Log; 5 | import android.view.View; 6 | 7 | import com.rx.demo.dao.ImageDao; 8 | import com.rx.demo.di.annotation.HistoryViewBus; 9 | import com.rx.demo.di.annotation.ImageViewBus; 10 | import com.rx.demo.model.ImageRequest; 11 | import com.rx.demo.model.Result; 12 | import com.rx.demo.ui.view.SearchView; 13 | import com.rx.demo.util.SubscriptionManager; 14 | 15 | import java.util.Queue; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | import javax.inject.Inject; 19 | import javax.inject.Singleton; 20 | 21 | import rx.Observable; 22 | import rx.android.schedulers.AndroidSchedulers; 23 | import rx.android.widget.WidgetObservable; 24 | import rx.schedulers.Schedulers; 25 | import rx.subjects.PublishSubject; 26 | 27 | @Singleton 28 | public class ImageSearchPresenter implements IViewPresenter { 29 | @Inject 30 | Queue que; 31 | 32 | @Inject 33 | ImageDao dao; 34 | 35 | @Inject 36 | SubscriptionManager subs; 37 | 38 | @Inject 39 | @ImageViewBus 40 | PublishSubject imagesBus; 41 | 42 | @Inject 43 | @HistoryViewBus 44 | PublishSubject historyViewBus; 45 | 46 | private SearchView view; 47 | 48 | /** 49 | * Binds to view 50 | * Initializes subscriptions 51 | * Adds an on Scroll listner to load more images 52 | * 53 | * @param view view with search term and image results 54 | */ 55 | @Override 56 | public void takeView(View view) { 57 | if (view == null) return; 58 | this.view = (SearchView) view; 59 | initSubs(); 60 | addOnScrollListener(); 61 | } 62 | 63 | /** 64 | * subscribes to search view 65 | * transforms the onTextChange event into the search String 66 | * filters any blank searches 67 | * emits only the last emitted value when no search for 1 second 68 | * on a new valid search occurs 69 | * clear screen and queue 70 | * emit new search terms to history view bus 71 | * 72 | * @return Observable containing the search term 73 | */ 74 | private Observable searchTermObservable() { 75 | return WidgetObservable.text(view.getSearchView()) 76 | .map(onTextChangeEvent -> onTextChangeEvent.text().toString()) 77 | .filter(searchTerm -> searchTerm.length() > 0) 78 | .debounce(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) 79 | .doOnNext(results -> clearResults()) 80 | .doOnNext(getHistoryViewBus()::onNext); 81 | } 82 | 83 | /** 84 | * on search term change request images 85 | * subscribe with imagesBus 86 | *

87 | * images become available -> emit to imagesBus 88 | *

89 | * on emit to imagesBus -> try to add new rows 90 | *

91 | * NOTE: imagesBus drops events followed by another event within 300ms 92 | */ 93 | private void initSubs() { 94 | searchTermObservable() 95 | .observeOn(Schedulers.io()) 96 | .flatMap(s -> dao.fetchImageResults(new ImageRequest(s))) 97 | .doOnNext(que::add) 98 | .doOnError(throwable -> { 99 | throw new RuntimeException(throwable); 100 | }) 101 | .subscribe(imagesBus); 102 | 103 | imagesBus.debounce(50, TimeUnit.MILLISECONDS, Schedulers.computation()) 104 | .filter(o -> isLastRowVisible() && que.size() > 3) 105 | .observeOn(AndroidSchedulers.mainThread()) 106 | .subscribe(o -> displayNextRow()); 107 | } 108 | 109 | 110 | /** 111 | * If scrolled to last row, emit new item to imagesBus 112 | */ 113 | private void addOnScrollListener() { 114 | view.cardsLayout.getViewTreeObserver().addOnScrollChangedListener(() -> { 115 | imagesBus.onNext(que.peek()); 116 | }); 117 | } 118 | 119 | /** 120 | * gets images from queue and binds to newly created views 121 | */ 122 | public void displayNextRow() { 123 | Log.e(this.getClass().getSimpleName(), "last row is visible on screen, load next rows"); 124 | view.addRow(); 125 | 126 | } 127 | 128 | public void drawNewRowIfNeeded() { 129 | imagesBus.onNext(que.peek()); 130 | } 131 | 132 | 133 | /** 134 | * clear queue and view of previous results 135 | */ 136 | public void clearResults() { 137 | view.cardsLayout.removeAllViews(); 138 | que.clear(); 139 | } 140 | 141 | 142 | /** 143 | * determines whether the last row of images is visible on the screen 144 | * 145 | * @return boolean 146 | */ 147 | public boolean isLastRowVisible() { 148 | if (que.size() == 0 || view == null) { 149 | return false; 150 | } 151 | Rect scrollBounds = new Rect(); 152 | view.cardsLayout.getHitRect(scrollBounds); 153 | View lastRow = view.cardsLayout.getChildAt(view.cardsLayout.getChildCount() - 1); 154 | return lastRow == null || lastRow.getLocalVisibleRect(scrollBounds); 155 | } 156 | 157 | /** 158 | * unsubscribe when view is destroyed 159 | */ 160 | @Override 161 | public void dropView() { 162 | view = null; 163 | subs.unsubscribeAll(); 164 | } 165 | 166 | public void changeSearchTerm(String s) { 167 | view.updateSearchView(s); 168 | } 169 | 170 | public PublishSubject getHistoryViewBus() { 171 | return historyViewBus; 172 | } 173 | 174 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rx/demo/util/SubscriptionManager.java: -------------------------------------------------------------------------------- 1 | package com.rx.demo.util; 2 | 3 | import java.util.ArrayList; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | import rx.Subscription; 9 | 10 | /** 11 | * Created by Nakhimovich on 3/28/15. 12 | */ 13 | 14 | /** 15 | * manages observable subscriptions 16 | * unsubscribeAll is normally called from onDestroy of an Activity 17 | */ 18 | @Singleton 19 | public class SubscriptionManager { 20 | private ArrayList subscriptions; 21 | 22 | @Inject 23 | public SubscriptionManager() { 24 | subscriptions = new ArrayList<>(); 25 | } 26 | 27 | 28 | public void add(Subscription subscription) { 29 | subscriptions.add(subscription); 30 | } 31 | 32 | public void unsubscribeAll() { 33 | for (Subscription subscription : subscriptions) { 34 | if (subscription != null) { 35 | subscription.unsubscribe(); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_in.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_to_left.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_out_to_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digitalbuddha/RxStarter/f0935831cba5c732332eb889469d5f8338e6481e/app/src/main/res/drawable-xhdpi/history.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/grey_with_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/white_view_with_border_clicked.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/white_with_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_layout.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 14 | 15 | 16 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/history_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/history_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/image_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/row_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/search_view.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | 26 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 |

5 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs_civ.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ffff0000 4 | #ffffffff 5 | #f5f5f5 6 | #ffcccccc 7 | #ff111111 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2dp 4 | 2dp 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Who To Follow 5 | PostActivity 6 | Hello world! 7 | Settings 8 | MainActivity 9 | Enter Search Term 10 | history 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.0.0' 9 | classpath 'me.tatarka:gradle-retrolambda:2.5.0' 10 | 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | jcenter() 17 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } 18 | maven {url "https://clojars.org/repo/"} 19 | 20 | 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Settings specified in this file will override any Gradle settings 5 | # configured through the IDE. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | artifactory_user=user 21 | artifactory_password=RgMgpwWzuyn6 22 | artifactory_contextUrl=http://code.touchlab.co/artifactory -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/digitalbuddha/RxStarter/f0935831cba5c732332eb889469d5f8338e6481e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Jan 25 18:05:52 EST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /import-summary.txt: -------------------------------------------------------------------------------- 1 | ECLIPSE ANDROID PROJECT IMPORT SUMMARY 2 | ====================================== 3 | 4 | Ignored Files: 5 | -------------- 6 | The following files were *not* copied into the new Gradle project; you 7 | should evaluate whether these are still needed in your project and if 8 | so manually move them: 9 | 10 | * README.md 11 | * pom.xml 12 | 13 | Moved Files: 14 | ------------ 15 | Android Gradle projects use a different directory structure than ADT 16 | Eclipse projects. Here's how the projects were restructured: 17 | 18 | * AndroidManifest.xml => app/src/main/AndroidManifest.xml 19 | * res/ => app/src/main/res/ 20 | * src/ => app/src/main/java/ 21 | 22 | Next Steps: 23 | ----------- 24 | You can now build the project. The Gradle project needs network 25 | connectivity to download dependencies. 26 | 27 | Bugs: 28 | ----- 29 | If for some reason your project does not build, and you determine that 30 | it is due to a bug or limitation of the Eclipse to Gradle importer, 31 | please file a bug at http://b.android.com with category 32 | Component-Tools. 33 | 34 | (This import summary is for your information only, and can be deleted 35 | after import once you are satisfied with the results.) 36 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------