├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── markdown-exported-files.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── x1unix │ │ └── avi │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── changelog_template.html │ │ ├── help-en.html │ │ ├── help-ru.html │ │ ├── help-uk.html │ │ ├── help.css │ │ ├── hosts-rus.txt │ │ ├── hosts.txt │ │ └── urls.txt │ ├── java │ │ └── com │ │ │ ├── kinopoisk │ │ │ ├── Constants.java │ │ │ ├── KinopoiskRequestInterceptor.java │ │ │ └── NetworkApiFactory.java │ │ │ └── x1unix │ │ │ └── avi │ │ │ ├── AppCompatPreferenceActivity.java │ │ │ ├── Application.java │ │ │ ├── ClickListener.java │ │ │ ├── DashboardActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── MovieDetailsActivity.java │ │ │ ├── MoviePlayerActivity.java │ │ │ ├── RecyclerTouchListener.java │ │ │ ├── SearchActivity.java │ │ │ ├── SearchWidgetProvider.java │ │ │ ├── SettingsActivity.java │ │ │ ├── SupportActivity.java │ │ │ ├── UpdateDownloaderActivity.java │ │ │ ├── adapter │ │ │ ├── CachedMoviesListAdapter.java │ │ │ └── MoviesAdapter.java │ │ │ ├── dashboard │ │ │ ├── DashboardFragmentPagerAdapter.java │ │ │ ├── DashboardTabFragment.java │ │ │ ├── FavoritesTabFragment.java │ │ │ └── HistoryTabFragment.java │ │ │ ├── helpers │ │ │ ├── AdBlocker.java │ │ │ ├── DownloadFileFromURL.java │ │ │ └── PermissionHelper.java │ │ │ ├── model │ │ │ ├── AviSemVersion.java │ │ │ ├── KPMovie.java │ │ │ ├── KPMovieDetailViewResponse.java │ │ │ ├── KPMovieItem.java │ │ │ ├── KPMovieSearchResult.java │ │ │ ├── KPPeople.java │ │ │ ├── KPResponse.java │ │ │ └── KPSearchResponse.java │ │ │ ├── rest │ │ │ ├── KPApiInterface.java │ │ │ └── KPRestClient.java │ │ │ ├── storage │ │ │ ├── DBHelper.java │ │ │ └── MoviesRepository.java │ │ │ ├── updateManager │ │ │ ├── BuildParser.java │ │ │ ├── ISO8601.java │ │ │ ├── OTARepoClientInterface.java │ │ │ ├── OTARestClient.java │ │ │ ├── OTAStateListener.java │ │ │ └── OTAUpdateChecker.java │ │ │ └── webplayer │ │ │ ├── AviWebChromeClient.java │ │ │ ├── AviWebView.java │ │ │ └── AviWebViewClient.java │ └── res │ │ ├── drawable-v22 │ │ ├── ic_bookmark_full_wrapped.xml │ │ └── ic_bookmark_wrapped.xml │ │ ├── drawable │ │ ├── avi_r.png │ │ ├── avi_wide_r.png │ │ ├── block_rounded.xml │ │ ├── border_bottom.xml │ │ ├── effect_ripple.xml │ │ ├── ic_avi.xml │ │ ├── ic_avi_wide.xml │ │ ├── ic_bookmark.xml │ │ ├── ic_bookmark_full.xml │ │ ├── ic_bookmark_full_wrapped.xml │ │ ├── ic_bookmark_gray.xml │ │ ├── ic_bookmark_wrapped.xml │ │ ├── ic_cloud_off_black.xml │ │ ├── ic_error.xml │ │ ├── ic_restore_gray.xml │ │ ├── ic_search.xml │ │ ├── ic_search_24dp.xml │ │ ├── ic_search_gray.xml │ │ ├── no_poster.png │ │ ├── progress_drawable.xml │ │ ├── splash_image.xml │ │ └── star.xml │ │ ├── layout-sw600dp-land │ │ └── activity_movie_details.xml │ │ ├── layout-sw600dp │ │ └── list_item_movie.xml │ │ ├── layout │ │ ├── activity_dashboard.xml │ │ ├── activity_main.xml │ │ ├── activity_movie_details.xml │ │ ├── activity_movie_player.xml │ │ ├── activity_search.xml │ │ ├── activity_settings.xml │ │ ├── activity_support.xml │ │ ├── activity_update_downloader.xml │ │ ├── list_item_movie.xml │ │ ├── tab_favorites.xml │ │ ├── tab_history.xml │ │ ├── view_loading_video.xml │ │ └── widget_search.xml │ │ ├── menu │ │ ├── main_menu.xml │ │ ├── menu_movie_details.xml │ │ └── search_menu.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-land │ │ └── dimens.xml │ │ ├── values-port-v21 │ │ └── dimens.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-sw600dp-land │ │ └── props.xml │ │ ├── values-sw600dp │ │ └── props.xml │ │ ├── values-sw720dp-land │ │ ├── dimens.xml │ │ └── props.xml │ │ ├── values-sw720dp │ │ └── dimens.xml │ │ ├── values-uk │ │ └── strings.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── keys.xml │ │ ├── props.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ ├── xml-v14 │ │ └── pref_main.xml │ │ └── xml │ │ ├── pref_main.xml │ │ ├── search_widget_provider.xml │ │ └── searchable.xml │ └── test │ └── java │ └── com │ └── x1unix │ └── avi │ └── ExampleUnitTest.java ├── build.gradle ├── deploy.sh ├── 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/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/markdown-exported-files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 33 | 34 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | android: 4 | components: 5 | # Uncomment the lines below if you want to 6 | # use the latest revision of Android SDK Tools 7 | - tools 8 | - tools 9 | - platform-tools 10 | - add-on 11 | - extra 12 | 13 | # The BuildTools version used by your project 14 | - build-tools-24.0.3 15 | 16 | # The SDK version used to compile your project 17 | - android-24 18 | 19 | # Additional components 20 | - extra-google-google_play_services 21 | - extra-google-m2repository 22 | - extra-android-m2repository 23 | - addon-google_apis-google-19 24 | 25 | # Specify at least one system image, 26 | # if you need to run emulator(s) during your tests 27 | - sys-img-armeabi-v7a-android-24 28 | # - sys-img-x86-android-23 29 | licenses: 30 | - 'android-sdk-preview-license-.+' 31 | - 'android-sdk-license-.+' 32 | - 'google-gdk-license-.+' 33 | sudo: required 34 | after_failure: "cat $TRAVIS_BUILD_DIR/app/build/outputs/lint-results-debug.xml" 35 | after_success: 36 | - ./deploy.sh 37 | script: 38 | - ./gradlew assemble lint 39 | # after_script: 40 | # - echo no | android create avd --force -n test -t android-24 --abi armeabi-v7a 41 | # - emulator -avd test -no-skin -no-audio -no-window & 42 | # - android-wait-for-emulator 43 | # - adb shell input keyevent 82 & 44 | # - ./gradlew connectedCheck 45 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Environment 2 | **App Version:** Version of Avi related to the issue 3 | 4 | **Device:** Type your device name and model (for ex. Samsung Galaxy S2 GT-9100) 5 | 6 | **Android Version:** Your Android verision + ROM name if it's not stock. 7 | 8 | ## Description 9 | Describe short issue description 10 | 11 | ### Steps 12 | Describe your actions step by step. 13 | 14 | ### Result 15 | Describe received behavior 16 | 17 | 18 | ### Expected 19 | Describe expected result or behavior 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016-present, Denis Sedchenko 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Avi 2 | 3 | [![Build Status](https://travis-ci.org/odin3/Avi.svg?branch=master)](https://travis-ci.org/odin3/Avi) 4 | 5 | Application that allows to watch movies online from any Android device. 6 | 7 | ## How it works 8 | This application uses internal KinoPoisk API to search, identify and collect data about the movie. 9 | To watch movies, I use [moonwalk.co](http://moonwalk.co/the_api) service that hosts movies and TV shows. 10 | Received Kinopoisk ID from movie used to get player's URL for the movie from Moonwalk. 11 | 12 | ## Requirements 13 | * Android 4.0+ 14 | * Network connection 15 | 16 | ## Disclaimer 17 | Author doesn't take responsibillity for the video content. All data is hosted and provided by Moonwalk. 18 | 19 | ## Bug reports 20 | You can report a bug or propose a feature in [issues](https://github.com/odin3/Avi/issues) section. 21 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.3" 6 | defaultConfig { 7 | applicationId "com.x1unix.avi" 8 | minSdkVersion 14 9 | targetSdkVersion 24 10 | versionCode 20170319 11 | versionName "1.8.2" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | vectorDrawables { 14 | useSupportLibrary true 15 | } 16 | vectorDrawables.useSupportLibrary = true 17 | } 18 | lintOptions { 19 | abortOnError false 20 | } 21 | buildTypes { 22 | debug { 23 | debuggable true 24 | } 25 | release { 26 | debuggable false 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | 32 | packagingOptions { 33 | exclude 'META-INF/LICENSE' 34 | } 35 | } 36 | 37 | dependencies { 38 | compile fileTree(dir: 'libs', include: ['*.jar']) 39 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 40 | exclude group: 'com.android.support', module: 'support-annotations' 41 | }) 42 | compile 'com.android.support:appcompat-v7:24.2.1' 43 | compile 'com.google.code.gson:gson:2.6.2' 44 | compile 'com.squareup.retrofit2:retrofit:2.0.2' 45 | compile 'com.squareup.retrofit2:converter-gson:2.0.2' 46 | compile 'com.squareup.retrofit2:converter-jackson:2.0.2' 47 | compile 'com.android.support:recyclerview-v7:24.2.1' 48 | compile 'com.android.support:design:24.2.1' 49 | compile 'com.android.support:support-v4:24.2.1' 50 | compile 'com.android.support:support-vector-drawable:24.2.1' 51 | compile 'commons-codec:commons-codec:1.8' 52 | compile 'com.rollbar:rollbar-android:0.1.3' 53 | compile 'com.squareup.okhttp3:logging-interceptor:3.3.1' 54 | testCompile 'junit:junit:4.12' 55 | compile 'org.apache.commons:commons-lang3:3.5' 56 | compile 'com.android.support:cardview-v7:24.2.1' 57 | compile "com.squareup.okhttp3:okhttp-urlconnection:3.2.0" 58 | 59 | compile 'com.fasterxml.jackson.core:jackson-core:2.7.3' 60 | compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.3' 61 | compile 'com.fasterxml.jackson.core:jackson-databind:2.7.3' 62 | 63 | compile 'com.github.bumptech.glide:glide:3.5.2' 64 | 65 | compile 'com.yandex.android:mobmetricalib:2.62' 66 | } 67 | -------------------------------------------------------------------------------- /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 D:\AppData\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/com/x1unix/avi/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 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("com.x1unix.avi", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 20 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 45 | 48 | 49 | 54 | 57 | 58 | 62 | 66 | 69 | 70 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/src/main/assets/changelog_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Changelog 7 | 35 | 36 | 37 | %CONTENT% 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/assets/help-en.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Avi 6 | 7 | 8 | 9 | 10 |
11 |

I have an issue when watching movies

12 |

13 | Try to do several steps: 14 |

19 |

20 |
21 |
22 |

Each search query returns 'not found' message

23 |

24 | This issue may appear when KinoPoisk's API is down or not available. 25 |

26 |

27 | Try to wait for an hour and retry search. 28 |

29 |
30 |
31 |

How do I can update the app?

32 |

33 | You can check out for new updates at settings or at our repository. 34 |

35 |
36 |
37 |

I found an issue. Where can I submit it?

38 |

39 | Send all issues and ideas to issues tracker 40 | or send by email to avi-app@x1unix.com. 41 |

42 |
43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/assets/help-ru.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Avi 6 | 7 | 8 | 9 | 10 |
11 |

У меня возникают проблемы при просмотре фильма

12 |

13 | Попробуйте следующее: 14 |

19 |

20 |
21 |
22 |

При любом поисковом запросе нету результатов

23 |

24 | Для поиска фильмов и сериалов, приложение использует API КиноПоиска. 25 |

26 |

27 | Если у вас случается подобная ситуация, это чаще всего означает что сервера, которые 28 | отвечают за работу API, временно не работают. 29 |

30 |

31 | Попробуйте подождать около часа и повторить поиск снова. 32 |

33 |
34 |
35 |

Как я могу обновить приложение?

36 |

37 | Вы можете проверить обновления в настройках или на нашем репозитории. 38 |

39 |
40 |
41 |

Я нашел ошибку. Куда мне сообщить?

42 |

43 | Отправляйте все найденые ошибки и предложения на баг-трекер 44 | или на avi-app@x1unix.com. 45 |

46 |
47 | 48 | -------------------------------------------------------------------------------- /app/src/main/assets/help-uk.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Avi 6 | 7 | 8 | 9 | 10 |
11 |

У мене виникають проблеми під час перегляду фільмів

12 |

13 | Спробуйте наступне: 14 |

19 |

20 |
21 |
22 |

При будь-якому пошуковому запиті немає результатів

23 |

24 | Для пошуку фільмів і серіалів, додаток використовує API сервiсу "Кинопоиск". 25 |

26 |

27 | Якщо у вас трапляється подібна ситуація, це найчастіше означає що сервера, які 28 | відповідають за роботу API, тимчасово не працюють. 29 |

30 |

31 | Спробуйте почекати близько години і повторити пошук знову. 32 |

33 |
34 |
35 |

Як я можу оновити аплікацію?

36 |

37 | Ви можете завантажувати оновлення через налаштування або з нашого репозиторію. 38 |

39 |
40 |
41 |

Я знайшов помилку. Куди мені повідомити?

42 |

43 | Відсилайте усі знайдені помилки та пропозиціі на баг-трекер 44 | або на avi-app@x1unix.com. 45 |

46 |
47 | 48 | -------------------------------------------------------------------------------- /app/src/main/assets/help.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin:0; 3 | padding:0; 4 | font: 10pt 'Roboto', sans-serif; 5 | } 6 | 7 | h1 { 8 | font-size: 14pt; 9 | color: white; 10 | background: #555; 11 | display: block; 12 | margin-top: 0; 13 | padding: 15px; 14 | } 15 | 16 | p { 17 | padding-left: 15px; 18 | padding-right: 15px; 19 | } 20 | 21 | section { 22 | padding-bottom: 32px; 23 | border-bottom: 1px solid #cecece; 24 | } -------------------------------------------------------------------------------- /app/src/main/assets/urls.txt: -------------------------------------------------------------------------------- 1 | http://moonwalk.co/web/js/advertisment.js 2 | http://moonwalk.co/api/morefunc 3 | http://moonwalk.co/web/js/jquery_simpletip.js -------------------------------------------------------------------------------- /app/src/main/java/com/kinopoisk/Constants.java: -------------------------------------------------------------------------------- 1 | package com.kinopoisk; 2 | 3 | public final class Constants { 4 | public static final String API_VERSION = "3.11.0"; 5 | public static final String KINOPOISK_ENDPOINT = "https://ext.kinopoisk.ru/ios/"; 6 | public static String getPosterUrl(String kpId) { 7 | return "http://st.kp.yandex.net/images/film_iphone/iphone360_" + kpId + ".jpg"; 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/kinopoisk/KinopoiskRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.kinopoisk; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.Locale; 9 | 10 | import org.apache.commons.codec.binary.Hex; 11 | import org.apache.commons.codec.digest.DigestUtils; 12 | 13 | import java.io.IOException; 14 | import java.net.URL; 15 | import java.text.DateFormat; 16 | import java.text.SimpleDateFormat; 17 | import java.util.Date; 18 | import java.util.Locale; 19 | 20 | import okhttp3.HttpUrl; 21 | import okhttp3.Interceptor; 22 | import okhttp3.Request; 23 | import okhttp3.Response; 24 | 25 | 26 | public class KinopoiskRequestInterceptor implements Interceptor { 27 | 28 | private static final String KP_SECRET = "a17qbcw1du0aedm"; 29 | private static final String KP_UUID = "84e8b92499a32a3d0d8ea956e6a05d76"; 30 | private static final String KP_CLIENT_ID = "55decdcf6d4cd1bcaa1b3856"; 31 | private static final String API_CHECK_FOR_UPDATE = "ios/check-new-version.php"; 32 | 33 | @Override 34 | public Response intercept(Chain chain) throws IOException { 35 | 36 | Request interceptedRequest = chain.request(); 37 | HttpUrl originalHttpUrl = interceptedRequest.url(); 38 | 39 | // chain.proceed(buildCheckForUpdateRequest(originalHttpUrl.host())).close(); 40 | return chain.proceed(buildMainRequest(interceptedRequest)); 41 | } 42 | 43 | private String encodeWithSecret(String action) { 44 | return new String(Hex.encodeHex(DigestUtils.md5(action + KP_SECRET))); 45 | } 46 | 47 | /** build main request with additional parameters, required by kinopoisk */ 48 | private Request buildMainRequest(Request interceptedRequest) { 49 | HttpUrl interceptedUrl = interceptedRequest.url(); 50 | String commandName = interceptedUrl.pathSegments().get(2); 51 | URL oUrl = interceptedUrl.url(); 52 | 53 | String action; 54 | if (oUrl.getQuery() == null) { 55 | action = commandName + "?" + "uuid=" + KP_UUID; 56 | } else { 57 | action = commandName + "?" + oUrl.getQuery() + "&uuid=" + KP_UUID; 58 | } 59 | 60 | // Build encoded key for query 61 | String encodedKey = encodeWithSecret(action); 62 | 63 | // Put additional params to auth 64 | HttpUrl url = interceptedUrl.newBuilder() 65 | .addQueryParameter("uuid", KP_UUID) 66 | .addQueryParameter("key", encodedKey) 67 | .build(); 68 | 69 | // Put additional headers to look like KP client 70 | interceptedRequest = basicHeaders(interceptedRequest.newBuilder() 71 | .removeHeader("User-Agent") 72 | .url(url)) 73 | .build(); 74 | 75 | return interceptedRequest; 76 | } 77 | 78 | /** build a request to check if our client is deprecated 79 | * with new kinopoisk api we should do it with any request to setup cookies */ 80 | private Request buildCheckForUpdateRequest(String host) { 81 | 82 | String action = "check-new-version.php" + "?" + "appVersion=" + Constants.API_VERSION + "&uuid=" + KP_UUID; 83 | String encodedKey = encodeWithSecret(action); 84 | 85 | HttpUrl url = new HttpUrl.Builder() 86 | .scheme("https") 87 | .host(host) 88 | .addPathSegments(API_CHECK_FOR_UPDATE) 89 | .addQueryParameter("key", encodedKey) 90 | .addQueryParameter("appVersion", Constants.API_VERSION) 91 | .addQueryParameter("uuid", KP_UUID) 92 | .build(); 93 | 94 | Request.Builder builder = basicHeaders(new Request.Builder()) 95 | .url(url) 96 | .addHeader("Cookie", "user_country=ru"); 97 | 98 | return builder.build(); 99 | } 100 | 101 | 102 | private Request.Builder basicHeaders(Request.Builder builder) { 103 | // Generate req date 104 | Date date = new Date(); 105 | DateFormat dateFormat = new SimpleDateFormat("HH:mm MM.dd.yyyy", Locale.getDefault()); 106 | String clientDate = dateFormat.format(date); 107 | 108 | builder 109 | .addHeader("device", "android") 110 | .addHeader("Android-Api-Version", "22") 111 | .addHeader("countryID", "2") 112 | .addHeader("ClientId", KP_CLIENT_ID) 113 | .addHeader("clientDate", clientDate) 114 | .addHeader("cityID", "2") 115 | .addHeader("Image-Scale", "3") 116 | .addHeader("Cache-Control", "max-stale=0") 117 | .addHeader("User-Agent", "Android client (5.1 / api22), ru.kinopoisk/3.7.0 (45)") 118 | .addHeader("Accept-Encoding", "gzip"); 119 | 120 | return builder; 121 | } 122 | } -------------------------------------------------------------------------------- /app/src/main/java/com/kinopoisk/NetworkApiFactory.java: -------------------------------------------------------------------------------- 1 | package com.kinopoisk; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.core.Version; 5 | import com.fasterxml.jackson.databind.DeserializationFeature; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import com.fasterxml.jackson.databind.module.SimpleModule; 8 | import com.x1unix.avi.BuildConfig; 9 | 10 | import java.net.CookieManager; 11 | import java.net.CookiePolicy; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import okhttp3.Cache; 15 | import okhttp3.JavaNetCookieJar; 16 | import okhttp3.OkHttpClient; 17 | import okhttp3.logging.HttpLoggingInterceptor; 18 | import retrofit2.Retrofit; 19 | import retrofit2.converter.gson.GsonConverterFactory; 20 | import retrofit2.converter.jackson.JacksonConverterFactory; 21 | 22 | public class NetworkApiFactory { 23 | 24 | OkHttpClient provideHttpClient() { 25 | OkHttpClient.Builder builder = new OkHttpClient().newBuilder() 26 | .connectTimeout(30, TimeUnit.SECONDS); 27 | 28 | 29 | builder.addInterceptor(new KinopoiskRequestInterceptor()); 30 | 31 | // Log http requests on debug 32 | if (BuildConfig.DEBUG) { 33 | //logging interceptor should be last interceptor in chain 34 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); 35 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 36 | 37 | builder.addInterceptor(loggingInterceptor); 38 | } 39 | 40 | CookieManager cookieManager = new CookieManager(); 41 | cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); 42 | builder.cookieJar(new JavaNetCookieJar(cookieManager)); 43 | 44 | return builder.build(); 45 | } 46 | 47 | ObjectMapper provideObjectMapper() { 48 | final SimpleModule module = new SimpleModule("", Version.unknownVersion()); 49 | 50 | //jackson object mapper setup 51 | ObjectMapper objectMapper = new ObjectMapper(); 52 | objectMapper.registerModule(module); 53 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 54 | 55 | // don not fail while unknown json props on release version 56 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 57 | 58 | return objectMapper; 59 | } 60 | 61 | JacksonConverterFactory provideJacksonConverterFactory(ObjectMapper objectMapper) { 62 | return JacksonConverterFactory.create(objectMapper); 63 | } 64 | 65 | public Retrofit getClient() { 66 | return new Retrofit.Builder() 67 | .baseUrl(Constants.KINOPOISK_ENDPOINT + Constants.API_VERSION + "/") 68 | .client(provideHttpClient()) 69 | .addConverterFactory(GsonConverterFactory.create()) 70 | .build(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/AppCompatPreferenceActivity.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.content.res.Configuration; 4 | import android.os.Bundle; 5 | import android.preference.PreferenceActivity; 6 | import android.support.annotation.LayoutRes; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.ActionBar; 9 | import android.support.v7.app.AppCompatDelegate; 10 | import android.support.v7.widget.Toolbar; 11 | import android.view.MenuInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | /** 16 | * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls 17 | * to be used with AppCompat. 18 | */ 19 | public abstract class AppCompatPreferenceActivity extends PreferenceActivity { 20 | 21 | private AppCompatDelegate mDelegate; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | getDelegate().installViewFactory(); 26 | getDelegate().onCreate(savedInstanceState); 27 | super.onCreate(savedInstanceState); 28 | } 29 | 30 | @Override 31 | protected void onPostCreate(Bundle savedInstanceState) { 32 | super.onPostCreate(savedInstanceState); 33 | getDelegate().onPostCreate(savedInstanceState); 34 | } 35 | 36 | public ActionBar getSupportActionBar() { 37 | return getDelegate().getSupportActionBar(); 38 | } 39 | 40 | public void setSupportActionBar(@Nullable Toolbar toolbar) { 41 | getDelegate().setSupportActionBar(toolbar); 42 | } 43 | 44 | @Override 45 | public MenuInflater getMenuInflater() { 46 | return getDelegate().getMenuInflater(); 47 | } 48 | 49 | @Override 50 | public void setContentView(@LayoutRes int layoutResID) { 51 | getDelegate().setContentView(layoutResID); 52 | } 53 | 54 | @Override 55 | public void setContentView(View view) { 56 | getDelegate().setContentView(view); 57 | } 58 | 59 | @Override 60 | public void setContentView(View view, ViewGroup.LayoutParams params) { 61 | getDelegate().setContentView(view, params); 62 | } 63 | 64 | @Override 65 | public void addContentView(View view, ViewGroup.LayoutParams params) { 66 | getDelegate().addContentView(view, params); 67 | } 68 | 69 | @Override 70 | protected void onPostResume() { 71 | super.onPostResume(); 72 | getDelegate().onPostResume(); 73 | } 74 | 75 | @Override 76 | protected void onTitleChanged(CharSequence title, int color) { 77 | super.onTitleChanged(title, color); 78 | getDelegate().setTitle(title); 79 | } 80 | 81 | @Override 82 | public void onConfigurationChanged(Configuration newConfig) { 83 | super.onConfigurationChanged(newConfig); 84 | getDelegate().onConfigurationChanged(newConfig); 85 | } 86 | 87 | @Override 88 | protected void onStop() { 89 | super.onStop(); 90 | getDelegate().onStop(); 91 | } 92 | 93 | @Override 94 | protected void onDestroy() { 95 | super.onDestroy(); 96 | getDelegate().onDestroy(); 97 | } 98 | 99 | public void invalidateOptionsMenu() { 100 | getDelegate().invalidateOptionsMenu(); 101 | } 102 | 103 | private AppCompatDelegate getDelegate() { 104 | if (mDelegate == null) { 105 | mDelegate = AppCompatDelegate.create(this, null); 106 | } 107 | return mDelegate; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/Application.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.os.Build; 4 | import android.support.v7.app.AppCompatDelegate; 5 | 6 | import com.yandex.metrica.YandexMetrica; 7 | public class Application extends android.app.Application { 8 | protected final String AM_KEY = "7968f25e-5d6b-4cb3-be18-069f2bf8bd8b"; 9 | 10 | @Override 11 | public void onCreate() { 12 | super.onCreate(); 13 | 14 | if (!BuildConfig.DEBUG) { 15 | // AppMetrica SDK 16 | YandexMetrica.activate(getApplicationContext(), AM_KEY); 17 | YandexMetrica.enableActivityAutoTracking(this); 18 | 19 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 20 | YandexMetrica.enableActivityAutoTracking(this); 21 | } 22 | } 23 | } 24 | 25 | static { 26 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/ClickListener.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.view.View; 4 | 5 | public interface ClickListener { 6 | void onClick(View view, int position); 7 | 8 | void onLongClick(View view, int position); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/DashboardActivity.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.app.SearchManager; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.content.res.Configuration; 8 | import android.os.Build; 9 | import android.os.Handler; 10 | import android.preference.PreferenceManager; 11 | import android.support.design.widget.TabLayout; 12 | import android.support.v4.view.MenuItemCompat; 13 | import android.support.v4.view.ViewPager; 14 | import android.support.v7.app.ActionBar; 15 | import android.support.v7.app.AppCompatActivity; 16 | import android.os.Bundle; 17 | import android.support.v7.widget.SearchView; 18 | import android.support.v7.widget.Toolbar; 19 | import android.view.Menu; 20 | import android.view.MenuItem; 21 | 22 | import com.x1unix.avi.dashboard.*; 23 | import com.x1unix.avi.model.AviSemVersion; 24 | import com.x1unix.avi.storage.MoviesRepository; 25 | import com.x1unix.avi.updateManager.OTAStateListener; 26 | import com.x1unix.avi.updateManager.OTAUpdateChecker; 27 | 28 | public class DashboardActivity extends AppCompatActivity { 29 | 30 | private Toolbar toolbar; 31 | 32 | private MenuItem searchItem; 33 | 34 | // Menu items 35 | private MenuItem menuItemSettings; 36 | private MenuItem menuItemHelp; 37 | private DashboardFragmentPagerAdapter pageAdapter; 38 | private MoviesRepository moviesRepository; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_dashboard); 44 | 45 | moviesRepository = MoviesRepository.getInstance(this); 46 | 47 | toolbar = (Toolbar) findViewById(R.id.toolbar); 48 | setSupportActionBar(toolbar); 49 | ActionBar actionBar = getSupportActionBar(); 50 | 51 | // Get the ViewPager and set it's PagerAdapter so that it can display items 52 | ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); 53 | 54 | pageAdapter = new DashboardFragmentPagerAdapter(getSupportFragmentManager(), 55 | DashboardActivity.this, moviesRepository); 56 | viewPager.setAdapter(pageAdapter); 57 | 58 | // Give the TabLayout the ViewPager 59 | TabLayout tabLayout = (TabLayout) findViewById(R.id.sliding_tabs); 60 | tabLayout.setupWithViewPager(viewPager); 61 | 62 | // Init background updates check 63 | initBackgroundUpdateCheck(); 64 | } 65 | 66 | @Override 67 | public void onRestart() { 68 | if (pageAdapter != null) { 69 | pageAdapter.triggerRescan(); 70 | } 71 | 72 | super.onRestart(); 73 | } 74 | 75 | @Override 76 | public void onConfigurationChanged(Configuration newConfig) { 77 | super.onConfigurationChanged(newConfig); 78 | pageAdapter.triggerUpdate(); 79 | } 80 | 81 | @Override 82 | public boolean onCreateOptionsMenu(Menu menu) { 83 | getMenuInflater().inflate(R.menu.main_menu, menu); 84 | 85 | 86 | // Import menu items 87 | menuItemSettings = (MenuItem) menu.findItem(R.id.menu_action_settings); 88 | menuItemHelp = (MenuItem) menu.findItem(R.id.menu_action_help); 89 | registerMenuItemsClickListeners(); 90 | 91 | // Retrieve the SearchView and plug it into SearchManager 92 | final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search)); 93 | SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE); 94 | searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); 95 | 96 | searchItem = menu.findItem(R.id.action_search); 97 | 98 | searchView.setQueryHint(getResources().getString(R.string.avi_search_hint)); 99 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 100 | @Override 101 | public boolean onQueryTextChange(String newText) { 102 | // your text view here 103 | // textView.setText(newText); 104 | return false; 105 | } 106 | 107 | @Override 108 | public boolean onQueryTextSubmit(String query) { 109 | performSearch(query); 110 | return false; 111 | } 112 | }); 113 | 114 | return true; 115 | } 116 | 117 | private void performSearch(String query) { 118 | 119 | if (Build.VERSION.SDK_INT > 14) { 120 | searchItem.collapseActionView(); 121 | } 122 | startActivity( 123 | (new Intent(this, SearchActivity.class)).putExtra("query", query) 124 | ); 125 | } 126 | 127 | /** 128 | * Load event handlers for menu buttons in paralel thread 129 | */ 130 | private void registerMenuItemsClickListeners() { 131 | new Handler().postDelayed(new Runnable() { 132 | @Override 133 | public void run() { 134 | menuItemSettings.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { 135 | @Override 136 | public boolean onMenuItemClick(MenuItem item) { 137 | Intent i = new Intent(getBaseContext(), SettingsActivity.class); 138 | startActivity(i); 139 | return false; 140 | } 141 | }); 142 | 143 | menuItemHelp.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { 144 | @Override 145 | public boolean onMenuItemClick(MenuItem item) { 146 | Intent i = new Intent(getBaseContext(), SupportActivity.class); 147 | startActivity(i); 148 | return false; 149 | } 150 | }); 151 | } 152 | }, 100); 153 | } 154 | 155 | private void startUpdate(AviSemVersion newVer) { 156 | startActivity( 157 | new Intent(this, UpdateDownloaderActivity.class) 158 | .putExtra("update", newVer) 159 | ); 160 | } 161 | 162 | private void showUpdateDialog(final AviSemVersion newVer) { 163 | OTAUpdateChecker.makeDialog(this, newVer) 164 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 165 | public void onClick(DialogInterface dialog, int id) { 166 | startUpdate(newVer); 167 | dialog.cancel(); 168 | } 169 | }).show(); 170 | } 171 | 172 | private void initBackgroundUpdateCheck() { 173 | final Context context = getApplicationContext(); 174 | boolean allowNightlies = PreferenceManager. 175 | getDefaultSharedPreferences(context). 176 | getBoolean(getResources().getString(R.string.avi_prop_allow_unstable), false); 177 | 178 | OTAUpdateChecker.checkForUpdates(new OTAStateListener() { 179 | @Override 180 | protected void onUpdateAvailable(AviSemVersion availableVersion, AviSemVersion currentVersion) { 181 | showUpdateDialog(availableVersion); 182 | } 183 | }, allowNightlies); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.content.Intent; 4 | import android.os.Handler; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.os.Bundle; 7 | 8 | import com.rollbar.android.Rollbar; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | protected int SPLASH_DISPLAY_LENGTH = 300; 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | 18 | if (!BuildConfig.DEBUG) { 19 | // Init Rollbar monitor on release 20 | Rollbar.init(this, "b2769f6943314e52be560fe9c2ab7626", "production"); 21 | } 22 | } 23 | 24 | @Override 25 | protected void onPostCreate(Bundle savedInstanceState) { 26 | super.onPostCreate(savedInstanceState); 27 | 28 | new Handler().postDelayed(new Runnable(){ 29 | @Override 30 | public void run() { 31 | Intent mainIntent = new Intent(MainActivity.this, DashboardActivity.class); 32 | MainActivity.this.startActivity(mainIntent); 33 | overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); 34 | MainActivity.this.finish(); 35 | } 36 | }, SPLASH_DISPLAY_LENGTH); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/RecyclerTouchListener.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.GestureDetector; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | 9 | public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener { 10 | private GestureDetector gestureDetector; 11 | private ClickListener clickListener; 12 | 13 | public RecyclerTouchListener(Context context, final RecyclerView recyclerView, final ClickListener clickListener) { 14 | this.clickListener = clickListener; 15 | gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { 16 | @Override 17 | public boolean onSingleTapUp(MotionEvent e) { 18 | return true; 19 | } 20 | 21 | @Override 22 | public void onLongPress(MotionEvent e) { 23 | View child = recyclerView.findChildViewUnder(e.getX(), e.getY()); 24 | if (child != null && clickListener != null) { 25 | clickListener.onLongClick(child, recyclerView.getChildPosition(child)); 26 | } 27 | } 28 | }); 29 | } 30 | 31 | @Override 32 | public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { 33 | 34 | View child = rv.findChildViewUnder(e.getX(), e.getY()); 35 | if (child != null && clickListener != null && gestureDetector.onTouchEvent(e)) { 36 | clickListener.onClick(child, rv.getChildPosition(child)); 37 | } 38 | return false; 39 | } 40 | 41 | @Override 42 | public void onTouchEvent(RecyclerView rv, MotionEvent e) { 43 | } 44 | 45 | @Override 46 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/SearchWidgetProvider.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.app.PendingIntent; 4 | import android.appwidget.AppWidgetManager; 5 | import android.appwidget.AppWidgetProvider; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.widget.RemoteViews; 9 | 10 | public class SearchWidgetProvider extends AppWidgetProvider { 11 | @Override 12 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 13 | for (int i = 0; i < appWidgetIds.length; i++) { 14 | int appWidgetId = appWidgetIds[i]; 15 | 16 | Intent intent = new Intent(context, SearchActivity.class); 17 | PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); 18 | 19 | RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_search); 20 | views.setOnClickPendingIntent(R.id.search_widget_layout, pendingIntent); 21 | appWidgetManager.updateAppWidget(appWidgetId, views); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/SupportActivity.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | import android.view.MenuItem; 6 | import android.webkit.WebView; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Locale; 12 | 13 | public class SupportActivity extends AppCompatActivity { 14 | 15 | private String[] langs = new String[]{"ru", "uk"}; 16 | private List langsList = new ArrayList(); 17 | WebView webView; 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | langsList.addAll(Arrays.asList(langs)); 22 | 23 | setContentView(R.layout.activity_support); 24 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 25 | } 26 | 27 | @Override 28 | protected void onPostCreate(Bundle savedInstanceState) { 29 | super.onPostCreate(savedInstanceState); 30 | 31 | webView = (WebView) findViewById(R.id.webview_support); 32 | Locale cLocale = getResources().getConfiguration().locale; 33 | String cLanguage = cLocale.getLanguage(); 34 | 35 | String fileLangPrefix = (langsList.contains(cLanguage)) ? cLanguage : "en"; 36 | webView.loadUrl("file:///android_asset/help-" + fileLangPrefix + ".html"); 37 | 38 | cLocale = null; 39 | cLanguage = null; 40 | } 41 | 42 | @Override 43 | public boolean onOptionsItemSelected(MenuItem item) { 44 | switch (item.getItemId()) { 45 | case android.R.id.home: 46 | finish(); 47 | return true; 48 | } 49 | 50 | return super.onOptionsItemSelected(item); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/adapter/CachedMoviesListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.adapter; 2 | 3 | 4 | import android.content.Context; 5 | import android.support.v7.widget.GridLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.LinearLayout; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Locale; 18 | 19 | import com.bumptech.glide.Glide; 20 | import com.kinopoisk.Constants; 21 | import com.x1unix.avi.R; 22 | import com.x1unix.avi.model.KPMovie; 23 | import com.x1unix.avi.model.KPMovieItem; 24 | 25 | public class CachedMoviesListAdapter extends RecyclerView.Adapter { 26 | 27 | private ArrayList movies; 28 | private int rowLayout; 29 | private Context context; 30 | private String currentLang = "ru"; 31 | private MovieViewHolder movieViewHolder; 32 | 33 | 34 | public static class MovieViewHolder extends RecyclerView.ViewHolder { 35 | LinearLayout moviesLayout; 36 | TextView movieTitle; 37 | TextView data; 38 | TextView movieDescription; 39 | TextView rating; 40 | ImageView posterView; 41 | String kpId; 42 | Context context; 43 | 44 | 45 | public MovieViewHolder(View v) { 46 | super(v); 47 | moviesLayout = (LinearLayout) v.findViewById(R.id.movies_layout); 48 | movieTitle = (TextView) v.findViewById(R.id.title); 49 | data = (TextView) v.findViewById(R.id.subtitle); 50 | movieDescription = (TextView) v.findViewById(R.id.description); 51 | rating = (TextView) v.findViewById(R.id.rating); 52 | posterView = (ImageView) v.findViewById(R.id.poster_preview); 53 | context = v.getContext(); 54 | } 55 | 56 | public void loadPoster(Context context) { 57 | Glide.with(context) 58 | .load(Constants.getPosterUrl(kpId)) 59 | .placeholder(R.drawable.no_poster) 60 | .into(posterView); 61 | } 62 | } 63 | 64 | public CachedMoviesListAdapter(ArrayList movies, int rowLayout, Context context, Locale currentLocale) { 65 | this.movies = movies; 66 | this.rowLayout = rowLayout; 67 | this.context = context; 68 | this.currentLang = currentLocale.getLanguage(); 69 | } 70 | 71 | @Override 72 | public CachedMoviesListAdapter.MovieViewHolder onCreateViewHolder(ViewGroup parent, 73 | int viewType) { 74 | View view = LayoutInflater.from(parent.getContext()).inflate(rowLayout, parent, false); 75 | return new MovieViewHolder(view); 76 | } 77 | 78 | 79 | @Override 80 | public void onBindViewHolder(MovieViewHolder holder, final int position) { 81 | KPMovie cMovie = movies.get(position); 82 | holder.movieTitle.setText(cMovie.getLocalizedTitle(currentLang)); 83 | holder.data.setText(cMovie.getYear()); 84 | holder.movieDescription.setText(cMovie.getShortDescription()); 85 | holder.rating.setText(String.valueOf(cMovie.getStars())); 86 | holder.kpId = cMovie.getId(); 87 | holder.loadPoster(context); 88 | } 89 | 90 | @Override 91 | public int getItemCount() { 92 | return (movies == null) ? 0 : movies.size(); 93 | } 94 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/adapter/MoviesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.adapter; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.GridLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | import android.widget.Toast; 13 | 14 | import java.util.List; 15 | import java.util.Locale; 16 | 17 | import com.bumptech.glide.Glide; 18 | import com.kinopoisk.Constants; 19 | import com.x1unix.avi.R; 20 | import com.x1unix.avi.model.KPMovieItem; 21 | 22 | public class MoviesAdapter extends RecyclerView.Adapter { 23 | 24 | private List movies; 25 | private int rowLayout; 26 | private Context context; 27 | private String currentLang = "ru"; 28 | 29 | 30 | public static class MovieViewHolder extends RecyclerView.ViewHolder { 31 | LinearLayout moviesLayout; 32 | TextView movieTitle; 33 | TextView data; 34 | TextView movieDescription; 35 | TextView rating; 36 | ImageView posterView; 37 | String kpId; 38 | Context context; 39 | 40 | 41 | public MovieViewHolder(View v) { 42 | super(v); 43 | moviesLayout = (LinearLayout) v.findViewById(R.id.movies_layout); 44 | movieTitle = (TextView) v.findViewById(R.id.title); 45 | data = (TextView) v.findViewById(R.id.subtitle); 46 | movieDescription = (TextView) v.findViewById(R.id.description); 47 | rating = (TextView) v.findViewById(R.id.rating); 48 | posterView = (ImageView) v.findViewById(R.id.poster_preview); 49 | context = v.getContext(); 50 | } 51 | 52 | public void loadPoster(Context context) { 53 | Glide.with(context) 54 | .load(Constants.getPosterUrl(kpId)) 55 | .placeholder(R.drawable.no_poster) 56 | .into(posterView); 57 | } 58 | } 59 | 60 | public MoviesAdapter(List movies, int rowLayout, Context context, Locale currentLocale) { 61 | this.movies = movies; 62 | this.rowLayout = rowLayout; 63 | this.context = context; 64 | this.currentLang = currentLocale.getLanguage(); 65 | 66 | // Show toast message if no items 67 | if (getItemCount() == 0) { 68 | Toast noItemsMsg = Toast.makeText( 69 | context, 70 | context.getResources().getString(R.string.avi_no_items_msg), 71 | Toast.LENGTH_LONG); 72 | noItemsMsg.show(); 73 | } 74 | } 75 | 76 | @Override 77 | public MoviesAdapter.MovieViewHolder onCreateViewHolder(ViewGroup parent, 78 | int viewType) { 79 | View view = LayoutInflater.from(parent.getContext()).inflate(rowLayout, parent, false); 80 | return new MovieViewHolder(view); 81 | } 82 | 83 | 84 | @Override 85 | public void onBindViewHolder(MovieViewHolder holder, final int position) { 86 | KPMovieItem cMovie = movies.get(position); 87 | holder.movieTitle.setText(cMovie.getLocalizedTitle(currentLang)); 88 | holder.data.setText(cMovie.getReleaseDate()); 89 | holder.movieDescription.setText(cMovie.getDescription()); 90 | holder.rating.setText(String.valueOf(cMovie.getVoteAverage())); 91 | holder.kpId = cMovie.getId(); 92 | holder.loadPoster(context); 93 | } 94 | 95 | @Override 96 | public int getItemCount() { 97 | return (movies == null) ? 0 : movies.size(); 98 | } 99 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/dashboard/DashboardFragmentPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.dashboard; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentPagerAdapter; 8 | 9 | import com.x1unix.avi.R; 10 | import com.x1unix.avi.storage.MoviesRepository; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class DashboardFragmentPagerAdapter extends FragmentPagerAdapter { 16 | private final int tabTitles[] = new int[] { R.string.favorites, R.string.viewed}; 17 | private final List fragments = new ArrayList<>(); 18 | private Context context; 19 | private Resources res; 20 | 21 | public DashboardFragmentPagerAdapter(FragmentManager fm, Context context, MoviesRepository aMoviesRepository) { 22 | super(fm); 23 | this.context = context; 24 | this.res = context.getResources(); 25 | 26 | // Add two test fragments 27 | this.fragments.add(FavoritesTabFragment.getInstance(aMoviesRepository)); 28 | this.fragments.add(HistoryTabFragment.getInstance(aMoviesRepository)); 29 | } 30 | 31 | @Override 32 | public int getCount() { 33 | return fragments.size(); 34 | } 35 | 36 | @Override 37 | public Fragment getItem(int position) { 38 | return fragments.get(position); 39 | } 40 | 41 | @Override 42 | public CharSequence getPageTitle(int position) { 43 | // Generate title based on item position 44 | return res.getString(tabTitles[position]); 45 | } 46 | 47 | public void triggerUpdate() { 48 | for (DashboardTabFragment tab: fragments) { 49 | tab.updateLayout(); 50 | } 51 | } 52 | 53 | public void triggerRescan() { 54 | for (DashboardTabFragment tab: fragments) { 55 | tab.rescanElements(); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/dashboard/FavoritesTabFragment.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.dashboard; 2 | 3 | import com.x1unix.avi.R; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import com.x1unix.avi.model.KPMovie; 9 | import com.x1unix.avi.storage.MoviesRepository; 10 | import java.util.ArrayList; 11 | 12 | public class FavoritesTabFragment extends DashboardTabFragment { 13 | 14 | @Override 15 | protected ArrayList getContentItems() { 16 | return moviesRepository.getFavoritesMovies(); 17 | } 18 | 19 | @Override 20 | protected int getTabView() { 21 | return R.layout.tab_favorites; 22 | } 23 | 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | } 28 | 29 | @Override 30 | protected String getPlaylistGenitivusName() { 31 | return getResources().getString(R.string.playlist_genitivus_favorites); 32 | } 33 | 34 | @Override 35 | protected boolean onItemRemoveRequest(KPMovie item) { 36 | moviesRepository.removeFromFavorites(item.getId()); 37 | return true; 38 | } 39 | 40 | @Override 41 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 42 | Bundle savedInstanceState) { 43 | return super.onCreateView(inflater, container, savedInstanceState); 44 | } 45 | 46 | public static DashboardTabFragment getInstance(MoviesRepository m) { 47 | return (new FavoritesTabFragment()).setMoviesRepository(m); 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/dashboard/HistoryTabFragment.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.dashboard; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import com.x1unix.avi.R; 8 | import com.x1unix.avi.model.KPMovie; 9 | import com.x1unix.avi.storage.MoviesRepository; 10 | 11 | import java.util.ArrayList; 12 | 13 | public class HistoryTabFragment extends DashboardTabFragment { 14 | 15 | @Override 16 | protected ArrayList getContentItems() { 17 | return moviesRepository.getViewedMovies(); 18 | } 19 | 20 | @Override 21 | protected int getTabView() { 22 | return R.layout.tab_history; 23 | } 24 | 25 | @Override 26 | public void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | } 29 | 30 | @Override 31 | protected boolean onItemRemoveRequest(KPMovie item) { 32 | moviesRepository.removeFromHistory(item.getId()); 33 | return true; 34 | } 35 | 36 | @Override 37 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 38 | Bundle savedInstanceState) { 39 | return super.onCreateView(inflater, container, savedInstanceState); 40 | } 41 | 42 | public static DashboardTabFragment getInstance(MoviesRepository m) { 43 | return (new HistoryTabFragment()).setMoviesRepository(m); 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/helpers/AdBlocker.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.helpers; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.AsyncTask; 6 | import android.os.Build; 7 | import android.support.annotation.WorkerThread; 8 | import android.text.TextUtils; 9 | import android.util.Log; 10 | import android.webkit.WebResourceResponse; 11 | 12 | import java.io.ByteArrayInputStream; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.net.URI; 16 | import java.net.URISyntaxException; 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | 20 | import okhttp3.HttpUrl; 21 | import okio.BufferedSource; 22 | import okio.Okio; 23 | 24 | public class AdBlocker { 25 | private static final String AD_HOSTS_FILE = "hosts.txt"; 26 | private static final String BAD_URLS_FILE = "urls.txt"; 27 | private static final Set AD_HOSTS = new HashSet<>(); 28 | private static final Set BAD_URS = new HashSet<>(); 29 | 30 | public static void init(final Context context) { 31 | new AsyncTask() { 32 | @Override 33 | protected Void doInBackground(Void... params) { 34 | try { 35 | loadFromAssets(context); 36 | } catch (IOException e) { 37 | // noop 38 | } 39 | return null; 40 | } 41 | }.execute(); 42 | } 43 | 44 | @WorkerThread 45 | private static void loadFromAssets(Context context) throws IOException { 46 | InputStream stream = context.getAssets().open(AD_HOSTS_FILE); 47 | BufferedSource buffer = Okio.buffer(Okio.source(stream)); 48 | String line; 49 | while ((line = buffer.readUtf8Line()) != null) { 50 | AD_HOSTS.add(line); 51 | } 52 | buffer.close(); 53 | stream.close(); 54 | 55 | loadUrlsFromAssets(context); 56 | } 57 | 58 | private static void loadUrlsFromAssets(Context context) throws IOException { 59 | InputStream stream = context.getAssets().open(BAD_URLS_FILE); 60 | BufferedSource buffer = Okio.buffer(Okio.source(stream)); 61 | String line; 62 | while ((line = buffer.readUtf8Line()) != null) { 63 | BAD_URS.add(line); 64 | } 65 | buffer.close(); 66 | stream.close(); 67 | } 68 | 69 | public static boolean isBadUrl(String url) { 70 | return BAD_URS.contains(url); 71 | } 72 | 73 | public static boolean isAd(String url) { 74 | String host; 75 | try { 76 | host = getDomainName(url); 77 | } catch(Exception ex) { 78 | return false; 79 | } 80 | return isBadUrl(url) || isAdHost(host); 81 | } 82 | 83 | public static String getDomainName(String url) throws URISyntaxException { 84 | URI uri = new URI(url); 85 | String domain = uri.getHost(); 86 | return domain.startsWith("www.") ? domain.substring(4) : domain; 87 | } 88 | 89 | private static boolean isAdHost(String host) { 90 | if (TextUtils.isEmpty(host)) { 91 | return false; 92 | } 93 | int index = host.indexOf("."); 94 | return index >= 0 && (AD_HOSTS.contains(host) || 95 | index + 1 < host.length() && isAdHost(host.substring(index + 1))); 96 | } 97 | 98 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 99 | public static WebResourceResponse createEmptyResource() { 100 | return new WebResourceResponse("text/plain", "UTF-8", new ByteArrayInputStream("".getBytes())); 101 | } 102 | 103 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 104 | public static WebResourceResponse createEmptyResource(String type) { 105 | return new WebResourceResponse(type, "UTF-8", new ByteArrayInputStream("".getBytes())); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/helpers/DownloadFileFromURL.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.helpers; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Environment; 5 | import android.util.Log; 6 | 7 | import java.io.BufferedInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | import java.net.URL; 12 | import java.net.URLConnection; 13 | 14 | public class DownloadFileFromURL extends AsyncTask { 15 | private String LOG_TAG = "OTA_Downloader"; 16 | public boolean failed = false; 17 | public String error = ""; 18 | 19 | @Override 20 | protected void onPreExecute() { 21 | super.onPreExecute(); 22 | } 23 | 24 | @Override 25 | protected String doInBackground(String... f_url) { 26 | int count; 27 | try { 28 | URL url = new URL(f_url[0]); 29 | String fileName = f_url[1]; 30 | URLConnection conection = url.openConnection(); 31 | conection.connect(); 32 | // this will be useful so that you can show a tipical 0-100% progress bar 33 | int lenghtOfFile = conection.getContentLength(); 34 | 35 | // download the file 36 | InputStream input = new BufferedInputStream(url.openStream(), 8192); 37 | 38 | // Output stream 39 | OutputStream output = new FileOutputStream(Environment 40 | .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + fileName); 41 | 42 | byte data[] = new byte[1024]; 43 | 44 | long total = 0; 45 | 46 | while ((count = input.read(data)) != -1) { 47 | total += count; 48 | // publishing the progress.... 49 | // After this onProgressUpdate will be called 50 | publishProgress(""+(int)((total*100)/lenghtOfFile)); 51 | 52 | // writing data to file 53 | output.write(data, 0, count); 54 | } 55 | 56 | // flushing output 57 | output.flush(); 58 | 59 | // closing streams 60 | output.close(); 61 | input.close(); 62 | 63 | } catch (Exception e) { 64 | Log.e(LOG_TAG, e.getMessage()); 65 | failed = true; 66 | error = e.getMessage(); 67 | } 68 | 69 | return null; 70 | } 71 | 72 | protected void onError(String error) {} 73 | 74 | protected void onProgressUpdate(String... progress) {} 75 | 76 | @Override 77 | protected void onPostExecute(String file_url) {} 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/helpers/PermissionHelper.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.helpers; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.annotation.TargetApi; 6 | import android.app.Activity; 7 | import android.content.pm.PackageManager; 8 | import android.support.v4.app.ActivityCompat; 9 | import android.support.v4.content.ContextCompat; 10 | 11 | import java.lang.annotation.Target; 12 | 13 | /** 14 | * This file contains permission helpers for Android 7+ 15 | */ 16 | 17 | public class PermissionHelper { 18 | // Storage Permissions 19 | public static final int REQUEST_EXTERNAL_STORAGE = 1; 20 | 21 | @SuppressLint("NewApi") 22 | private static String[] PERMISSIONS_STORAGE = { 23 | Manifest.permission.READ_EXTERNAL_STORAGE, 24 | Manifest.permission.WRITE_EXTERNAL_STORAGE 25 | }; 26 | 27 | /** 28 | * Checks if the app has permission to write to device storage 29 | * 30 | * If the app does not has permission then the user will be prompted to grant permissions 31 | * 32 | * @param activity 33 | */ 34 | public static void verifyStoragePermissions(Activity activity) { 35 | // Check if we have write permission 36 | int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 37 | 38 | if (permission != PackageManager.PERMISSION_GRANTED) { 39 | // We don't have permission so prompt the user 40 | ActivityCompat.requestPermissions( 41 | activity, 42 | PERMISSIONS_STORAGE, 43 | REQUEST_EXTERNAL_STORAGE 44 | ); 45 | } 46 | } 47 | 48 | public static boolean hasPermissions(Activity activity, String permission) { 49 | return (ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED); 50 | } 51 | 52 | public static boolean hasWritePermission(Activity activity) { 53 | return hasPermissions(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/AviSemVersion.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.gson.annotations.SerializedName; 6 | import com.x1unix.avi.BuildConfig; 7 | import com.x1unix.avi.updateManager.ISO8601; 8 | 9 | import java.io.Serializable; 10 | import java.text.ParseException; 11 | import java.util.Calendar; 12 | import java.util.regex.Pattern; 13 | 14 | public class AviSemVersion implements Serializable{ 15 | @SerializedName("version") 16 | protected String original = "0.0.0"; 17 | 18 | @SerializedName("stable") 19 | protected boolean stable = true; 20 | 21 | @SerializedName("tag") 22 | protected String tag; 23 | 24 | @SerializedName("date") 25 | protected String date; 26 | 27 | @SerializedName("downs") 28 | protected int downloadsCount = 0; 29 | 30 | @SerializedName("apk") 31 | protected String apkUrl; 32 | 33 | @SerializedName("homepage") 34 | protected String homepage; 35 | 36 | @SerializedName("changelog") 37 | protected String changelog; 38 | 39 | public AviSemVersion(String semVerString, boolean isStable, String tag, String date, 40 | int downs, String apkUrl, String homepage) { 41 | this.original = semVerString; 42 | this.stable = isStable; 43 | this.tag = tag; 44 | this.date = date; 45 | this.downloadsCount = downs; 46 | this.apkUrl = apkUrl; 47 | this.homepage = homepage; 48 | } 49 | 50 | public AviSemVersion(String semVerString) { 51 | this.original = semVerString; 52 | } 53 | 54 | public String getApkUrl() { 55 | return this.apkUrl; 56 | } 57 | 58 | public String getHomePageUrl() { 59 | return this.homepage; 60 | } 61 | 62 | public String toString() { 63 | return this.original; 64 | } 65 | 66 | public Calendar getReleaseDate() throws ParseException { 67 | return ISO8601.toCalendar(this.date); 68 | } 69 | 70 | public boolean hasChangelog() { 71 | return this.changelog != null; 72 | } 73 | public String getChangelog() { 74 | return this.changelog; 75 | } 76 | 77 | public String getTag() { 78 | return this.tag; 79 | } 80 | 81 | public static AviSemVersion getApplicationVersion() { 82 | return new AviSemVersion(BuildConfig.VERSION_NAME); 83 | } 84 | 85 | public boolean isStable() { 86 | return stable; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/KPMovie.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class KPMovie { 9 | @SerializedName("filmID") 10 | private String filmID; 11 | 12 | @SerializedName("nameRU") 13 | private String nameRU; 14 | 15 | @SerializedName("nameEN") 16 | private String nameEN; 17 | 18 | @SerializedName("year") 19 | private String year; 20 | 21 | @SerializedName("filmLength") 22 | private String filmLength; 23 | 24 | @SerializedName("county") 25 | private String country; 26 | 27 | @SerializedName("genre") 28 | private String genre; 29 | 30 | @SerializedName("description") 31 | private String description; 32 | 33 | @SerializedName("ratingMPAA") 34 | private String ratingMPAA; 35 | 36 | @SerializedName("ratingAgeLimits") 37 | private String ratingAgeLimits; 38 | 39 | @SerializedName("type") 40 | private String type; 41 | 42 | @SerializedName("creators") 43 | private List creators; 44 | 45 | // Values stored in db 46 | private String shortDescription = ""; 47 | 48 | private String stars = "5.0"; 49 | 50 | public KPMovie(String ifilmID, String inameRU, String inameEN, String iyear, String ifilmLength, 51 | String icountry, String igenre, String idescription, String iratingMPAA, 52 | String iratingAgeLimits, String itype, List icreators, 53 | String ishortDescription, String istars) { 54 | filmID = ifilmID; 55 | nameRU = inameRU; 56 | nameEN = inameEN; 57 | year = iyear; 58 | filmLength = ifilmLength; 59 | country = icountry; 60 | genre = igenre; 61 | description = idescription; 62 | ratingMPAA = iratingMPAA; 63 | ratingAgeLimits = iratingAgeLimits; 64 | type = itype; 65 | creators = icreators; 66 | shortDescription = ishortDescription; 67 | stars = istars; 68 | } 69 | 70 | public String getId() { 71 | return filmID; 72 | } 73 | 74 | public String getYear() { 75 | return year; 76 | } 77 | 78 | public String getFilmLength() { 79 | return filmLength; 80 | } 81 | 82 | public String getCountry() { 83 | return country; 84 | } 85 | 86 | public String getDescription() { 87 | return (description == null) ? "" : description; 88 | } 89 | 90 | public String getGenre() { 91 | return genre; 92 | } 93 | 94 | public String getRatingMPAA() { 95 | String value; 96 | if (ratingMPAA != null) { 97 | value = ratingMPAA + " (" + ratingAgeLimits + "+)"; 98 | } else if (ratingAgeLimits != null) { 99 | value = ratingAgeLimits; 100 | } else { 101 | value = "-"; 102 | } 103 | return value; 104 | } 105 | 106 | public String getTrueRatingMPAA() { 107 | return ratingMPAA; 108 | } 109 | 110 | public String getRatingAgeLimits() { 111 | return ratingAgeLimits; 112 | } 113 | 114 | public String getType() { 115 | return type; 116 | } 117 | 118 | public KPPeople[] getDirectors() { 119 | KPPeople[] result; 120 | boolean found = false; 121 | if ((creators == null) || (creators.size() == 0)) { 122 | result = new KPPeople[]{}; 123 | } else { 124 | found = true; 125 | result = creators.get(0); 126 | } 127 | return result; 128 | } 129 | 130 | public KPPeople[] getActors() { 131 | KPPeople[] result; 132 | if ((creators == null) || (creators.size() < 2)) { 133 | result = new KPPeople[]{}; 134 | } else { 135 | result = creators.get(1); 136 | } 137 | return result; 138 | } 139 | 140 | public KPPeople[] getProducers() { 141 | KPPeople[] result; 142 | if ((creators == null) || (creators.size() < 3)) { 143 | result = new KPPeople[]{}; 144 | } else { 145 | result = creators.get(2); 146 | } 147 | return result; 148 | } 149 | 150 | public String getNameRU() { 151 | return this.nameRU; 152 | } 153 | 154 | public String getNameEN() { 155 | return this.nameEN; 156 | } 157 | 158 | public List getCreators() { 159 | return creators; 160 | } 161 | 162 | public KPMovie setStars(String istars) { 163 | stars = istars; 164 | return this; 165 | } 166 | 167 | public KPMovie setShortDescription(String idescription) { 168 | shortDescription = idescription; 169 | return this; 170 | } 171 | 172 | public String getLocalizedTitle(String currentLocale) { 173 | Boolean isSlavic = ( currentLocale.equals("ru") || currentLocale.equals("uk") ); 174 | Boolean isSlavicAvailable = (nameRU != null) && (nameRU.length() > 0); 175 | Boolean isLatinAvailable = (nameEN != null) && (nameEN.length() > 0); 176 | 177 | String title = nameRU; 178 | 179 | if (isSlavic) { 180 | if (isSlavicAvailable) { 181 | title = nameRU; 182 | } else { 183 | title = nameEN; 184 | } 185 | } else { 186 | if (isLatinAvailable) { 187 | title = nameEN; 188 | } else { 189 | title = nameRU; 190 | } 191 | } 192 | 193 | return title; 194 | } 195 | 196 | public String getStars() { 197 | return (stars == null) ? "5.0" : stars; 198 | } 199 | 200 | public String getShortDescription() { 201 | return shortDescription; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/KPMovieDetailViewResponse.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ascii on 27.11.2016. 7 | */ 8 | 9 | public class KPMovieDetailViewResponse { 10 | @SerializedName("resultCode") 11 | protected int resultCode = 0; 12 | 13 | @SerializedName("message") 14 | protected String message = ""; 15 | 16 | @SerializedName("data") 17 | protected KPMovie data; 18 | 19 | public KPMovieDetailViewResponse(int code, String msg, KPMovie res) { 20 | resultCode = code; 21 | message = msg; 22 | data = res; 23 | }; 24 | 25 | public KPMovie getResult() { 26 | return data; 27 | } 28 | 29 | public String getMessage() { 30 | return (message == null) ? "" : message; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/KPMovieItem.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import android.util.Log; 4 | 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class KPMovieItem { 11 | @SerializedName("id") 12 | public String id; 13 | 14 | @SerializedName("nameRU") 15 | public String nameRu; 16 | 17 | @SerializedName("nameEN") 18 | public String nameEn; 19 | 20 | @SerializedName("description") 21 | public String description; 22 | 23 | @SerializedName("posterURL") 24 | public String posterUrl; 25 | 26 | @SerializedName("year") 27 | public String year; 28 | 29 | @SerializedName("filmLength") 30 | public String duration; 31 | 32 | @SerializedName("county") 33 | public String country; 34 | 35 | @SerializedName("genre") 36 | public String genre; 37 | 38 | @SerializedName("rating") 39 | public String rating; 40 | 41 | public KPMovieItem(String id, String nameRu, String nameEn, String description, String posterUrl, 42 | String year, String duration, String country, String genre, String rating) { 43 | this.id = id; 44 | this.nameRu = nameRu; 45 | this.nameEn = nameEn; 46 | this.description = description; 47 | this.posterUrl = posterUrl; 48 | this.year = year; 49 | this.duration = duration; 50 | this.country = country; 51 | this.genre = genre; 52 | this.rating = rating; 53 | } 54 | 55 | public String getTitle() { 56 | if((this.nameRu == null) || (this.nameRu.length() == 0)) { 57 | return this.nameEn; 58 | } else { 59 | return this.nameRu; 60 | } 61 | } 62 | 63 | public String getLocalizedTitle(String currentLocale) { 64 | Boolean isSlavic = ( currentLocale.equals("ru") || currentLocale.equals("uk") ); 65 | Boolean isSlavicAvailable = (nameRu != null) && (nameRu.length() > 0); 66 | Boolean isLatinAvailable = (nameEn != null) && (nameEn.length() > 0); 67 | 68 | if (isSlavic) { 69 | if (isSlavicAvailable) { 70 | return nameRu; 71 | } else { 72 | return nameEn; 73 | } 74 | } else { 75 | if (isLatinAvailable) { 76 | return nameEn; 77 | } else { 78 | return nameRu; 79 | } 80 | } 81 | } 82 | 83 | public String getReleaseDate() { 84 | return this.year; 85 | } 86 | 87 | public String getDescription() { 88 | return this.description; 89 | } 90 | 91 | public String getGenre() { 92 | return (this.genre == null) ? "" : this.genre; 93 | } 94 | public String getRating() { 95 | return (this.rating == null) ? "" : this.rating; 96 | } 97 | 98 | public double getVoteAverage() { 99 | if (this.rating == null) return 0; 100 | String splited[] = this.rating.split(" "); 101 | double val = 0; 102 | try { 103 | val = (splited.length > 0) ? Double.parseDouble(splited[0]) : 0; 104 | } catch(Exception ex) { 105 | val = 0; 106 | } 107 | return val; 108 | } 109 | 110 | public String getId() { 111 | return this.id; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/KPMovieSearchResult.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class KPMovieSearchResult { 9 | @SerializedName("keyword") 10 | private String keyword; 11 | 12 | @SerializedName("pagesCount") 13 | private String pagesTotal; 14 | 15 | @SerializedName("searchFilms") 16 | private List results = new ArrayList(); 17 | 18 | public KPMovieSearchResult(String keyword, String pagesTotal, String itemsTotal, List results) { 19 | this.keyword = keyword; 20 | this.pagesTotal = pagesTotal; 21 | this.results = results; 22 | } 23 | 24 | public int getTotalPages() { 25 | return Integer.parseInt(this.pagesTotal); 26 | } 27 | 28 | public void setTotalPages(String total) { 29 | this.pagesTotal = total; 30 | } 31 | 32 | public void setKeyword(String keyword) { 33 | this.keyword = keyword; 34 | } 35 | 36 | public void setResults(List items) { 37 | this.results = items; 38 | } 39 | 40 | public List getResults() { 41 | return this.results; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/KPPeople.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class KPPeople { 6 | @SerializedName("id") 7 | private String id; 8 | 9 | @SerializedName("type") 10 | private String type; 11 | 12 | @SerializedName("nameRU") 13 | private String nameRU; 14 | 15 | @SerializedName("nameEN") 16 | private String nameEN; 17 | 18 | @SerializedName("professionKey") 19 | private String professionKey; 20 | 21 | 22 | 23 | public KPPeople(String iid, String itype, String inameRU, String inameEN, String iprofessionKey) { 24 | id = iid; 25 | type = itype; 26 | nameRU = inameRU; 27 | nameEN = inameEN; 28 | professionKey = iprofessionKey; 29 | } 30 | 31 | public String getName(String currentLocale) { 32 | String value; 33 | 34 | try { 35 | Boolean isSlavic = ( currentLocale.equals("ru") || currentLocale.equals("uk") ); 36 | Boolean isSlavicAvailable = (nameRU != null) && (nameRU.length() > 0); 37 | Boolean isLatinAvailable = (nameEN != null) && (nameEN.length() > 0); 38 | 39 | 40 | if (isSlavic) { 41 | if (isSlavicAvailable) { 42 | value = nameRU; 43 | } else { 44 | value = nameEN; 45 | } 46 | } else { 47 | if (isLatinAvailable) { 48 | value = nameEN; 49 | } else { 50 | value = nameRU; 51 | } 52 | } 53 | } catch (Exception ex) { 54 | value = null; 55 | } 56 | 57 | return value; 58 | } 59 | 60 | public String getId() { 61 | return id; 62 | } 63 | 64 | public String getType() { 65 | return type; 66 | } 67 | 68 | public String getRole() { 69 | return (professionKey == null) ? "unknown" : professionKey; 70 | } 71 | 72 | public boolean isActor() { 73 | return getRole().equals("actor"); 74 | } 75 | 76 | public boolean isDirector() { 77 | return getRole().equals("director"); 78 | } 79 | 80 | public boolean isProducer() { 81 | return getRole().equals("producer"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/KPResponse.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ascii on 27.11.2016. 7 | */ 8 | 9 | public class KPResponse { 10 | @SerializedName("resultCode") 11 | protected int resultCode = 0; 12 | 13 | @SerializedName("message") 14 | protected String message = ""; 15 | 16 | 17 | protected Object data; 18 | 19 | public KPResponse(int code, String message) { 20 | this.message = message; 21 | this.resultCode = code; 22 | } 23 | 24 | public Object getResult() { 25 | return data; 26 | } 27 | 28 | public String getMessage() { 29 | return (message == null) ? "" : message; 30 | } 31 | 32 | public int getResultCode() { 33 | return resultCode; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/model/KPSearchResponse.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by ascii on 25.11.2016. 7 | */ 8 | 9 | public class KPSearchResponse { 10 | @SerializedName("resultCode") 11 | protected int resultCode = 0; 12 | 13 | @SerializedName("message") 14 | protected String message = ""; 15 | 16 | @SerializedName("data") 17 | protected KPMovieSearchResult data; 18 | 19 | public KPSearchResponse(int code, String msg, KPMovieSearchResult res) { 20 | resultCode = code; 21 | message = msg; 22 | data = res; 23 | } 24 | 25 | public KPSearchResponse() {} 26 | 27 | public KPMovieSearchResult getData() { 28 | return data; 29 | } 30 | 31 | public String getMessage() { 32 | return message; 33 | } 34 | 35 | public int getResultCode() { 36 | return resultCode; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/rest/KPApiInterface.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.rest; 2 | 3 | import com.x1unix.avi.model.KPMovieDetailViewResponse; 4 | import com.x1unix.avi.model.KPSearchResponse; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.Path; 9 | import retrofit2.http.Query; 10 | 11 | 12 | public interface KPApiInterface { 13 | @GET("getKPSearchInFilms") 14 | Call findMovies(@Query("keyword") String keyword); 15 | 16 | @GET("getKPFilmDetailView") 17 | Call getMovieById(@Query("filmID") String filmId); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/rest/KPRestClient.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.rest; 2 | 3 | import retrofit2.Retrofit; 4 | 5 | import com.kinopoisk.*; 6 | 7 | public class KPRestClient { 8 | public static final String BASE_URL = "https://ext.kinopoisk.ru/ios/3.11.0/"; 9 | private static Retrofit retrofit = null; 10 | 11 | public static Retrofit getClient() { 12 | if (retrofit == null) { 13 | retrofit = (new NetworkApiFactory()).getClient(); 14 | } 15 | return retrofit; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/storage/DBHelper.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.storage; 2 | 3 | 4 | import android.content.Context; 5 | import android.database.sqlite.SQLiteDatabase; 6 | import android.database.sqlite.SQLiteOpenHelper; 7 | 8 | class DBHelper extends SQLiteOpenHelper { 9 | 10 | public DBHelper(Context context) { 11 | super(context, "avi", null, 1); 12 | } 13 | 14 | @Override 15 | public void onCreate(SQLiteDatabase db) { 16 | // Init db 17 | createMovies(db); 18 | createFavorites(db); 19 | createViewed(db); 20 | } 21 | 22 | private void createMovies(SQLiteDatabase db) { 23 | db.execSQL("CREATE TABLE movies (\n" + 24 | " filmID INT PRIMARY KEY\n" + 25 | " UNIQUE\n" + 26 | " NOT NULL,\n" + 27 | " nameRU VARCHAR (128) DEFAULT \"\",\n" + 28 | " nameEN VARCHAR (128) DEFAULT \"\",\n" + 29 | " year VARCHAR (12) DEFAULT \"\",\n" + 30 | " filmLength VARCHAR (10) DEFAULT 0,\n" + 31 | " country VARCHAR (48) DEFAULT \"\",\n" + 32 | " genre VARCHAR (64) DEFAULT \"\",\n" + 33 | " description TEXT DEFAULT \"\",\n" + 34 | " ratingMPAA VARCHAR (10) DEFAULT \"\",\n" + 35 | " ratingAgeLimits VARCHAR (5) DEFAULT \"\",\n" + 36 | " type VARCHAR (32) DEFAULT \"\",\n" + 37 | " creators BLOB DEFAULT \"\",\n" + 38 | " shortDescription VARCHAR (64) DEFAULT \"\",\n" + 39 | " stars VARCHAR (10) DEFAULT \"5.0\"" + 40 | ");"); 41 | } 42 | 43 | private void createFavorites(SQLiteDatabase db) { 44 | db.execSQL("CREATE TABLE favorites (\n" + 45 | " filmID INT PRIMARY KEY\n" + 46 | " REFERENCES movies (filmID) \n" + 47 | " NOT NULL\n" + 48 | " UNIQUE\n" + 49 | ");\n"); 50 | } 51 | 52 | private DBHelper createViewed(SQLiteDatabase db) { 53 | db.execSQL("CREATE TABLE viewed (\n" + 54 | " filmID INT PRIMARY KEY\n" + 55 | " REFERENCES movies (filmID) \n" + 56 | " NOT NULL\n" + 57 | " UNIQUE,\n" + 58 | " lastViewed BIGINT DEFAULT (0)" + 59 | ");\n"); 60 | 61 | return this; 62 | } 63 | 64 | @Override 65 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 66 | 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/storage/MoviesRepository.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.storage; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.util.Log; 8 | 9 | import com.google.gson.Gson; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.x1unix.avi.model.KPMovie; 12 | import com.x1unix.avi.model.KPPeople; 13 | 14 | import java.lang.reflect.Type; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public class MoviesRepository { 19 | 20 | public static final String TABLE_MOVIES = "movies"; 21 | public static final String TABLE_FAVORITES = "favorites"; 22 | public static final String TABLE_VIEWED = "viewed"; 23 | 24 | private static final String LOG_TAG = "MoviesRepository"; 25 | public static final Type MOVIE_TYPE = new TypeToken>() { 26 | }.getType(); 27 | 28 | private Context context; 29 | private DBHelper dbHelper; 30 | private SQLiteDatabase db; 31 | private Boolean connected = false; 32 | 33 | public MoviesRepository(Context context) { 34 | this.context = context; 35 | dbHelper = new DBHelper(context); 36 | connect(); 37 | } 38 | 39 | public MoviesRepository addMovie(KPMovie movie) { 40 | ContentValues cv = new ContentValues(); 41 | 42 | // Write basic fields 43 | cv.put("filmID", movie.getId()); 44 | cv.put("nameRU", movie.getNameRU()); 45 | cv.put("nameEN", movie.getNameEN()); 46 | cv.put("year", movie.getYear()); 47 | cv.put("filmLength", movie.getFilmLength()); 48 | cv.put("country", movie.getCountry()); 49 | cv.put("genre", movie.getGenre()); 50 | cv.put("description", movie.getDescription()); 51 | cv.put("ratingMPAA", movie.getTrueRatingMPAA()); 52 | cv.put("ratingAgeLimits", movie.getRatingAgeLimits()); 53 | cv.put("type", movie.getType()); 54 | cv.put("shortDescription", movie.getShortDescription()); 55 | cv.put("stars", movie.getStars()); 56 | 57 | // Serialize data to JSON and save as BLOB 58 | cv.put("creators", (new Gson()).toJson(movie.getCreators())); 59 | 60 | // Write movie to db 61 | long rowID = db.insert(MoviesRepository.TABLE_MOVIES, null, cv); 62 | Log.d(MoviesRepository.LOG_TAG, "Movie row inserted, ID: " + rowID); 63 | 64 | return this; 65 | } 66 | 67 | public boolean movieExists(String kpId) { 68 | String query = "SELECT * FROM " + MoviesRepository.TABLE_MOVIES + " where filmID = " + kpId + ";"; 69 | Cursor cursor = db.rawQuery(query, null); 70 | 71 | boolean exists = (cursor.getCount() > 0); 72 | cursor.close(); 73 | 74 | return exists; 75 | } 76 | 77 | public boolean movieHistoryExists(String kpId) { 78 | String query = "SELECT * FROM " + MoviesRepository.TABLE_VIEWED + " where filmID = " + kpId + ";"; 79 | Cursor cursor = db.rawQuery(query, null); 80 | 81 | boolean exists = (cursor.getCount() > 0); 82 | cursor.close(); 83 | 84 | return exists; 85 | } 86 | 87 | public void addItemToHistory(String kpId) { 88 | ContentValues cv = new ContentValues(); 89 | cv.put("filmID", kpId); 90 | cv.put("lastViewed", getCurrentTimeStamp()); 91 | 92 | long rowID = db.insert(MoviesRepository.TABLE_VIEWED, null, cv); 93 | Log.d(MoviesRepository.LOG_TAG, "Movie added to history #" + kpId); 94 | } 95 | 96 | public void updateItemHistory(String kpId) { 97 | ContentValues cv = new ContentValues(); 98 | cv.put("filmID", kpId); 99 | cv.put("lastViewed", getCurrentTimeStamp()); 100 | 101 | db.update(MoviesRepository.TABLE_VIEWED, cv, "filmID="+kpId, null); 102 | } 103 | 104 | private long getCurrentTimeStamp() { 105 | return System.currentTimeMillis()/1000; 106 | } 107 | 108 | private String getSelect() { 109 | return "SELECT * FROM " + MoviesRepository.TABLE_MOVIES; 110 | } 111 | 112 | public KPMovie getMovieById(String filmId) { 113 | KPMovie movie = null; 114 | String query = getSelect() + " where filmID = " + filmId + ";"; 115 | Cursor c = db.rawQuery(query, null); 116 | 117 | try { 118 | if ((c != null) && c.moveToFirst()) { 119 | movie = MoviesRepository.getMovieFromCursor(c); 120 | } 121 | } catch (Exception e) { 122 | Log.e(LOG_TAG, "Failed to get movie from cursor, query: \n" + query + "\n Message: " + e.getMessage()); 123 | movie = null; 124 | } 125 | 126 | if (c != null) c.close(); 127 | 128 | return movie; 129 | } 130 | 131 | public boolean isInFavorites(String kpId) { 132 | String query = "SELECT * FROM " + MoviesRepository.TABLE_FAVORITES + " where filmID = " + kpId + ";"; 133 | Cursor cursor = db.rawQuery(query, null); 134 | 135 | boolean exists = (cursor.getCount() > 0); 136 | cursor.close(); 137 | 138 | return exists; 139 | } 140 | 141 | public boolean removeFromFavorites(String kpId) { 142 | return db.delete(MoviesRepository.TABLE_FAVORITES, "filmID = " + kpId, null) > 0; 143 | } 144 | 145 | public boolean removeFromHistory(String kpId) { 146 | return db.delete(MoviesRepository.TABLE_VIEWED, "filmID = " + kpId, null) > 0; 147 | } 148 | 149 | public void addToFavorites(String kpId) { 150 | ContentValues cv = new ContentValues(); 151 | cv.put("filmID", kpId); 152 | 153 | long rowID = db.insert(MoviesRepository.TABLE_FAVORITES, null, cv); 154 | Log.d(MoviesRepository.LOG_TAG, "Add to favorites, ID: " + rowID); 155 | } 156 | 157 | private ArrayList getMoviesFromPlaylist(String playlistName, String extraQuery) { 158 | ArrayList result = new ArrayList(); 159 | 160 | if (extraQuery == null) extraQuery = ""; 161 | 162 | String q = "select m.*\n" + 163 | " from movies m\n" + 164 | " join " + playlistName + " f\n" + 165 | " on m.filmID = f.filmID " + extraQuery + ";"; 166 | 167 | Cursor c = db.rawQuery(q, null); 168 | 169 | if (c.moveToFirst()) { 170 | do { 171 | result.add(MoviesRepository.getMovieFromCursor(c)); 172 | } while(c.moveToNext()); 173 | } 174 | 175 | if (c != null && !c.isClosed()){ 176 | c.close(); 177 | } 178 | 179 | return result; 180 | } 181 | 182 | public ArrayList getFavoritesMovies() { 183 | return getMoviesFromPlaylist(TABLE_FAVORITES, null); 184 | } 185 | 186 | public ArrayList getViewedMovies() { 187 | return getMoviesFromPlaylist(TABLE_VIEWED, "order by f.lastViewed DESC limit 80"); 188 | } 189 | 190 | public void close() { 191 | connected = false; 192 | db.close(); 193 | } 194 | 195 | public void connect() { 196 | db = dbHelper.getWritableDatabase(); 197 | connected = true; 198 | } 199 | 200 | private static String getStringValue(String value, Cursor c) { 201 | return c.getString(c.getColumnIndex(value)); 202 | } 203 | 204 | public static KPMovie getMovieFromCursor(Cursor c) { 205 | String cr = getStringValue("creators", c); 206 | 207 | List p = (new Gson()).fromJson(cr, MOVIE_TYPE); 208 | 209 | return new KPMovie( 210 | getStringValue("filmID", c), 211 | getStringValue("nameRU", c), 212 | getStringValue("nameEN", c), 213 | getStringValue("year", c), 214 | getStringValue("filmLength", c), 215 | getStringValue("country", c), 216 | getStringValue("genre", c), 217 | getStringValue("description", c), 218 | getStringValue("ratingMPAA", c), 219 | getStringValue("ratingAgeLimits", c), 220 | getStringValue("type", c), 221 | p, 222 | getStringValue("shortDescription", c), 223 | getStringValue("stars", c) 224 | ); 225 | } 226 | 227 | public static MoviesRepository getInstance(Context context) { 228 | return new MoviesRepository(context); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/updateManager/BuildParser.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.updateManager; 2 | 3 | import org.apache.commons.lang3.time.DateUtils; 4 | import com.x1unix.avi.BuildConfig; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | 8 | public class BuildParser { 9 | public static boolean compareBuild(Calendar newCal) { 10 | int build = BuildConfig.VERSION_CODE; 11 | boolean isNew = false; 12 | 13 | try { 14 | Calendar cal = BuildParser.getBuildDate(); 15 | 16 | long time = cal.getTime().getTime(); 17 | long timeNew = newCal.getTime().getTime(); 18 | 19 | isNew = (timeNew > time); 20 | } catch(Exception ex) { 21 | isNew = true; 22 | } 23 | 24 | return isNew; 25 | 26 | } 27 | 28 | public static Calendar getBuildDate() { 29 | int build = BuildConfig.VERSION_CODE; 30 | String buildString = String.valueOf(build); 31 | int year = Integer.valueOf(buildString.substring(0, 4)); 32 | int month = Integer.valueOf(buildString.substring(4, 6)); 33 | int day = Integer.valueOf(buildString.substring(6, 8)); 34 | 35 | if (month > 0) month--; 36 | 37 | buildString = null; 38 | 39 | Calendar cal = Calendar.getInstance(); 40 | cal.set(Calendar.YEAR, year); 41 | cal.set(Calendar.MONTH, month); 42 | cal.set(Calendar.DAY_OF_MONTH, day); 43 | 44 | return cal; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/updateManager/ISO8601.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.updateManager; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | import java.util.GregorianCalendar; 8 | 9 | /** 10 | * Helper class for handling a most common subset of ISO 8601 strings 11 | * (in the following format: "2008-03-01T13:00:00+01:00"). It supports 12 | * parsing the "Z" timezone, but many other less-used features are 13 | * missing. 14 | */ 15 | public final class ISO8601 { 16 | /** Transform Calendar to ISO 8601 string. */ 17 | public static String fromCalendar(final Calendar calendar) { 18 | Date date = calendar.getTime(); 19 | String formatted = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") 20 | .format(date); 21 | return formatted.substring(0, 22) + ":" + formatted.substring(22); 22 | } 23 | 24 | /** Get current date and time formatted as ISO 8601 string. */ 25 | public static String now() { 26 | return fromCalendar(GregorianCalendar.getInstance()); 27 | } 28 | 29 | /** Transform ISO 8601 string to Calendar. */ 30 | public static Calendar toCalendar(final String iso8601string) 31 | throws ParseException { 32 | Calendar calendar = GregorianCalendar.getInstance(); 33 | String s = iso8601string.replace("Z", "+00:00"); 34 | try { 35 | s = s.substring(0, 22) + s.substring(23); // to get rid of the ":" 36 | } catch (IndexOutOfBoundsException e) { 37 | throw new ParseException("Invalid length", 0); 38 | } 39 | Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(s); 40 | calendar.setTime(date); 41 | return calendar; 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/updateManager/OTARepoClientInterface.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.updateManager; 2 | 3 | import com.x1unix.avi.model.AviSemVersion; 4 | 5 | import retrofit2.Call; 6 | import retrofit2.http.GET; 7 | 8 | public interface OTARepoClientInterface { 9 | @GET("repo/latest-release/") 10 | Call getLatestRelease(); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/updateManager/OTARestClient.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.updateManager; 2 | 3 | 4 | import retrofit2.Retrofit; 5 | import retrofit2.converter.gson.GsonConverterFactory; 6 | 7 | public class OTARestClient { 8 | public static final String BASE_URL = "http://avi.x1unix.com"; 9 | private static Retrofit retrofit = null; 10 | 11 | public static Retrofit getClient() { 12 | if (retrofit==null) { 13 | retrofit = new Retrofit.Builder() 14 | .baseUrl(BASE_URL) 15 | .addConverterFactory(GsonConverterFactory.create()) 16 | .build(); 17 | } 18 | return retrofit; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/updateManager/OTAStateListener.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.updateManager; 2 | 3 | import com.x1unix.avi.model.AviSemVersion; 4 | 5 | public class OTAStateListener { 6 | protected void onUpdateAvailable(AviSemVersion availableVersion, AviSemVersion currentVersion) { 7 | 8 | } 9 | 10 | protected void onUpdateMissing(AviSemVersion availableVersion, AviSemVersion currentVersion) { 11 | 12 | } 13 | 14 | protected void onError(Throwable t) { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/updateManager/OTAUpdateChecker.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.updateManager; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.content.res.Resources; 9 | import android.net.Uri; 10 | 11 | import retrofit2.Call; 12 | import retrofit2.Callback; 13 | import retrofit2.Response; 14 | 15 | import com.rollbar.android.Rollbar; 16 | import com.x1unix.avi.R; 17 | import com.x1unix.avi.UpdateDownloaderActivity; 18 | import com.x1unix.avi.model.AviSemVersion; 19 | 20 | public class OTAUpdateChecker { 21 | public static void checkForUpdates(final OTAStateListener otaEventListener, final boolean allowNightlies) { 22 | OTARepoClientInterface repoClient = OTARestClient.getClient().create(OTARepoClientInterface.class); 23 | Call call = repoClient.getLatestRelease(); 24 | call.enqueue(new Callback() { 25 | @Override 26 | public void onResponse(Callcall, Response response) { 27 | int statusCode = response.code(); 28 | try { 29 | AviSemVersion receivedVersion = response.body(); 30 | AviSemVersion current = AviSemVersion.getApplicationVersion(); 31 | 32 | boolean isNew = false; 33 | 34 | try { 35 | isNew = BuildParser.compareBuild(receivedVersion.getReleaseDate()); 36 | } catch(Exception ex) { 37 | isNew = true; 38 | } 39 | 40 | boolean isStable = receivedVersion.isStable(); 41 | boolean isSuitable = (isStable || allowNightlies); 42 | 43 | if (isNew && isSuitable) { 44 | otaEventListener.onUpdateAvailable(receivedVersion, current); 45 | } else { 46 | otaEventListener.onUpdateMissing(receivedVersion, current); 47 | } 48 | } catch (Exception ex) { 49 | otaEventListener.onError(ex); 50 | } 51 | } 52 | 53 | @Override 54 | public void onFailure(Callcall, Throwable t) { 55 | // Log error here since request failed 56 | otaEventListener.onError(t); 57 | } 58 | }); 59 | } 60 | 61 | public static AlertDialog.Builder makeDialog(final Context owner, final AviSemVersion newVer) { 62 | Resources res = owner.getResources(); 63 | AlertDialog.Builder dialInstallUpdate = new AlertDialog.Builder(owner); 64 | String modConfimText = res.getString(R.string.upd_confirm); 65 | modConfimText = modConfimText.replace("@version", newVer.toString()); 66 | 67 | dialInstallUpdate.setMessage(modConfimText); 68 | dialInstallUpdate.setTitle(res.getString(R.string.upd_new_available)) 69 | .setCancelable(false) 70 | .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { 71 | public void onClick(DialogInterface dialog, int id) { 72 | dialog.cancel(); 73 | } 74 | }); 75 | 76 | return dialInstallUpdate; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/webplayer/AviWebView.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.webplayer; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.webkit.WebChromeClient; 10 | import android.webkit.WebView; 11 | 12 | import java.util.Map; 13 | 14 | /** 15 | * This class serves as a WebView to be used in conjunction with a VideoEnabledWebChromeClient. 16 | * It makes possible: 17 | * - To detect the HTML5 video ended event so that the VideoEnabledWebChromeClient can exit full-screen. 18 | * 19 | * Important notes: 20 | * - Javascript is enabled by default and must not be disabled with getSettings().setJavaScriptEnabled(false). 21 | * - setWebChromeClient() must be called before any loadData(), loadDataWithBaseURL() or loadUrl() method. 22 | * 23 | * @author Cristian Perez (http://cpr.name) 24 | * 25 | */ 26 | public class AviWebView extends WebView 27 | { 28 | public class JavascriptInterface 29 | { 30 | @android.webkit.JavascriptInterface @SuppressWarnings("unused") 31 | public void notifyVideoEnd() // Must match Javascript interface method of VideoEnabledWebChromeClient 32 | { 33 | Log.d("___", "GOT IT"); 34 | // This code is not executed in the UI thread, so we must force that to happen 35 | new Handler(Looper.getMainLooper()).post(new Runnable() 36 | { 37 | @Override 38 | public void run() 39 | { 40 | if (aviWebChromeClient != null) 41 | { 42 | aviWebChromeClient.onHideCustomView(); 43 | } 44 | } 45 | }); 46 | } 47 | } 48 | 49 | private AviWebChromeClient aviWebChromeClient; 50 | private boolean addedJavascriptInterface; 51 | 52 | @SuppressWarnings("unused") 53 | public AviWebView(Context context) 54 | { 55 | super(context); 56 | addedJavascriptInterface = false; 57 | } 58 | 59 | @SuppressWarnings("unused") 60 | public AviWebView(Context context, AttributeSet attrs) 61 | { 62 | super(context, attrs); 63 | addedJavascriptInterface = false; 64 | } 65 | 66 | @SuppressWarnings("unused") 67 | public AviWebView(Context context, AttributeSet attrs, int defStyle) 68 | { 69 | super(context, attrs, defStyle); 70 | addedJavascriptInterface = false; 71 | } 72 | 73 | /** 74 | * Indicates if the video is being displayed using a custom view (typically full-screen) 75 | * @return true it the video is being displayed using a custom view (typically full-screen) 76 | */ 77 | @SuppressWarnings("unused") 78 | public boolean isVideoFullscreen() 79 | { 80 | return aviWebChromeClient != null && aviWebChromeClient.isVideoFullscreen(); 81 | } 82 | 83 | /** 84 | * Pass only a VideoEnabledWebChromeClient instance. 85 | */ 86 | @Override @SuppressLint("SetJavaScriptEnabled") 87 | public void setWebChromeClient(WebChromeClient client) 88 | { 89 | getSettings().setJavaScriptEnabled(true); 90 | 91 | if (client instanceof AviWebChromeClient) 92 | { 93 | this.aviWebChromeClient = (AviWebChromeClient) client; 94 | } 95 | 96 | super.setWebChromeClient(client); 97 | } 98 | 99 | @Override 100 | public void loadData(String data, String mimeType, String encoding) 101 | { 102 | addJavascriptInterface(); 103 | super.loadData(data, mimeType, encoding); 104 | } 105 | 106 | @Override 107 | public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) 108 | { 109 | addJavascriptInterface(); 110 | super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); 111 | } 112 | 113 | @Override 114 | public void loadUrl(String url) 115 | { 116 | addJavascriptInterface(); 117 | super.loadUrl(url); 118 | } 119 | 120 | @Override 121 | public void loadUrl(String url, Map additionalHttpHeaders) 122 | { 123 | addJavascriptInterface(); 124 | super.loadUrl(url, additionalHttpHeaders); 125 | } 126 | 127 | private void addJavascriptInterface() 128 | { 129 | if (!addedJavascriptInterface) 130 | { 131 | // Add javascript interface to be called when the video ends (must be done before page load) 132 | //noinspection all 133 | addJavascriptInterface(new JavascriptInterface(), "_VideoEnabledWebView"); // Must match Javascript interface name of VideoEnabledWebChromeClient 134 | 135 | addedJavascriptInterface = true; 136 | } 137 | } 138 | 139 | } -------------------------------------------------------------------------------- /app/src/main/java/com/x1unix/avi/webplayer/AviWebViewClient.java: -------------------------------------------------------------------------------- 1 | package com.x1unix.avi.webplayer; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.util.Log; 6 | import android.webkit.WebResourceResponse; 7 | import android.webkit.WebView; 8 | import android.webkit.WebViewClient; 9 | 10 | import com.x1unix.avi.helpers.AdBlocker; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | public class AviWebViewClient extends WebViewClient { 16 | private String LSECTION = AviWebViewClient.class.getName(); 17 | private Map loadedUrls = new HashMap<>(); 18 | private String currentUrl = ""; 19 | private boolean disableAds = false; 20 | 21 | public AviWebViewClient(boolean blockAds) { 22 | super(); 23 | disableAds = blockAds; 24 | } 25 | 26 | @Override 27 | public boolean shouldOverrideUrlLoading(WebView view, String url) { 28 | Log.w(LSECTION, "Client has tried to redirect to '" + url + "'"); 29 | return true; 30 | } 31 | 32 | @TargetApi(Build.VERSION_CODES.HONEYCOMB) 33 | @Override 34 | public WebResourceResponse shouldInterceptRequest(WebView view, String url) { 35 | // Do nothing if adblock disabled 36 | if (!disableAds) return super.shouldInterceptRequest(view, url); 37 | 38 | boolean ad; 39 | if (!loadedUrls.containsKey(url)) { 40 | ad = AdBlocker.isAd(url); 41 | loadedUrls.put(url, ad); 42 | } else { 43 | ad = loadedUrls.get(url); 44 | }; 45 | return ad ? AdBlocker.createEmptyResource("text/javascript") : super.shouldInterceptRequest(view, url); 46 | } 47 | 48 | public void onPageStarted (WebView view, String url) 49 | { 50 | if (url != currentUrl) { 51 | // stop loading page if its not the originalurl. 52 | Log.w(LSECTION, "Page loading prevented"); 53 | view.stopLoading(); 54 | } 55 | } 56 | 57 | public void updateCurrentUrl(String newUrl) { 58 | currentUrl = newUrl; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v22/ic_bookmark_full_wrapped.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v22/ic_bookmark_wrapped.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/avi_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1unix/Avi/b9c2002b3953ebfe51c93f19fa9c67415c6f93b6/app/src/main/res/drawable/avi_r.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avi_wide_r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1unix/Avi/b9c2002b3953ebfe51c93f19fa9c67415c6f93b6/app/src/main/res/drawable/avi_wide_r.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/block_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/border_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/effect_ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_avi.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_avi_wide.xml: -------------------------------------------------------------------------------- 1 | 6 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark_full.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark_full_wrapped.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark_gray.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bookmark_wrapped.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cloud_off_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_restore_gray.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_gray.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/no_poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/x1unix/Avi/b9c2002b3953ebfe51c93f19fa9c67415c6f93b6/app/src/main/res/drawable/no_poster.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/progress_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/star.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout-sw600dp/list_item_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 31 | 32 | 39 | 40 | 41 | 56 | 57 | 68 | 69 | 74 | 82 | 83 | 91 | 92 | 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 20 | 21 | 28 | 29 | 30 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_movie_player.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 23 | 24 | 25 | 36 | 41 | 52 |