├── .codebeatignore ├── .gitignore ├── .idea └── gradle.xml ├── .travis.yml ├── CODEOWNERS ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── schemas │ └── com.koalatea.thehollidayinn.softwareengineeringdaily.data.AppDatabase │ │ └── 1.json └── src │ ├── androidTest │ ├── java │ │ └── com │ │ │ └── koalatea │ │ │ └── thehollidayinn │ │ │ └── softwareengineeringdaily │ │ │ └── ExampleInstrumentedTest.java │ └── kotlin │ │ └── com │ │ └── koalatea │ │ └── thehollidayinn │ │ └── softwareengineeringdaily │ │ └── ExampleInstrumentedKTest.kt │ ├── dev │ └── google-services.json │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── Roboto-Regular.ttf │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── koalatea │ │ │ └── thehollidayinn │ │ │ └── softwareengineeringdaily │ │ │ ├── MainActivity.java │ │ │ ├── MainNavAdapter.java │ │ │ ├── PlaybackControllerActivity.java │ │ │ ├── analytics │ │ │ ├── AnalyticsFacade.java │ │ │ ├── AnalyticsFacadeImpl.java │ │ │ └── AnalyticsModule.java │ │ │ ├── app │ │ │ ├── AppComponent.java │ │ │ ├── AppModule.java │ │ │ ├── AppScope.java │ │ │ └── SEDApp.java │ │ │ ├── audio │ │ │ ├── LocalPlayback.java │ │ │ ├── MediaIDHelper.java │ │ │ ├── MediaNotificationManager.java │ │ │ ├── MusicProvider.java │ │ │ ├── MusicService.java │ │ │ ├── Playback.java │ │ │ ├── PlaybackManager.java │ │ │ ├── QueueHelper.java │ │ │ └── QueueManager.java │ │ │ ├── auth │ │ │ └── LoginRegisterActivity.java │ │ │ ├── dagger │ │ │ └── NetworkModule.java │ │ │ ├── data │ │ │ ├── AppDatabase.java │ │ │ ├── models │ │ │ │ ├── Bookmark.java │ │ │ │ ├── Content.java │ │ │ │ ├── Download.java │ │ │ │ ├── Post.java │ │ │ │ ├── PostItem.java │ │ │ │ ├── SubscriptionResponse.java │ │ │ │ ├── Title.java │ │ │ │ ├── User.java │ │ │ │ └── UserResponse.java │ │ │ ├── remote │ │ │ │ └── APIInterface.java │ │ │ └── repositories │ │ │ │ ├── BookmarkDao.java │ │ │ │ ├── DownloadDao.java │ │ │ │ └── DownloadRepository.java │ │ │ ├── downloads │ │ │ └── MP3FileManager.java │ │ │ ├── latest │ │ │ ├── RecentPodcastFragment.java │ │ │ └── RecentPodcastsPageAdapter.java │ │ │ ├── mediaui │ │ │ ├── FullscreenPlayerFragment.java │ │ │ └── SpeedDialog.java │ │ │ ├── notifications │ │ │ ├── DailyAlarmReceiver.kt │ │ │ └── NotificationActivity.kt │ │ │ ├── playbar │ │ │ ├── PlaybarFragment.java │ │ │ └── PlaybarViewModel.java │ │ │ ├── podcast │ │ │ ├── PodListFragment.java │ │ │ ├── PodcastAdapter.java │ │ │ ├── PodcastDetailActivity.java │ │ │ ├── PodcastListViewModel.java │ │ │ ├── PodcastSessionStateManager.java │ │ │ └── TopRecListFragment.java │ │ │ ├── repositories │ │ │ ├── DownloadNotificationManager.java │ │ │ ├── FilterRepository.java │ │ │ ├── PodcastDownloadsRepository.java │ │ │ ├── PostRepository.java │ │ │ ├── RepoConstants.kt │ │ │ ├── RepositoryModule.java │ │ │ └── UserRepository.java │ │ │ ├── subscription │ │ │ ├── PaymentFragment.kt │ │ │ ├── PlanFragment.kt │ │ │ ├── PlanInfoFragment.kt │ │ │ └── SubscriptionActivity.kt │ │ │ └── util │ │ │ ├── AlertUtil.java │ │ │ └── ReactiveUtil.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_bookmark.png │ │ ├── ic_bookmark_set.png │ │ ├── ic_launcher_app.png │ │ ├── ic_pause_black_36dp.png │ │ ├── ic_play_arrow_black_36dp.png │ │ ├── ic_tab_mic.png │ │ ├── ic_tab_recommendation.png │ │ └── ic_tab_top.png │ │ ├── drawable-mdpi │ │ ├── ic_bookmark.png │ │ ├── ic_launcher_app.png │ │ ├── ic_pause_black_36dp.png │ │ ├── ic_play_arrow_black_36dp.png │ │ ├── ic_tab_mic.png │ │ ├── ic_tab_recommendation.png │ │ └── ic_tab_top.png │ │ ├── drawable-xhdpi │ │ ├── ic_bookmark.png │ │ ├── ic_bookmark_set.png │ │ ├── ic_launcher_app.png │ │ ├── ic_pause_black_36dp.png │ │ ├── ic_play_arrow_black_36dp.png │ │ ├── ic_tab_mic.png │ │ ├── ic_tab_recommendation.png │ │ └── ic_tab_top.png │ │ ├── drawable-xxhdpi │ │ ├── ic_bookmark.png │ │ ├── ic_bookmark_set.png │ │ ├── ic_launcher_app.png │ │ ├── ic_pause_black_36dp.png │ │ ├── ic_play_arrow_black_36dp.png │ │ ├── ic_tab_mic.png │ │ ├── ic_tab_recommendation.png │ │ └── ic_tab_top.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_bookmark.png │ │ ├── ic_bookmark_set.png │ │ ├── ic_launcher_app.png │ │ ├── ic_pause_black_36dp.png │ │ ├── ic_play_arrow_black_36dp.png │ │ ├── ic_tab_mic.png │ │ ├── ic_tab_recommendation.png │ │ └── ic_tab_top.png │ │ ├── drawable │ │ ├── gradient.xml │ │ ├── ic_down_arrow.xml │ │ ├── ic_up_arrow.xml │ │ ├── sedaily_logo.png │ │ └── textlines.xml │ │ ├── layout │ │ ├── activity_login_register.xml │ │ ├── activity_main.xml │ │ ├── activity_notification.xml │ │ ├── activity_podcast_detail.xml │ │ ├── activity_subscription.xml │ │ ├── content_login_register.xml │ │ ├── content_subscription.xml │ │ ├── fragment_payment.xml │ │ ├── fragment_plan.xml │ │ ├── fragment_plan_info.xml │ │ ├── fragment_playback_controls.xml │ │ ├── fragment_podcast_grid.xml │ │ ├── fragment_podcast_horizontal.xml │ │ ├── fragment_podcast_list.xml │ │ ├── fragment_recent_podcast.xml │ │ ├── fragment_toprec_list.xml │ │ ├── item_skeleton_news.xml │ │ └── login_screen.xml │ │ ├── menu │ │ ├── bottom_navigation_main.xml │ │ ├── menu_main.xml │ │ └── podcast_detail_menu.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── transition │ │ ├── change_image_transform.xml │ │ └── explode.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings-localize.xml │ │ ├── strings-login.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── backup_descriptor.xml │ │ └── searchable.xml │ └── test │ ├── java │ └── com │ │ └── koalatea │ │ └── thehollidayinn │ │ └── softwareengineeringdaily │ │ ├── ExampleUnitTest.java │ │ └── ExampleUnitTest2.kt │ ├── kotlin │ └── com │ │ └── koalatea │ │ └── thehollidayinn │ │ └── softwareengineeringdaily │ │ └── data │ │ └── ExampleUnitTest.kt │ └── resources │ └── api │ ├── 200_auth_response.json │ ├── 200_post_response.json │ ├── 200_post_response_logged_in.json │ ├── 201_registration_success_response.json │ ├── 400_registration_missing_password_response.json │ ├── 401_login_wrong_password_response.json │ ├── 401_registration_user_exists_response.json │ └── 404_login_user_not_found_response.json ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── issue_template.md └── settings.gradle /.codebeatignore: -------------------------------------------------------------------------------- 1 | app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/* 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | app/app-release.apk 2 | app/google-services.json 3 | 4 | ### Android ### 5 | 6 | # Generated files 7 | bin/ 8 | gen/ 9 | out/ 10 | app/release/ 11 | 12 | # Gradle files 13 | .gradle/ 14 | build/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Proguard folder generated by Eclipse 20 | proguard/ 21 | 22 | # Log Files 23 | *.log 24 | 25 | # Android Studio Navigation editor temp files 26 | .navigation/ 27 | 28 | # Android Studio captures folder 29 | captures/ 30 | 31 | # Intellij 32 | *.iml 33 | .idea/ 34 | # External native build folder generated in Android Studio 2.2 and later 35 | .externalNativeBuild 36 | 37 | # Freeline 38 | freeline.py 39 | freeline/ 40 | freeline_project_description.json 41 | 42 | ### Android Patch ### 43 | gen-external-apklibs 44 | 45 | # Signing files 46 | .signing/ 47 | 48 | # Local configuration file (sdk path, etc) 49 | 50 | # Proguard folder generated by Eclipse 51 | 52 | # Log Files 53 | 54 | # Android Studio 55 | /*/local.properties 56 | /*/out 57 | /*/*/build 58 | /*/*/production 59 | *.ipr 60 | *~ 61 | *.swp 62 | 63 | # IntelliJ IDEA 64 | *.iws 65 | /out/ 66 | 67 | # OS-specific files 68 | .DS_Store 69 | .DS_Store? 70 | ._* 71 | .Spotlight-V100 72 | .Trashes 73 | ehthumbs.db 74 | Thumbs.db 75 | 76 | # Legacy Eclipse project files 77 | .classpath 78 | .project 79 | 80 | # Mobile Tools for Java (J2ME) 81 | .mtj.tmp/ 82 | 83 | # Package Files # 84 | *.war 85 | *.ear 86 | 87 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 88 | hs_err_pid* 89 | 90 | ## Plugin-specific files: 91 | 92 | # mpeltonen/sbt-idea plugin 93 | .idea_modules/ 94 | 95 | # JIRA plugin 96 | atlassian-ide-plugin.xml 97 | 98 | # Mongo Explorer plugin 99 | 100 | # Crashlytics plugin (for Android Studio and IntelliJ) 101 | com_crashlytics_export_strings.xml 102 | crashlytics.properties 103 | crashlytics-build.properties 104 | fabric.properties 105 | 106 | ### AndroidStudio Patch ### 107 | 108 | !/gradle/wrapper/gradle-wrapper.jar 109 | 110 | ### Java ### 111 | # Compiled class file 112 | 113 | # Log file 114 | 115 | # BlueJ files 116 | *.ctxt 117 | 118 | # Mobile Tools for Java (J2ME) 119 | 120 | # Package Files # 121 | *.jar 122 | *.zip 123 | *.tar.gz 124 | *.rar 125 | 126 | ### Linux ### 127 | 128 | # temporary files which can be created if a process still has a handle open of a deleted file 129 | .fuse_hidden* 130 | 131 | # KDE directory preferences 132 | .directory 133 | 134 | # Linux trash folder which might appear on any partition or disk 135 | .Trash-* 136 | 137 | # .nfs files are created when an open file is removed but is still being accessed 138 | .nfs* 139 | 140 | ### macOS ### 141 | *.DS_Store 142 | .AppleDouble 143 | .LSOverride 144 | 145 | # Icon must end with two \r 146 | Icon 147 | 148 | # Files that might appear in the root of a volume 149 | .DocumentRevisions-V100 150 | .fseventsd 151 | .TemporaryItems 152 | .VolumeIcon.icns 153 | .com.apple.timemachine.donotpresent 154 | 155 | # Directories potentially created on remote AFP share 156 | .AppleDB 157 | .AppleDesktop 158 | Network Trash Folder 159 | Temporary Items 160 | .apdisk 161 | 162 | ### Windows ### 163 | # Windows thumbnail cache files 164 | ehthumbs_vista.db 165 | 166 | # Folder config file 167 | Desktop.ini 168 | 169 | # Recycle Bin used on file shares 170 | $RECYCLE.BIN/ 171 | 172 | # Windows Installer files 173 | *.cab 174 | *.msi 175 | *.msm 176 | *.msp 177 | 178 | # Windows shortcuts 179 | *.lnk 180 | app/src/main/res/raw/third_party_license_metadata 181 | app/src/main/res/raw/third_party_licenses 182 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: android 3 | jdk: oraclejdk8 4 | 5 | android: 6 | components: 7 | - tools 8 | - platform-tools 9 | - tools # Second invocation to get the latest SDK tools 10 | # The BuildTools version used by your project 11 | - build-tools-$ANDROID_BUILD_TOOLS 12 | # The SDK version used to compile your project 13 | - android-$API 14 | 15 | # Additional components 16 | - extra-google-android-support 17 | - extra-google-google_play_services 18 | - extra-google-m2repository 19 | - extra-android-m2repository 20 | - addon-google_apis-google-$API 21 | 22 | # Specify at least one system image, 23 | # if you need to run emulator(s) during your tests 24 | - sys-img-armeabi-v7a-android-$API 25 | 26 | licenses: 27 | - 'android-sdk-license-.+' 28 | - 'android-sdk-preview-license-.+' 29 | - 'google-gdk-license-.+' 30 | 31 | # Avoid uploading the cache after every build 32 | before_cache: 33 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 34 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 35 | cache: 36 | directories: 37 | - $HOME/.gradle/caches/ 38 | - $HOME/.gradle/wrapper/ 39 | - $HOME/.android/build-cache 40 | 41 | env: 42 | global: 43 | - API=27 # Android API level default 44 | - ABI=armeabi-v7a # ARM ABI v7a by default 45 | - ANDROID_BUILD_TOOLS=27.0.3 46 | before_install: 47 | # Update sdk tools to latest version and install/update components 48 | - yes | sdkmanager "platforms;android-$API" 49 | # Ensure Gradle wrapper is executable, download wrapper and show version 50 | - chmod +x ./gradlew; ls -l gradlew; ./gradlew wrapper -v 51 | install: 52 | # Check/list components status 53 | - sdkmanager --list || true 54 | 55 | script: 56 | - ./gradlew clean build -Pbuild=dev jacocoTestReport 57 | - ./gradlew test -Pbuild=dev 58 | # script: ./gradlew connectedAndroidTest -Pbuild=dev 59 | 60 | after_success: 61 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence, 6 | # @global-owner1 and @global-owner2 will be requested for 7 | # review when someone opens a pull request. 8 | * @thehollidayinn 9 | 10 | # Order is important; the last matching pattern takes the most 11 | # precedence. When someone opens a pull request that only 12 | # modifies JS files, only @js-owner and not the global 13 | # owner(s) will be requested for a review. 14 | * @kvithayathil 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Kunal Kapadia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![logo](https://i.imgur.com/3OtP3p8.png)](https://softwareengineeringdaily.com/) 3 | 4 | [![Build Status](https://travis-ci.org/SoftwareEngineeringDaily/SEDaily-Android.svg?branch=develop)](https://travis-ci.org/SoftwareEngineeringDaily/SEDaily-Android) 5 | [![codecov](https://codecov.io/gh/SoftwareEngineeringDaily/SEDaily-Android/branch/develop/graph/badge.svg)](https://codecov.io/gh/SoftwareEngineeringDaily/SEDaily-Android) 6 | [![codebeat badge](https://codebeat.co/badges/a5d6976a-9163-491b-8618-0673b703529a)](https://codebeat.co/projects/github-com-softwareengineeringdaily-sedaily-android-develop) 7 | [![Help Contribute to Open Source](https://www.codetriage.com/softwareengineeringdaily/sedaily-android/badges/users.svg)](https://www.codetriage.com/softwareengineeringdaily/sedaily-android) 8 | 9 | # SEDaily-Android 10 | 11 | A player for the Software Engineering Daily Podcast 12 | 13 | ## Getting Started 14 | 15 | ### Cloning the Project 16 | ```sh 17 | $ git clone https://github.com/SoftwareEngineeringDaily/SEDaily-Android.git 18 | $ cd SEDaily-Android 19 | ``` 20 | 21 | ### Setting up Firebase 22 | 1. Go to the [Firebase website](https://firebase.google.com/) and create an account if you do not already have one 23 | 2. Create a new project on the Firebase dashboard 24 | 3. Add a new Android app in the Firebase Dashboard. 25 | 4. Set the package name to `com.koalatea.thehollidayinn.softwareengineeringdaily.debug` 26 | 5. Download `google-services.json` and place it in `/app/` 27 | 28 | 29 | Compile the project with Gradle using 30 | ```sh 31 | ./gradlew build 32 | ``` 33 | 34 | 35 | ## Built With 36 | 37 | * [Android](https://www.android.com/) 38 | * [Gradle](https://gradle.org/) 39 | * [Firebase](https://firebase.google.com/) 40 | * [Material UI Chip Input](https://github.com/TeamWertarbyte/material-ui-chip-input) 41 | * [ReactiveX](http://reactivex.io/) 42 | * [Leak Canary](https://github.com/square/leakcanary) 43 | * [OkHttp](https://github.com/square/okhttp) 44 | * [Retrofit](https://github.com/square/retrofit) 45 | 46 | ## Contributing 47 | 48 | ## License 49 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /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:\Users\krh12\AppData\Local\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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/schemas/com.koalatea.thehollidayinn.softwareengineeringdaily.data.AppDatabase/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": 1, 3 | "database": { 4 | "version": 1, 5 | "identityHash": "659a50ac2fb84b17beb61aba662cb055", 6 | "entities": [ 7 | { 8 | "tableName": "bookmark", 9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`postId` TEXT NOT NULL, `active` INTEGER, PRIMARY KEY(`postId`))", 10 | "fields": [ 11 | { 12 | "fieldPath": "postId", 13 | "columnName": "postId", 14 | "affinity": "TEXT", 15 | "notNull": true 16 | }, 17 | { 18 | "fieldPath": "active", 19 | "columnName": "active", 20 | "affinity": "INTEGER", 21 | "notNull": false 22 | } 23 | ], 24 | "primaryKey": { 25 | "columnNames": [ 26 | "postId" 27 | ], 28 | "autoGenerate": false 29 | }, 30 | "indices": [], 31 | "foreignKeys": [] 32 | } 33 | ], 34 | "setupQueries": [ 35 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", 36 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"659a50ac2fb84b17beb61aba662cb055\")" 37 | ] 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/koalatea/thehollidayinn/softwareengineeringdaily/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily; 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.koalatea.thehollidayinn.softwareengineeringdaily.debug", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/koalatea/thehollidayinn/softwareengineeringdaily/ExampleInstrumentedKTest.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Created by Kurian on 05-Nov-17. 11 | * Instrumentation test, which will execute on an Android device. 12 | * 13 | * @see [Testing documentation](http://d.android.com/tools/testing) 14 | */ 15 | @RunWith(AndroidJUnit4::class) 16 | class ExampleInstrumentedKTest { 17 | 18 | @Test 19 | @Throws(Exception::class) 20 | fun useAppContext() { 21 | // Context of the app under test. 22 | val appContext = InstrumentationRegistry.getTargetContext() 23 | assertEquals("com.koalatea.thehollidayinn.softwareengineeringdaily.debug", 24 | appContext.packageName) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/dev/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "", 4 | "project_id": "dev", 5 | "firebase_url": "https://www.example.com", 6 | "storage_bucket": "sed-android.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:000000000000:android:aaaaaaaaaaaaaaaa", 12 | "android_client_info": { 13 | "package_name": "com.koalatea.thehollidayinn.softwareengineeringdaily.dev" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "" 25 | } 26 | ], 27 | "services": { 28 | "analytics_service": { 29 | "status": 1 30 | }, 31 | "appinvite_service": { 32 | "status": 1, 33 | "other_platform_oauth_client": [] 34 | }, 35 | "ads_service": { 36 | "status": 2 37 | } 38 | } 39 | }, 40 | { 41 | "client_info": { 42 | "mobilesdk_app_id": "1:000000000000:android:ffffffffffffffff", 43 | "android_client_info": { 44 | "package_name": "com.koalatea.thehollidayinn.softwareengineeringdaily.dev" 45 | } 46 | }, 47 | "oauth_client": [ 48 | { 49 | "client_id": "", 50 | "client_type": 1, 51 | "android_info": { 52 | "package_name": "com.koalatea.thehollidayinn.softwareengineeringdaily.dev", 53 | "certificate_hash": "" 54 | } 55 | }, 56 | { 57 | "client_id": "", 58 | "client_type": 3 59 | } 60 | ], 61 | "api_key": [ 62 | { 63 | "current_key": "" 64 | } 65 | ], 66 | "services": { 67 | "analytics_service": { 68 | "status": 1 69 | }, 70 | "appinvite_service": { 71 | "status": 2, 72 | "other_platform_oauth_client": [ 73 | { 74 | "client_id": "", 75 | "client_type": 3 76 | } 77 | ] 78 | }, 79 | "ads_service": { 80 | "status": 2 81 | } 82 | } 83 | } 84 | ], 85 | "configuration_version": "1" 86 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 61 | 64 | 65 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/assets/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/assets/Roboto-Regular.ttf -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/MainNavAdapter.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /* 11 | Created by krh12 on 5/22/2017. 12 | */ 13 | 14 | class MainNavAdapter extends FragmentPagerAdapter { 15 | private final List mFragmentList = new ArrayList<>(); 16 | private final List mFragmentTitleList = new ArrayList<>(); 17 | 18 | public MainNavAdapter(FragmentManager manager) { 19 | super(manager); 20 | } 21 | 22 | @Override 23 | public Fragment getItem(int position) { 24 | return mFragmentList.get(position); 25 | } 26 | 27 | @Override 28 | public int getCount() { 29 | return mFragmentList.size(); 30 | } 31 | 32 | public void addFragment(Fragment fragment, String title) { 33 | mFragmentList.add(fragment); 34 | mFragmentTitleList.add(title); 35 | } 36 | 37 | @Override 38 | public CharSequence getPageTitle(int position) { 39 | return mFragmentTitleList.get(position); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/analytics/AnalyticsFacade.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.analytics; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | /** 6 | * Facade to capture events for use in any analytics processing 7 | * Created by Kurian on 20-Oct-17. 8 | */ 9 | public interface AnalyticsFacade { 10 | 11 | /** 12 | * Track an up-vote event 13 | * @param postId Id of the post that's been up-voted 14 | */ 15 | void trackUpVote(@NonNull String postId); 16 | 17 | /** 18 | * Track an down-vote event 19 | * @param postId Id of the post that's been down-voted 20 | */ 21 | void trackDownVote(@NonNull String postId); 22 | 23 | /** 24 | * Track a user registration event 25 | * @param username the username that has been registered 26 | */ 27 | void trackRegistration(@NonNull String username); 28 | 29 | /** 30 | * Track a user login event 31 | * @param username the username that has been logged in 32 | */ 33 | void trackLogin(@NonNull String username); 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/analytics/AnalyticsFacadeImpl.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.analytics; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import com.google.firebase.analytics.FirebaseAnalytics; 6 | 7 | /** 8 | * Created by Kurian on 20-Oct-17. 9 | */ 10 | class AnalyticsFacadeImpl implements AnalyticsFacade { 11 | 12 | private final FirebaseAnalytics firebaseAnalytics; 13 | 14 | AnalyticsFacadeImpl(@NonNull FirebaseAnalytics firebaseAnalytics) { 15 | this.firebaseAnalytics = firebaseAnalytics; 16 | } 17 | 18 | private void trackFirebaseBasic(String itemId, String name, String contentType) { 19 | Bundle bundle = new Bundle(); 20 | bundle.putString(FirebaseAnalytics.Param.ITEM_ID, itemId); 21 | bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, name); 22 | bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, contentType); 23 | firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle); 24 | } 25 | 26 | @Override 27 | public void trackUpVote(@NonNull String postId) { 28 | trackFirebaseBasic(postId, "UP", "VOTE"); 29 | } 30 | 31 | @Override 32 | public void trackDownVote(@NonNull String postId) { 33 | trackFirebaseBasic(postId, "DOWN", "VOTE"); 34 | } 35 | 36 | @Override 37 | public void trackRegistration(@NonNull String username) { 38 | trackFirebaseBasic(username, "Register", "Register"); 39 | } 40 | 41 | @Override 42 | public void trackLogin(@NonNull String username) { 43 | trackFirebaseBasic(username, "Login", "Login"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/analytics/AnalyticsModule.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.analytics; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import com.google.firebase.analytics.FirebaseAnalytics; 6 | 7 | import javax.inject.Singleton; 8 | 9 | import dagger.Module; 10 | import dagger.Provides; 11 | 12 | /** 13 | * Created by Kurian on 20-Oct-17. 14 | */ 15 | @Module 16 | public class AnalyticsModule { 17 | 18 | @Provides 19 | @Singleton 20 | FirebaseAnalytics providesFirebaseAnalytics(@NonNull Context context) { 21 | return FirebaseAnalytics.getInstance(context); 22 | } 23 | 24 | @Provides 25 | @Singleton 26 | AnalyticsFacade providesAnalyticsManager(@NonNull FirebaseAnalytics firebaseAnalytics) { 27 | return new AnalyticsFacadeImpl(firebaseAnalytics); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/app/AppComponent.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.app; 2 | 3 | import android.content.Context; 4 | 5 | import com.koalatea.thehollidayinn.softwareengineeringdaily.analytics.AnalyticsFacade; 6 | import com.koalatea.thehollidayinn.softwareengineeringdaily.analytics.AnalyticsModule; 7 | import com.koalatea.thehollidayinn.softwareengineeringdaily.dagger.NetworkModule; 8 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.remote.APIInterface; 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.RepositoryModule; 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.UserRepository; 11 | 12 | import javax.inject.Singleton; 13 | 14 | import dagger.Component; 15 | 16 | /** 17 | * Created by Kurian on 25-Sep-17. 18 | */ 19 | @Singleton 20 | @Component(modules = { 21 | AppModule.class, 22 | AnalyticsModule.class, 23 | NetworkModule.class, 24 | RepositoryModule.class}) 25 | public interface AppComponent { 26 | Context context(); 27 | AnalyticsFacade analyticsFacade(); 28 | APIInterface kibblService(); 29 | UserRepository userRepository(); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/app/AppModule.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.app; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.preference.PreferenceManager; 7 | import android.support.annotation.NonNull; 8 | 9 | import javax.inject.Singleton; 10 | 11 | import dagger.Module; 12 | import dagger.Provides; 13 | 14 | /** 15 | * Created by Kurian on 25-Sep-17. 16 | */ 17 | @Module 18 | public class AppModule { 19 | 20 | private final SEDApp app; 21 | 22 | public AppModule(@NonNull SEDApp app) { 23 | this.app = app; 24 | } 25 | 26 | @Provides 27 | @Singleton 28 | Application providesApplication() { 29 | return this.app; 30 | } 31 | 32 | @Provides 33 | @Singleton 34 | Context providesContext() { 35 | return this.app; 36 | } 37 | 38 | @Provides 39 | @Singleton 40 | SharedPreferences providesSharedPreferences(@NonNull Context context) { 41 | return PreferenceManager.getDefaultSharedPreferences(context); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/app/AppScope.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.app; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * Created by Kurian on 25-Sep-17. 10 | */ 11 | @Scope 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface AppScope { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/app/SEDApp.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.app; 2 | 3 | import android.app.Application; 4 | import android.support.annotation.VisibleForTesting; 5 | 6 | import com.akaita.java.rxjava2debug.RxJava2Debug; 7 | import com.koalatea.thehollidayinn.softwareengineeringdaily.BuildConfig; 8 | 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R; 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.AppDatabase; 11 | import com.squareup.leakcanary.LeakCanary; 12 | import timber.log.Timber; 13 | import uk.co.chrisjenx.calligraphy.CalligraphyConfig; 14 | 15 | /** 16 | * Created by Kurian on 25-Sep-17. 17 | */ 18 | 19 | public class SEDApp extends Application { 20 | 21 | @VisibleForTesting 22 | public static AppComponent component; 23 | 24 | @Override 25 | public void onCreate() { 26 | super.onCreate(); 27 | 28 | CalligraphyConfig.initDefault(new CalligraphyConfig.Builder() 29 | .setDefaultFontPath("Roboto-RobotoRegular.ttf") 30 | .setFontAttrId(R.attr.fontPath) 31 | .build() 32 | ); 33 | 34 | initLeakCanary(); 35 | initDependencies(); 36 | createLogger(); 37 | // Enable RxJava assembly stack collection, to make RxJava crash reports clear and unique 38 | // Make sure this is called AFTER setting up any Crash reporting mechanism as Crashlytics 39 | RxJava2Debug.enableRxJava2AssemblyTracking(new String[] { BuildConfig.APPLICATION_ID }); 40 | } 41 | 42 | private void initLeakCanary() { 43 | if (LeakCanary.isInAnalyzerProcess(this)) { 44 | // This process is dedicated to LeakCanary for heap analysis. 45 | // You should not init your app in this process. 46 | return; 47 | } 48 | LeakCanary.install(this); 49 | } 50 | 51 | private void createLogger() { 52 | if (BuildConfig.DEBUG) { 53 | Timber.plant(new Timber.DebugTree()); 54 | } 55 | } 56 | 57 | private void initDependencies() { 58 | if (component == null) { 59 | component = DaggerAppComponent.builder() 60 | .appModule(new AppModule(this)) 61 | .build(); 62 | } 63 | } 64 | 65 | public static AppComponent component() { 66 | return component; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/audio/MediaIDHelper.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.audio; 2 | 3 | import android.app.Activity; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.media.MediaBrowserCompat; 6 | import android.support.v4.media.session.MediaControllerCompat; 7 | import android.text.TextUtils; 8 | 9 | import java.util.Arrays; 10 | 11 | /** 12 | * Created by keithholliday on 5/2/18. 13 | */ 14 | 15 | public class MediaIDHelper { 16 | 17 | // Media IDs used on browseable items of MediaBrowser 18 | public static final String MEDIA_ID_EMPTY_ROOT = "__EMPTY_ROOT__"; 19 | public static final String MEDIA_ID_ROOT = "__ROOT__"; 20 | public static final String MEDIA_ID_MUSICS_BY_GENRE = "__BY_GENRE__"; 21 | public static final String MEDIA_ID_MUSICS_BY_SEARCH = "__BY_SEARCH__"; 22 | 23 | private static final char CATEGORY_SEPARATOR = '/'; 24 | private static final char LEAF_SEPARATOR = '|'; 25 | 26 | /** 27 | * Create a String value that represents a playable or a browsable media. 28 | * 29 | * Encode the media browseable categories, if any, and the unique music ID, if any, 30 | * into a single String mediaID. 31 | * 32 | * MediaIDs are of the form /|, to make it easy 33 | * to find the category (like genre) that a music was selected from, so we 34 | * can correctly build the playing queue. This is specially useful when 35 | * one music can appear in more than one list, like "by genre -> genre_1" 36 | * and "by artist -> artist_1". 37 | * @param musicID Unique music ID for playable items, or null for browseable items. 38 | * @param categories hierarchy of categories representing this item's browsing parents 39 | * @return a hierarchy-aware media ID 40 | */ 41 | public static String createMediaID(String musicID, String... categories) { 42 | StringBuilder sb = new StringBuilder(); 43 | if (categories != null) { 44 | for (int i=0; i < categories.length; i++) { 45 | if (!isValidCategory(categories[i])) { 46 | throw new IllegalArgumentException("Invalid category: " + categories[i]); 47 | } 48 | sb.append(categories[i]); 49 | if (i < categories.length - 1) { 50 | sb.append(CATEGORY_SEPARATOR); 51 | } 52 | } 53 | } 54 | if (musicID != null) { 55 | sb.append(LEAF_SEPARATOR).append(musicID); 56 | } 57 | return sb.toString(); 58 | } 59 | 60 | private static boolean isValidCategory(String category) { 61 | return category == null || 62 | ( 63 | category.indexOf(CATEGORY_SEPARATOR) < 0 && 64 | category.indexOf(LEAF_SEPARATOR) < 0 65 | ); 66 | } 67 | 68 | /** 69 | * Extracts unique musicID from the mediaID. mediaID is, by this sample's convention, a 70 | * concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and unique 71 | * musicID. This is necessary so we know where the user selected the music from, when the music 72 | * exists in more than one music list, and thus we are able to correctly build the playing queue. 73 | * 74 | * @param mediaID that contains the musicID 75 | * @return musicID 76 | */ 77 | public static String extractMusicIDFromMediaID(@NonNull String mediaID) { 78 | int pos = mediaID.indexOf(LEAF_SEPARATOR); 79 | if (pos >= 0) { 80 | return mediaID.substring(pos+1); 81 | } 82 | return null; 83 | } 84 | 85 | /** 86 | * Extracts category and categoryValue from the mediaID. mediaID is, by this sample's 87 | * convention, a concatenation of category (eg "by_genre"), categoryValue (eg "Classical") and 88 | * mediaID. This is necessary so we know where the user selected the music from, when the music 89 | * exists in more than one music list, and thus we are able to correctly build the playing queue. 90 | * 91 | * @param mediaID that contains a category and categoryValue. 92 | */ 93 | public static @NonNull String[] getHierarchy(@NonNull String mediaID) { 94 | int pos = mediaID.indexOf(LEAF_SEPARATOR); 95 | if (pos >= 0) { 96 | mediaID = mediaID.substring(0, pos); 97 | } 98 | return mediaID.split(String.valueOf(CATEGORY_SEPARATOR)); 99 | } 100 | 101 | public static String extractBrowseCategoryValueFromMediaID(@NonNull String mediaID) { 102 | String[] hierarchy = getHierarchy(mediaID); 103 | if (hierarchy.length == 2) { 104 | return hierarchy[1]; 105 | } 106 | return null; 107 | } 108 | 109 | public static boolean isBrowseable(@NonNull String mediaID) { 110 | return mediaID.indexOf(LEAF_SEPARATOR) < 0; 111 | } 112 | 113 | public static String getParentMediaID(@NonNull String mediaID) { 114 | String[] hierarchy = getHierarchy(mediaID); 115 | if (!isBrowseable(mediaID)) { 116 | return createMediaID(null, hierarchy); 117 | } 118 | if (hierarchy.length <= 1) { 119 | return MEDIA_ID_ROOT; 120 | } 121 | String[] parentHierarchy = Arrays.copyOf(hierarchy, hierarchy.length-1); 122 | return createMediaID(null, parentHierarchy); 123 | } 124 | 125 | public static boolean isMediaItemPlaying(Activity context, MediaBrowserCompat.MediaItem mediaItem) { 126 | // Media item is considered to be playing or paused based on the controller's current 127 | // media id 128 | MediaControllerCompat controller = MediaControllerCompat.getMediaController(context); 129 | if (controller != null && controller.getMetadata() != null) { 130 | String currentPlayingMediaId = controller.getMetadata().getDescription() 131 | .getMediaId(); 132 | String itemMusicId = MediaIDHelper.extractMusicIDFromMediaID( 133 | mediaItem.getDescription().getMediaId()); 134 | if (currentPlayingMediaId != null 135 | && TextUtils.equals(currentPlayingMediaId, itemMusicId)) { 136 | return true; 137 | } 138 | } 139 | return false; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/audio/Playback.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.audio; 2 | 3 | import android.support.v4.media.session.MediaSessionCompat; 4 | 5 | /** 6 | * Created by keithholliday on 4/25/18. 7 | */ 8 | 9 | public interface Playback { 10 | /** 11 | * Start/setup the playback. 12 | * Resources/listeners would be allocated by implementations. 13 | */ 14 | void start(); 15 | 16 | /** 17 | * Stop the playback. All resources can be de-allocated by implementations here. 18 | * @param notifyListeners if true and a callback has been set by setCallback, 19 | * callback.onPlaybackStatusChanged will be called after changing 20 | * the state. 21 | */ 22 | void stop(boolean notifyListeners); 23 | 24 | /** 25 | * Set the latest playback state as determined by the caller. 26 | */ 27 | void setState(int state); 28 | 29 | /** 30 | * Get the current {@link android.media.session.PlaybackState#getState()} 31 | */ 32 | int getState(); 33 | 34 | /** 35 | * @return boolean that indicates that this is ready to be used. 36 | */ 37 | boolean isConnected(); 38 | 39 | /** 40 | * @return boolean indicating whether the player is playing or is supposed to be 41 | * playing when we gain audio focus. 42 | */ 43 | boolean isPlaying(); 44 | 45 | /** 46 | * @return pos if currently playing an item 47 | */ 48 | long getCurrentStreamPosition(); 49 | 50 | /** 51 | * Queries the underlying stream and update the internal last known stream position. 52 | */ 53 | void updateLastKnownStreamPosition(); 54 | 55 | void play(MediaSessionCompat.QueueItem item); 56 | 57 | void pause(); 58 | 59 | void seekTo(long position); 60 | 61 | void setCurrentMediaId(String mediaId); 62 | 63 | String getCurrentMediaId(); 64 | 65 | void setSpeed(int speed); 66 | 67 | void moveForward(int distance); 68 | 69 | void moveBack(int distance); 70 | 71 | interface Callback { 72 | /** 73 | * On current music completed. 74 | */ 75 | void onCompletion(); 76 | /** 77 | * on Playback status changed 78 | * Implementations can use this callback to update 79 | * playback state on the media sessions. 80 | */ 81 | void onPlaybackStatusChanged(int state); 82 | 83 | /** 84 | * @param error to be added to the PlaybackState 85 | */ 86 | void onError(String error); 87 | 88 | /** 89 | * @param mediaId being currently played 90 | */ 91 | void setCurrentMediaId(String mediaId); 92 | } 93 | 94 | void setCallback(Callback callback); 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/dagger/NetworkModule.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.dagger; 2 | 3 | import android.app.Application; 4 | 5 | import com.google.gson.FieldNamingPolicy; 6 | import com.google.gson.Gson; 7 | import com.google.gson.GsonBuilder; 8 | import com.koalatea.thehollidayinn.softwareengineeringdaily.BuildConfig; 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp; 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.remote.APIInterface; 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.UserRepository; 12 | 13 | import java.io.IOException; 14 | 15 | import javax.inject.Singleton; 16 | 17 | import dagger.Module; 18 | import dagger.Provides; 19 | import okhttp3.Cache; 20 | import okhttp3.Interceptor; 21 | import okhttp3.OkHttpClient; 22 | import okhttp3.Request; 23 | import okhttp3.Response; 24 | import okhttp3.logging.HttpLoggingInterceptor; 25 | import retrofit2.Retrofit; 26 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 27 | import retrofit2.converter.gson.GsonConverterFactory; 28 | 29 | /** 30 | * Created by keithholliday on 1/3/18. 31 | */ 32 | 33 | @Module 34 | public class NetworkModule { 35 | String baseUrl = "https://software-enginnering-daily-api.herokuapp.com/api/"; 36 | // Staging 37 | // String baseUrl = "https://sedaily-backend-staging.herokuapp.com/api/"; 38 | 39 | // Local 40 | // String baseUrl = "http://192.168.1.251:4040/api/"; 41 | 42 | // public NetworkModule(String baseUrl) { 43 | // this.baseUrl = baseUrl; 44 | // } 45 | 46 | @Provides 47 | @Singleton 48 | Cache provideHttpCache(Application application) { 49 | int cacheSize = 10 * 1024 * 1024; 50 | Cache cache = new Cache(application.getCacheDir(), cacheSize); 51 | return cache; 52 | } 53 | 54 | @Provides 55 | @Singleton 56 | Gson provideGson() { 57 | GsonBuilder gsonBuilder = new GsonBuilder(); 58 | gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); 59 | return gsonBuilder.create(); 60 | } 61 | 62 | @Provides 63 | @Singleton 64 | OkHttpClient provideOkhttpClient(Cache cache) { 65 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 66 | if (BuildConfig.DEBUG) { 67 | logging.setLevel(HttpLoggingInterceptor.Level.BODY); 68 | } 69 | final UserRepository userLogin = UserRepository.getInstance(SEDApp.component.context()); 70 | OkHttpClient.Builder clientbuilder = new OkHttpClient.Builder() 71 | .addInterceptor(new Interceptor() { 72 | @Override 73 | public Response intercept(Interceptor.Chain chain) throws IOException { 74 | Request.Builder ongoing = chain.request().newBuilder(); 75 | ongoing.addHeader("Accept", "application/json;versions=1"); 76 | 77 | if (!userLogin.getToken().isEmpty()) { 78 | ongoing.addHeader("Authorization", "Bearer " + userLogin.getToken()); 79 | } 80 | 81 | return chain.proceed(ongoing.build()); 82 | } 83 | }); 84 | 85 | if (BuildConfig.DEBUG) { 86 | clientbuilder.addInterceptor(logging); 87 | } 88 | // clientbuilder.cache(cache); 89 | 90 | return clientbuilder.build(); 91 | } 92 | 93 | @Provides 94 | @Singleton 95 | Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) { 96 | return new Retrofit.Builder() 97 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 98 | .addConverterFactory(GsonConverterFactory.create(gson)) 99 | .baseUrl(baseUrl) 100 | .client(okHttpClient) 101 | .build(); 102 | } 103 | 104 | @Provides 105 | APIInterface providesKibbleService(Retrofit retrofitAdapter) { 106 | return retrofitAdapter.create(APIInterface.class); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.Room; 5 | import android.arch.persistence.room.RoomDatabase; 6 | import android.content.Context; 7 | 8 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp; 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Bookmark; 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Download; 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.repositories.BookmarkDao; 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.repositories.DownloadDao; 13 | 14 | /** 15 | * Created by samuelrey on 12/1/17. 16 | */ 17 | 18 | @Database(entities = {Bookmark.class, Download.class}, version = 2) 19 | public abstract class AppDatabase extends RoomDatabase { 20 | public abstract BookmarkDao bookmarkDao(); 21 | public abstract DownloadDao downloadDao(); 22 | 23 | private static AppDatabase INSTANCE; 24 | 25 | public static AppDatabase getDatabase() { 26 | Context context = SEDApp.component().context(); 27 | 28 | if (INSTANCE == null) { 29 | synchronized (AppDatabase.class) { 30 | if (INSTANCE == null) { 31 | INSTANCE = Room.databaseBuilder(context.getApplicationContext(), 32 | AppDatabase.class, "sed-db") 33 | .fallbackToDestructiveMigration() 34 | .build(); 35 | } 36 | } 37 | } 38 | return INSTANCE; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/Bookmark.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Entity; 5 | import android.arch.persistence.room.PrimaryKey; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | /** 10 | * Created by samuelrey on 11/30/17. 11 | */ 12 | 13 | @Entity(tableName = "bookmark") 14 | public class Bookmark { 15 | @PrimaryKey 16 | @NotNull 17 | private String postId; 18 | 19 | @ColumnInfo(name = "active") 20 | private Boolean active; 21 | 22 | public Bookmark(@NotNull Post post) { 23 | this.postId = post.get_id(); 24 | } 25 | 26 | public Bookmark(String postId, Boolean active) { 27 | this.postId = postId; 28 | this.active = active; 29 | } 30 | 31 | public String getPostId() { 32 | return postId; 33 | } 34 | 35 | public void setPostId(String postId) { 36 | this.postId = postId; 37 | } 38 | 39 | public Boolean getActive() { 40 | return active; 41 | } 42 | 43 | public void setActive(Boolean active) { 44 | this.active = active; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/Content.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | /* 4 | * Created by krh12 on 6/17/2017. 5 | */ 6 | 7 | public class Content { 8 | private String rendered; 9 | 10 | public String getRendered() { 11 | return rendered; 12 | } 13 | 14 | public void setRendered(String rendered) { 15 | this.rendered = rendered; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/Download.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Entity; 5 | import android.arch.persistence.room.PrimaryKey; 6 | import android.support.annotation.NonNull; 7 | 8 | @Entity(tableName = "downloads_table") 9 | public class Download { 10 | 11 | @PrimaryKey 12 | @NonNull 13 | @ColumnInfo(name = "postId") 14 | private String postId; 15 | 16 | public Download(String postId) {this.postId = postId;} 17 | 18 | public String getPostId(){return this.postId;} 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/Post.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.Date; 6 | 7 | /* 8 | * Created by krh12 on 6/17/2017. 9 | */ 10 | 11 | public class Post { 12 | private String _id; 13 | private String id; 14 | private Date date; 15 | private String slug; 16 | private String link; 17 | private Title title; 18 | private Content content; 19 | private Integer score; 20 | private Boolean upvoted; 21 | private Boolean downvoted; 22 | private String mp3; 23 | @SerializedName("featuredImage") 24 | private String featuredImage; 25 | 26 | public String get_id() { 27 | return _id; 28 | } 29 | 30 | public void set_id(String _id) { 31 | this._id = _id; 32 | } 33 | 34 | public String getId() { 35 | return id; 36 | } 37 | 38 | public void setId(String id) { 39 | this.id = id; 40 | } 41 | 42 | public Date getDate() { 43 | return date; 44 | } 45 | 46 | public void setDate(Date date) { 47 | this.date = date; 48 | } 49 | 50 | public String getSlug() { 51 | return slug; 52 | } 53 | 54 | public void setSlug(String slug) { 55 | this.slug = slug; 56 | } 57 | 58 | public String getLink() { 59 | return link; 60 | } 61 | 62 | public void setLink(String link) { 63 | this.link = link; 64 | } 65 | 66 | public Title getTitle() { 67 | return title; 68 | } 69 | 70 | public void setTitle(Title title) { 71 | this.title = title; 72 | } 73 | 74 | public Content getContent() { 75 | return content; 76 | } 77 | 78 | public void setContent(Content content) { 79 | this.content = content; 80 | } 81 | 82 | public Integer getScore() { 83 | return score; 84 | } 85 | 86 | public void setScore(Integer score) { 87 | this.score = score; 88 | } 89 | 90 | public Boolean getUpvoted() { 91 | return upvoted; 92 | } 93 | 94 | public void setUpvoted(Boolean upvoted) { 95 | this.upvoted = upvoted; 96 | } 97 | 98 | public Boolean getDownvoted() { 99 | return downvoted; 100 | } 101 | 102 | public void setDownvoted(Boolean downvoted) { 103 | this.downvoted = downvoted; 104 | } 105 | 106 | public String getMp3() { 107 | return mp3; 108 | } 109 | 110 | public void setMp3(String mp3) { 111 | this.mp3 = mp3; 112 | } 113 | 114 | public String getFeaturedImage() { 115 | return featuredImage; 116 | } 117 | 118 | public void setFeaturedImage(String featuredImage) { 119 | this.featuredImage = featuredImage; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/PostItem.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | import com.google.auto.value.AutoValue; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * Created by Kurian on 26-Sep-17. 9 | */ 10 | @AutoValue 11 | public abstract class PostItem { 12 | public abstract String id(); 13 | public abstract Date date(); 14 | public abstract String episodeLink(); 15 | public abstract String audioLink(); 16 | public abstract String featuredImgLink(); 17 | public abstract String content(); 18 | public abstract String title(); 19 | public abstract int score(); 20 | public abstract boolean upVoted(); 21 | public abstract boolean downVoted(); 22 | 23 | public static Builder builder() { 24 | return new AutoValue_PostItem.Builder(); 25 | } 26 | 27 | @AutoValue.Builder 28 | public abstract static class Builder { 29 | public abstract Builder id(String id); 30 | public abstract Builder date(Date date); 31 | public abstract Builder episodeLink(String episodeLink); 32 | public abstract Builder audioLink(String audioLink); 33 | public abstract Builder featuredImgLink(String featuredImgLink); 34 | public abstract Builder content(String content); 35 | public abstract Builder title(String title); 36 | public abstract Builder score(int score); 37 | public abstract Builder upVoted(boolean upVoted); 38 | public abstract Builder downVoted(boolean downVoted); 39 | public abstract PostItem build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/SubscriptionResponse.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | /** 4 | * Created by keithholliday on 1/7/18. 5 | */ 6 | 7 | public class SubscriptionResponse { 8 | public Boolean active; 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/Title.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | /* 4 | * Created by krh12 on 6/17/2017. 5 | */ 6 | 7 | public class Title { 8 | private String rendered; 9 | 10 | public String getRendered() { 11 | return rendered; 12 | } 13 | 14 | public void setRendered(String rendered) { 15 | this.rendered = rendered; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/User.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | /* 4 | * Created by krh12 on 6/22/2017. 5 | */ 6 | 7 | public class User { 8 | private String token; 9 | 10 | public String getToken() { 11 | return token; 12 | } 13 | 14 | public void setToken(String token) { 15 | this.token = token; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/models/UserResponse.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.models; 2 | 3 | /** 4 | * Created by keithholliday on 1/6/18. 5 | */ 6 | 7 | public class UserResponse { 8 | SubscriptionResponse subscription; 9 | 10 | public SubscriptionResponse getSubscription() { 11 | return subscription; 12 | } 13 | 14 | public void setSubscription(SubscriptionResponse subscription) { 15 | this.subscription = subscription; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/remote/APIInterface.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.remote; 2 | 3 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Post; 4 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.User; 5 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.UserResponse; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import io.reactivex.Completable; 11 | import io.reactivex.Observable; 12 | import retrofit2.http.DELETE; 13 | import retrofit2.http.Field; 14 | import retrofit2.http.FormUrlEncoded; 15 | import retrofit2.http.GET; 16 | import retrofit2.http.POST; 17 | import retrofit2.http.Path; 18 | import retrofit2.http.QueryMap; 19 | 20 | /* 21 | * Created by krh12 on 6/17/2017. 22 | */ 23 | 24 | public interface APIInterface { 25 | @GET("posts") 26 | Observable> getPosts(@QueryMap Map options); 27 | 28 | @GET("posts/recommendations") 29 | Observable> getRecommendations(@QueryMap Map options); 30 | 31 | @POST("posts/{postid}/upvote") 32 | Observable upVote(@Path("postid") String postId); 33 | 34 | @POST("posts/{postid}/downvote") 35 | Observable downVote(@Path("postid") String postId); 36 | 37 | @FormUrlEncoded 38 | @POST("auth/login") 39 | Observable login(@Field("username") String username, @Field("email") String email, @Field("password") String password); 40 | 41 | @FormUrlEncoded 42 | @POST("auth/register") 43 | Observable register(@Field("username") String username, @Field("email") String email, @Field("password") String password); 44 | 45 | @FormUrlEncoded 46 | @POST("subscription") 47 | Completable createSubscription(@Field("stripeToken") String stripeToken, @Field("planType") String planType); 48 | 49 | @DELETE("subscription") 50 | Completable cancelSubscription(); 51 | 52 | @GET("users/me") 53 | Observable me(); 54 | 55 | @GET("users/me/bookmarked") 56 | Observable> getBookmarks(); 57 | 58 | @POST("posts/{postid}/favorite") 59 | Observable addBookmark(@Path("postid") String postid); 60 | 61 | @POST("posts/{postid}/unfavorite") 62 | Observable removeBookmark(@Path("postid") String postid); 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/repositories/BookmarkDao.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.repositories; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Delete; 5 | import android.arch.persistence.room.Insert; 6 | import android.arch.persistence.room.OnConflictStrategy; 7 | import android.arch.persistence.room.Query; 8 | 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Bookmark; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * Created by samuelrey on 11/30/17. 15 | */ 16 | 17 | @Dao 18 | public interface BookmarkDao { 19 | @Query("SELECT * FROM bookmark") 20 | List getAll(); 21 | 22 | @Query("SELECT * FROM bookmark WHERE postId == :postId") 23 | Bookmark loadById(String postId); 24 | 25 | @Insert 26 | void insertOne(Bookmark bookmark); 27 | 28 | @Insert(onConflict = OnConflictStrategy.REPLACE) 29 | void insertAll(List bookmarks); 30 | 31 | @Delete 32 | void delete(Bookmark bookmark); 33 | 34 | @Query("DELETE FROM bookmark") 35 | void deleteAll(); 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/repositories/DownloadDao.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.repositories; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Delete; 5 | import android.arch.persistence.room.Insert; 6 | import android.arch.persistence.room.OnConflictStrategy; 7 | import android.arch.persistence.room.Query; 8 | 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Download; 10 | 11 | import java.util.List; 12 | 13 | @Dao 14 | public interface DownloadDao { 15 | @Query("SELECT * FROM downloads_table") 16 | List getAll(); 17 | 18 | @Query("SELECT * FROM bookmark WHERE postId == :postId") 19 | Download loadById(String postId); 20 | 21 | @Insert(onConflict = OnConflictStrategy.REPLACE) 22 | void insertOne(Download download); 23 | 24 | @Insert(onConflict = OnConflictStrategy.REPLACE) 25 | void insertAll(List downloads); 26 | 27 | @Delete 28 | void delete(Download download); 29 | 30 | @Query("DELETE FROM downloads_table") 31 | void deleteAll(); 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/data/repositories/DownloadRepository.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.data.repositories; 2 | 3 | import android.arch.lifecycle.LiveData; 4 | import android.os.AsyncTask; 5 | 6 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.AppDatabase; 7 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Download; 8 | 9 | import java.util.List; 10 | 11 | public class DownloadRepository { 12 | private DownloadDao downloadDao; 13 | 14 | public DownloadRepository() { 15 | AppDatabase db = AppDatabase.getDatabase(); 16 | downloadDao = db.downloadDao(); 17 | } 18 | 19 | public void insert(Download download) { 20 | new insertAsyncTask(downloadDao).execute(download); 21 | } 22 | 23 | public void remove(String podcastId) { 24 | new removeAsyncTask(downloadDao).execute(podcastId); 25 | } 26 | 27 | public List getDownloads() { 28 | return downloadDao.getAll(); 29 | } 30 | 31 | private static class insertAsyncTask extends AsyncTask { 32 | private DownloadDao downloadAsyncDao; 33 | 34 | insertAsyncTask(DownloadDao dao) { 35 | downloadAsyncDao = dao; 36 | } 37 | 38 | @Override 39 | protected Void doInBackground(final Download... params) { 40 | downloadAsyncDao.insertOne(params[0]); 41 | return null; 42 | } 43 | } 44 | 45 | private static class removeAsyncTask extends AsyncTask { 46 | private DownloadDao downloadAsyncDao; 47 | 48 | removeAsyncTask(DownloadDao dao) { 49 | downloadAsyncDao = dao; 50 | } 51 | 52 | @Override 53 | protected Void doInBackground(final String... params) { 54 | Download download = downloadAsyncDao.loadById(params[0]); 55 | if (download == null) return null; 56 | downloadAsyncDao.delete(download); 57 | return null; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/downloads/MP3FileManager.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.downloads; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | import android.support.v4.content.ContextCompat; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * Created by keithholliday on 10/15/17. 11 | */ 12 | 13 | public class MP3FileManager { 14 | public String getRootDirPath(Context context) { 15 | if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { 16 | File file = ContextCompat.getExternalFilesDirs(context.getApplicationContext(), 17 | null)[0]; 18 | return file.getAbsolutePath(); 19 | } else { 20 | return context.getApplicationContext().getFilesDir().getAbsolutePath(); 21 | } 22 | } 23 | 24 | public String getFileNameFromUrl(String urlString) { 25 | return urlString.substring(urlString.lastIndexOf('/') + 1, urlString.length()); 26 | } 27 | 28 | public File getFileFromUrl (String urlString, Context context) { 29 | return new File(context.getFilesDir(), getFileNameFromUrl(urlString)); 30 | } 31 | 32 | public String getExternalFileString (String urlString, Context context) { 33 | return context.getFilesDir() + "/" + getFileNameFromUrl(urlString); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/latest/RecentPodcastFragment.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.latest; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.TabLayout; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.view.ViewPager; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.MainActivity; 13 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R; 14 | 15 | import butterknife.BindView; 16 | import butterknife.ButterKnife; 17 | 18 | /** 19 | * Created by keithholliday on 9/16/17. 20 | */ 21 | 22 | public class RecentPodcastFragment extends Fragment { 23 | RecentPodcastsPageAdapter recentPodcatsPageAdapter; 24 | 25 | @BindView(R.id.pager) 26 | ViewPager viewPager; 27 | 28 | private TabLayout tabLayout; 29 | 30 | public static RecentPodcastFragment newInstance() { 31 | RecentPodcastFragment f = new RecentPodcastFragment(); 32 | return f; 33 | } 34 | 35 | @Override 36 | public View onCreateView(LayoutInflater inflater, 37 | ViewGroup container, Bundle savedInstanceState) { 38 | 39 | View rootView = (View) inflater.inflate( 40 | R.layout.fragment_recent_podcast, container, false); 41 | 42 | ButterKnife.bind(this, rootView); 43 | 44 | recentPodcatsPageAdapter = new RecentPodcastsPageAdapter(getChildFragmentManager()); 45 | viewPager.setAdapter(recentPodcatsPageAdapter); 46 | 47 | tabLayout = this.getActivity().findViewById(R.id.tabs); 48 | tabLayout.post(new Runnable() { 49 | @Override 50 | public void run() { 51 | tabLayout.setupWithViewPager(viewPager); 52 | } 53 | }); 54 | 55 | return rootView; 56 | } 57 | 58 | public void goHome () { 59 | TabLayout.Tab tab = tabLayout.getTabAt(0); 60 | tab.select(); 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/latest/RecentPodcastsPageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.latest; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | 7 | import com.koalatea.thehollidayinn.softwareengineeringdaily.podcast.PodListFragment; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | /* 14 | * Created by keithholliday on 9/16/17. 15 | */ 16 | 17 | class RecentPodcastsPageAdapter extends FragmentPagerAdapter { 18 | private List fragmentList = new ArrayList<>(); 19 | private List titles; 20 | 21 | public RecentPodcastsPageAdapter(FragmentManager fm) { 22 | super(fm); 23 | 24 | String LATEST = "Latest"; 25 | titles = Arrays.asList( 26 | "All", 27 | "Business and Philosophy", 28 | "Blockchain", 29 | "Cloud Engineering", 30 | "Data", 31 | "JavaScript", 32 | "Machine Learning", 33 | "Open Source", 34 | "Security", 35 | "Hackers", 36 | "Greatest Hits" 37 | ); 38 | 39 | List categories = Arrays.asList( 40 | "", 41 | "1068", 42 | "1082", 43 | "1079", 44 | "1081", 45 | "1084", 46 | "1080", 47 | "1078", 48 | "1083", 49 | "1085", 50 | "1069" 51 | ); 52 | 53 | for (String category : categories) { 54 | fragmentList.add(PodListFragment.newInstance(LATEST, category)); 55 | } 56 | } 57 | 58 | @Override 59 | public int getCount() { 60 | return titles.size(); 61 | } 62 | 63 | @Override 64 | public Fragment getItem(int position) { 65 | return fragmentList.get(position); 66 | } 67 | 68 | @Override 69 | public CharSequence getPageTitle(int position) { 70 | return titles.get(position); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/mediaui/FullscreenPlayerFragment.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.mediaui; 2 | 3 | import android.app.Fragment; 4 | 5 | /** 6 | * Created by George Lin on 10/19/2017. 7 | */ 8 | 9 | public class FullscreenPlayerFragment extends Fragment{ 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/mediaui/SpeedDialog.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.mediaui; 2 | 3 | import android.app.Dialog; 4 | import android.content.DialogInterface; 5 | import android.os.Bundle; 6 | import android.support.v4.app.DialogFragment; 7 | import android.support.v7.app.AlertDialog; 8 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R; 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.podcast.PodcastSessionStateManager; 10 | 11 | /* 12 | * Created by keithholliday on 11/3/17. 13 | */ 14 | 15 | public class SpeedDialog extends DialogFragment { 16 | @Override 17 | public Dialog onCreateDialog(Bundle savedInstanceState) { 18 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 19 | 20 | builder.setTitle(R.string.speed) 21 | .setItems(R.array.speed_options, new DialogInterface.OnClickListener() { 22 | public void onClick(DialogInterface dialog, int which) { 23 | PodcastSessionStateManager.getInstance().setCurrentSpeed(which); 24 | } 25 | }); 26 | 27 | return builder.create(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/notifications/DailyAlarmReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.notifications 2 | 3 | import android.app.PendingIntent 4 | import android.content.BroadcastReceiver 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.media.RingtoneManager 8 | import android.net.Uri 9 | import android.support.v4.app.NotificationCompat 10 | import android.support.v4.app.TaskStackBuilder 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.MainActivity 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R 13 | import android.support.v4.app.NotificationManagerCompat 14 | import android.app.NotificationChannel 15 | import android.app.NotificationManager 16 | import android.os.Build 17 | import android.util.Log 18 | 19 | 20 | /** 21 | * Created by keithholliday on 3/23/18. 22 | */ 23 | class DailyAlarmReceiver: BroadcastReceiver() { 24 | var DAILY_REMINDER_REQUEST_CODE = 198762999 25 | var CHANNEL_ID = "sedaily-channel-local" 26 | 27 | override fun onReceive(context: Context, intent: Intent) { 28 | // @TODO: Restart on reboot 29 | // if (intent.action != null) 30 | // { 31 | // if (intent.action.equalsIgnoreCase(Intent.ACTION_BOOT_COMPLETED)) 32 | // { 33 | // val localData = LocalData(context) 34 | // NotificationScheduler.setReminder(context, AlarmReceiver::class.java, 35 | // localData.get_hour(), localData.get_min()) 36 | // return 37 | // } 38 | // } 39 | showNotification(context, "We have some news podcasts for you!", "Come check them out. :D") 40 | } 41 | 42 | fun showNotification(context: Context, title: String, content: String) { 43 | var alarmSound: Uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) 44 | 45 | var notificationIntent = Intent(context, MainActivity::class.java) 46 | notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 47 | 48 | val stackBuilder = TaskStackBuilder.create(context) 49 | stackBuilder.addParentStack(MainActivity::class.java) 50 | stackBuilder.addNextIntent(notificationIntent) 51 | 52 | val pendingIntent = stackBuilder.getPendingIntent( 53 | DAILY_REMINDER_REQUEST_CODE, PendingIntent.FLAG_UPDATE_CURRENT) 54 | 55 | val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 56 | 57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 58 | val importance = NotificationManager.IMPORTANCE_DEFAULT 59 | val channel = NotificationChannel(CHANNEL_ID, CHANNEL_ID, importance) 60 | // val notificationManager = NotificationManagerCompat.from(context) 61 | notificationManager.createNotificationChannel(channel) 62 | } 63 | 64 | val builder = NotificationCompat.Builder(context, CHANNEL_ID) 65 | val notification = builder.setContentTitle(title) 66 | .setContentText(content).setAutoCancel(true) 67 | .setSound(alarmSound).setSmallIcon(R.drawable.sedaily_logo) 68 | .setContentIntent(pendingIntent).build() 69 | 70 | 71 | notificationManager.notify(DAILY_REMINDER_REQUEST_CODE, notification) 72 | } 73 | } -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/notifications/NotificationActivity.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.notifications 2 | 3 | import android.app.AlarmManager 4 | import android.app.PendingIntent 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.PackageManager 9 | import android.support.v7.app.AppCompatActivity 10 | import android.os.Bundle 11 | import android.view.View 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.MainActivity 13 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R 14 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.UserRepository 15 | import kotlinx.android.synthetic.main.activity_notification.* 16 | import java.util.* 17 | 18 | // http://droidmentor.com/schedule-notifications-using-alarmmanager/ 19 | 20 | class NotificationActivity : AppCompatActivity() { 21 | 22 | var DAILY_REMINDER_REQUEST_CODE = 198762999 23 | var DAILY_REMINDER_REQUEST_CODE2 = 198762998 24 | var DAILY_REMINDER_REQUEST_CODE3 = 198762998 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | setContentView(R.layout.activity_notification) 29 | 30 | val userRepo: UserRepository = UserRepository.getInstance(this); 31 | 32 | if (userRepo.subscribed) { 33 | switch1.isChecked = true 34 | } 35 | 36 | switch1.setOnClickListener(object : View.OnClickListener { 37 | override fun onClick(v: View?) { 38 | if (userRepo.subscribed) { 39 | userRepo.subscribed = false 40 | cancelReminder(applicationContext, DAILY_REMINDER_REQUEST_CODE) 41 | cancelReminder(applicationContext, DAILY_REMINDER_REQUEST_CODE2) 42 | cancelReminder(applicationContext, DAILY_REMINDER_REQUEST_CODE3) 43 | return 44 | } 45 | 46 | setReminder(applicationContext, Calendar.MONDAY,10, 0, DAILY_REMINDER_REQUEST_CODE) 47 | setReminder(applicationContext, Calendar.WEDNESDAY, 10, 0, DAILY_REMINDER_REQUEST_CODE2) 48 | setReminder(applicationContext, Calendar.FRIDAY, 10, 0, DAILY_REMINDER_REQUEST_CODE3) 49 | userRepo.subscribed = true 50 | } 51 | }) 52 | } 53 | 54 | fun setReminder(context: Context, day: Int, hour: Int, min: Int, code: Int) { 55 | var setCalendar: Calendar = Calendar.getInstance() 56 | 57 | setCalendar.set(Calendar.DAY_OF_WEEK, day) 58 | setCalendar.set(Calendar.HOUR_OF_DAY, hour) 59 | setCalendar.set(Calendar.MINUTE, min) 60 | setCalendar.set(Calendar.SECOND, 0) 61 | 62 | cancelReminder(context, code) 63 | 64 | val receiver = ComponentName(context, MainActivity::class.java) 65 | val pm = context.packageManager 66 | pm.setComponentEnabledSetting(receiver, 67 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 68 | PackageManager.DONT_KILL_APP) 69 | 70 | val intent1 = Intent(context, DailyAlarmReceiver::class.java) 71 | val pendingIntent = PendingIntent.getBroadcast(context, 72 | code, 73 | intent1, 74 | PendingIntent.FLAG_UPDATE_CURRENT) 75 | 76 | val alarmMgr = context.getSystemService(ALARM_SERVICE) as AlarmManager 77 | alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, setCalendar.timeInMillis, 78 | AlarmManager.INTERVAL_DAY, pendingIntent) 79 | } 80 | 81 | fun cancelReminder(context:Context, code: Int) { 82 | // Disable a receiver 83 | val receiver = ComponentName(context, MainActivity::class.java) 84 | 85 | val pm = context.packageManager 86 | pm.setComponentEnabledSetting(receiver, 87 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 88 | PackageManager.DONT_KILL_APP) 89 | 90 | val intent1 = Intent(context, MainActivity::class.java) 91 | val pendingIntent = PendingIntent.getBroadcast( 92 | context, 93 | code, 94 | intent1, 95 | PendingIntent.FLAG_UPDATE_CURRENT) 96 | 97 | val am = context.getSystemService(ALARM_SERVICE) as AlarmManager 98 | am.cancel(pendingIntent) 99 | 100 | pendingIntent.cancel() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/playbar/PlaybarViewModel.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.playbar; 2 | 3 | import android.app.Activity; 4 | import android.arch.lifecycle.ViewModel; 5 | import android.os.Bundle; 6 | import android.os.SystemClock; 7 | import android.support.v4.media.session.MediaControllerCompat; 8 | import android.support.v4.media.session.PlaybackStateCompat; 9 | 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.podcast.PodcastSessionStateManager; 11 | 12 | /** 13 | * Created by keithholliday on 2/6/18. 14 | */ 15 | 16 | public class PlaybarViewModel extends ViewModel { 17 | public void sendSpeedChangeIntent(int currentSpeed, Activity activity) { 18 | MediaControllerCompat controller = MediaControllerCompat.getMediaController(activity); 19 | if (controller != null) { 20 | Bundle args = new Bundle(); 21 | args.putInt("SPEED", currentSpeed); 22 | // @TODO: Make constant 23 | controller.getTransportControls().sendCustomAction("SPEED_CHANGE", args); 24 | } 25 | } 26 | 27 | public long setListenedProgress(PlaybackStateCompat mLastPlaybackState) { 28 | long currentPosition = mLastPlaybackState.getPosition(); 29 | if (mLastPlaybackState.getState() == PlaybackStateCompat.STATE_PLAYING) { 30 | // Calculate the elapsed time between the last position update and now and unless 31 | // paused, we can assume (delta * speed) + current position is approximately the 32 | // latest position. This ensure that we do not repeatedly call the getPlaybackState() 33 | // on MediaControllerCompat. 34 | long timeDelta = SystemClock.elapsedRealtime() - 35 | mLastPlaybackState.getLastPositionUpdateTime(); 36 | currentPosition += (int) timeDelta * mLastPlaybackState.getPlaybackSpeed(); 37 | } 38 | 39 | // Save progress for episode 40 | String postTile = PodcastSessionStateManager.getInstance().getCurrentTitle(); 41 | if (!postTile.isEmpty()) { 42 | PodcastSessionStateManager.getInstance().setProgressForEpisode(postTile, currentPosition); 43 | } 44 | 45 | return currentPosition; 46 | } 47 | 48 | public void playPause(MediaControllerCompat controller) { 49 | PlaybackStateCompat stateObj = controller.getPlaybackState(); 50 | final int state = stateObj == null ? PlaybackStateCompat.STATE_NONE : stateObj.getState(); 51 | 52 | if (state == PlaybackStateCompat.STATE_PAUSED || 53 | state == PlaybackStateCompat.STATE_STOPPED || 54 | state == PlaybackStateCompat.STATE_NONE) { 55 | playMedia(controller); 56 | } else if (state == PlaybackStateCompat.STATE_PLAYING || 57 | state == PlaybackStateCompat.STATE_BUFFERING || 58 | state == PlaybackStateCompat.STATE_CONNECTING) { 59 | pauseMedia(controller); 60 | } 61 | } 62 | 63 | private void playMedia(MediaControllerCompat controller) { 64 | if (controller != null) { 65 | controller.getTransportControls().play(); 66 | } 67 | } 68 | 69 | private void pauseMedia(MediaControllerCompat controller) { 70 | if (controller != null) { 71 | controller.getTransportControls().pause(); 72 | } 73 | } 74 | 75 | public void back15(Activity activity) { 76 | movePlayback(activity, "MOVE_BACK"); 77 | } 78 | 79 | public void skip15(Activity activity) { 80 | movePlayback(activity, "MOVE_FORWARD"); 81 | } 82 | 83 | private void movePlayback(Activity activity, String action) { 84 | MediaControllerCompat controller = MediaControllerCompat.getMediaController(activity); 85 | if (controller == null) return; 86 | 87 | Bundle args = new Bundle(); 88 | args.putInt("DISTANCE", 15000); 89 | // @TODO: Make constant 90 | controller.getTransportControls().sendCustomAction(action, args); 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/podcast/PodcastAdapter.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.podcast; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ImageView; 8 | import android.widget.TextView; 9 | 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R; 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp; 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Post; 13 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Title; 14 | import com.squareup.picasso.Picasso; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import butterknife.BindView; 20 | import butterknife.ButterKnife; 21 | import io.reactivex.Observable; 22 | import io.reactivex.subjects.PublishSubject; 23 | 24 | /* 25 | * Created by krh12 on 5/22/2017. 26 | */ 27 | 28 | class PodcastAdapter extends RecyclerView.Adapter { 29 | private List posts = new ArrayList<>(); 30 | private final PublishSubject onClickSubject = PublishSubject.create(); 31 | 32 | static class ViewHolder extends RecyclerView.ViewHolder { 33 | @BindView(R.id.card_title) 34 | TextView mTextView; 35 | 36 | @BindView(R.id.card_image) 37 | ImageView imageView; 38 | 39 | // @BindView(R.id.card_desc) 40 | // TextView description; 41 | 42 | private ViewHolder(View v) { 43 | super(v); 44 | ButterKnife.bind(this, v); 45 | } 46 | } 47 | 48 | void setPosts(List posts) { 49 | this.posts = posts; 50 | this.notifyDataSetChanged(); 51 | } 52 | 53 | @Override 54 | public PodcastAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, 55 | int viewType) { 56 | final View view = LayoutInflater.from(parent.getContext()) 57 | .inflate(R.layout.fragment_podcast_grid, parent, false); 58 | 59 | final ViewHolder viewHolder = new ViewHolder(view); 60 | 61 | view.setOnClickListener(v -> { 62 | final int position = viewHolder.getAdapterPosition(); 63 | Post post = posts.get(position); 64 | 65 | // @TODO: How to pass text view as well? 66 | onClickSubject.onNext(post); 67 | }); 68 | 69 | return viewHolder; 70 | } 71 | 72 | @Override 73 | public void onBindViewHolder(ViewHolder holder, int position) { 74 | Post post = posts.get(position); 75 | Title postTitle = post.getTitle(); 76 | 77 | if (postTitle == null) return; 78 | 79 | holder.mTextView.setText(postTitle.getRendered()); 80 | // holder.description.setText(postTitle.getRendered()); 81 | 82 | String imageLink = "https://softwareengineeringdaily.com/wp-content/uploads/2015/08/sed21.png"; 83 | if (post.getFeaturedImage() != null) { 84 | imageLink = post.getFeaturedImage(); 85 | } 86 | 87 | Picasso.with(SEDApp.component.context()) 88 | .load(imageLink) 89 | .resize(100, 100) 90 | .centerCrop() 91 | .into(holder.imageView); 92 | } 93 | 94 | @Override 95 | public int getItemCount() { 96 | return posts.size(); 97 | } 98 | 99 | public Observable getPositionClicks() { 100 | return onClickSubject; 101 | } 102 | } -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/podcast/PodcastListViewModel.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.podcast; 2 | 3 | import android.arch.lifecycle.MutableLiveData; 4 | import android.arch.lifecycle.ViewModel; 5 | import android.util.Log; 6 | 7 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp; 8 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Post; 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.remote.APIInterface; 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.PostRepository; 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.UserRepository; 12 | 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | import io.reactivex.Observable; 18 | import io.reactivex.android.schedulers.AndroidSchedulers; 19 | import io.reactivex.observers.DisposableObserver; 20 | import io.reactivex.schedulers.Schedulers; 21 | 22 | /** 23 | * Created by keithholliday on 12/22/17. 24 | */ 25 | 26 | public class PodcastListViewModel extends ViewModel { 27 | private MutableLiveData> postList; 28 | 29 | public PodcastListViewModel(){ 30 | postList = new MutableLiveData<>(); 31 | } 32 | 33 | public MutableLiveData> getPostList() { 34 | return this.postList; 35 | } 36 | 37 | public void setPostList(List postList) { 38 | this.postList.postValue(postList); 39 | } 40 | 41 | public void getPosts(String search, String title, String tagId) { 42 | APIInterface mService = SEDApp.component.kibblService(); 43 | 44 | // @TODO: Replace tmp with query 45 | 46 | Map data = new HashMap<>(); 47 | Observable> query = mService.getPosts(data); 48 | 49 | UserRepository userRepository = UserRepository.getInstance(SEDApp.component().context()); 50 | final PostRepository postRepository = PostRepository.getInstance(); 51 | 52 | if (title != null && title.equals("Greatest Hits")) { 53 | data.put("type", "top"); 54 | } else if (title != null && title.equals("Just For You") && !userRepository.getToken().isEmpty()) { 55 | query = mService.getRecommendations(data); 56 | } else if (tagId != null && !tagId.isEmpty()) { 57 | data.put("categories", tagId); 58 | } 59 | 60 | if (!search.isEmpty()) { 61 | data.put("search", search); 62 | } 63 | 64 | query 65 | .subscribeOn(Schedulers.io()) 66 | .observeOn(AndroidSchedulers.mainThread()) 67 | .subscribe(new DisposableObserver>() { 68 | @Override 69 | public void onComplete() { 70 | } 71 | 72 | @Override 73 | public void onError(Throwable e) { 74 | // Log.v(TAG, e.toString()); 75 | } 76 | 77 | @Override 78 | public void onNext(List posts) { 79 | setPostList(posts); 80 | postRepository.setPosts(posts); 81 | } 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/podcast/PodcastSessionStateManager.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.podcast; 2 | 3 | import android.content.SharedPreferences; 4 | import android.preference.PreferenceManager; 5 | import android.support.v4.media.MediaMetadataCompat; 6 | import android.support.v4.media.session.PlaybackStateCompat; 7 | import android.util.Log; 8 | 9 | import com.google.gson.Gson; 10 | import com.google.gson.GsonBuilder; 11 | import com.google.gson.reflect.TypeToken; 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp; 13 | 14 | import java.lang.reflect.Type; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import io.reactivex.Observable; 19 | import io.reactivex.subjects.PublishSubject; 20 | 21 | /* 22 | * Created by keithholliday on 9/30/17. 23 | */ 24 | 25 | // @TODO: This should probably be a viewmodal 26 | public class PodcastSessionStateManager { 27 | private static PodcastSessionStateManager instance = null; 28 | 29 | private final PublishSubject speedChangeObservable = PublishSubject.create(); 30 | private final PublishSubject mediaMetaDataChange = PublishSubject.create(); 31 | 32 | private final String PROGRESS_KEY = "sedaily-progress-key"; 33 | private final SharedPreferences preferences; 34 | private Gson gson; 35 | 36 | private String currentTitle = ""; 37 | private long previousSave = 0; 38 | private long currentProgress = 0; 39 | private int currentSpeed = 0; 40 | private MediaMetadataCompat mediaMetadataCompat; 41 | private Map episodeProgress; 42 | 43 | private PlaybackStateCompat lastPlaybackState; 44 | 45 | private PodcastSessionStateManager() { 46 | episodeProgress = new HashMap<>(); 47 | preferences = PreferenceManager.getDefaultSharedPreferences(SEDApp.component().context()); 48 | gson = new GsonBuilder().create(); 49 | String progressString = preferences.getString(PROGRESS_KEY, ""); 50 | if (!progressString.isEmpty()) { 51 | Type typeOfHashMap = new TypeToken>() { }.getType(); 52 | episodeProgress = gson.fromJson(progressString, typeOfHashMap); 53 | } 54 | } 55 | 56 | public static PodcastSessionStateManager getInstance() { 57 | if(instance == null) { 58 | instance = new PodcastSessionStateManager(); 59 | } 60 | return instance; 61 | } 62 | 63 | public long getCurrentProgress() { 64 | return currentProgress; 65 | } 66 | 67 | public void setCurrentProgress(long currentProgress) { 68 | this.currentProgress = currentProgress; 69 | } 70 | 71 | public PlaybackStateCompat getLastPlaybackState() { 72 | return lastPlaybackState; 73 | } 74 | 75 | public void setLastPlaybackState(PlaybackStateCompat lastPlaybackState) { 76 | this.lastPlaybackState = lastPlaybackState; 77 | } 78 | 79 | public int getCurrentSpeed() { 80 | return currentSpeed; 81 | } 82 | 83 | public void setCurrentSpeed(int currentSpeed) { 84 | this.currentSpeed = currentSpeed; 85 | speedChangeObservable.onNext(this.currentSpeed); 86 | } 87 | 88 | public void setMediaMetaData (MediaMetadataCompat mediaMetaData) { 89 | this.mediaMetadataCompat = mediaMetaData; 90 | mediaMetaDataChange.onNext(mediaMetaData); 91 | } 92 | 93 | public void setProgressForEpisode(String _id, long currentProgress) { 94 | this.episodeProgress.put(_id, currentProgress); 95 | 96 | // Save every 10 seconds 97 | long progress = currentProgress/1000; 98 | if (previousSave == 0) { 99 | previousSave = progress; 100 | } 101 | 102 | if (progress - previousSave == 10 && gson != null) { 103 | previousSave = progress; 104 | String json = gson.toJson(this.episodeProgress); 105 | SharedPreferences.Editor editor = preferences.edit(); 106 | editor.putString(PROGRESS_KEY, json); 107 | editor.apply(); 108 | } 109 | 110 | } 111 | 112 | public long getProgressForEpisode(String _id) { 113 | if (this.episodeProgress.get(_id) == null) { 114 | return 0; 115 | } 116 | return this.episodeProgress.get(_id); 117 | } 118 | 119 | public Observable getSpeedChanges() { 120 | return speedChangeObservable; 121 | } 122 | 123 | public Observable getMetadataChanges() { 124 | return mediaMetaDataChange; 125 | } 126 | 127 | public void setCurrentTitle(String title) { 128 | this.currentTitle = title; 129 | this.previousSave = 0; 130 | } 131 | 132 | public String getCurrentTitle() { 133 | return this.currentTitle; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/podcast/TopRecListFragment.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.podcast; 2 | 3 | import android.arch.lifecycle.ViewModelProviders; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.support.v7.widget.GridLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.ethanhua.skeleton.RecyclerViewSkeletonScreen; 14 | import com.ethanhua.skeleton.Skeleton; 15 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R; 16 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Post; 17 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.FilterRepository; 18 | import com.koalatea.thehollidayinn.softwareengineeringdaily.util.ReactiveUtil; 19 | 20 | import java.util.List; 21 | 22 | import butterknife.ButterKnife; 23 | import io.reactivex.android.schedulers.AndroidSchedulers; 24 | import io.reactivex.observers.DisposableObserver; 25 | import io.reactivex.schedulers.Schedulers; 26 | import timber.log.Timber; 27 | 28 | /** 29 | * Created by keithholliday on 2/4/18. 30 | */ 31 | 32 | public class TopRecListFragment extends Fragment { 33 | private String title; 34 | private String tagId; 35 | private PodcastAdapter podcastAdapter; 36 | private DisposableObserver myDisposableObserver; 37 | private RecyclerViewSkeletonScreen skeletonScreen; 38 | private SwipeRefreshLayout swipeRefreshLayout; 39 | private PodcastListViewModel podcastListViewModel; 40 | 41 | public static TopRecListFragment newInstance(String title, String tagId) { 42 | TopRecListFragment f = new TopRecListFragment(); 43 | Bundle args = new Bundle(); 44 | f.title = title; 45 | f.tagId = tagId; 46 | f.setArguments(args); 47 | return f; 48 | } 49 | 50 | @Override 51 | public View onCreateView(LayoutInflater inflater, 52 | ViewGroup container, 53 | Bundle savedInstanceState) { 54 | 55 | View rootView = inflater.inflate( 56 | R.layout.fragment_toprec_list, 57 | container, 58 | false); 59 | 60 | ButterKnife.bind(this, rootView); 61 | 62 | RecyclerView recyclerView = getRecycleView(rootView); 63 | setUpSkeletonLoading(recyclerView, rootView); 64 | setUpViewModel(); 65 | this.setUpSubscription(); 66 | 67 | return rootView; 68 | } 69 | 70 | private RecyclerView getRecycleView(View rootView) { 71 | RecyclerView recyclerView = rootView.findViewById(R.id.my_recycler_view); 72 | recyclerView.setHasFixedSize(true); 73 | GridLayoutManager mLayoutManager = new GridLayoutManager(this.getContext(), 2); 74 | recyclerView.setLayoutManager(mLayoutManager); 75 | podcastAdapter = new PodcastAdapter(); 76 | recyclerView.setAdapter(podcastAdapter); 77 | 78 | return recyclerView; 79 | } 80 | 81 | private void setUpSkeletonLoading(RecyclerView recyclerView, View rootView) { 82 | skeletonScreen = Skeleton.bind(recyclerView) 83 | .adapter(podcastAdapter) 84 | .load(R.layout.item_skeleton_news) 85 | .shimmer(true) 86 | .show(); 87 | 88 | swipeRefreshLayout = rootView.findViewById(R.id.swiperefresh); 89 | swipeRefreshLayout.setOnRefreshListener( 90 | new SwipeRefreshLayout.OnRefreshListener() { 91 | @Override 92 | public void onRefresh() { 93 | podcastListViewModel.getPosts("", title, tagId); 94 | } 95 | } 96 | ); 97 | } 98 | 99 | private void setUpViewModel() { 100 | podcastListViewModel = ViewModelProviders 101 | .of(this) 102 | .get(PodcastListViewModel.class); 103 | podcastListViewModel.getPostList().observe(this, posts -> { 104 | if (posts != null) updatePosts(posts); 105 | }); 106 | } 107 | 108 | public void setUpSubscription() { 109 | if (myDisposableObserver != null) { 110 | return; 111 | } 112 | 113 | myDisposableObserver = new DisposableObserver() { 114 | @Override 115 | public void onNext(String s) { 116 | podcastListViewModel.getPosts(s, title, tagId); 117 | } 118 | 119 | @Override 120 | public void onComplete() { } 121 | 122 | @Override 123 | public void onError(Throwable e) { } 124 | }; 125 | FilterRepository filterRepository = FilterRepository.getInstance(); 126 | filterRepository.getModelChanges().subscribe(myDisposableObserver); 127 | 128 | podcastAdapter.getPositionClicks() 129 | .subscribeOn(Schedulers.io()) 130 | .observeOn(AndroidSchedulers.mainThread()) 131 | .subscribe(ReactiveUtil.toDetailObservable(getActivity())); 132 | } 133 | 134 | @Override 135 | public void onStart() { 136 | super.onStart(); 137 | podcastListViewModel.getPosts("", title, tagId); 138 | } 139 | 140 | @Override 141 | public void onDestroy() { 142 | super.onDestroy(); 143 | 144 | if (myDisposableObserver != null) { 145 | myDisposableObserver.isDisposed(); 146 | } 147 | } 148 | 149 | private void updatePosts(List postList) { 150 | podcastAdapter.setPosts(postList); 151 | swipeRefreshLayout.setRefreshing(false); 152 | skeletonScreen.hide(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/repositories/DownloadNotificationManager.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.repositories; 2 | 3 | import android.app.NotificationChannel; 4 | import android.app.NotificationManager; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.support.v4.app.NotificationCompat; 8 | 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R; 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.AppComponent; 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp; 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Post; 13 | 14 | public class DownloadNotificationManager { 15 | private static DownloadNotificationManager instance = null; 16 | 17 | private NotificationManager mNotifyManager; 18 | private NotificationCompat.Builder mBuilder; 19 | 20 | public static DownloadNotificationManager getInstance() { 21 | if(instance == null) { 22 | instance = new DownloadNotificationManager(); 23 | } 24 | return instance; 25 | } 26 | 27 | public void showDownloadNotification(int downloadId, Post post) { 28 | AppComponent app = SEDApp.component(); 29 | Context context = app.context(); 30 | 31 | mNotifyManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 32 | 33 | String CHANNEL_ID = "sedaily_player_notifications"; 34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 35 | CharSequence name = context.getString(R.string.app_name); 36 | int importance = NotificationManager.IMPORTANCE_LOW; 37 | 38 | NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, name, importance); 39 | 40 | mNotifyManager.createNotificationChannel(mChannel); 41 | } 42 | 43 | String postTitle = post.getTitle().getRendered(); 44 | 45 | mBuilder = 46 | new NotificationCompat.Builder(context, CHANNEL_ID) 47 | .setContentTitle("Downloading " + postTitle) 48 | .setContentText("Download in progress") 49 | .setSmallIcon(R.drawable.sedaily_logo); 50 | 51 | mNotifyManager.notify(downloadId, mBuilder.build()); 52 | } 53 | 54 | public void hideNotification(int download) { 55 | mBuilder.setContentText("Download complete") 56 | .setProgress(0,0,false); 57 | mNotifyManager.notify(download, mBuilder.build()); 58 | } 59 | 60 | public void cancelNotification () { 61 | mNotifyManager.cancel(1); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/repositories/FilterRepository.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.repositories; 2 | 3 | /* 4 | * Created by keithholliday on 9/16/17. 5 | */ 6 | 7 | import io.reactivex.Observable; 8 | import io.reactivex.subjects.PublishSubject; 9 | 10 | public class FilterRepository { 11 | private final PublishSubject changeObservable = PublishSubject.create(); 12 | private static FilterRepository instance = null; 13 | 14 | private FilterRepository() { 15 | } 16 | 17 | public static FilterRepository getInstance() { 18 | if(instance == null) { 19 | instance = new FilterRepository(); 20 | } 21 | return instance; 22 | } 23 | 24 | public Observable getModelChanges() { 25 | return changeObservable; 26 | } 27 | 28 | public void setSearch(String search) { 29 | changeObservable.onNext(search); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/repositories/PostRepository.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.repositories; 2 | 3 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Post; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /* 10 | * Created by krh12 on 6/21/2017. 11 | */ 12 | 13 | public class PostRepository { 14 | private static PostRepository instance = null; 15 | 16 | private List posts; 17 | private Map postsById; 18 | 19 | private PostRepository() { 20 | } 21 | 22 | public static PostRepository getInstance() { 23 | if(instance == null) { 24 | instance = new PostRepository(); 25 | } 26 | return instance; 27 | } 28 | 29 | public void setPosts (List posts) { 30 | this.posts = posts; 31 | 32 | if (postsById == null) { 33 | postsById = new HashMap<>(); 34 | } 35 | 36 | for (Post post : posts) { 37 | postsById.put(post.get_id(), post); 38 | } 39 | } 40 | 41 | public Post getPostById (String postId) { 42 | if (this.posts == null || this.postsById == null) return null; 43 | 44 | return postsById.get(postId); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/repositories/RepoConstants.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.repositories 2 | 3 | internal class RepoConstants { 4 | companion object { 5 | val TOKEN_KEY = "token-key" 6 | val SUBSCRIBED_KEY = "subscribe-key" 7 | } 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/repositories/RepositoryModule.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.repositories; 2 | 3 | import android.app.Application; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | 10 | /** 11 | * Created by keithholliday on 1/7/18. 12 | */ 13 | 14 | @Module 15 | public class RepositoryModule { 16 | @Provides 17 | @Singleton 18 | UserRepository providesUserRepository(Application application) { 19 | return UserRepository.getInstance(application); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/repositories/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.repositories; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | /* 8 | Created by krh12 on 6/11/2017. 9 | */ 10 | 11 | public class UserRepository { 12 | private static UserRepository instance = null; 13 | 14 | private boolean isSubscribedToNotifications; 15 | private String token; 16 | private final SharedPreferences preferences; 17 | private Boolean hasPremium = false; 18 | 19 | private UserRepository(Context context) { 20 | preferences = PreferenceManager.getDefaultSharedPreferences(context); 21 | this.token = preferences.getString(RepoConstants.Companion.getTOKEN_KEY(), ""); 22 | this.isSubscribedToNotifications = preferences.getBoolean(RepoConstants.Companion.getSUBSCRIBED_KEY(), false); 23 | } 24 | 25 | public static UserRepository getInstance(Context context) { 26 | if(instance == null) { 27 | instance = new UserRepository(context); 28 | } 29 | return instance; 30 | } 31 | 32 | public void setToken (String token) { 33 | this.token = token; 34 | 35 | SharedPreferences.Editor editor = preferences.edit(); 36 | editor.putString(RepoConstants.Companion.getTOKEN_KEY(), token); 37 | editor.apply(); 38 | } 39 | 40 | public String getToken () { 41 | return this.token; 42 | } 43 | 44 | public void setSubscribed (Boolean isSubscribedToNotifications) { 45 | this.isSubscribedToNotifications = isSubscribedToNotifications; 46 | 47 | SharedPreferences.Editor editor = preferences.edit(); 48 | editor.putBoolean(RepoConstants.Companion.getSUBSCRIBED_KEY(), isSubscribedToNotifications); 49 | editor.apply(); 50 | } 51 | 52 | public Boolean getSubscribed () { 53 | return this.isSubscribedToNotifications; 54 | } 55 | 56 | public Boolean getHasPremium() { 57 | return hasPremium; 58 | } 59 | 60 | public void setHasPremium(Boolean hasPremium) { 61 | this.hasPremium = hasPremium; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/subscription/PaymentFragment.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.subscription 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.Button 10 | import android.widget.Toast 11 | 12 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R 13 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp 14 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.remote.APIInterface 15 | import com.stripe.android.Stripe 16 | import com.stripe.android.TokenCallback 17 | import com.stripe.android.model.Token 18 | import com.stripe.android.view.CardInputWidget 19 | import io.reactivex.CompletableObserver 20 | import io.reactivex.android.schedulers.AndroidSchedulers 21 | import io.reactivex.disposables.Disposable 22 | import io.reactivex.schedulers.Schedulers 23 | 24 | class PaymentFragment : Fragment() { 25 | 26 | private var mListener: OnFragmentInteractionListener? = null 27 | private var type: String = "" 28 | private lateinit var mCardInputWidget: CardInputWidget 29 | private lateinit var payButton: Button 30 | 31 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 32 | savedInstanceState: Bundle?): View? { 33 | 34 | val rootView = inflater.inflate(R.layout.fragment_payment, container, false) 35 | 36 | payButton = rootView.findViewById(R.id.paymentButton) 37 | payButton.setOnClickListener({ pay() }) 38 | 39 | mCardInputWidget = rootView.findViewById(R.id.card_input_widget) 40 | 41 | return rootView 42 | } 43 | 44 | private fun pay() { 45 | val cardToSave = mCardInputWidget.card 46 | if (cardToSave == null) { 47 | Toast.makeText(SEDApp.component.context(), 48 | "Invalid Card Data", 49 | Toast.LENGTH_LONG 50 | ).show() 51 | return 52 | } 53 | 54 | payButton.isEnabled = false 55 | 56 | val stripe = Stripe(SEDApp.component.context(), "pk_live_Cfttsv5i5ZG5IBfrmllzNoSA") 57 | stripe.createToken( 58 | cardToSave, 59 | object: TokenCallback { 60 | override fun onSuccess(token: Token) { 61 | createSubscription(token) 62 | } 63 | 64 | override fun onError(error: Exception) { 65 | Toast.makeText(SEDApp.component.context(), 66 | error.toString(), 67 | Toast.LENGTH_LONG 68 | ).show() 69 | payButton.isEnabled = true 70 | } 71 | } 72 | ) 73 | } 74 | 75 | private fun createSubscription(token: Token) { 76 | val service: APIInterface = SEDApp.component.kibblService() 77 | service.createSubscription(token.id, type) 78 | .subscribeOn(Schedulers.io()) 79 | .observeOn(AndroidSchedulers.mainThread()) 80 | .subscribe(object: CompletableObserver { 81 | override fun onSubscribe(d: Disposable) { 82 | } 83 | 84 | override fun onComplete() { 85 | Toast.makeText(SEDApp.component.context(), 86 | "Success!", 87 | Toast.LENGTH_LONG 88 | ).show() 89 | payButton.isEnabled = true 90 | mListener?.paymentSuccess() 91 | } 92 | 93 | override fun onError(e: Throwable ) { 94 | Toast.makeText(SEDApp.component.context(), 95 | e.message, 96 | Toast.LENGTH_LONG 97 | ).show() 98 | payButton.isEnabled = true 99 | } 100 | }) 101 | } 102 | 103 | override fun onAttach(context: Context?) { 104 | super.onAttach(context) 105 | if (context is OnFragmentInteractionListener) { 106 | mListener = context 107 | } else { 108 | throw RuntimeException(context!!.toString() + " must implement PaymentFragment.OnFragmentInteractionListener") 109 | } 110 | } 111 | 112 | override fun onDetach() { 113 | super.onDetach() 114 | mListener = null 115 | } 116 | 117 | interface OnFragmentInteractionListener { 118 | fun paymentSuccess() 119 | } 120 | 121 | companion object { 122 | // TODO: Rename and change types and number of parameters 123 | fun newInstance(type: String): PaymentFragment { 124 | val fragment = PaymentFragment() 125 | fragment.type = type 126 | return fragment 127 | } 128 | } 129 | }// Required empty public constructor 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/subscription/PlanFragment.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.subscription 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.Button 10 | 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R 12 | 13 | class PlanFragment : Fragment() { 14 | 15 | private var mListener: OnFragmentInteractionListener? = null 16 | 17 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 18 | savedInstanceState: Bundle?): View? { 19 | 20 | val rootView = inflater.inflate(R.layout.fragment_plan, container, false) 21 | 22 | val monthlyPlan: Button = rootView.findViewById(R.id.monthlyButton) 23 | monthlyPlan.setOnClickListener({ selectMonthlyPlan() }) 24 | 25 | val yearlyPlan: Button = rootView.findViewById(R.id.yearlyButton) 26 | yearlyPlan.setOnClickListener({ selectYearlyPlan() }) 27 | 28 | return rootView 29 | } 30 | 31 | private fun selectMonthlyPlan () { 32 | mListener?.onPlanSelected("monthly"); 33 | } 34 | 35 | private fun selectYearlyPlan () { 36 | mListener?.onPlanSelected("yearly"); 37 | } 38 | 39 | override fun onAttach(context: Context?) { 40 | super.onAttach(context) 41 | if (context is OnFragmentInteractionListener) { 42 | mListener = context 43 | } else { 44 | throw RuntimeException(context!!.toString() + " must implement PlanFragment.OnFragmentInteractionListener") 45 | } 46 | } 47 | 48 | override fun onDetach() { 49 | super.onDetach() 50 | mListener = null 51 | } 52 | 53 | interface OnFragmentInteractionListener { 54 | fun onPlanSelected(type: String) 55 | } 56 | 57 | companion object { 58 | fun newInstance(): PlanFragment { 59 | val fragment = PlanFragment() 60 | return fragment 61 | } 62 | } 63 | }// Required empty public constructor 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/subscription/PlanInfoFragment.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.subscription 2 | 3 | import android.app.AlertDialog 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.support.v4.app.Fragment 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.Button 11 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R 12 | import android.content.DialogInterface 13 | import android.widget.Toast 14 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp 15 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.remote.APIInterface 16 | import io.reactivex.CompletableObserver 17 | import io.reactivex.android.schedulers.AndroidSchedulers 18 | import io.reactivex.disposables.Disposable 19 | import io.reactivex.schedulers.Schedulers 20 | import kotlinx.android.synthetic.main.fragment_plan_info.* 21 | 22 | class PlanInfoFragment : Fragment() { 23 | private var mListener: OnFragmentInteractionListener? = null 24 | 25 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 26 | savedInstanceState: Bundle?): View? { 27 | val rootView: View = inflater.inflate(R.layout.fragment_plan_info, container, false) 28 | 29 | val cancelButton: Button = rootView.findViewById(R.id.cancelButton) 30 | cancelButton.setOnClickListener({ confirmCancel() }) 31 | 32 | return rootView 33 | } 34 | 35 | override fun onAttach(context: Context?) { 36 | super.onAttach(context) 37 | if (context is OnFragmentInteractionListener) { 38 | mListener = context 39 | } else { 40 | throw RuntimeException(context?.toString() + " must implement OnFragmentInteractionListener") 41 | } 42 | } 43 | 44 | override fun onDetach() { 45 | super.onDetach() 46 | mListener = null 47 | } 48 | 49 | private fun confirmCancel () { 50 | cancelButton.isEnabled = false 51 | 52 | AlertDialog.Builder(this.activity) 53 | .setTitle("Cancel Subscription") 54 | .setMessage("Do you really want to cancel?") 55 | .setIcon(android.R.drawable.ic_dialog_alert) 56 | .setPositiveButton(android.R.string.yes, DialogInterface.OnClickListener { 57 | dialog, _ -> 58 | run { 59 | cancelSubscription() 60 | } 61 | }) 62 | .setNegativeButton(android.R.string.no, null).show() 63 | } 64 | 65 | private fun cancelSubscription () { 66 | val service: APIInterface = SEDApp.component.kibblService() 67 | service.cancelSubscription() 68 | .subscribeOn(Schedulers.io()) 69 | .observeOn(AndroidSchedulers.mainThread()) 70 | .subscribe(object: CompletableObserver { 71 | override fun onSubscribe(d: Disposable) { 72 | } 73 | 74 | override fun onComplete() { 75 | Toast.makeText(SEDApp.component.context(), 76 | "Success!", 77 | Toast.LENGTH_LONG 78 | ).show() 79 | cancelButton.isEnabled = true 80 | mListener?.canceledSubscription() 81 | } 82 | 83 | override fun onError(e: Throwable ) { 84 | Toast.makeText(SEDApp.component.context(), 85 | e.message, 86 | Toast.LENGTH_LONG 87 | ).show() 88 | cancelButton.isEnabled = true 89 | } 90 | }) 91 | } 92 | 93 | interface OnFragmentInteractionListener { 94 | fun canceledSubscription() 95 | } 96 | 97 | companion object { 98 | fun newInstance(): PlanInfoFragment { 99 | val fragment = PlanInfoFragment() 100 | return fragment 101 | } 102 | } 103 | }// Required empty public constructor 104 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/subscription/SubscriptionActivity.kt: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.subscription 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.support.v4.app.Fragment 6 | import android.support.v7.app.AppCompatActivity 7 | import com.koalatea.thehollidayinn.softwareengineeringdaily.MainActivity 8 | import com.koalatea.thehollidayinn.softwareengineeringdaily.R 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.app.SEDApp 10 | import com.koalatea.thehollidayinn.softwareengineeringdaily.repositories.UserRepository 11 | 12 | import kotlinx.android.synthetic.main.activity_subscription.* 13 | 14 | class SubscriptionActivity : AppCompatActivity(), 15 | PaymentFragment.OnFragmentInteractionListener, 16 | PlanFragment.OnFragmentInteractionListener, 17 | PlanInfoFragment.OnFragmentInteractionListener { 18 | 19 | private lateinit var userRepository: UserRepository 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | setContentView(R.layout.activity_subscription) 24 | setSupportActionBar(toolbar) 25 | 26 | 27 | userRepository = SEDApp.component.userRepository() 28 | if (userRepository.hasPremium) { 29 | showInfoFragment() 30 | return 31 | } 32 | 33 | showPlanOptions() 34 | } 35 | 36 | private fun showInfoFragment () { 37 | // @TODO: save instance? 38 | val planFragment = PlanInfoFragment.newInstance() 39 | this.supportFragmentManager 40 | .beginTransaction() 41 | .replace(R.id.fragment_container, planFragment) 42 | .commit() 43 | } 44 | 45 | private fun showFragement(fragment: Fragment) { 46 | this.supportFragmentManager 47 | .beginTransaction() 48 | .replace(R.id.fragment_container, fragment) 49 | .commit() 50 | } 51 | 52 | private fun showPlanOptions () { 53 | // @TODO: save instance? 54 | showFragement(PlanFragment.newInstance()) 55 | } 56 | 57 | private fun showPayment (type: String) { 58 | // @TODO: save instance? 59 | showFragement(PaymentFragment.newInstance(type)) 60 | } 61 | 62 | override fun paymentSuccess() { 63 | val intent = Intent(this, MainActivity::class.java) 64 | startActivity(intent) 65 | } 66 | 67 | override fun onPlanSelected(type: String) { 68 | showPayment(type); 69 | } 70 | 71 | override fun canceledSubscription() { 72 | userRepository.hasPremium = false 73 | val intent = Intent(this, MainActivity::class.java) 74 | startActivity(intent) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/util/AlertUtil.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.os.Build; 7 | import android.support.v7.app.AlertDialog; 8 | 9 | /** 10 | * Created by keithholliday on 4/24/18. 11 | */ 12 | 13 | public class AlertUtil { 14 | public static void displayMessage (Context context, String message) { 15 | AlertDialog.Builder builder; 16 | 17 | // Ensure context is active 18 | if ( context instanceof Activity ) { 19 | Activity activity = (Activity) context; 20 | if (activity.isFinishing()) { 21 | return; 22 | } 23 | } 24 | 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 26 | builder = new AlertDialog.Builder(context, android.R.style.Theme_Material_Dialog_Alert); 27 | } else { 28 | builder = new AlertDialog.Builder(context); 29 | } 30 | 31 | builder.setTitle("Error") 32 | .setMessage(message) 33 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { 34 | public void onClick(DialogInterface dialog, int which) {} 35 | }) 36 | .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { 37 | public void onClick(DialogInterface dialog, int which) {} 38 | }) 39 | .setIcon(android.R.drawable.ic_dialog_alert) 40 | .show(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/koalatea/thehollidayinn/softwareengineeringdaily/util/ReactiveUtil.java: -------------------------------------------------------------------------------- 1 | package com.koalatea.thehollidayinn.softwareengineeringdaily.util; 2 | 3 | import android.app.Activity; 4 | import android.app.ActivityOptions; 5 | import android.content.Intent; 6 | import android.util.Log; 7 | 8 | import com.koalatea.thehollidayinn.softwareengineeringdaily.data.models.Post; 9 | import com.koalatea.thehollidayinn.softwareengineeringdaily.podcast.PodcastDetailActivity; 10 | 11 | import io.reactivex.observers.DisposableObserver; 12 | 13 | /** 14 | * Created by keithholliday on 4/24/18. 15 | */ 16 | 17 | public class ReactiveUtil { 18 | public static DisposableObserver getEmptyObservable() { 19 | return new DisposableObserver() { 20 | @Override 21 | public void onComplete() { 22 | } 23 | @Override 24 | public void onError(Throwable e) { 25 | // Log.v(TAG, e.toString()); 26 | } 27 | 28 | @Override 29 | public void onNext(Void posts) { 30 | } 31 | }; 32 | } 33 | 34 | public static DisposableObserver toDetailObservable(Activity activity) { 35 | return new DisposableObserver() { 36 | @Override 37 | public void onComplete() {} 38 | @Override 39 | public void onError(Throwable e) { 40 | // Log.v(TAG, e.toString()); 41 | } 42 | @Override 43 | public void onNext(Post post) { 44 | Intent intent = new Intent(activity, PodcastDetailActivity.class); 45 | intent.putExtra("POST_ID", post.get_id()); 46 | 47 | // @TODO: shared element 48 | // val options = ActivityOptions 49 | // .makeSceneTransitionAnimation(this, androidRobotView, "robot") 50 | 51 | activity.startActivity(intent, 52 | ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()); 53 | } 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_bookmark_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_bookmark_set.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tab_mic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_tab_mic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tab_recommendation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_tab_recommendation.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_tab_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-hdpi/ic_tab_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-mdpi/ic_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-mdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-mdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-mdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tab_mic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-mdpi/ic_tab_mic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tab_recommendation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-mdpi/ic_tab_recommendation.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_tab_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-mdpi/ic_tab_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_bookmark_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_bookmark_set.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tab_mic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_tab_mic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tab_recommendation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_tab_recommendation.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_tab_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xhdpi/ic_tab_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_bookmark_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_bookmark_set.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tab_mic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_tab_mic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tab_recommendation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_tab_recommendation.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_tab_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxhdpi/ic_tab_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_bookmark.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_bookmark_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_bookmark_set.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_launcher_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_launcher_app.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_pause_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_black_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tab_mic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_tab_mic.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tab_recommendation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_tab_recommendation.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_tab_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable-xxxhdpi/ic_tab_top.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_down_arrow.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_up_arrow.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sedaily_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareEngineeringDaily/SEDaily-Android/3c845c0ebddd60fd1a060e5244d6a9351cf175f4/app/src/main/res/drawable/sedaily_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/textlines.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_login_register.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 21 | 22 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 32 | 33 | 34 | 43 | 44 | 45 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_notification.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 25 | 26 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 50 | 51 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 83 | 84 | 91 | 92 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_subscription.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_login_register.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 22 | 23 | 31 | 32 | 33 | 42 | 43 | 52 | 53 |