├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── iammert │ │ └── com │ │ └── instagramtags │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── iammert │ │ │ └── com │ │ │ └── instagramtags │ │ │ ├── InstagramTagsApp.java │ │ │ ├── di │ │ │ ├── Activity.java │ │ │ ├── Fragment.java │ │ │ ├── app │ │ │ │ ├── AppComponent.java │ │ │ │ ├── AppModule.java │ │ │ │ └── NetworkModule.java │ │ │ ├── main │ │ │ │ ├── MainComponent.java │ │ │ │ └── MainModule.java │ │ │ ├── medialist │ │ │ │ ├── MediaListComponent.java │ │ │ │ └── MediaListModule.java │ │ │ ├── searchtag │ │ │ │ ├── SearchTagComponent.java │ │ │ │ └── SearchTagModule.java │ │ │ └── splash │ │ │ │ ├── SplashComponent.java │ │ │ │ └── SplashModule.java │ │ │ ├── domain │ │ │ ├── medialist │ │ │ │ ├── MediaListUsecase.java │ │ │ │ └── MediaListUsecaseImpl.java │ │ │ ├── searchtag │ │ │ │ ├── SearchTagUsecase.java │ │ │ │ └── SearchTagUsecaseImpl.java │ │ │ └── splash │ │ │ │ ├── SplashUsecase.java │ │ │ │ └── SplashUsecaseImpl.java │ │ │ ├── model │ │ │ ├── api │ │ │ │ ├── ApiSource.java │ │ │ │ ├── ApiSourceImpl.java │ │ │ │ ├── RetrofitInterface.java │ │ │ │ └── entity │ │ │ │ │ ├── Comment.java │ │ │ │ │ ├── Image.java │ │ │ │ │ ├── ImageWrapper.java │ │ │ │ │ ├── Like.java │ │ │ │ │ ├── Media.java │ │ │ │ │ ├── MediaListResponse.java │ │ │ │ │ ├── Tag.java │ │ │ │ │ ├── TagSearchResponse.java │ │ │ │ │ └── User.java │ │ │ └── preferences │ │ │ │ ├── PreferencesHelper.java │ │ │ │ └── PreferencesHelperImpl.java │ │ │ ├── util │ │ │ ├── Constants.java │ │ │ ├── DialogBuilder.java │ │ │ ├── ImageDataBinding.java │ │ │ ├── RxBus.java │ │ │ ├── RxTransformer.java │ │ │ └── SimpleTextWatcher.java │ │ │ ├── view │ │ │ ├── login │ │ │ │ └── LoginActivity.java │ │ │ ├── main │ │ │ │ └── MainActivity.java │ │ │ ├── medialist │ │ │ │ ├── MediaListActivity.java │ │ │ │ ├── MediaListAdapter.java │ │ │ │ └── MediaListFragment.java │ │ │ ├── searchtag │ │ │ │ ├── SearchTagAdapter.java │ │ │ │ └── SearchTagFragment.java │ │ │ └── splash │ │ │ │ └── SplashActivity.java │ │ │ └── viewmodel │ │ │ ├── main │ │ │ └── MainViewModel.java │ │ │ ├── medialist │ │ │ ├── MediaListItemViewModel.java │ │ │ └── MediaListViewModel.java │ │ │ ├── searchtag │ │ │ ├── SearchTagItemViewModel.java │ │ │ ├── SearchTagViewModel.java │ │ │ └── TagClickEvent.java │ │ │ └── splash │ │ │ └── SplashViewModel.java │ └── res │ │ ├── drawable │ │ ├── bg_image.xml │ │ ├── bg_search_bar.xml │ │ ├── ic_favorite_border_white_24dp.xml │ │ └── shadow_image.xml │ │ ├── layout-w900dp │ │ └── activity_main.xml │ │ ├── layout │ │ ├── activity_login.xml │ │ ├── activity_main.xml │ │ ├── activity_media_list.xml │ │ ├── activity_splash.xml │ │ ├── fragment_media_list.xml │ │ ├── fragment_search_tag.xml │ │ ├── item_media_list.xml │ │ └── item_search_tag.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── iammert │ └── com │ └── instagramtags │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screeshots ├── arch.png ├── mobile_mockup.png └── tablet_mockup.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/libraries 41 | .idea/ 42 | .idea 43 | 44 | # Keystore files 45 | *.jks 46 | 47 | # External native build folder generated in Android Studio 2.2 and later 48 | .externalNativeBuild -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InstagramTags 2 | Sample MVVM project uses instagram API. Supported two-pane layouts for tablets. 3 | 4 | #Screenshots 5 | 6 | 7 | 8 | #Technology used 9 | * Clean MVVM architecture 10 | * [Android Databinding](https://developer.android.com/topic/libraries/data-binding/index.html) 11 | * [Dagger 2](https://google.github.io/dagger/) 12 | * [RxJava](https://github.com/ReactiveX/RxJava) 13 | * [OkHttp](http://square.github.io/okhttp/) 14 | * [Retfofit](https://square.github.io/retrofit/) 15 | * [Picasso](http://square.github.io/picasso/) 16 | 17 | #Architectural Design 18 | 19 | 20 | #Setup 21 | Add your client_id and redirect_uri to Constants class for authentication. For more detail you can follow [instagram developer documentation](https://www.instagram.com/developer/). 22 | ```java 23 | public static final String CLIENT_ID = "REPLACE_HERE"; 24 | public static final String REDIRECT_URI = "REPLACE_HERE"; 25 | public static final String RESPONSE_TYPE = "token"; //you can change auth type 26 | public static final String SCOPE = "public_content"; //you can change auth scope 27 | ``` 28 | 29 | 30 | 31 | 32 | License 33 | -------- 34 | 35 | 36 | Copyright 2017 Mert Şimşek. 37 | 38 | Licensed under the Apache License, Version 2.0 (the "License"); 39 | you may not use this file except in compliance with the License. 40 | You may obtain a copy of the License at 41 | 42 | http://www.apache.org/licenses/LICENSE-2.0 43 | 44 | Unless required by applicable law or agreed to in writing, software 45 | distributed under the License is distributed on an "AS IS" BASIS, 46 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 47 | See the License for the specific language governing permissions and 48 | limitations under the License. 49 | 50 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | .idea/tasks.xml 39 | .idea/gradle.xml 40 | .idea/libraries 41 | .idea/ 42 | .idea 43 | 44 | # Keystore files 45 | *.jks 46 | 47 | # External native build folder generated in Android Studio 2.2 and later 48 | .externalNativeBuild -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | apply plugin: 'com.neenbedankt.android-apt' 4 | 5 | android { 6 | 7 | compileSdkVersion rootProject.ext.compileSdkVersion 8 | buildToolsVersion rootProject.ext.buildToolsVersion 9 | 10 | productFlavors { 11 | dev { 12 | minSdkVersion rootProject.ext.devMinSdkVersion 13 | } 14 | prod { 15 | minSdkVersion rootProject.ext.minSdkVersion 16 | } 17 | } 18 | 19 | dataBinding { 20 | enabled = true 21 | } 22 | 23 | defaultConfig { 24 | applicationId "iammert.com.instagramtags" 25 | minSdkVersion rootProject.ext.minSdkVersion 26 | targetSdkVersion rootProject.ext.targetSdkVersion 27 | versionCode rootProject.ext.versionCode 28 | versionName rootProject.ext.versionName 29 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 30 | } 31 | buildTypes { 32 | release { 33 | minifyEnabled false 34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 35 | } 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility JavaVersion.VERSION_1_8 40 | targetCompatibility JavaVersion.VERSION_1_8 41 | } 42 | 43 | lintOptions { 44 | disable 'InvalidPackage' 45 | } 46 | 47 | packagingOptions { 48 | exclude 'META-INF/services/javax.annotation.processing.Processor' 49 | exclude 'META-INF/LICENSE.txt' 50 | exclude 'META-INF/NOTICE.txt' 51 | exclude 'META-INF/LICENSE' 52 | } 53 | } 54 | 55 | dependencies { 56 | compile fileTree(dir: 'libs', include: ['*.jar']) 57 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 58 | exclude group: 'com.android.support', module: 'support-annotations' 59 | }) 60 | 61 | testCompile 'junit:junit:4.12' 62 | 63 | //dagger 64 | apt rootProject.ext.daggerCompiler 65 | compile rootProject.ext.daggerGlassfish 66 | compile rootProject.ext.dagger 67 | 68 | //support lib 69 | compile rootProject.ext.supportLibAppCompat 70 | compile rootProject.ext.supportLibDesign 71 | compile rootProject.ext.supportLibCardView 72 | 73 | //rxjava 74 | compile rootProject.ext.rxjava 75 | compile rootProject.ext.retrofitRxAdapter 76 | 77 | //rxAndroid & rxBinding 78 | compile rootProject.ext.rxAndroid 79 | compile rootProject.ext.rxBinding 80 | 81 | //retrofit 82 | compile rootProject.ext.retrofit 83 | compile rootProject.ext.gsonConverter 84 | compile rootProject.ext.retrofitLogging 85 | 86 | //ui 87 | compile rootProject.ext.picasso 88 | compile rootProject.ext.materialDialog 89 | compile rootProject.ext.circleImage 90 | 91 | //Parceler 92 | compile rootProject.ext.parcelerApi 93 | apt rootProject.ext.parceler 94 | } 95 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/mertsimsek/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/iammert/com/instagramtags/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags; 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 | * Instrumentation 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("iammert.com.instagramtags", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/InstagramTagsApp.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags; 2 | 3 | import android.app.Application; 4 | 5 | import iammert.com.instagramtags.di.app.AppComponent; 6 | import iammert.com.instagramtags.di.app.AppModule; 7 | import iammert.com.instagramtags.di.app.DaggerAppComponent; 8 | import iammert.com.instagramtags.di.app.NetworkModule; 9 | 10 | /** 11 | * Created by mertsimsek on 13/01/17. 12 | */ 13 | 14 | public class InstagramTagsApp extends Application { 15 | 16 | AppComponent appComponent; 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | initializeInjector(); 22 | } 23 | 24 | private void initializeInjector() { 25 | appComponent = DaggerAppComponent.builder() 26 | .appModule(new AppModule(this)) 27 | .networkModule(new NetworkModule()) 28 | .build(); 29 | } 30 | 31 | public AppComponent getAppComponent() { 32 | return appComponent; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/Activity.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di; 2 | 3 | import java.lang.annotation.Retention; 4 | 5 | import javax.inject.Scope; 6 | 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Created by mertsimsek on 13/01/17. 11 | */ 12 | 13 | @Scope 14 | @Retention(RUNTIME) 15 | public @interface Activity { 16 | } -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/Fragment.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di; 2 | 3 | import java.lang.annotation.Retention; 4 | 5 | import javax.inject.Scope; 6 | 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | /** 10 | * Created by mertsimsek on 13/01/17. 11 | */ 12 | 13 | @Scope 14 | @Retention(RUNTIME) 15 | public @interface Fragment { 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/app/AppComponent.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.app; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import javax.inject.Singleton; 8 | 9 | import dagger.Component; 10 | import iammert.com.instagramtags.model.api.ApiSource; 11 | import iammert.com.instagramtags.util.RxBus; 12 | 13 | /** 14 | * Created by mertsimsek on 13/01/17. 15 | */ 16 | 17 | @Singleton 18 | @Component(modules = {AppModule.class, NetworkModule.class}) 19 | public interface AppComponent { 20 | ApiSource apiSource(); 21 | SharedPreferences sharedPreferences(); 22 | Gson gson(); 23 | RxBus bus(); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/app/AppModule.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.app; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.preference.PreferenceManager; 7 | 8 | import com.google.gson.Gson; 9 | 10 | import javax.inject.Singleton; 11 | 12 | import dagger.Module; 13 | import dagger.Provides; 14 | import iammert.com.instagramtags.util.RxBus; 15 | 16 | /** 17 | * Created by mertsimsek on 13/01/17. 18 | */ 19 | 20 | @Module 21 | public class AppModule { 22 | 23 | Application app; 24 | 25 | public AppModule(Application app) { 26 | this.app = app; 27 | } 28 | 29 | @Provides 30 | @Singleton 31 | Application provideApplication() { 32 | return app; 33 | } 34 | 35 | @Provides 36 | @Singleton 37 | Context provideContext() { 38 | return app.getApplicationContext(); 39 | } 40 | 41 | @Provides 42 | @Singleton 43 | Gson provideGson() { 44 | return new Gson(); 45 | } 46 | 47 | @Provides 48 | @Singleton 49 | RxBus provideBus() { 50 | return RxBus.getInstance(); 51 | } 52 | 53 | @Provides 54 | @Singleton 55 | SharedPreferences providesSharedPreferences(Application application) { 56 | return PreferenceManager.getDefaultSharedPreferences(application); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/app/NetworkModule.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.app; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import javax.inject.Singleton; 8 | 9 | import dagger.Module; 10 | import dagger.Provides; 11 | import iammert.com.instagramtags.model.api.ApiSource; 12 | import iammert.com.instagramtags.model.api.ApiSourceImpl; 13 | import iammert.com.instagramtags.util.Constants; 14 | import okhttp3.OkHttpClient; 15 | import okhttp3.Protocol; 16 | import okhttp3.logging.HttpLoggingInterceptor; 17 | import retrofit2.Retrofit; 18 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 19 | import retrofit2.converter.gson.GsonConverterFactory; 20 | 21 | /** 22 | * Created by mertsimsek on 13/01/17. 23 | */ 24 | 25 | @Module 26 | public class NetworkModule { 27 | 28 | @Provides 29 | @Singleton 30 | HttpLoggingInterceptor provideHttpInterceptor() { 31 | HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); 32 | interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 33 | return interceptor; 34 | } 35 | 36 | @Provides 37 | @Singleton 38 | OkHttpClient provideOkHttpClient(HttpLoggingInterceptor httpLoggingInterceptor) { 39 | List protocols = new ArrayList<>(); 40 | protocols.add(Protocol.HTTP_1_1); 41 | OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder(); 42 | okHttpClient.connectTimeout(1, TimeUnit.MINUTES); 43 | okHttpClient.readTimeout(1, TimeUnit.MINUTES); 44 | okHttpClient.protocols(protocols); 45 | okHttpClient.addInterceptor(httpLoggingInterceptor); 46 | return okHttpClient.build(); 47 | } 48 | 49 | @Provides 50 | @Singleton 51 | Retrofit provideRetrofit(OkHttpClient okHttpClient) { 52 | Retrofit retrofit = new Retrofit.Builder() 53 | .baseUrl(Constants.BASE_URL) 54 | .addConverterFactory(GsonConverterFactory.create()) 55 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 56 | .client(okHttpClient) 57 | .build(); 58 | return retrofit; 59 | } 60 | 61 | @Provides 62 | @Singleton 63 | ApiSource provideApiSource(Retrofit retrofit) { 64 | return new ApiSourceImpl(retrofit); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/main/MainComponent.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.main; 2 | 3 | import dagger.Component; 4 | import iammert.com.instagramtags.di.Activity; 5 | import iammert.com.instagramtags.di.app.AppComponent; 6 | import iammert.com.instagramtags.view.main.MainActivity; 7 | 8 | /** 9 | * Created by mertsimsek on 13/01/17. 10 | */ 11 | @Activity 12 | @Component(dependencies = AppComponent.class, modules = MainModule.class) 13 | public interface MainComponent { 14 | void inject(MainActivity mainActivity); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/main/MainModule.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.main; 2 | 3 | import dagger.Module; 4 | import dagger.Provides; 5 | import iammert.com.instagramtags.di.Activity; 6 | import iammert.com.instagramtags.util.RxBus; 7 | import iammert.com.instagramtags.viewmodel.main.MainViewModel; 8 | 9 | /** 10 | * Created by mertsimsek on 13/01/17. 11 | */ 12 | @Module 13 | public class MainModule { 14 | 15 | MainViewModel.MainListener mainListener; 16 | 17 | public MainModule(MainViewModel.MainListener mainListener) { 18 | this.mainListener = mainListener; 19 | } 20 | 21 | @Provides 22 | @Activity 23 | MainViewModel provideMainViewModel(RxBus rxBus){ 24 | return new MainViewModel(rxBus, mainListener); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/medialist/MediaListComponent.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.medialist; 2 | 3 | import dagger.Component; 4 | import iammert.com.instagramtags.di.Fragment; 5 | import iammert.com.instagramtags.di.app.AppComponent; 6 | import iammert.com.instagramtags.view.medialist.MediaListFragment; 7 | 8 | /** 9 | * Created by mertsimsek on 14/01/17. 10 | */ 11 | @Fragment 12 | @Component(dependencies = AppComponent.class, modules = MediaListModule.class) 13 | public interface MediaListComponent { 14 | void inject(MediaListFragment fragment); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/medialist/MediaListModule.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.medialist; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | import iammert.com.instagramtags.di.Fragment; 10 | import iammert.com.instagramtags.domain.medialist.MediaListUsecase; 11 | import iammert.com.instagramtags.domain.medialist.MediaListUsecaseImpl; 12 | import iammert.com.instagramtags.model.api.ApiSource; 13 | import iammert.com.instagramtags.model.api.entity.Tag; 14 | import iammert.com.instagramtags.model.preferences.PreferencesHelper; 15 | import iammert.com.instagramtags.model.preferences.PreferencesHelperImpl; 16 | import iammert.com.instagramtags.view.medialist.MediaListAdapter; 17 | import iammert.com.instagramtags.viewmodel.medialist.MediaListViewModel; 18 | 19 | /** 20 | * Created by mertsimsek on 14/01/17. 21 | */ 22 | @Module 23 | public class MediaListModule { 24 | 25 | MediaListViewModel.MediaListListener listener; 26 | Tag tag; 27 | 28 | public MediaListModule(MediaListViewModel.MediaListListener listener, Tag tag) { 29 | this.listener = listener; 30 | this.tag = tag; 31 | } 32 | 33 | @Provides 34 | @Fragment 35 | PreferencesHelper provideTokenPreferencesHelper(SharedPreferences preferences, Gson gson){ 36 | return new PreferencesHelperImpl<>(preferences, gson); 37 | } 38 | 39 | @Provides 40 | @Fragment 41 | MediaListUsecase provideMediaListUsecase(ApiSource apiSource, PreferencesHelper tokenPreferencesHelper){ 42 | return new MediaListUsecaseImpl(apiSource, tokenPreferencesHelper); 43 | } 44 | 45 | @Provides 46 | @Fragment 47 | MediaListViewModel provideMediaListViewModel(MediaListUsecase mediaListUsecase){ 48 | return new MediaListViewModel(mediaListUsecase, listener, tag); 49 | } 50 | 51 | @Provides 52 | @Fragment 53 | MediaListAdapter provideMediaListAdapter(){ 54 | return new MediaListAdapter(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/searchtag/SearchTagComponent.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.searchtag; 2 | 3 | import dagger.Component; 4 | import iammert.com.instagramtags.di.Fragment; 5 | import iammert.com.instagramtags.di.app.AppComponent; 6 | import iammert.com.instagramtags.view.searchtag.SearchTagFragment; 7 | 8 | /** 9 | * Created by mertsimsek on 13/01/17. 10 | */ 11 | @Fragment 12 | @Component(dependencies = AppComponent.class, modules = SearchTagModule.class) 13 | public interface SearchTagComponent { 14 | void inject(SearchTagFragment searchTagFragment); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/searchtag/SearchTagModule.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.searchtag; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | import iammert.com.instagramtags.di.Fragment; 10 | import iammert.com.instagramtags.domain.searchtag.SearchTagUsecase; 11 | import iammert.com.instagramtags.domain.searchtag.SearchTagUsecaseImpl; 12 | import iammert.com.instagramtags.model.api.ApiSource; 13 | import iammert.com.instagramtags.model.preferences.PreferencesHelper; 14 | import iammert.com.instagramtags.model.preferences.PreferencesHelperImpl; 15 | import iammert.com.instagramtags.util.RxBus; 16 | import iammert.com.instagramtags.view.searchtag.SearchTagAdapter; 17 | import iammert.com.instagramtags.viewmodel.searchtag.SearchTagViewModel; 18 | 19 | /** 20 | * Created by mertsimsek on 13/01/17. 21 | */ 22 | @Module 23 | public class SearchTagModule { 24 | 25 | SearchTagViewModel.SearchTagListener listener; 26 | 27 | public SearchTagModule(SearchTagViewModel.SearchTagListener listener) { 28 | this.listener = listener; 29 | } 30 | 31 | @Provides 32 | @Fragment 33 | PreferencesHelper provideTokenPreferencesHelper(SharedPreferences sharedPreferences, Gson gson){ 34 | return new PreferencesHelperImpl<>(sharedPreferences, gson); 35 | } 36 | 37 | @Provides 38 | @Fragment 39 | SearchTagUsecase provideSearchTagUsecase(PreferencesHelper tokenHelper, ApiSource apiSource){ 40 | return new SearchTagUsecaseImpl(tokenHelper, apiSource); 41 | } 42 | 43 | @Provides 44 | @Fragment 45 | SearchTagViewModel provideSearchTagViewModel(SearchTagUsecase usecase){ 46 | return new SearchTagViewModel(usecase, listener); 47 | } 48 | 49 | @Provides 50 | @Fragment 51 | SearchTagAdapter provideSearchTagAdapter(RxBus rxBus){ 52 | return new SearchTagAdapter(rxBus); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/splash/SplashComponent.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.splash; 2 | 3 | import dagger.Component; 4 | import iammert.com.instagramtags.di.Activity; 5 | import iammert.com.instagramtags.di.app.AppComponent; 6 | import iammert.com.instagramtags.view.splash.SplashActivity; 7 | 8 | /** 9 | * Created by mertsimsek on 13/01/17. 10 | */ 11 | 12 | @Activity 13 | @Component(dependencies = AppComponent.class, modules = SplashModule.class) 14 | public interface SplashComponent { 15 | void inject(SplashActivity activity); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/di/splash/SplashModule.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.di.splash; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | import iammert.com.instagramtags.di.Activity; 10 | import iammert.com.instagramtags.domain.splash.SplashUsecase; 11 | import iammert.com.instagramtags.domain.splash.SplashUsecaseImpl; 12 | import iammert.com.instagramtags.model.preferences.PreferencesHelper; 13 | import iammert.com.instagramtags.model.preferences.PreferencesHelperImpl; 14 | import iammert.com.instagramtags.viewmodel.splash.SplashViewModel; 15 | 16 | /** 17 | * Created by mertsimsek on 13/01/17. 18 | */ 19 | 20 | @Module 21 | public class SplashModule { 22 | 23 | SplashViewModel.SplashListener listener; 24 | 25 | public SplashModule(SplashViewModel.SplashListener listener) { 26 | this.listener = listener; 27 | } 28 | 29 | @Provides 30 | @Activity 31 | PreferencesHelper provideTokenPreferencesHelper(SharedPreferences sharedPreferences, Gson gson){ 32 | return new PreferencesHelperImpl<>(sharedPreferences, gson); 33 | } 34 | 35 | @Provides 36 | @Activity 37 | SplashUsecase provideSplashUsecase(PreferencesHelper tokenPreferencesHelper){ 38 | return new SplashUsecaseImpl(tokenPreferencesHelper); 39 | } 40 | 41 | @Provides 42 | @Activity 43 | SplashViewModel provideSplashViewModel(SplashUsecase splashUsecase){ 44 | return new SplashViewModel(splashUsecase, listener); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/domain/medialist/MediaListUsecase.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.domain.medialist; 2 | 3 | import iammert.com.instagramtags.model.api.entity.MediaListResponse; 4 | import rx.Observable; 5 | 6 | /** 7 | * Created by mertsimsek on 13/01/17. 8 | */ 9 | 10 | public interface MediaListUsecase { 11 | 12 | Observable searchMedia(String tag); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/domain/medialist/MediaListUsecaseImpl.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.domain.medialist; 2 | 3 | import javax.inject.Inject; 4 | 5 | import iammert.com.instagramtags.model.api.ApiSource; 6 | import iammert.com.instagramtags.model.api.entity.MediaListResponse; 7 | import iammert.com.instagramtags.model.preferences.PreferencesHelper; 8 | import iammert.com.instagramtags.util.Constants; 9 | import iammert.com.instagramtags.util.RxTransformer; 10 | import rx.Observable; 11 | 12 | /** 13 | * Created by mertsimsek on 13/01/17. 14 | */ 15 | 16 | public class MediaListUsecaseImpl implements MediaListUsecase{ 17 | 18 | private ApiSource apiSource; 19 | private PreferencesHelper tokenPreferencesHelper; 20 | 21 | @Inject 22 | public MediaListUsecaseImpl(ApiSource apiSource, PreferencesHelper tokenPreferencesHelper) { 23 | this.apiSource = apiSource; 24 | this.tokenPreferencesHelper = tokenPreferencesHelper; 25 | } 26 | 27 | @Override 28 | public Observable searchMedia(String tag) { 29 | return tokenPreferencesHelper.get(Constants.SHARED_KEY_TOKEN, String.class) 30 | .flatMap(s -> apiSource.searchMedia(tag, s)) 31 | .compose(RxTransformer.applyIOSchedulers()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/domain/searchtag/SearchTagUsecase.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.domain.searchtag; 2 | 3 | import iammert.com.instagramtags.model.api.entity.TagSearchResponse; 4 | import rx.Observable; 5 | 6 | /** 7 | * Created by mertsimsek on 13/01/17. 8 | */ 9 | 10 | public interface SearchTagUsecase { 11 | 12 | Observable searchTag(String query); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/domain/searchtag/SearchTagUsecaseImpl.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.domain.searchtag; 2 | 3 | import javax.inject.Inject; 4 | 5 | import iammert.com.instagramtags.model.api.ApiSource; 6 | import iammert.com.instagramtags.model.api.entity.TagSearchResponse; 7 | import iammert.com.instagramtags.model.preferences.PreferencesHelper; 8 | import iammert.com.instagramtags.util.Constants; 9 | import iammert.com.instagramtags.util.RxTransformer; 10 | import rx.Observable; 11 | 12 | /** 13 | * Created by mertsimsek on 13/01/17. 14 | */ 15 | 16 | public class SearchTagUsecaseImpl implements SearchTagUsecase { 17 | 18 | private PreferencesHelper tokenHelper; 19 | private ApiSource apiSource; 20 | 21 | @Inject 22 | public SearchTagUsecaseImpl(PreferencesHelper tokenHelper, ApiSource apiSource) { 23 | this.tokenHelper = tokenHelper; 24 | this.apiSource = apiSource; 25 | } 26 | 27 | @Override 28 | public Observable searchTag(String query) { 29 | return tokenHelper.get(Constants.SHARED_KEY_TOKEN, String.class) 30 | .flatMap(s -> apiSource.searchTag(query, s)) 31 | .compose(RxTransformer.applyIOSchedulers()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/domain/splash/SplashUsecase.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.domain.splash; 2 | 3 | 4 | import rx.Observable; 5 | 6 | /** 7 | * Created by mertsimsek on 13/01/17. 8 | */ 9 | 10 | public interface SplashUsecase { 11 | 12 | Observable saveToken(String token); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/domain/splash/SplashUsecaseImpl.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.domain.splash; 2 | 3 | import javax.inject.Inject; 4 | 5 | import iammert.com.instagramtags.model.preferences.PreferencesHelper; 6 | import iammert.com.instagramtags.util.Constants; 7 | import iammert.com.instagramtags.util.RxTransformer; 8 | import rx.Observable; 9 | 10 | /** 11 | * Created by mertsimsek on 13/01/17. 12 | */ 13 | 14 | public class SplashUsecaseImpl implements SplashUsecase{ 15 | 16 | PreferencesHelper tokenHelper; 17 | 18 | @Inject 19 | public SplashUsecaseImpl(PreferencesHelper tokenHelper) { 20 | this.tokenHelper = tokenHelper; 21 | } 22 | 23 | @Override 24 | public Observable saveToken(String token) { 25 | return tokenHelper.save(Constants.SHARED_KEY_TOKEN, token) 26 | .compose(RxTransformer.applyComputationSchedulers()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/ApiSource.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api; 2 | 3 | import iammert.com.instagramtags.model.api.entity.MediaListResponse; 4 | import iammert.com.instagramtags.model.api.entity.TagSearchResponse; 5 | import rx.Observable; 6 | 7 | /** 8 | * Created by mertsimsek on 13/01/17. 9 | */ 10 | 11 | public interface ApiSource { 12 | 13 | Observable searchTag(String query, String token); 14 | 15 | Observable searchMedia(String tag, String token); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/ApiSourceImpl.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api; 2 | 3 | import iammert.com.instagramtags.model.api.entity.MediaListResponse; 4 | import iammert.com.instagramtags.model.api.entity.TagSearchResponse; 5 | import retrofit2.Retrofit; 6 | import rx.Observable; 7 | 8 | /** 9 | * Created by mertsimsek on 13/01/17. 10 | */ 11 | 12 | public class ApiSourceImpl implements ApiSource{ 13 | 14 | RetrofitInterface retrofitInterface; 15 | 16 | public ApiSourceImpl(Retrofit retrofit) { 17 | retrofitInterface = retrofit.create(RetrofitInterface.class); 18 | } 19 | 20 | @Override 21 | public Observable searchTag(String query, String token) { 22 | return retrofitInterface.searchTag(query, token); 23 | } 24 | 25 | @Override 26 | public Observable searchMedia(String tag, String token) { 27 | return retrofitInterface.searchMediaWithTag(tag, token); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/RetrofitInterface.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api; 2 | 3 | import iammert.com.instagramtags.model.api.entity.MediaListResponse; 4 | import iammert.com.instagramtags.model.api.entity.TagSearchResponse; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Query; 8 | import rx.Observable; 9 | 10 | /** 11 | * Created by mertsimsek on 13/01/17. 12 | */ 13 | 14 | public interface RetrofitInterface { 15 | 16 | @GET("tags/search") 17 | Observable searchTag(@Query("q") String query, 18 | @Query("access_token") String token); 19 | 20 | @GET("tags/{tag_name}/media/recent") 21 | Observable searchMediaWithTag(@Path("tag_name") String tag, 22 | @Query("access_token") String token); 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/Comment.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import org.parceler.Parcel; 4 | 5 | /** 6 | * Created by mertsimsek on 14/01/17. 7 | */ 8 | @Parcel 9 | public class Comment { 10 | public int count; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/Image.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import org.parceler.Parcel; 4 | 5 | /** 6 | * Created by mertsimsek on 14/01/17. 7 | */ 8 | @Parcel 9 | public class Image { 10 | public String url; 11 | public int width; 12 | public int height; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/ImageWrapper.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import org.parceler.Parcel; 6 | 7 | /** 8 | * Created by mertsimsek on 14/01/17. 9 | */ 10 | @Parcel 11 | public class ImageWrapper { 12 | @SerializedName("low_resolution") 13 | public Image lowResolution; 14 | public Image thumbnail; 15 | @SerializedName("standard_resolution") 16 | public Image standartResolution; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/Like.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import org.parceler.Parcel; 4 | 5 | /** 6 | * Created by mertsimsek on 14/01/17. 7 | */ 8 | @Parcel 9 | public class Like { 10 | public int count; 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/Media.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import org.parceler.Parcel; 4 | 5 | /** 6 | * Created by mertsimsek on 14/01/17. 7 | */ 8 | @Parcel 9 | public class Media { 10 | public String id; 11 | public String type; 12 | public String filter; 13 | public Comment comments; 14 | public Like likes; 15 | public User user; 16 | public ImageWrapper images; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/MediaListResponse.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import org.parceler.Parcel; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by mertsimsek on 14/01/17. 9 | */ 10 | @Parcel 11 | public class MediaListResponse { 12 | public List data; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/Tag.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import org.parceler.Parcel; 6 | 7 | /** 8 | * Created by mertsimsek on 13/01/17. 9 | */ 10 | @Parcel 11 | public class Tag { 12 | public String name; 13 | @SerializedName("media_count") 14 | public int mediaCount; 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/TagSearchResponse.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import org.parceler.Parcel; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by mertsimsek on 13/01/17. 9 | */ 10 | @Parcel 11 | public class TagSearchResponse { 12 | public List data; 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/api/entity/User.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.api.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import org.parceler.Parcel; 6 | 7 | /** 8 | * Created by mertsimsek on 14/01/17. 9 | */ 10 | @Parcel 11 | public class User { 12 | public String id; 13 | public String username; 14 | @SerializedName("profile_picture") 15 | public String profilePicture; 16 | @SerializedName("full_name") 17 | public String fullName; 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/preferences/PreferencesHelper.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.preferences; 2 | 3 | import rx.Observable; 4 | 5 | /** 6 | * Created by mertsimsek on 13/01/17. 7 | */ 8 | 9 | public interface PreferencesHelper { 10 | 11 | Observable save(String key, T value); 12 | 13 | Observable get(String key, Class generic); 14 | 15 | Observable clear(); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/model/preferences/PreferencesHelperImpl.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.model.preferences; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import javax.inject.Inject; 8 | 9 | import rx.Observable; 10 | 11 | /** 12 | * Created by mertsimsek on 13/01/17. 13 | */ 14 | 15 | public class PreferencesHelperImpl implements PreferencesHelper { 16 | 17 | SharedPreferences sharedPreferences; 18 | Gson gson; 19 | 20 | @Inject 21 | public PreferencesHelperImpl(SharedPreferences sharedPreferences, Gson gson) { 22 | this.sharedPreferences = sharedPreferences; 23 | this.gson = gson; 24 | } 25 | 26 | @Override 27 | public Observable save(String key, T value) { 28 | return Observable.create(subscriber -> { 29 | sharedPreferences.edit().putString(key, gson.toJson(value)).apply(); 30 | subscriber.onNext(value); 31 | subscriber.onCompleted(); 32 | }); 33 | } 34 | 35 | @Override 36 | public Observable get(String key, Class generic) { 37 | return Observable.create(subscriber -> { 38 | String json = sharedPreferences.getString(key, ""); 39 | T myClass = gson.fromJson(json, generic); 40 | subscriber.onNext(myClass); 41 | subscriber.onCompleted(); 42 | }); 43 | } 44 | 45 | @Override 46 | public Observable clear() { 47 | return Observable.create(subscriber -> { 48 | sharedPreferences.edit().clear().commit(); 49 | subscriber.onNext(true); 50 | subscriber.onCompleted(); 51 | }); 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/util/Constants.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.util; 2 | 3 | /** 4 | * Created by mertsimsek on 13/01/17. 5 | */ 6 | 7 | public class Constants { 8 | 9 | /** 10 | * Base url for api call 11 | */ 12 | public static final String BASE_URL = "https://api.instagram.com/v1/"; 13 | 14 | /** 15 | * Client info, scope info, response type 16 | */ 17 | public static final String CLIENT_ID = ""; 18 | public static final String REDIRECT_URI = ""; 19 | public static final String RESPONSE_TYPE = "token"; 20 | public static final String SCOPE = "public_content"; 21 | 22 | /** 23 | * Static login url 24 | */ 25 | public static final String LOGIN_URL = "https://www.instagram.com/oauth/authorize/?client_id=" + 26 | CLIENT_ID + "&redirect_uri=" + REDIRECT_URI + 27 | "&response_type=" + RESPONSE_TYPE + "&scope=" + SCOPE; 28 | 29 | /** 30 | * Intent Keys 31 | */ 32 | public static final String KEY_INTENT_TOKEN = "token"; 33 | 34 | /** 35 | * Shared Preferences Keys 36 | */ 37 | public static final String SHARED_KEY_TOKEN = "shared_key_token"; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/util/DialogBuilder.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.util; 2 | 3 | import android.content.Context; 4 | 5 | import com.afollestad.materialdialogs.MaterialDialog; 6 | 7 | import iammert.com.instagramtags.R; 8 | 9 | /** 10 | * Created by mertsimsek on 15/01/17. 11 | */ 12 | 13 | public class DialogBuilder { 14 | 15 | public static MaterialDialog.Builder progressDialog(Context context, int title, int content) { 16 | return new MaterialDialog.Builder(context) 17 | .title(title) 18 | .content(content) 19 | .canceledOnTouchOutside(false) 20 | .progress(true, 0); 21 | } 22 | 23 | public static MaterialDialog.Builder infoDialog(Context context, int title, int content) { 24 | return new MaterialDialog.Builder(context) 25 | .title(title) 26 | .content(content) 27 | .positiveText(R.string.dialog_action_ok) 28 | .positiveColorRes(R.color.colorPrimary); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/util/ImageDataBinding.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.util; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.graphics.drawable.Drawable; 5 | import android.widget.ImageView; 6 | 7 | import com.squareup.picasso.Picasso; 8 | 9 | import de.hdodenhof.circleimageview.CircleImageView; 10 | 11 | /** 12 | * Created by mertsimsek on 14/01/17. 13 | */ 14 | 15 | public class ImageDataBinding { 16 | 17 | @BindingAdapter({"bind:imageUrl", "bind:placeHolder"}) 18 | public static void loadImage(ImageView view, String url, Drawable placeHolder) { 19 | if (url != null && !url.equals("")) 20 | Picasso.with(view.getContext()).load(url).placeholder(placeHolder).resize(500, 500).centerCrop().into(view); 21 | } 22 | 23 | @BindingAdapter({"bind:circleLibImageUrl", "bind:circleLibPlaceHolder"}) 24 | public static void loadCircleImage(CircleImageView view, String url, Drawable placeHolder) { 25 | if (url != null && !url.equals("")) 26 | Picasso.with(view.getContext()).load(url).placeholder(placeHolder).resize(500, 500).centerCrop().into(view); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/util/RxBus.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.util; 2 | 3 | import rx.Observable; 4 | import rx.subjects.PublishSubject; 5 | import rx.subjects.SerializedSubject; 6 | import rx.subjects.Subject; 7 | 8 | /** 9 | * Created by mertsimsek on 13/01/17. 10 | */ 11 | 12 | public class RxBus { 13 | 14 | private static RxBus instance = null; 15 | private final Subject _bus = new SerializedSubject<>(PublishSubject.create()); 16 | 17 | private RxBus() { 18 | } 19 | 20 | public static RxBus getInstance() { 21 | if (instance == null) 22 | instance = new RxBus(); 23 | return instance; 24 | } 25 | 26 | public void send(Object o) { 27 | _bus.onNext(o); 28 | } 29 | 30 | public Observable toObserverable() { 31 | return _bus; 32 | } 33 | 34 | public boolean hasObservers() { 35 | return _bus.hasObservers(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/util/RxTransformer.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.util; 2 | 3 | import rx.Observable; 4 | import rx.android.schedulers.AndroidSchedulers; 5 | import rx.schedulers.Schedulers; 6 | 7 | /** 8 | * Created by mertsimsek on 13/01/17. 9 | */ 10 | 11 | public class RxTransformer { 12 | 13 | public static Observable.Transformer applyIOSchedulers() { 14 | return observable -> observable.subscribeOn(Schedulers.io()) 15 | .unsubscribeOn(Schedulers.io()) 16 | .observeOn(AndroidSchedulers.mainThread()); 17 | } 18 | 19 | public static Observable.Transformer applyComputationSchedulers() { 20 | return observable -> observable.subscribeOn(Schedulers.newThread()) 21 | .observeOn(AndroidSchedulers.mainThread()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/util/SimpleTextWatcher.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.util; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | /** 7 | * Created by mertsimsek on 13/01/17. 8 | */ 9 | 10 | public abstract class SimpleTextWatcher implements TextWatcher { 11 | @Override 12 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 13 | } 14 | 15 | @Override 16 | public void afterTextChanged(Editable s) { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/login/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.login; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.webkit.WebView; 8 | import android.webkit.WebViewClient; 9 | 10 | import com.afollestad.materialdialogs.MaterialDialog; 11 | 12 | import iammert.com.instagramtags.R; 13 | import iammert.com.instagramtags.util.Constants; 14 | import iammert.com.instagramtags.util.DialogBuilder; 15 | 16 | /** 17 | * Created by mertsimsek on 13/01/17. 18 | */ 19 | 20 | public class LoginActivity extends AppCompatActivity { 21 | 22 | MaterialDialog webviewProgress; 23 | 24 | @Override 25 | protected void onCreate(@Nullable Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setContentView(R.layout.activity_login); 28 | 29 | WebView webView = (WebView) findViewById(R.id.webview); 30 | webView.setWebViewClient(client); 31 | webView.getSettings().setJavaScriptEnabled(true); 32 | webView.getSettings().setDomStorageEnabled(true); 33 | webView.loadUrl(Constants.LOGIN_URL); 34 | 35 | webviewProgress = DialogBuilder 36 | .progressDialog(this, 37 | R.string.dialog_loading_title, 38 | R.string.dialog_loading_content) 39 | .show(); 40 | } 41 | 42 | private WebViewClient client = new WebViewClient() { 43 | @Override 44 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 45 | if (url.contains("access_token")) { 46 | Intent intent = new Intent(); 47 | intent.putExtra(Constants.KEY_INTENT_TOKEN, getAccessTokenFromURL(url)); 48 | setResult(RESULT_OK, intent); 49 | finish(); 50 | } 51 | return false; 52 | } 53 | 54 | @Override 55 | public void onPageFinished(WebView view, String url) { 56 | super.onPageFinished(view, url); 57 | if(webviewProgress != null && webviewProgress.isShowing()) 58 | webviewProgress.dismiss(); 59 | } 60 | }; 61 | 62 | @Override 63 | protected void onDestroy() { 64 | super.onDestroy(); 65 | if(webviewProgress != null && webviewProgress.isShowing()) 66 | webviewProgress.dismiss(); 67 | } 68 | 69 | private String getAccessTokenFromURL(String url) { 70 | int index = url.indexOf("access_token="); 71 | return url.substring(index + "access_token=".length()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.main; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.databinding.DataBindingUtil; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.os.Bundle; 8 | import android.util.Log; 9 | 10 | import javax.inject.Inject; 11 | 12 | import iammert.com.instagramtags.InstagramTagsApp; 13 | import iammert.com.instagramtags.R; 14 | import iammert.com.instagramtags.databinding.ActivityMainBinding; 15 | import iammert.com.instagramtags.di.main.DaggerMainComponent; 16 | import iammert.com.instagramtags.di.main.MainModule; 17 | import iammert.com.instagramtags.model.api.entity.Tag; 18 | import iammert.com.instagramtags.view.medialist.MediaListActivity; 19 | import iammert.com.instagramtags.view.medialist.MediaListFragment; 20 | import iammert.com.instagramtags.view.searchtag.SearchTagFragment; 21 | import iammert.com.instagramtags.viewmodel.main.MainViewModel; 22 | 23 | public class MainActivity extends AppCompatActivity implements MainViewModel.MainListener { 24 | 25 | ActivityMainBinding binding; 26 | 27 | @Inject 28 | MainViewModel viewModel; 29 | 30 | public static Intent newIntent(Context context) { 31 | Intent intent = new Intent(context, MainActivity.class); 32 | Bundle bundle = new Bundle(); 33 | intent.putExtras(bundle); 34 | return intent; 35 | } 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 41 | initializeInjectors(); 42 | initializeViews(savedInstanceState); 43 | binding.setViewModel(viewModel); 44 | } 45 | 46 | private void initializeViews(Bundle savedInstance) { 47 | if (findViewById(R.id.containerDetail) != null) 48 | viewModel.setTwoPane(true); 49 | 50 | if (savedInstance == null) 51 | getSupportFragmentManager().beginTransaction() 52 | .add(R.id.containerMaster, SearchTagFragment.newInstance()) 53 | .commitAllowingStateLoss(); 54 | } 55 | 56 | private void initializeInjectors() { 57 | DaggerMainComponent.builder() 58 | .appComponent(((InstagramTagsApp) getApplication()).getAppComponent()) 59 | .mainModule(new MainModule(this)) 60 | .build().inject(this); 61 | } 62 | 63 | @Override 64 | protected void onDestroy() { 65 | super.onDestroy(); 66 | viewModel.stop(); 67 | } 68 | 69 | @Override 70 | public void onTagItemClicked(Tag tag) { 71 | if (!viewModel.isTwoPane()) 72 | startActivity(MediaListActivity.newIntent(this, tag)); 73 | else { 74 | if (!isFinishing()) 75 | getSupportFragmentManager().beginTransaction() 76 | .replace(R.id.containerDetail, MediaListFragment.newInstance(tag)) 77 | .commitAllowingStateLoss(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/medialist/MediaListActivity.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.medialist; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | 10 | import org.parceler.Parcels; 11 | 12 | import iammert.com.instagramtags.R; 13 | import iammert.com.instagramtags.model.api.entity.Tag; 14 | 15 | /** 16 | * Created by mertsimsek on 13/01/17. 17 | */ 18 | public class MediaListActivity extends AppCompatActivity { 19 | 20 | public static final String KEY_TAG = "key_tag"; 21 | 22 | public static Intent newIntent(Context context, Tag tag) { 23 | Intent intent = new Intent(context, MediaListActivity.class); 24 | Bundle bundle = new Bundle(); 25 | bundle.putParcelable(KEY_TAG, Parcels.wrap(tag)); 26 | intent.putExtras(bundle); 27 | return intent; 28 | } 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_media_list); 34 | 35 | Tag tag = Parcels.unwrap(getIntent().getExtras().getParcelable(KEY_TAG)); 36 | setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); 37 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 38 | setTitle("#" + tag.name); 39 | 40 | if (savedInstanceState == null) 41 | getSupportFragmentManager().beginTransaction() 42 | .add(R.id.containerDetail, MediaListFragment.newInstance(tag)) 43 | .commitAllowingStateLoss(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/medialist/MediaListAdapter.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.medialist; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import iammert.com.instagramtags.databinding.ItemMediaListBinding; 12 | import iammert.com.instagramtags.model.api.entity.Media; 13 | import iammert.com.instagramtags.viewmodel.medialist.MediaListItemViewModel; 14 | 15 | /** 16 | * Created by mertsimsek on 14/01/17. 17 | */ 18 | 19 | public class MediaListAdapter extends RecyclerView.Adapter{ 20 | 21 | private List medias; 22 | 23 | public MediaListAdapter() { 24 | medias = new ArrayList<>(); 25 | } 26 | 27 | public void setMedias(List medias){ 28 | this.medias = medias; 29 | notifyDataSetChanged(); 30 | } 31 | 32 | public List getMedias(){ 33 | return medias; 34 | } 35 | 36 | @Override 37 | public MediaListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 38 | return MediaListViewHolder.create(LayoutInflater.from(parent.getContext()), parent); 39 | } 40 | 41 | @Override 42 | public void onBindViewHolder(MediaListViewHolder holder, int position) { 43 | holder.bind(new MediaListItemViewModel(holder.itemView.getContext(), medias.get(position))); 44 | } 45 | 46 | @Override 47 | public int getItemCount() { 48 | return medias.size(); 49 | } 50 | 51 | static class MediaListViewHolder extends RecyclerView.ViewHolder{ 52 | 53 | static MediaListAdapter.MediaListViewHolder create(LayoutInflater inflater, ViewGroup parent) { 54 | ItemMediaListBinding binding = ItemMediaListBinding.inflate(inflater, parent, false); 55 | return new MediaListAdapter.MediaListViewHolder(binding); 56 | } 57 | 58 | private ItemMediaListBinding binding; 59 | 60 | public MediaListViewHolder(ItemMediaListBinding binding) { 61 | super(binding.getRoot()); 62 | this.binding = binding; 63 | } 64 | 65 | public void bind(MediaListItemViewModel viewModel){ 66 | binding.setViewModel(viewModel); 67 | binding.executePendingBindings(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/medialist/MediaListFragment.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.medialist; 2 | 3 | import android.content.res.Configuration; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.support.v7.widget.GridLayoutManager; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import org.parceler.Parcels; 14 | 15 | import javax.inject.Inject; 16 | 17 | import iammert.com.instagramtags.InstagramTagsApp; 18 | import iammert.com.instagramtags.R; 19 | import iammert.com.instagramtags.databinding.FragmentMediaListBinding; 20 | import iammert.com.instagramtags.di.medialist.DaggerMediaListComponent; 21 | import iammert.com.instagramtags.di.medialist.MediaListModule; 22 | import iammert.com.instagramtags.model.api.entity.MediaListResponse; 23 | import iammert.com.instagramtags.model.api.entity.Tag; 24 | import iammert.com.instagramtags.util.DialogBuilder; 25 | import iammert.com.instagramtags.viewmodel.medialist.MediaListViewModel; 26 | 27 | /** 28 | * Created by mertsimsek on 13/01/17. 29 | */ 30 | 31 | public class MediaListFragment extends Fragment implements MediaListViewModel.MediaListListener { 32 | 33 | public static final String KEY_TAG = "key_tag"; 34 | public static final String KEY_STATE_LIST = "key_state_list"; 35 | 36 | FragmentMediaListBinding binding; 37 | 38 | @Inject 39 | MediaListViewModel viewModel; 40 | 41 | @Inject 42 | MediaListAdapter adapter; 43 | 44 | public static MediaListFragment newInstance(Tag tag) { 45 | Bundle args = new Bundle(); 46 | args.putParcelable(KEY_TAG, Parcels.wrap(tag)); 47 | MediaListFragment fragment = new MediaListFragment(); 48 | fragment.setArguments(args); 49 | return fragment; 50 | } 51 | 52 | @Nullable 53 | @Override 54 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 55 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_media_list, container, false); 56 | initializeInjectors(); 57 | initializeViews(); 58 | binding.setViewModel(viewModel); 59 | 60 | if (savedInstanceState == null) 61 | viewModel.loadMedias(); 62 | else 63 | adapter.setMedias(Parcels.unwrap(savedInstanceState.getParcelable(KEY_STATE_LIST))); 64 | 65 | return binding.getRoot(); 66 | } 67 | 68 | private void initializeViews() { 69 | int displayMode = getResources().getConfiguration().orientation; 70 | 71 | GridLayoutManager gridLayoutManager; 72 | if (displayMode == Configuration.ORIENTATION_PORTRAIT) 73 | gridLayoutManager = new GridLayoutManager(getActivity(), 2); 74 | else 75 | gridLayoutManager = new GridLayoutManager(getActivity(), 3); 76 | 77 | binding.recyclerView.setLayoutManager(gridLayoutManager); 78 | binding.recyclerView.setAdapter(adapter); 79 | } 80 | 81 | private void initializeInjectors() { 82 | Tag tag = Parcels.unwrap(getArguments().getParcelable(KEY_TAG)); 83 | DaggerMediaListComponent.builder() 84 | .appComponent(((InstagramTagsApp) getActivity().getApplication()).getAppComponent()) 85 | .mediaListModule(new MediaListModule(this, tag)) 86 | .build().inject(this); 87 | } 88 | 89 | @Override 90 | public void onDestroy() { 91 | super.onDestroy(); 92 | viewModel.stop(); 93 | } 94 | 95 | @Override 96 | public void onSaveInstanceState(Bundle outState) { 97 | super.onSaveInstanceState(outState); 98 | if (adapter.getMedias() != null) 99 | outState.putParcelable(KEY_STATE_LIST, Parcels.wrap(adapter.getMedias())); 100 | } 101 | 102 | @Override 103 | public void onMediaListLoaded(MediaListResponse response) { 104 | adapter.setMedias(response.data); 105 | } 106 | 107 | @Override 108 | public void onError(Throwable error) { 109 | DialogBuilder 110 | .infoDialog(getActivity(), 111 | R.string.dialog_error_title, 112 | R.string.dialog_error_content) 113 | .show(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/searchtag/SearchTagAdapter.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.searchtag; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import iammert.com.instagramtags.databinding.ItemSearchTagBinding; 12 | import iammert.com.instagramtags.model.api.entity.Tag; 13 | import iammert.com.instagramtags.util.RxBus; 14 | import iammert.com.instagramtags.viewmodel.searchtag.SearchTagItemViewModel; 15 | 16 | /** 17 | * Created by mertsimsek on 13/01/17. 18 | */ 19 | 20 | public class SearchTagAdapter extends RecyclerView.Adapter { 21 | 22 | private List tags; 23 | private RxBus rxBus; 24 | 25 | public SearchTagAdapter(RxBus rxBus) { 26 | this.rxBus = rxBus; 27 | tags = new ArrayList<>(); 28 | } 29 | 30 | public void setTags(List tags) { 31 | this.tags = tags; 32 | notifyDataSetChanged(); 33 | } 34 | 35 | public List getTags(){ 36 | return tags; 37 | } 38 | 39 | @Override 40 | public SearchTagViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 41 | return SearchTagViewHolder.create(LayoutInflater.from(parent.getContext()), parent); 42 | } 43 | 44 | @Override 45 | public void onBindViewHolder(SearchTagViewHolder holder, int position) { 46 | holder.bind(new SearchTagItemViewModel(holder.itemView.getContext(), rxBus, tags.get(position))); 47 | } 48 | 49 | @Override 50 | public int getItemCount() { 51 | return tags.size(); 52 | } 53 | 54 | static class SearchTagViewHolder extends RecyclerView.ViewHolder { 55 | 56 | static SearchTagAdapter.SearchTagViewHolder create(LayoutInflater inflater, ViewGroup parent) { 57 | ItemSearchTagBinding binding = ItemSearchTagBinding.inflate(inflater, parent, false); 58 | return new SearchTagAdapter.SearchTagViewHolder(binding); 59 | } 60 | 61 | private ItemSearchTagBinding binding; 62 | 63 | public SearchTagViewHolder(ItemSearchTagBinding binding) { 64 | super(binding.getRoot()); 65 | this.binding = binding; 66 | } 67 | 68 | public void bind(SearchTagItemViewModel viewModel){ 69 | binding.setViewModel(viewModel); 70 | binding.executePendingBindings(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/searchtag/SearchTagFragment.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.searchtag; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import org.parceler.Parcels; 12 | 13 | import javax.inject.Inject; 14 | 15 | import iammert.com.instagramtags.InstagramTagsApp; 16 | import iammert.com.instagramtags.R; 17 | import iammert.com.instagramtags.databinding.FragmentSearchTagBinding; 18 | import iammert.com.instagramtags.di.searchtag.DaggerSearchTagComponent; 19 | import iammert.com.instagramtags.di.searchtag.SearchTagModule; 20 | import iammert.com.instagramtags.model.api.entity.TagSearchResponse; 21 | import iammert.com.instagramtags.util.DialogBuilder; 22 | import iammert.com.instagramtags.viewmodel.searchtag.SearchTagViewModel; 23 | 24 | /** 25 | * Created by mertsimsek on 13/01/17. 26 | */ 27 | 28 | public class SearchTagFragment extends Fragment implements SearchTagViewModel.SearchTagListener { 29 | 30 | public static final String KEY_STATE_LIST = "key_state_list"; 31 | 32 | FragmentSearchTagBinding binding; 33 | 34 | @Inject 35 | SearchTagViewModel viewModel; 36 | 37 | @Inject 38 | SearchTagAdapter adapter; 39 | 40 | public static SearchTagFragment newInstance() { 41 | Bundle args = new Bundle(); 42 | SearchTagFragment fragment = new SearchTagFragment(); 43 | fragment.setArguments(args); 44 | return fragment; 45 | } 46 | 47 | @Nullable 48 | @Override 49 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 50 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_tag, container, false); 51 | initializeInjectors(); 52 | binding.setViewModel(viewModel); 53 | binding.recyclerView.setAdapter(adapter); 54 | 55 | if (savedInstanceState != null) 56 | adapter.setTags(Parcels.unwrap(savedInstanceState.getParcelable(KEY_STATE_LIST))); 57 | 58 | return binding.getRoot(); 59 | } 60 | 61 | private void initializeInjectors() { 62 | DaggerSearchTagComponent.builder() 63 | .appComponent(((InstagramTagsApp) getActivity().getApplication()).getAppComponent()) 64 | .searchTagModule(new SearchTagModule(this)) 65 | .build().inject(this); 66 | } 67 | 68 | @Override 69 | public void onSaveInstanceState(Bundle outState) { 70 | super.onSaveInstanceState(outState); 71 | if (adapter != null && adapter.getTags() != null) 72 | outState.putParcelable(KEY_STATE_LIST, Parcels.wrap(adapter.getTags())); 73 | } 74 | 75 | @Override 76 | public void onTagListLoaded(TagSearchResponse response) { 77 | adapter.setTags(response.data); 78 | } 79 | 80 | @Override 81 | public void onError(Throwable error) { 82 | DialogBuilder 83 | .infoDialog(getActivity(), 84 | R.string.dialog_error_title, 85 | R.string.dialog_error_content) 86 | .show(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/view/splash/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.view.splash; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.databinding.DataBindingUtil; 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.View; 10 | 11 | import javax.inject.Inject; 12 | 13 | import iammert.com.instagramtags.InstagramTagsApp; 14 | import iammert.com.instagramtags.R; 15 | import iammert.com.instagramtags.databinding.ActivitySplashBinding; 16 | import iammert.com.instagramtags.di.splash.DaggerSplashComponent; 17 | import iammert.com.instagramtags.di.splash.SplashModule; 18 | import iammert.com.instagramtags.util.Constants; 19 | import iammert.com.instagramtags.view.login.LoginActivity; 20 | import iammert.com.instagramtags.view.main.MainActivity; 21 | import iammert.com.instagramtags.viewmodel.splash.SplashViewModel; 22 | 23 | /** 24 | * Created by mertsimsek on 13/01/17. 25 | */ 26 | 27 | public class SplashActivity extends AppCompatActivity implements SplashViewModel.SplashListener{ 28 | 29 | private static int RC_LOGIN = 1001; 30 | 31 | ActivitySplashBinding binding; 32 | 33 | @Inject 34 | SplashViewModel viewModel; 35 | 36 | @Override 37 | protected void onCreate(@Nullable Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | binding = DataBindingUtil.setContentView(this, R.layout.activity_splash); 40 | initializeInjectors(); 41 | binding.setViewModel(viewModel); 42 | } 43 | 44 | private void initializeInjectors(){ 45 | InstagramTagsApp app = (InstagramTagsApp) getApplication(); 46 | DaggerSplashComponent.builder() 47 | .appComponent(app.getAppComponent()) 48 | .splashModule(new SplashModule(this)) 49 | .build().inject(this); 50 | } 51 | 52 | @Override 53 | protected void onDestroy() { 54 | super.onDestroy(); 55 | viewModel.stop(); 56 | } 57 | 58 | @Override 59 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 60 | if(RC_LOGIN == requestCode){ 61 | if(resultCode == Activity.RESULT_OK) 62 | viewModel.saveToken(data.getStringExtra(Constants.KEY_INTENT_TOKEN)); 63 | } 64 | } 65 | 66 | @Override 67 | public void onLoginClicked() { 68 | startActivityForResult(new Intent(SplashActivity.this, LoginActivity.class), RC_LOGIN); 69 | } 70 | 71 | @Override 72 | public void onUserLoggedIn() { 73 | startActivity(MainActivity.newIntent(this)); 74 | finish(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/viewmodel/main/MainViewModel.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.viewmodel.main; 2 | 3 | import iammert.com.instagramtags.model.api.entity.Tag; 4 | import iammert.com.instagramtags.util.RxBus; 5 | import iammert.com.instagramtags.viewmodel.searchtag.TagClickEvent; 6 | import rx.subscriptions.CompositeSubscription; 7 | 8 | /** 9 | * Created by mertsimsek on 13/01/17. 10 | */ 11 | 12 | public class MainViewModel { 13 | 14 | private RxBus rxBus; 15 | private MainListener listener; 16 | private boolean isTwoPane = false; 17 | private CompositeSubscription subscription; 18 | 19 | public MainViewModel(RxBus rxBus, MainListener listener) { 20 | this.rxBus = rxBus; 21 | this.listener = listener; 22 | subscription = new CompositeSubscription(); 23 | 24 | subscription.add(rxBus.toObserverable() 25 | .filter(o -> o instanceof TagClickEvent) 26 | .map(o -> (TagClickEvent) o) 27 | .map(tagClickEvent -> tagClickEvent.tag) 28 | .subscribe(listener::onTagItemClicked)); 29 | } 30 | 31 | public void stop() { 32 | if (subscription != null && !subscription.isUnsubscribed()) 33 | subscription.unsubscribe(); 34 | } 35 | 36 | public void setTwoPane(boolean isTwoPane) { 37 | this.isTwoPane = isTwoPane; 38 | } 39 | 40 | public boolean isTwoPane() { 41 | return isTwoPane; 42 | } 43 | 44 | public interface MainListener { 45 | void onTagItemClicked(Tag tag); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/viewmodel/medialist/MediaListItemViewModel.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.viewmodel.medialist; 2 | 3 | import android.content.Context; 4 | import android.databinding.ObservableField; 5 | 6 | import iammert.com.instagramtags.R; 7 | import iammert.com.instagramtags.model.api.entity.Media; 8 | 9 | /** 10 | * Created by mertsimsek on 13/01/17. 11 | */ 12 | 13 | public class MediaListItemViewModel { 14 | 15 | public ObservableField userImage; 16 | public ObservableField image; 17 | public ObservableField userName; 18 | public ObservableField likeCount; 19 | 20 | private Media media; 21 | 22 | public MediaListItemViewModel(Context context, Media media) { 23 | this.media = media; 24 | 25 | likeCount = new ObservableField<>(context.getString(R.string.like_count, String.valueOf(media.likes.count))); 26 | userImage = new ObservableField<>(media.user.profilePicture); 27 | userName = new ObservableField<>(media.user.username); 28 | image = new ObservableField<>(media.images.lowResolution.url); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/viewmodel/medialist/MediaListViewModel.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.viewmodel.medialist; 2 | 3 | import android.databinding.ObservableField; 4 | 5 | import javax.inject.Inject; 6 | 7 | import iammert.com.instagramtags.domain.medialist.MediaListUsecase; 8 | import iammert.com.instagramtags.model.api.entity.MediaListResponse; 9 | import iammert.com.instagramtags.model.api.entity.Tag; 10 | import rx.subscriptions.CompositeSubscription; 11 | 12 | /** 13 | * Created by mertsimsek on 13/01/17. 14 | */ 15 | 16 | public class MediaListViewModel { 17 | 18 | public ObservableField tagName; 19 | public ObservableField isLoading; 20 | public ObservableField refreshEnabled; 21 | 22 | private CompositeSubscription subscription; 23 | private MediaListUsecase mediaListUsecase; 24 | private MediaListListener listener; 25 | private Tag tag; 26 | 27 | @Inject 28 | public MediaListViewModel(MediaListUsecase mediaListUsecase, MediaListListener listener, Tag tag) { 29 | this.mediaListUsecase = mediaListUsecase; 30 | this.listener = listener; 31 | this.tag = tag; 32 | subscription = new CompositeSubscription(); 33 | 34 | isLoading = new ObservableField<>(true); 35 | refreshEnabled = new ObservableField<>(true); 36 | tagName = new ObservableField<>("#" + tag.name); 37 | } 38 | 39 | public void loadMedias(){ 40 | subscription.add(mediaListUsecase.searchMedia(tag.name) 41 | .doOnNext(response -> isLoading.set(false)) 42 | .doOnNext(response -> refreshEnabled.set(false)) 43 | .subscribe(listener::onMediaListLoaded, listener::onError)); 44 | } 45 | 46 | public void stop() { 47 | if (subscription != null && !subscription.isUnsubscribed()) 48 | subscription.unsubscribe(); 49 | } 50 | 51 | public interface MediaListListener { 52 | void onMediaListLoaded(MediaListResponse response); 53 | 54 | void onError(Throwable error); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/viewmodel/searchtag/SearchTagItemViewModel.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.viewmodel.searchtag; 2 | 3 | import android.content.Context; 4 | import android.databinding.ObservableField; 5 | import android.view.View; 6 | 7 | import iammert.com.instagramtags.R; 8 | import iammert.com.instagramtags.model.api.entity.Tag; 9 | import iammert.com.instagramtags.util.RxBus; 10 | 11 | /** 12 | * Created by mertsimsek on 13/01/17. 13 | */ 14 | 15 | public class SearchTagItemViewModel { 16 | 17 | public ObservableField tagName; 18 | public ObservableField postCount; 19 | 20 | private Tag tag; 21 | private RxBus rxBus; 22 | 23 | public SearchTagItemViewModel(Context context, RxBus rxBus, Tag tag) { 24 | this.tag = tag; 25 | this.rxBus = rxBus; 26 | 27 | tagName = new ObservableField<>(context.getString(R.string.tag, tag.name)); 28 | postCount = new ObservableField<>(context.getString(R.string.post_count, String.valueOf(tag.mediaCount))); 29 | } 30 | 31 | public void onTagClicked(View view) { 32 | rxBus.send(new TagClickEvent(tag)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/viewmodel/searchtag/SearchTagViewModel.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.viewmodel.searchtag; 2 | 3 | import android.databinding.ObservableField; 4 | import android.text.TextWatcher; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import iammert.com.instagramtags.domain.searchtag.SearchTagUsecase; 9 | import iammert.com.instagramtags.model.api.entity.TagSearchResponse; 10 | import iammert.com.instagramtags.util.SimpleTextWatcher; 11 | import rx.Observable; 12 | import rx.subjects.PublishSubject; 13 | 14 | /** 15 | * Created by mertsimsek on 13/01/17. 16 | */ 17 | 18 | public class SearchTagViewModel { 19 | 20 | public ObservableField isLoading; 21 | public ObservableField refreshEnabled; 22 | 23 | private PublishSubject publishSubject; 24 | private SearchTagUsecase usecase; 25 | private SearchTagListener listener; 26 | 27 | public SearchTagViewModel(SearchTagUsecase usecase, SearchTagListener listener) { 28 | this.usecase = usecase; 29 | this.listener = listener; 30 | 31 | isLoading = new ObservableField<>(false); 32 | refreshEnabled = new ObservableField<>(false); 33 | publishSubject = PublishSubject.create(); 34 | 35 | publishSubject.debounce(200, TimeUnit.MILLISECONDS) 36 | .doOnNext(response -> refreshEnabled.set(true)) 37 | .doOnNext(response -> isLoading.set(true)) 38 | .flatMap(usecase::searchTag) 39 | .doOnNext(response -> isLoading.set(false)) 40 | .doOnNext(response -> refreshEnabled.set(false)) 41 | .subscribe(listener::onTagListLoaded, listener::onError); 42 | } 43 | 44 | public TextWatcher getTextWatcher() { 45 | return new SimpleTextWatcher() { 46 | @Override 47 | public void onTextChanged(CharSequence s, int start, int before, int count) { 48 | if(s.toString().trim().length() > 2) 49 | publishSubject.onNext(s.toString()); 50 | } 51 | }; 52 | } 53 | 54 | public interface SearchTagListener{ 55 | void onTagListLoaded(TagSearchResponse response); 56 | 57 | void onError(Throwable error); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/viewmodel/searchtag/TagClickEvent.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.viewmodel.searchtag; 2 | 3 | import iammert.com.instagramtags.model.api.entity.Tag; 4 | 5 | /** 6 | * Created by mertsimsek on 14/01/17. 7 | */ 8 | 9 | public class TagClickEvent { 10 | public Tag tag; 11 | 12 | public TagClickEvent(Tag tag) { 13 | this.tag = tag; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/iammert/com/instagramtags/viewmodel/splash/SplashViewModel.java: -------------------------------------------------------------------------------- 1 | package iammert.com.instagramtags.viewmodel.splash; 2 | 3 | import android.util.Log; 4 | import android.view.View; 5 | 6 | import javax.inject.Inject; 7 | 8 | import iammert.com.instagramtags.domain.splash.SplashUsecase; 9 | import rx.subscriptions.CompositeSubscription; 10 | 11 | /** 12 | * Created by mertsimsek on 13/01/17. 13 | */ 14 | 15 | public class SplashViewModel { 16 | 17 | private CompositeSubscription subscription; 18 | private SplashUsecase usecase; 19 | private SplashListener listener; 20 | 21 | @Inject 22 | public SplashViewModel(SplashUsecase usecase, SplashListener listener) { 23 | this.usecase = usecase; 24 | this.listener = listener; 25 | subscription = new CompositeSubscription(); 26 | } 27 | 28 | public void onLoginClicked(View view) { 29 | listener.onLoginClicked(); 30 | } 31 | 32 | public void saveToken(String token) { 33 | subscription.add(usecase.saveToken(token).subscribe(s -> listener.onUserLoggedIn())); 34 | } 35 | 36 | public void stop() { 37 | if (subscription != null && !subscription.isUnsubscribed()) 38 | subscription.unsubscribe(); 39 | } 40 | 41 | public interface SplashListener { 42 | void onLoginClicked(); 43 | 44 | void onUserLoggedIn(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_search_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_favorite_border_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shadow_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout-w900dp/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 14 | 15 | 20 | 21 | 27 | 28 | 29 | 30 | 35 | 36 | 41 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | 11 | 12 | 15 | 16 | 21 | 22 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_media_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 21 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 15 | 16 |