├── .gitignore ├── .idea ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── info │ │ └── androidhive │ │ └── flighttickets │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── info │ │ │ └── androidhive │ │ │ └── flighttickets │ │ │ ├── app │ │ │ └── Const.java │ │ │ ├── network │ │ │ ├── ApiClient.java │ │ │ ├── ApiService.java │ │ │ └── model │ │ │ │ ├── Airline.java │ │ │ │ ├── Price.java │ │ │ │ └── Ticket.java │ │ │ └── view │ │ │ ├── MainActivity.java │ │ │ └── TicketsAdapter.java │ └── res │ │ ├── drawable-hdpi │ │ └── ic_arrow_forward_black_24dp.png │ │ ├── drawable-mdpi │ │ └── ic_arrow_forward_black_24dp.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ └── ic_arrow_forward_black_24dp.png │ │ ├── drawable-xxhdpi │ │ └── ic_arrow_forward_black_24dp.png │ │ ├── drawable-xxxhdpi │ │ └── ic_arrow_forward_black_24dp.png │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── content_main.xml │ │ └── ticket_row.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── info │ └── androidhive │ └── flighttickets │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | defaultConfig { 6 | applicationId "info.androidhive.flighttickets" 7 | minSdkVersion 16 8 | targetSdkVersion 26 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:26.1.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 25 | implementation 'com.android.support:design:26.1.0' 26 | testImplementation 'junit:junit:4.12' 27 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 29 | 30 | // RecyclerView and CardView 31 | implementation 'com.android.support:recyclerview-v7:26.1.0' 32 | implementation 'com.android.support:cardview-v7:26.1.0' 33 | 34 | // RxJava 35 | implementation 'io.reactivex.rxjava2:rxjava:2.1.9' 36 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 37 | 38 | // ButterKnife 39 | implementation "com.jakewharton:butterknife:8.8.1" 40 | annotationProcessor "com.jakewharton:butterknife-compiler:8.8.1" 41 | 42 | // Retrofit and OkHttp 43 | // OkHttp interceptors for logging 44 | implementation "com.squareup.retrofit2:retrofit:2.0.0" 45 | implementation "com.squareup.retrofit2:converter-gson:2.0.0" 46 | implementation "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0" 47 | implementation "com.squareup.okhttp3:okhttp:3.0.1" 48 | implementation "com.squareup.okhttp3:okhttp-urlconnection:3.0.1" 49 | implementation "com.squareup.okhttp3:logging-interceptor:3.4.1" 50 | 51 | // glide image library 52 | implementation "com.github.bumptech.glide:glide:4.3.1" 53 | 54 | 55 | implementation 'com.github.ybq:Android-SpinKit:1.1.0' 56 | } 57 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/info/androidhive/flighttickets/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("info.androidhive.flighttickets", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/app/Const.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.app; 2 | 3 | /** 4 | * Created by ravi on 01/03/18. 5 | */ 6 | 7 | public class Const { 8 | // To fetch the tickets 9 | // https://api.androidhive.info/json/airline-tickets.php 10 | 11 | // To fetch individual ticket price 12 | // https://api.androidhive.info/json/airline-tickets-price.php 13 | public static final String BASE_URL = "https://api.androidhive.info/json/"; 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/network/ApiClient.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.network; 2 | 3 | import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 4 | 5 | import java.io.IOException; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import info.androidhive.flighttickets.app.Const; 9 | import okhttp3.Interceptor; 10 | import okhttp3.OkHttpClient; 11 | import okhttp3.Request; 12 | import okhttp3.Response; 13 | import okhttp3.logging.HttpLoggingInterceptor; 14 | import retrofit2.Retrofit; 15 | import retrofit2.converter.gson.GsonConverterFactory; 16 | 17 | /** 18 | * Created by ravi on 02/03/18. 19 | */ 20 | 21 | public class ApiClient { 22 | private static String TAG = ApiClient.class.getSimpleName(); 23 | private static Retrofit retrofit = null; 24 | private static int REQUEST_TIMEOUT = 60; 25 | private static OkHttpClient okHttpClient; 26 | 27 | 28 | public static Retrofit getClient() { 29 | 30 | if (okHttpClient == null) 31 | initOkHttp(); 32 | 33 | if (retrofit == null) { 34 | retrofit = new Retrofit.Builder() 35 | .baseUrl(Const.BASE_URL) 36 | .client(okHttpClient) 37 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 38 | .addConverterFactory(GsonConverterFactory.create()) 39 | .build(); 40 | } 41 | return retrofit; 42 | } 43 | 44 | private static void initOkHttp() { 45 | OkHttpClient.Builder httpClient = new OkHttpClient().newBuilder() 46 | .connectTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS) 47 | .readTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS) 48 | .writeTimeout(REQUEST_TIMEOUT, TimeUnit.SECONDS); 49 | 50 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); 51 | interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 52 | 53 | httpClient.addInterceptor(interceptor); 54 | 55 | httpClient.addInterceptor(new Interceptor() { 56 | @Override 57 | public Response intercept(Chain chain) throws IOException { 58 | Request original = chain.request(); 59 | Request.Builder requestBuilder = original.newBuilder() 60 | .addHeader("Accept", "application/json") 61 | .addHeader("Request-Type", "Android") 62 | .addHeader("Content-Type", "application/json"); 63 | 64 | Request request = requestBuilder.build(); 65 | return chain.proceed(request); 66 | } 67 | }); 68 | 69 | okHttpClient = httpClient.build(); 70 | } 71 | 72 | public static void resetApiClient() { 73 | retrofit = null; 74 | okHttpClient = null; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/network/ApiService.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.network; 2 | 3 | /** 4 | * Created by ravi on 02/03/18. 5 | */ 6 | 7 | import java.util.List; 8 | 9 | import info.androidhive.flighttickets.network.model.Price; 10 | import info.androidhive.flighttickets.network.model.Ticket; 11 | import io.reactivex.Single; 12 | import retrofit2.http.GET; 13 | import retrofit2.http.Query; 14 | 15 | public interface ApiService { 16 | 17 | @GET("airline-tickets.php") 18 | Single> searchTickets(@Query("from") String from, @Query("to") String to); 19 | 20 | @GET("airline-tickets-price.php") 21 | Single getPrice(@Query("flight_number") String flightNumber, @Query("from") String from, @Query("to") String to); 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/network/model/Airline.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.network.model; 2 | 3 | /** 4 | * Created by ravi on 02/03/18. 5 | */ 6 | 7 | public class Airline { 8 | int id; 9 | String name; 10 | String logo; 11 | 12 | public int getId() { 13 | return id; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public String getLogo() { 21 | return logo; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/network/model/Price.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.network.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ravi on 02/03/18. 7 | */ 8 | 9 | public class Price { 10 | float price; 11 | String seats; 12 | String currency; 13 | 14 | @SerializedName("flight_number") 15 | String flightNumber; 16 | 17 | String from; 18 | String to; 19 | 20 | public float getPrice() { 21 | return price; 22 | } 23 | 24 | public String getSeats() { 25 | return seats; 26 | } 27 | 28 | public String getCurrency() { 29 | return currency; 30 | } 31 | 32 | public String getFlightNumber() { 33 | return flightNumber; 34 | } 35 | 36 | public String getFrom() { 37 | return from; 38 | } 39 | 40 | public String getTo() { 41 | return to; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/network/model/Ticket.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.network.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ravi on 02/03/18. 7 | */ 8 | 9 | public class Ticket { 10 | 11 | String from; 12 | String to; 13 | 14 | @SerializedName("flight_number") 15 | String flightNumber; 16 | 17 | String departure; 18 | String arrival; 19 | String duration; 20 | String instructions; 21 | 22 | @SerializedName("stops") 23 | int numberOfStops; 24 | 25 | Airline airline; 26 | 27 | Price price; 28 | 29 | public String getFrom() { 30 | return from; 31 | } 32 | 33 | public String getTo() { 34 | return to; 35 | } 36 | 37 | public String getFlightNumber() { 38 | return flightNumber; 39 | } 40 | 41 | public String getDeparture() { 42 | return departure; 43 | } 44 | 45 | public String getArrival() { 46 | return arrival; 47 | } 48 | 49 | public String getDuration() { 50 | return duration; 51 | } 52 | 53 | public String getInstructions() { 54 | return instructions; 55 | } 56 | 57 | public int getNumberOfStops() { 58 | return numberOfStops; 59 | } 60 | 61 | public Airline getAirline() { 62 | return airline; 63 | } 64 | 65 | public Price getPrice() { 66 | return price; 67 | } 68 | 69 | public void setPrice(Price price) { 70 | this.price = price; 71 | } 72 | 73 | @Override 74 | public boolean equals(Object obj) { 75 | if (obj == this) { 76 | return true; 77 | } 78 | 79 | if (!(obj instanceof Ticket)) { 80 | return false; 81 | } 82 | 83 | return flightNumber.equalsIgnoreCase(((Ticket) obj).getFlightNumber()); 84 | } 85 | 86 | @Override 87 | public int hashCode() { 88 | int hash = 3; 89 | hash = 53 * hash + (this.flightNumber != null ? this.flightNumber.hashCode() : 0); 90 | return hash; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/view/MainActivity.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.view; 2 | 3 | import android.content.res.Resources; 4 | import android.graphics.Color; 5 | import android.graphics.Rect; 6 | import android.os.Bundle; 7 | import android.support.design.widget.CoordinatorLayout; 8 | import android.support.design.widget.Snackbar; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.DefaultItemAnimator; 11 | import android.support.v7.widget.GridLayoutManager; 12 | import android.support.v7.widget.RecyclerView; 13 | import android.support.v7.widget.Toolbar; 14 | import android.util.Log; 15 | import android.util.TypedValue; 16 | import android.view.Menu; 17 | import android.view.MenuItem; 18 | import android.view.View; 19 | import android.widget.TextView; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import butterknife.BindView; 25 | import butterknife.ButterKnife; 26 | import butterknife.Unbinder; 27 | import info.androidhive.flighttickets.R; 28 | import info.androidhive.flighttickets.network.ApiClient; 29 | import info.androidhive.flighttickets.network.ApiService; 30 | import info.androidhive.flighttickets.network.model.Price; 31 | import info.androidhive.flighttickets.network.model.Ticket; 32 | import io.reactivex.Observable; 33 | import io.reactivex.ObservableSource; 34 | import io.reactivex.android.schedulers.AndroidSchedulers; 35 | import io.reactivex.disposables.CompositeDisposable; 36 | import io.reactivex.functions.Function; 37 | import io.reactivex.observables.ConnectableObservable; 38 | import io.reactivex.observers.DisposableObserver; 39 | import io.reactivex.schedulers.Schedulers; 40 | 41 | public class MainActivity extends AppCompatActivity implements TicketsAdapter.TicketsAdapterListener { 42 | 43 | private static final String TAG = MainActivity.class.getSimpleName(); 44 | private static final String from = "DEL"; 45 | private static final String to = "HYD"; 46 | 47 | private CompositeDisposable disposable = new CompositeDisposable(); 48 | private Unbinder unbinder; 49 | 50 | private ApiService apiService; 51 | private TicketsAdapter mAdapter; 52 | private ArrayList ticketsList = new ArrayList<>(); 53 | 54 | @BindView(R.id.recycler_view) 55 | RecyclerView recyclerView; 56 | 57 | @BindView(R.id.coordinator_layout) 58 | CoordinatorLayout coordinatorLayout; 59 | 60 | @Override 61 | protected void onCreate(Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | setContentView(R.layout.activity_main); 64 | unbinder = ButterKnife.bind(this); 65 | 66 | Toolbar toolbar = findViewById(R.id.toolbar); 67 | setSupportActionBar(toolbar); 68 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 69 | getSupportActionBar().setTitle(from + " > " + to); 70 | 71 | apiService = ApiClient.getClient().create(ApiService.class); 72 | 73 | mAdapter = new TicketsAdapter(this, ticketsList, this); 74 | 75 | RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(this, 1); 76 | recyclerView.setLayoutManager(mLayoutManager); 77 | recyclerView.addItemDecoration(new MainActivity.GridSpacingItemDecoration(1, dpToPx(5), true)); 78 | recyclerView.setItemAnimator(new DefaultItemAnimator()); 79 | recyclerView.setAdapter(mAdapter); 80 | 81 | ConnectableObservable> ticketsObservable = getTickets(from, to).replay(); 82 | 83 | /** 84 | * Fetching all tickets first 85 | * Observable emits List at once 86 | * All the items will be added to RecyclerView 87 | * */ 88 | disposable.add( 89 | ticketsObservable 90 | .subscribeOn(Schedulers.io()) 91 | .observeOn(AndroidSchedulers.mainThread()) 92 | .subscribeWith(new DisposableObserver>() { 93 | 94 | @Override 95 | public void onNext(List tickets) { 96 | // Refreshing list 97 | ticketsList.clear(); 98 | ticketsList.addAll(tickets); 99 | mAdapter.notifyDataSetChanged(); 100 | } 101 | 102 | @Override 103 | public void onError(Throwable e) { 104 | showError(e); 105 | } 106 | 107 | @Override 108 | public void onComplete() { 109 | 110 | } 111 | })); 112 | 113 | /** 114 | * Fetching individual ticket price 115 | * First FlatMap converts single List to multiple emissions 116 | * Second FlatMap makes HTTP call on each Ticket emission 117 | * */ 118 | disposable.add( 119 | ticketsObservable 120 | .subscribeOn(Schedulers.io()) 121 | .observeOn(AndroidSchedulers.mainThread()) 122 | /** 123 | * Converting List emission to single Ticket emissions 124 | * */ 125 | .flatMap(new Function, ObservableSource>() { 126 | @Override 127 | public ObservableSource apply(List tickets) throws Exception { 128 | return Observable.fromIterable(tickets); 129 | } 130 | }) 131 | /** 132 | * Fetching price on each Ticket emission 133 | * */ 134 | .flatMap(new Function>() { 135 | @Override 136 | public ObservableSource apply(Ticket ticket) throws Exception { 137 | return getPriceObservable(ticket); 138 | } 139 | }) 140 | .subscribeWith(new DisposableObserver() { 141 | 142 | @Override 143 | public void onNext(Ticket ticket) { 144 | int position = ticketsList.indexOf(ticket); 145 | 146 | if (position == -1) { 147 | // TODO - take action 148 | // Ticket not found in the list 149 | // This shouldn't happen 150 | return; 151 | } 152 | 153 | ticketsList.set(position, ticket); 154 | mAdapter.notifyItemChanged(position); 155 | } 156 | 157 | @Override 158 | public void onError(Throwable e) { 159 | showError(e); 160 | } 161 | 162 | @Override 163 | public void onComplete() { 164 | 165 | } 166 | })); 167 | 168 | // Calling connect to start emission 169 | ticketsObservable.connect(); 170 | } 171 | 172 | /** 173 | * Making Retrofit call to fetch all tickets 174 | */ 175 | private Observable> getTickets(String from, String to) { 176 | return apiService.searchTickets(from, to) 177 | .toObservable() 178 | .subscribeOn(Schedulers.io()) 179 | .observeOn(AndroidSchedulers.mainThread()); 180 | } 181 | 182 | /** 183 | * Making Retrofit call to get single ticket price 184 | * get price HTTP call returns Price object, but 185 | * map() operator is used to change the return type to Ticket 186 | */ 187 | private Observable getPriceObservable(final Ticket ticket) { 188 | return apiService 189 | .getPrice(ticket.getFlightNumber(), ticket.getFrom(), ticket.getTo()) 190 | .toObservable() 191 | .subscribeOn(Schedulers.io()) 192 | .observeOn(AndroidSchedulers.mainThread()) 193 | .map(new Function() { 194 | @Override 195 | public Ticket apply(Price price) throws Exception { 196 | ticket.setPrice(price); 197 | return ticket; 198 | } 199 | }); 200 | } 201 | 202 | @Override 203 | public boolean onCreateOptionsMenu(Menu menu) { 204 | // Inflate the menu; this adds items to the action bar if it is present. 205 | getMenuInflater().inflate(R.menu.menu_main, menu); 206 | return true; 207 | } 208 | 209 | @Override 210 | public boolean onOptionsItemSelected(MenuItem item) { 211 | // Handle action bar item clicks here. The action bar will 212 | // automatically handle clicks on the Home/Up button, so long 213 | // as you specify a parent activity in AndroidManifest.xml. 214 | int id = item.getItemId(); 215 | 216 | //noinspection SimplifiableIfStatement 217 | if (id == R.id.action_settings) { 218 | return true; 219 | } 220 | 221 | return super.onOptionsItemSelected(item); 222 | } 223 | 224 | public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { 225 | 226 | private int spanCount; 227 | private int spacing; 228 | private boolean includeEdge; 229 | 230 | public GridSpacingItemDecoration(int spanCount, int spacing, boolean includeEdge) { 231 | this.spanCount = spanCount; 232 | this.spacing = spacing; 233 | this.includeEdge = includeEdge; 234 | } 235 | 236 | @Override 237 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 238 | int position = parent.getChildAdapterPosition(view); // item position 239 | int column = position % spanCount; // item column 240 | 241 | if (includeEdge) { 242 | outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing) 243 | outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing) 244 | 245 | if (position < spanCount) { // top edge 246 | outRect.top = spacing; 247 | } 248 | outRect.bottom = spacing; // item bottom 249 | } else { 250 | outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing) 251 | outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing) 252 | if (position >= spanCount) { 253 | outRect.top = spacing; // item top 254 | } 255 | } 256 | } 257 | } 258 | 259 | private int dpToPx(int dp) { 260 | Resources r = getResources(); 261 | return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, r.getDisplayMetrics())); 262 | } 263 | 264 | @Override 265 | public void onTicketSelected(Ticket contact) { 266 | 267 | } 268 | 269 | /** 270 | * Snackbar shows observer error 271 | */ 272 | private void showError(Throwable e) { 273 | Log.e(TAG, "showError: " + e.getMessage()); 274 | 275 | Snackbar snackbar = Snackbar 276 | .make(coordinatorLayout, e.getMessage(), Snackbar.LENGTH_LONG); 277 | View sbView = snackbar.getView(); 278 | TextView textView = sbView.findViewById(android.support.design.R.id.snackbar_text); 279 | textView.setTextColor(Color.YELLOW); 280 | snackbar.show(); 281 | } 282 | 283 | @Override 284 | protected void onDestroy() { 285 | super.onDestroy(); 286 | disposable.dispose(); 287 | unbinder.unbind(); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /app/src/main/java/info/androidhive/flighttickets/view/TicketsAdapter.java: -------------------------------------------------------------------------------- 1 | package info.androidhive.flighttickets.view; 2 | 3 | /** 4 | * Created by ravi on 02/03/18. 5 | */ 6 | 7 | import android.content.Context; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.text.TextUtils; 10 | import android.view.LayoutInflater; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.ImageView; 14 | import android.widget.TextView; 15 | 16 | import com.bumptech.glide.Glide; 17 | import com.bumptech.glide.request.RequestOptions; 18 | import com.github.ybq.android.spinkit.SpinKitView; 19 | 20 | import java.util.List; 21 | 22 | import butterknife.BindView; 23 | import butterknife.ButterKnife; 24 | import info.androidhive.flighttickets.R; 25 | import info.androidhive.flighttickets.network.model.Ticket; 26 | 27 | 28 | public class TicketsAdapter extends RecyclerView.Adapter { 29 | private Context context; 30 | private List contactList; 31 | private TicketsAdapterListener listener; 32 | 33 | public class MyViewHolder extends RecyclerView.ViewHolder { 34 | @BindView(R.id.airline_name) 35 | TextView airlineName; 36 | 37 | @BindView(R.id.logo) 38 | ImageView logo; 39 | 40 | @BindView(R.id.number_of_stops) 41 | TextView stops; 42 | 43 | @BindView(R.id.number_of_seats) 44 | TextView seats; 45 | 46 | @BindView(R.id.departure) 47 | TextView departure; 48 | 49 | @BindView(R.id.arrival) 50 | TextView arrival; 51 | 52 | @BindView(R.id.duration) 53 | TextView duration; 54 | 55 | @BindView(R.id.price) 56 | TextView price; 57 | 58 | @BindView(R.id.loader) 59 | SpinKitView loader; 60 | 61 | public MyViewHolder(View view) { 62 | super(view); 63 | ButterKnife.bind(this, view); 64 | 65 | view.setOnClickListener(new View.OnClickListener() { 66 | @Override 67 | public void onClick(View view) { 68 | // send selected contact in callback 69 | listener.onTicketSelected(contactList.get(getAdapterPosition())); 70 | } 71 | }); 72 | } 73 | } 74 | 75 | public TicketsAdapter(Context context, List contactList, TicketsAdapterListener listener) { 76 | this.context = context; 77 | this.listener = listener; 78 | this.contactList = contactList; 79 | } 80 | 81 | @Override 82 | public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 83 | View itemView = LayoutInflater.from(parent.getContext()) 84 | .inflate(R.layout.ticket_row, parent, false); 85 | 86 | return new MyViewHolder(itemView); 87 | } 88 | 89 | @Override 90 | public void onBindViewHolder(MyViewHolder holder, final int position) { 91 | final Ticket ticket = contactList.get(position); 92 | 93 | Glide.with(context) 94 | .load(ticket.getAirline().getLogo()) 95 | .apply(RequestOptions.circleCropTransform()) 96 | .into(holder.logo); 97 | 98 | holder.airlineName.setText(ticket.getAirline().getName()); 99 | 100 | holder.departure.setText(ticket.getDeparture() + " Dep"); 101 | holder.arrival.setText(ticket.getArrival() + " Dest"); 102 | 103 | holder.duration.setText(ticket.getFlightNumber()); 104 | holder.duration.append(", " + ticket.getDuration()); 105 | holder.stops.setText(ticket.getNumberOfStops() + " Stops"); 106 | 107 | if (!TextUtils.isEmpty(ticket.getInstructions())) { 108 | holder.duration.append(", " + ticket.getInstructions()); 109 | } 110 | 111 | if (ticket.getPrice() != null) { 112 | holder.price.setText("₹" + String.format("%.0f", ticket.getPrice().getPrice())); 113 | holder.seats.setText(ticket.getPrice().getSeats() + " Seats"); 114 | holder.loader.setVisibility(View.INVISIBLE); 115 | } else { 116 | holder.loader.setVisibility(View.VISIBLE); 117 | } 118 | } 119 | 120 | @Override 121 | public int getItemCount() { 122 | return contactList.size(); 123 | } 124 | 125 | public interface TicketsAdapterListener { 126 | void onTicketSelected(Ticket contact); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_arrow_forward_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/drawable-hdpi/ic_arrow_forward_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_arrow_forward_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/drawable-mdpi/ic_arrow_forward_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_arrow_forward_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/drawable-xhdpi/ic_arrow_forward_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_arrow_forward_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/drawable-xxhdpi/ic_arrow_forward_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_arrow_forward_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/drawable-xxxhdpi/ic_arrow_forward_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/ticket_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 21 | 22 | 28 | 29 | 34 | 35 | 41 | 42 | 53 | 54 | 55 | 66 | 67 | 78 | 79 | 80 | 81 | 87 | 88 | 100 | 101 | 108 | 109 | 121 | 122 | 127 | 128 | 136 | 137 | 145 | 146 | 147 | 148 | 149 | 154 | 155 | 164 | 165 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ravi8x/RxJavaFlightTicket/5346e568793b003c46a29654d9d52432cbc56330/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #B91D44 4 | #B91D44 5 | #FF4081 6 | #515151 7 | #787878 8 | #dd000000 9 | #6E6E6E 10 | #dd595959 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 16dp 3 | 16dp 4 | 10dp 5 | 20dp 6 | 16sp 7 | 14sp 8 | 12sp 9 | 20sp 10 | 16sp 11 | 11sp 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Flight Tickets 3 | Settings 4 | Price 5 | TestActivity 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |