diff --git a/.gitignore b/.gitignore index 39fb081..a4c7838 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ -*.iml -.gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries -.DS_Store -/build -/captures -.externalNativeBuild +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 719bb8b..a0f5409 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -1,228 +1,228 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml index e7bedf3..c7d1c5a 100644 --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/.idea/dictionaries/Jacob.xml b/.idea/dictionaries/Jacob.xml index febd0df..39c0541 100644 --- a/.idea/dictionaries/Jacob.xml +++ b/.idea/dictionaries/Jacob.xml @@ -1,8 +1,8 @@ - - - - genymotion - unswipeable - - + + + + genymotion + unswipeable + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index e33d058..187f74b 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,19 +1,19 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d19981..ecda9b1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,46 +1,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index fe70a5d..eeb2cef 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..9661ac7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..857ccdd --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# NoriginMedia + +## Overview + +As part of the recruitment process at NoriginMedia I was asked to complete the native part of the following task: https://github.com/NoriginMedia/candidate-tester/ + +This project was created over a weekend (21 - 23 July), to complete the challenge. It was made in Android Studio, and displays its data with help from the GitHub repo https://github.com/korre/android-tv-epg + +## Running + +There are two alternative ways of running this Android-app: + 1. Download and install the .apk file: app/build/outputs/apk/app-debug.apk + 2. Clone the repo, open it in Android Studio, and hit run + +WARNING, the following might hinder the application from functioning correctly: + 1. During development, Genymotion was used as the runtime-environment. In Genymotion, localhost is accessed through 10.0.3.2 Thus, the constant EPG_FILE_REMOTE in app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/EPGFragment.java might have to be replaced when running the app in another environment. + 2. All the epg-data I was provided had start and end dates on 18 March. For the sake of demonstration, all dates are skewed to be in the current day. As the offset is based on the time between the current day and 18.03.2017, this will not work as inteded if the dates are changed. To eliminate this functionality, replace the code in hackyTimeRecenter() in app/src/main/java/rothschild/henning/jacob/noriginmedia/model/EPGDataCreator.java with return 0 + +## Philosophy + +As time was limited I had to focus my efforts. I believe my various apps show I can work with UI and solve complex tasks using complex technologies. What they do not show is how I structure my projects, or whether I write understandable, testable and decoupled code. As a young developer with little team-experience I notice that this is the exact area where my skills are usually questioned. Therefore, I have focused this project on displaying a good project structure, rather than having the prettiest UI or most complex or optimized functionality. + +## Problems + +Due to time-constraints, the following problems were not prioritized, and thus went unsolved: +- Limited testing: At first, everything was unit- and integration-tested, but it took too much time to keep up. +- Exception-throwing: I am no fan of unnecessary exception-throwing. Exessive exception-throwing is slow, and not the intended way of doing things. Currently the data-fetchers perform no checks before performing actions that might throw exceptions. Thus, exceptions are thrown unnecessarily. These exceptions are later caught, so no real harm is done to the user, but this excessive try-catch structure is neither fast nor elegant. +- Fetch-retry: If the app fails at fetching data during launch, it will not retry until restart. A BroadcastReceiver could be set up to notice when the user turns on their internet, and fetch data from server if a missing network-connection caused the last attempt to fail. +- Fetch on changes: Currently, the app only fetches server-data on start/restart, but ideally it should fetch whenever the servers data changes. I have already implemented this functionality in my previous app SKAMvarsler, so code could easily be copy-pasted to the current project. It is simply a matter of running a Google App Engine servlet regularly from a cron-job. This servlet will fetch data from the server, compare it with previously fetched data, and notify the app through Firebase Cloud Messaging if the servers data has changed. If the app is in the foreground when it is notified, it will fetch this new data from the epg-server. This solution is free and puts no excessive load on users' batteries or networks. +- Toasts: The user should see a toast whenever the server-data is not available, so they know the data displayed is possibly outdated. +- Unswipeable ViewPager: The swipe-functionality of the normal ViewPager overpowers and breaks the pan/scroll functionality of the epg-view. Thus, it was necessary to remove this swipe-functionality from the epg-tab. It seems possible and preffered to make only the epg-tab unswipeable, while the other tabs stay swipeable. +- Drawable resolution: Only one drawable resolution was provided, and it did not fit any of the normally requested resolutions. +- Library limitations: The epg-library I decided to use had certain limitations and problems. Some of these were solved with bandaid solutions, while others were ignored. The problems discovered were: + - Memory leak in example code (fixed) + - Miscalculation of own height (band-aid) + - Downloaded images are not cached (ignored) + - Current-time only updates on restart (ignored) + - Missing some desired functionality (ignored) + +## Note + +As I use Dropbox and was the only person working on this project, I saw no purpose in pushing to a remote repo during development. Therefore, once I finished the project, everything was simultaneously pushed to remote. Locally, however, Git was used heavily during development. After cloning the repo, you can type git log, git reflog, or use any git-tool of your choice to see the whole development-process. This might give a feel for how I typically work. + +## Author + +Jacob Henning Rothschild +Jacob.R.Developer@gmail.com +linkedin.com/in/jacob-h-rothschild diff --git a/app/.gitignore b/app/.gitignore index 796b96d..3543521 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1 @@ -/build +/build diff --git a/app/build.gradle b/app/build.gradle index 4b36313..c3868fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,34 +1,34 @@ -apply plugin: 'com.android.application' - -android { - compileSdkVersion 26 - buildToolsVersion "26.0.0" - defaultConfig { - applicationId "rothschild.henning.jacob.noriginmedia" - minSdkVersion 16 - targetSdkVersion 26 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile project(':epg') - compile 'com.android.support:appcompat-v7:26.+' - compile 'com.android.support:design:26.+' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - // FOR TESTS - testCompile 'junit:junit:4.12' - testCompile 'org.json:json:20080701' - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) -} +apply plugin: 'com.android.application' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.0" + defaultConfig { + applicationId "rothschild.henning.jacob.noriginmedia" + minSdkVersion 16 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile project(':epg') + compile 'com.android.support:appcompat-v7:26.+' + compile 'com.android.support:design:26.+' + compile 'com.android.support.constraint:constraint-layout:1.0.2' + // FOR TESTS + testCompile 'junit:junit:4.12' + testCompile 'org.json:json:20080701' + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 6fe68ce..a25a9f6 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,25 +1,25 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in C:\Users\Jacob\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\Jacob\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedIntegrationTest.java b/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedIntegrationTest.java index bb29f41..1c1847c 100644 --- a/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedIntegrationTest.java +++ b/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedIntegrationTest.java @@ -1,33 +1,33 @@ -package rothschild.henning.jacob.noriginmedia; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import rothschild.henning.jacob.noriginmedia.model.Fetcher; - -import static junit.framework.Assert.assertEquals; - -/** - * Instrumentation test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class InstrumentedIntegrationTest { - - private TestResourceReader testEPGReader = new TestResourceReader(TestResourceReader.EPG_LOCAL); - - /** Asserts that Fetcher.Remote(...) returns correct content from EPG. - * Until cache has been established, this should fail. */ - @Test - public void fetchLocal() throws Exception { - // Context of the app under test. - Context context = InstrumentationRegistry.getTargetContext(); - // NOTE: .toString() must be called. It seems like '==' is called otherwise - assertEquals(testEPGReader.jsonRead().toString(), Fetcher.local(context, TestResourceReader.EPG_LOCAL).toString()); - } -} +package rothschild.henning.jacob.noriginmedia; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import rothschild.henning.jacob.noriginmedia.model.Fetcher; + +import static junit.framework.Assert.assertEquals; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class InstrumentedIntegrationTest { + + private TestResourceReader testEPGReader = new TestResourceReader(TestResourceReader.EPG_LOCAL); + + /** Asserts that Fetcher.Remote(...) returns correct content from EPG. + * Until cache has been established, this should fail. */ + @Test + public void fetchLocal() throws Exception { + // Context of the app under test. + Context context = InstrumentationRegistry.getTargetContext(); + // NOTE: .toString() must be called. It seems like '==' is called otherwise + assertEquals(testEPGReader.jsonRead().toString(), Fetcher.local(context, TestResourceReader.EPG_LOCAL).toString()); + } +} diff --git a/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedUnitTest.java b/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedUnitTest.java index df4af02..20724d4 100644 --- a/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedUnitTest.java +++ b/app/src/androidTest/java/rothschild/henning/jacob/noriginmedia/InstrumentedUnitTest.java @@ -1,32 +1,32 @@ -package rothschild.henning.jacob.noriginmedia; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import rothschild.henning.jacob.noriginmedia.model.ReaderCreator; - -import static org.junit.Assert.assertEquals; - -/** - * Instrumentation test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class InstrumentedUnitTest { - - private TestResourceReader testEPGReader = new TestResourceReader(TestResourceReader.EPG_LOCAL); - - /** Asserts that ReaderCreator.localReader(...) returns a working BufferedReader. - * Until cache has been established, this should fail. */ - @Test - public void localReader() throws Exception { - // Context of the app under test. - Context context = InstrumentationRegistry.getTargetContext(); - assertEquals(testEPGReader.stringRead(), testEPGReader.bufferedReaderToContentString(ReaderCreator.localReader(context, TestResourceReader.EPG_LOCAL))); - } -} +package rothschild.henning.jacob.noriginmedia; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import rothschild.henning.jacob.noriginmedia.model.ReaderCreator; + +import static org.junit.Assert.assertEquals; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class InstrumentedUnitTest { + + private TestResourceReader testEPGReader = new TestResourceReader(TestResourceReader.EPG_LOCAL); + + /** Asserts that ReaderCreator.localReader(...) returns a working BufferedReader. + * Until cache has been established, this should fail. */ + @Test + public void localReader() throws Exception { + // Context of the app under test. + Context context = InstrumentationRegistry.getTargetContext(); + assertEquals(testEPGReader.stringRead(), testEPGReader.bufferedReaderToContentString(ReaderCreator.localReader(context, TestResourceReader.EPG_LOCAL))); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c30b9a4..d9f4f54 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,24 +1,24 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/SharedConstants.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/SharedConstants.java index ece9c6e..1028dcf 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/SharedConstants.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/SharedConstants.java @@ -1,16 +1,16 @@ -package rothschild.henning.jacob.noriginmedia; - -/** - * Created by Jacob H. Rothschild on 23.07.2017. - */ - -public final class SharedConstants { - public final static String CODE_BUNDLE_KEY = "result_code"; - public final static String READ_BUNDLE_KEY = "output_text"; - public static final String CACHE_BUNDLE_KEY = "cache_key"; - public static final String DESTINATION_BUNDLE_KEY = "destination_filename"; - /** Reading succeeded */ - public final static int SUCCESS_CODE = 0; - /** Reading failed */ - public final static int FAILED_CODE = 1; -} +package rothschild.henning.jacob.noriginmedia; + +/** + * Created by Jacob H. Rothschild on 23.07.2017. + */ + +public final class SharedConstants { + public final static String CODE_BUNDLE_KEY = "result_code"; + public final static String READ_BUNDLE_KEY = "output_text"; + public static final String CACHE_BUNDLE_KEY = "cache_key"; + public static final String DESTINATION_BUNDLE_KEY = "destination_filename"; + /** Reading succeeded */ + public final static int SUCCESS_CODE = 0; + /** Reading failed */ + public final static int FAILED_CODE = 1; +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/TestResourceReader.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/TestResourceReader.java index 903f4d6..00c7055 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/TestResourceReader.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/TestResourceReader.java @@ -1,58 +1,58 @@ -package rothschild.henning.jacob.noriginmedia; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - -/** - * A class used for reading files from test/resources, when testing. NOT used by any parts of the actual program (only by the tests). - * This file should NOT be in the main code folder, but I found no other way to make it available for both test-types (test and androidTest) - * Created by Jacob H. Rothschild on 23.07.2017. - */ - -public class TestResourceReader { - - public final static String EPG_LOCAL = "epg.txt"; - public final static String EPG_REMOTE = "http://localhost:1337/epg"; - - private String filename; - - TestResourceReader(String filename) { - this.filename = filename; - } - - /** @return All the content from the constructor-time initialized 'filename', in one, large JSONObject */ - public JSONObject jsonRead() throws JSONException, IOException { - return new JSONObject(stringRead()); - } - - /** @return All the content from the constructor-time initialized 'filename', in one, large String */ - public String stringRead() throws IOException { - BufferedReader reader = filenameToBufferedReader(); - String output = bufferedReaderToContentString(reader); - reader.close(); - return output; - } - - /** @return A BufferedReader for reading the content of the file 'filename' */ - public BufferedReader filenameToBufferedReader() throws IOException { - return new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(filename))); - } - - /** @return A long String made by all the contents of 'reader' */ - public String bufferedReaderToContentString(BufferedReader reader) throws IOException { - return bufferedReaderToContentStringBuilder(reader).toString(); - } - - /** @return A StringBuilder made up of all the contents of 'reader' */ - public StringBuilder bufferedReaderToContentStringBuilder(BufferedReader reader) throws IOException { - StringBuilder stringBuilder = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) stringBuilder.append(line); - return stringBuilder; - } - -} +package rothschild.henning.jacob.noriginmedia; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * A class used for reading files from test/resources, when testing. NOT used by any parts of the actual program (only by the tests). + * This file should NOT be in the main code folder, but I found no other way to make it available for both test-types (test and androidTest) + * Created by Jacob H. Rothschild on 23.07.2017. + */ + +public class TestResourceReader { + + public final static String EPG_LOCAL = "epg.txt"; + public final static String EPG_REMOTE = "http://localhost:1337/epg"; + + private String filename; + + TestResourceReader(String filename) { + this.filename = filename; + } + + /** @return All the content from the constructor-time initialized 'filename', in one, large JSONObject */ + public JSONObject jsonRead() throws JSONException, IOException { + return new JSONObject(stringRead()); + } + + /** @return All the content from the constructor-time initialized 'filename', in one, large String */ + public String stringRead() throws IOException { + BufferedReader reader = filenameToBufferedReader(); + String output = bufferedReaderToContentString(reader); + reader.close(); + return output; + } + + /** @return A BufferedReader for reading the content of the file 'filename' */ + public BufferedReader filenameToBufferedReader() throws IOException { + return new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(filename))); + } + + /** @return A long String made by all the contents of 'reader' */ + public String bufferedReaderToContentString(BufferedReader reader) throws IOException { + return bufferedReaderToContentStringBuilder(reader).toString(); + } + + /** @return A StringBuilder made up of all the contents of 'reader' */ + public StringBuilder bufferedReaderToContentStringBuilder(BufferedReader reader) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) stringBuilder.append(line); + return stringBuilder; + } + +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/AsyncReader.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/AsyncReader.java index c47dc4c..c874991 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/AsyncReader.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/AsyncReader.java @@ -1,66 +1,66 @@ -package rothschild.henning.jacob.noriginmedia.controller; - -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; -import android.support.v4.content.LocalBroadcastManager; - -import java.io.BufferedReader; -import java.io.IOException; - -import rothschild.henning.jacob.noriginmedia.SharedConstants; -import rothschild.henning.jacob.noriginmedia.model.ReaderCreator; -import rothschild.henning.jacob.noriginmedia.model.StringFetcher; - -/** - * Asynchronously reads the content of either a file (local) or page (remote) - * Created by Jacob H. Rothschild on 23.07.2017. - */ - -class AsyncReader extends AsyncTask { - - private final Context appContext; - private final String broadcastName; - private final LocationType locationType; - private final String contentLocation; - private final String cacheKey; - private final String destination; - - AsyncReader(Context appContext, String broadcastName, LocationType locationType, String contentLocation, String cacheKey, String destination) { - this.appContext = appContext; - this.broadcastName = broadcastName; - this.locationType = locationType; - this.contentLocation = contentLocation; - this.cacheKey = cacheKey; - this.destination = destination; - } - - @Override - protected String doInBackground(Void... voids) { - // FIXME: Shouldn't be using the general 'catch (Exception e)'. 'nulls' and other exception-sources should be spotted before becoming exceptions at all - try { - // Closest I could get to dependency injection while running everything asynchronously - return StringFetcher.fromBufferedReader(createBufferedReader()); - } catch (Exception e) { - e.printStackTrace(); - } - return ""; - } - - /** @return A BufferedReader, based on the parameters 'locationType' and 'contentLocation' received in the AsyncReader-constructor */ - private BufferedReader createBufferedReader() throws IOException { - return locationType == LocationType.LOCAL ? - ReaderCreator.localReader(appContext, contentLocation) : - ReaderCreator.remoteReader(contentLocation); - } - - @Override - protected void onPostExecute(String epg) { - Intent intent = new Intent(broadcastName); - intent.putExtra(SharedConstants.CODE_BUNDLE_KEY, epg.isEmpty() ? SharedConstants.FAILED_CODE : SharedConstants.SUCCESS_CODE); - intent.putExtra(SharedConstants.READ_BUNDLE_KEY, epg); - intent.putExtra(SharedConstants.CACHE_BUNDLE_KEY, cacheKey); - intent.putExtra(SharedConstants.DESTINATION_BUNDLE_KEY, destination); - LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent); - } +package rothschild.henning.jacob.noriginmedia.controller; + +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; +import android.support.v4.content.LocalBroadcastManager; + +import java.io.BufferedReader; +import java.io.IOException; + +import rothschild.henning.jacob.noriginmedia.SharedConstants; +import rothschild.henning.jacob.noriginmedia.model.ReaderCreator; +import rothschild.henning.jacob.noriginmedia.model.StringFetcher; + +/** + * Asynchronously reads the content of either a file (local) or page (remote) + * Created by Jacob H. Rothschild on 23.07.2017. + */ + +class AsyncReader extends AsyncTask { + + private final Context appContext; + private final String broadcastName; + private final LocationType locationType; + private final String contentLocation; + private final String cacheKey; + private final String destination; + + AsyncReader(Context appContext, String broadcastName, LocationType locationType, String contentLocation, String cacheKey, String destination) { + this.appContext = appContext; + this.broadcastName = broadcastName; + this.locationType = locationType; + this.contentLocation = contentLocation; + this.cacheKey = cacheKey; + this.destination = destination; + } + + @Override + protected String doInBackground(Void... voids) { + // FIXME: Shouldn't be using the general 'catch (Exception e)'. 'nulls' and other exception-sources should be spotted before becoming exceptions at all + try { + // Closest I could get to dependency injection while running everything asynchronously + return StringFetcher.fromBufferedReader(createBufferedReader()); + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + } + + /** @return A BufferedReader, based on the parameters 'locationType' and 'contentLocation' received in the AsyncReader-constructor */ + private BufferedReader createBufferedReader() throws IOException { + return locationType == LocationType.LOCAL ? + ReaderCreator.localReader(appContext, contentLocation) : + ReaderCreator.remoteReader(contentLocation); + } + + @Override + protected void onPostExecute(String epg) { + Intent intent = new Intent(broadcastName); + intent.putExtra(SharedConstants.CODE_BUNDLE_KEY, epg.isEmpty() ? SharedConstants.FAILED_CODE : SharedConstants.SUCCESS_CODE); + intent.putExtra(SharedConstants.READ_BUNDLE_KEY, epg); + intent.putExtra(SharedConstants.CACHE_BUNDLE_KEY, cacheKey); + intent.putExtra(SharedConstants.DESTINATION_BUNDLE_KEY, destination); + LocalBroadcastManager.getInstance(appContext).sendBroadcast(intent); + } } \ No newline at end of file diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/EPGFragment.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/EPGFragment.java index c5336ad..d97b90e 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/EPGFragment.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/EPGFragment.java @@ -1,124 +1,124 @@ -package rothschild.henning.jacob.noriginmedia.controller; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.content.LocalBroadcastManager; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import java.text.SimpleDateFormat; -import java.util.Locale; - -import rothschild.henning.jacob.epg.EPG; -import rothschild.henning.jacob.epg.EPGClickListener; -import rothschild.henning.jacob.epg.EPGData; -import rothschild.henning.jacob.epg.domain.EPGChannel; -import rothschild.henning.jacob.epg.domain.EPGEvent; -import rothschild.henning.jacob.noriginmedia.R; -import rothschild.henning.jacob.noriginmedia.SharedConstants; -import rothschild.henning.jacob.noriginmedia.model.CacheHandler; -import rothschild.henning.jacob.noriginmedia.model.EPGDataCreator; -import rothschild.henning.jacob.noriginmedia.model.StorageWriter; - -public class EPGFragment extends Fragment { - - // TODO: Update epg-view regularly, to represent that the time is constantly changing (right now the visual current-time-indicator is stuck until restart) - - private static final String TAG = EPGFragment.class.getSimpleName(); - private static final String EPG_KEY = "epg"; - private static final String EPG_FILE_LOCAL = "epg.txt"; - // 10.0.3.2 is localhost's IP address in Genymotion emulator (10.0.2.2 in Android emulator) - private static final String EPG_FILE_REMOTE = "http://10.0.3.2:1337/epg"; // localhost - private static final String EPG_BROADCAST_LOCAL = "EPG_BROADCAST_LOCAL"; - private static final String EPG_BROADCAST_REMOTE = "EPG_BROADCAST_REMOTE"; - private final static String EPG_INPUT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX"; - - private CacheHandler cache = new CacheHandler(); - private EPG epg; - private BroadcastReceiver receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - handleReceivedBroadcast(intent); - } - }; - - @Override - public void onDestroyView() { - if (epg != null) epg.clearEPGImageCache(); - LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(receiver); - super.onDestroyView(); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.fragment_epg, container, false); - initEpg((EPG) rootView.findViewById(R.id.epg)); - return rootView; - } - - private void initEpg(EPG epg) { - this.epg = epg; - setEPGClickListener(); - fetchEPGData(); - } - - private void setEPGClickListener() { - epg.setEPGClickListener(new EPGClickListener() { - @Override - public void onChannelClicked(int channelPosition, EPGChannel epgChannel) { - Toast.makeText(getActivity(), epgChannel.getName() + " clicked", Toast.LENGTH_SHORT).show(); - } - - @Override - public void onEventClicked(int channelPosition, int programPosition, EPGEvent epgEvent) { - Toast.makeText(getActivity(), epgEvent.getTitle() + " clicked", Toast.LENGTH_SHORT).show(); - } - - @Override - public void onResetButtonClicked() { - epg.recalculateAndRedraw(true); - } - }); - } - - private void fetchEPGData() { - fetchData(EPG_BROADCAST_LOCAL, LocationType.LOCAL, EPG_FILE_LOCAL, EPG_KEY, null); - fetchData(EPG_BROADCAST_REMOTE, LocationType.REMOTE, EPG_FILE_REMOTE, EPG_KEY, EPG_FILE_LOCAL); - } - - private void fetchData(String broadcastKey, LocationType locationType, String contentLocation, String cacheKey, String destination) { - LocalBroadcastManager.getInstance(getActivity()).registerReceiver(receiver, new IntentFilter(broadcastKey)); - new AsyncReader(getActivity().getApplicationContext(), broadcastKey, locationType, contentLocation, cacheKey, destination).execute(); - } - - private void handleReceivedBroadcast(Intent intent) { - try { - int resultCode = intent.getIntExtra(SharedConstants.CODE_BUNDLE_KEY, SharedConstants.FAILED_CODE); - String cacheKey = intent.getStringExtra(SharedConstants.CACHE_BUNDLE_KEY); - String read = intent.getStringExtra(SharedConstants.READ_BUNDLE_KEY); - String destinationFilename = intent.getStringExtra(SharedConstants.DESTINATION_BUNDLE_KEY); - handleReceivedBroadcastValues(resultCode, cacheKey, read, destinationFilename); - } catch (Exception e) { - e.printStackTrace(); - // I'm not a big fan of these general exception catchers... - } - } - - private void handleReceivedBroadcastValues(int resultCode, String cacheKey, String read, String destinationFilename) throws Exception { - if (resultCode == SharedConstants.SUCCESS_CODE && cache.ifNewWillCacheAndTell(cacheKey, read)) { - StorageWriter.writeIfFilenameNotNull(getActivity().getApplicationContext(), destinationFilename, read); - setAndRedrawEPGData(EPGDataCreator.fromJSONString(read, new SimpleDateFormat(EPG_INPUT_DATE_FORMAT, Locale.UK))); - } - } - - private void setAndRedrawEPGData(EPGData data) { - epg.setEPGData(data); - epg.recalculateAndRedraw(false); - } -} +package rothschild.henning.jacob.noriginmedia.controller; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.content.LocalBroadcastManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import java.text.SimpleDateFormat; +import java.util.Locale; + +import rothschild.henning.jacob.epg.EPG; +import rothschild.henning.jacob.epg.EPGClickListener; +import rothschild.henning.jacob.epg.EPGData; +import rothschild.henning.jacob.epg.domain.EPGChannel; +import rothschild.henning.jacob.epg.domain.EPGEvent; +import rothschild.henning.jacob.noriginmedia.R; +import rothschild.henning.jacob.noriginmedia.SharedConstants; +import rothschild.henning.jacob.noriginmedia.model.CacheHandler; +import rothschild.henning.jacob.noriginmedia.model.EPGDataCreator; +import rothschild.henning.jacob.noriginmedia.model.StorageWriter; + +public class EPGFragment extends Fragment { + + // TODO: Update epg-view regularly, to represent that the time is constantly changing (right now the visual current-time-indicator is stuck until restart) + + private static final String TAG = EPGFragment.class.getSimpleName(); + private static final String EPG_KEY = "epg"; + private static final String EPG_FILE_LOCAL = "epg.txt"; + // 10.0.3.2 is localhost's IP address in Genymotion emulator (10.0.2.2 in Android emulator) + private static final String EPG_FILE_REMOTE = "http://10.0.3.2:1337/epg"; // localhost + private static final String EPG_BROADCAST_LOCAL = "EPG_BROADCAST_LOCAL"; + private static final String EPG_BROADCAST_REMOTE = "EPG_BROADCAST_REMOTE"; + private final static String EPG_INPUT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX"; + + private CacheHandler cache = new CacheHandler(); + private EPG epg; + private BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleReceivedBroadcast(intent); + } + }; + + @Override + public void onDestroyView() { + if (epg != null) epg.clearEPGImageCache(); + LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(receiver); + super.onDestroyView(); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_epg, container, false); + initEpg((EPG) rootView.findViewById(R.id.epg)); + return rootView; + } + + private void initEpg(EPG epg) { + this.epg = epg; + setEPGClickListener(); + fetchEPGData(); + } + + private void setEPGClickListener() { + epg.setEPGClickListener(new EPGClickListener() { + @Override + public void onChannelClicked(int channelPosition, EPGChannel epgChannel) { + Toast.makeText(getActivity(), epgChannel.getName() + " clicked", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onEventClicked(int channelPosition, int programPosition, EPGEvent epgEvent) { + Toast.makeText(getActivity(), epgEvent.getTitle() + " clicked", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onResetButtonClicked() { + epg.recalculateAndRedraw(true); + } + }); + } + + private void fetchEPGData() { + fetchData(EPG_BROADCAST_LOCAL, LocationType.LOCAL, EPG_FILE_LOCAL, EPG_KEY, null); + fetchData(EPG_BROADCAST_REMOTE, LocationType.REMOTE, EPG_FILE_REMOTE, EPG_KEY, EPG_FILE_LOCAL); + } + + private void fetchData(String broadcastKey, LocationType locationType, String contentLocation, String cacheKey, String destination) { + LocalBroadcastManager.getInstance(getActivity()).registerReceiver(receiver, new IntentFilter(broadcastKey)); + new AsyncReader(getActivity().getApplicationContext(), broadcastKey, locationType, contentLocation, cacheKey, destination).execute(); + } + + private void handleReceivedBroadcast(Intent intent) { + try { + int resultCode = intent.getIntExtra(SharedConstants.CODE_BUNDLE_KEY, SharedConstants.FAILED_CODE); + String cacheKey = intent.getStringExtra(SharedConstants.CACHE_BUNDLE_KEY); + String read = intent.getStringExtra(SharedConstants.READ_BUNDLE_KEY); + String destinationFilename = intent.getStringExtra(SharedConstants.DESTINATION_BUNDLE_KEY); + handleReceivedBroadcastValues(resultCode, cacheKey, read, destinationFilename); + } catch (Exception e) { + e.printStackTrace(); + // I'm not a big fan of these general exception catchers... + } + } + + private void handleReceivedBroadcastValues(int resultCode, String cacheKey, String read, String destinationFilename) throws Exception { + if (resultCode == SharedConstants.SUCCESS_CODE && cache.ifNewWillCacheAndTell(cacheKey, read)) { + StorageWriter.writeIfFilenameNotNull(getActivity().getApplicationContext(), destinationFilename, read); + setAndRedrawEPGData(EPGDataCreator.fromJSONString(read, new SimpleDateFormat(EPG_INPUT_DATE_FORMAT, Locale.UK))); + } + } + + private void setAndRedrawEPGData(EPGData data) { + epg.setEPGData(data); + epg.recalculateAndRedraw(false); + } +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/LocationType.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/LocationType.java index 4104d60..1990031 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/LocationType.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/LocationType.java @@ -1,9 +1,9 @@ -package rothschild.henning.jacob.noriginmedia.controller; - -/** - * Created by Jacob H. Rothschild on 23.07.2017. - */ - -enum LocationType { - LOCAL, REMOTE -} +package rothschild.henning.jacob.noriginmedia.controller; + +/** + * Created by Jacob H. Rothschild on 23.07.2017. + */ + +enum LocationType { + LOCAL, REMOTE +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/MainActivity.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/MainActivity.java index 5f2a516..28351ad 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/MainActivity.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/MainActivity.java @@ -1,64 +1,64 @@ -package rothschild.henning.jacob.noriginmedia.controller; - -import android.os.Bundle; -import android.support.design.widget.TabLayout; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.Menu; - -import rothschild.henning.jacob.noriginmedia.R; -import rothschild.henning.jacob.noriginmedia.view.UnswipeableViewPager; - -/** - * Auto-generated by Android Studio. - * - * The head-controller of everything. Pulls the tabs, content and menus together. - */ -public class MainActivity extends AppCompatActivity { - - private static final int[] TAB_ICONS = {R.drawable.home, R.drawable.video, R.drawable.list, R.drawable.replay, R.drawable.read}; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - // Create the adapter that will return a fragment for each of the three - // primary sections of the activity. - /* - The {@link android.support.v4.view.PagerAdapter} that will provide - fragments for each of the sections. We use a - {@link FragmentPagerAdapter} derivative, which will keep every - loaded fragment in memory. If this becomes too memory intensive, it - may be best to switch to a - {@link android.support.v4.app.FragmentStatePagerAdapter}. - */ - SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); - - // Set up the TabLayout with the UnswipeableViewPager with the sections adapter. - /* - The {@link ViewPager} that will host the section contents. - */ - UnswipeableViewPager mViewPager = (UnswipeableViewPager) findViewById(R.id.container); - mViewPager.setAdapter(mSectionsPagerAdapter); - mViewPager.setCurrentItem(2); - TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); - tabLayout.setupWithViewPager(mViewPager); - setTabIcons(tabLayout); - } - - private void setTabIcons(TabLayout tabLayout) { - for (int tabN = 0; tabN < tabLayout.getTabCount(); tabN++) { - tabLayout.getTabAt(tabN).setIcon(TAB_ICONS[tabN]); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_main, menu); - return true; - } -} +package rothschild.henning.jacob.noriginmedia.controller; + +import android.os.Bundle; +import android.support.design.widget.TabLayout; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.Menu; + +import rothschild.henning.jacob.noriginmedia.R; +import rothschild.henning.jacob.noriginmedia.view.UnswipeableViewPager; + +/** + * Auto-generated by Android Studio. + * + * The head-controller of everything. Pulls the tabs, content and menus together. + */ +public class MainActivity extends AppCompatActivity { + + private static final int[] TAB_ICONS = {R.drawable.home, R.drawable.video, R.drawable.list, R.drawable.replay, R.drawable.read}; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + // Create the adapter that will return a fragment for each of the three + // primary sections of the activity. + /* + The {@link android.support.v4.view.PagerAdapter} that will provide + fragments for each of the sections. We use a + {@link FragmentPagerAdapter} derivative, which will keep every + loaded fragment in memory. If this becomes too memory intensive, it + may be best to switch to a + {@link android.support.v4.app.FragmentStatePagerAdapter}. + */ + SectionsPagerAdapter mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager()); + + // Set up the TabLayout with the UnswipeableViewPager with the sections adapter. + /* + The {@link ViewPager} that will host the section contents. + */ + UnswipeableViewPager mViewPager = (UnswipeableViewPager) findViewById(R.id.container); + mViewPager.setAdapter(mSectionsPagerAdapter); + mViewPager.setCurrentItem(2); + TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs); + tabLayout.setupWithViewPager(mViewPager); + setTabIcons(tabLayout); + } + + private void setTabIcons(TabLayout tabLayout) { + for (int tabN = 0; tabN < tabLayout.getTabCount(); tabN++) { + tabLayout.getTabAt(tabN).setIcon(TAB_ICONS[tabN]); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/PlaceholderFragment.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/PlaceholderFragment.java index 23f040d..3a59628 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/PlaceholderFragment.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/PlaceholderFragment.java @@ -1,45 +1,45 @@ -package rothschild.henning.jacob.noriginmedia.controller; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import rothschild.henning.jacob.noriginmedia.R; - -/** - * A placeholder fragment containing a simple view. - */ -public class PlaceholderFragment extends Fragment { - /** - * The fragment argument representing the section number for this - * fragment. - */ - private static final String ARG_SECTION_NUMBER = "section_number"; - - public PlaceholderFragment() { - } - - /** - * Returns a new instance of this fragment for the given section - * number. - */ - public static PlaceholderFragment newInstance(int sectionNumber) { - PlaceholderFragment fragment = new PlaceholderFragment(); - Bundle args = new Bundle(); - args.putInt(ARG_SECTION_NUMBER, sectionNumber); - fragment.setArguments(args); - return fragment; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.fragment_placeholder, container, false); - TextView textView = rootView.findViewById(R.id.section_label); - textView.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER))); - return rootView; - } -} +package rothschild.henning.jacob.noriginmedia.controller; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import rothschild.henning.jacob.noriginmedia.R; + +/** + * A placeholder fragment containing a simple view. + */ +public class PlaceholderFragment extends Fragment { + /** + * The fragment argument representing the section number for this + * fragment. + */ + private static final String ARG_SECTION_NUMBER = "section_number"; + + public PlaceholderFragment() { + } + + /** + * Returns a new instance of this fragment for the given section + * number. + */ + public static PlaceholderFragment newInstance(int sectionNumber) { + PlaceholderFragment fragment = new PlaceholderFragment(); + Bundle args = new Bundle(); + args.putInt(ARG_SECTION_NUMBER, sectionNumber); + fragment.setArguments(args); + return fragment; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_placeholder, container, false); + TextView textView = rootView.findViewById(R.id.section_label); + textView.setText(getString(R.string.section_format, getArguments().getInt(ARG_SECTION_NUMBER))); + return rootView; + } +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/SectionsPagerAdapter.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/SectionsPagerAdapter.java index da0e588..f86a9b0 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/SectionsPagerAdapter.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/controller/SectionsPagerAdapter.java @@ -1,31 +1,31 @@ -package rothschild.henning.jacob.noriginmedia.controller; - -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentPagerAdapter; - -/** - * A {@link FragmentPagerAdapter} that returns a fragment corresponding to - * one of the sections/tabs/pages. - * - * Auto-generated by Android Studio. - * - * Can be thought of as the controller of the tab-functionality. - */ -public class SectionsPagerAdapter extends FragmentPagerAdapter { - - public SectionsPagerAdapter(FragmentManager fm) { - super(fm); - } - - @Override - public Fragment getItem(int position) { - return position == 2 ? new EPGFragment() : PlaceholderFragment.newInstance(position + 1); - } - - @Override - public int getCount() { - // Show 5 pages - return 5; - } +package rothschild.henning.jacob.noriginmedia.controller; + +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; + +/** + * A {@link FragmentPagerAdapter} that returns a fragment corresponding to + * one of the sections/tabs/pages. + * + * Auto-generated by Android Studio. + * + * Can be thought of as the controller of the tab-functionality. + */ +public class SectionsPagerAdapter extends FragmentPagerAdapter { + + public SectionsPagerAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + return position == 2 ? new EPGFragment() : PlaceholderFragment.newInstance(position + 1); + } + + @Override + public int getCount() { + // Show 5 pages + return 5; + } } \ No newline at end of file diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/CacheHandler.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/CacheHandler.java index ce27182..86e11e2 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/CacheHandler.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/CacheHandler.java @@ -1,34 +1,34 @@ -package rothschild.henning.jacob.noriginmedia.model; - -import java.util.HashMap; - -/** - * Created by Jacob H. Rothschild on 24.07.2017. - */ - -public class CacheHandler { - - private HashMap cache = new HashMap<>(); - - /** - * Puts key-value pair in cache, if it is NOT identical to something stored there already - * @return Whether this key-value pair is NOT stored in cache - */ - public boolean ifNewWillCacheAndTell(String key, Object value) { - boolean isNew = isNew(key, value); - if (isNew) putInCache(key, value); - return isNew; - } - - /** @return Whether value is NOT stored in our cache (and thus tells us something new) */ - public boolean isNew(String key, Object value) { - Object cached = cache.get(key); - return cached == null || !value.equals(cached); - } - - /** Puts key-value pair in cache */ - public void putInCache(String key, Object value) { - cache.put(key, value); - } - -} +package rothschild.henning.jacob.noriginmedia.model; + +import java.util.HashMap; + +/** + * Created by Jacob H. Rothschild on 24.07.2017. + */ + +public class CacheHandler { + + private HashMap cache = new HashMap<>(); + + /** + * Puts key-value pair in cache, if it is NOT identical to something stored there already + * @return Whether this key-value pair is NOT stored in cache + */ + public boolean ifNewWillCacheAndTell(String key, Object value) { + boolean isNew = isNew(key, value); + if (isNew) putInCache(key, value); + return isNew; + } + + /** @return Whether value is NOT stored in our cache (and thus tells us something new) */ + public boolean isNew(String key, Object value) { + Object cached = cache.get(key); + return cached == null || !value.equals(cached); + } + + /** Puts key-value pair in cache */ + public void putInCache(String key, Object value) { + cache.put(key, value); + } + +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/EPGDataCreator.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/EPGDataCreator.java index 1b9fa88..4c3b4eb 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/EPGDataCreator.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/EPGDataCreator.java @@ -1,76 +1,76 @@ -package rothschild.henning.jacob.noriginmedia.model; - -import android.annotation.SuppressLint; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import rothschild.henning.jacob.epg.EPGData; -import rothschild.henning.jacob.epg.domain.EPGChannel; -import rothschild.henning.jacob.epg.domain.EPGEvent; -import rothschild.henning.jacob.epg.misc.EPGDataImpl; - -/** - * Created by Jacob H. Rothschild on 23.07.2017. - */ - -public class EPGDataCreator { - - public static EPGData fromJSONString(String jsonString, SimpleDateFormat dateFormat) throws JSONException, ParseException { - return new EPGDataImpl(jsonToEPGHashMap(new JSONObject(jsonString).getJSONArray("channels"), dateFormat)); - } - - private static LinkedHashMap> jsonToEPGHashMap(JSONArray channels, SimpleDateFormat dateFormat) throws JSONException, ParseException { - LinkedHashMap> epgMap = new LinkedHashMap<>(); - for (int index = 0; index < channels.length(); index++) { - JSONObject channel = channels.getJSONObject(index); - epgMap.put(extractEPGChannel(channel), extractEPGEvents(channel, dateFormat)); - } - Log.i("EPGDataCreator", "TimeAdjustment: " + hackyTimeRecenter()); - return returnWithDummyChannelAtEnd(returnWithDummyChannelAtEnd(epgMap)); - } - - /** There is a bug in the EPG-library, which make it believe it has the whole screen-height. Thus, our toolbars result in the bottom-most channels not being displayed at all. Therefore, an empty dummy-channel is added here. This is obviously NOT an ideal solution. Had I not been pressed on time, I would probably have edited the library, but I am pressed on time. Thus, this is the ugly band-aid solution that will be used for now. */ - private static LinkedHashMap> returnWithDummyChannelAtEnd(LinkedHashMap> epgMap) { - ArrayList dummyEventList = new ArrayList<>(); - dummyEventList.add(new EPGEvent(0, 0, " ")); - epgMap.put(new EPGChannel("dummy", " ", "https://upload.wikimedia.org/wikipedia/commons/c/ca/1x1.png"), dummyEventList); - return epgMap; - } - - private static EPGChannel extractEPGChannel(JSONObject channel) throws JSONException { - return new EPGChannel(channel.getJSONObject("images").getString("logo"), channel.getString("title"), channel.getString("id")); - } - - private static ArrayList extractEPGEvents(JSONObject channel, SimpleDateFormat dateFormat) throws JSONException, ParseException { - JSONArray events = channel.getJSONArray("schedules"); - ArrayList epgEvents = new ArrayList<>(); - for (int index = 0; index < events.length(); index++) { - JSONObject event = events.getJSONObject(index); - epgEvents.add(new EPGEvent(stringDateToLongDate(event.getString("start"), dateFormat), stringDateToLongDate(event.getString("end"), dateFormat), event.getString("title"))); - } - return epgEvents; - } - - private static long stringDateToLongDate(String string, SimpleDateFormat dateFormat) throws ParseException { - return dateFormat.parse(string).getTime() + hackyTimeRecenter(); - } - - /** WARNING: ONLY for demonstration purposes. Remove in any deployment version!

- * There is no reason in showing an empty screen with no content. Thus I decided to, for the sake of demonstration, always make the programs center around the current day. This would of course never be part of a deployment version, but is necessary for the purpose of demonstration, at least with the input-data I have been provided. */ - @SuppressLint("LongLogTag") - private static long hackyTimeRecenter() { - // 1489791600000L is 2017-03-18T00:00:00+01:00 - return TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - 1489791600000L)); - } - -} +package rothschild.henning.jacob.noriginmedia.model; + +import android.annotation.SuppressLint; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import rothschild.henning.jacob.epg.EPGData; +import rothschild.henning.jacob.epg.domain.EPGChannel; +import rothschild.henning.jacob.epg.domain.EPGEvent; +import rothschild.henning.jacob.epg.misc.EPGDataImpl; + +/** + * Created by Jacob H. Rothschild on 23.07.2017. + */ + +public class EPGDataCreator { + + public static EPGData fromJSONString(String jsonString, SimpleDateFormat dateFormat) throws JSONException, ParseException { + return new EPGDataImpl(jsonToEPGHashMap(new JSONObject(jsonString).getJSONArray("channels"), dateFormat)); + } + + private static LinkedHashMap> jsonToEPGHashMap(JSONArray channels, SimpleDateFormat dateFormat) throws JSONException, ParseException { + LinkedHashMap> epgMap = new LinkedHashMap<>(); + for (int index = 0; index < channels.length(); index++) { + JSONObject channel = channels.getJSONObject(index); + epgMap.put(extractEPGChannel(channel), extractEPGEvents(channel, dateFormat)); + } + Log.i("EPGDataCreator", "TimeAdjustment: " + hackyTimeRecenter()); + return returnWithDummyChannelAtEnd(returnWithDummyChannelAtEnd(epgMap)); + } + + /** There is a bug in the EPG-library, which make it believe it has the whole screen-height. Thus, our toolbars result in the bottom-most channels not being displayed at all. Therefore, an empty dummy-channel is added here. This is obviously NOT an ideal solution. Had I not been pressed on time, I would probably have edited the library, but I am pressed on time. Thus, this is the ugly band-aid solution that will be used for now. */ + private static LinkedHashMap> returnWithDummyChannelAtEnd(LinkedHashMap> epgMap) { + ArrayList dummyEventList = new ArrayList<>(); + dummyEventList.add(new EPGEvent(0, 0, " ")); + epgMap.put(new EPGChannel("dummy", " ", "https://upload.wikimedia.org/wikipedia/commons/c/ca/1x1.png"), dummyEventList); + return epgMap; + } + + private static EPGChannel extractEPGChannel(JSONObject channel) throws JSONException { + return new EPGChannel(channel.getJSONObject("images").getString("logo"), channel.getString("title"), channel.getString("id")); + } + + private static ArrayList extractEPGEvents(JSONObject channel, SimpleDateFormat dateFormat) throws JSONException, ParseException { + JSONArray events = channel.getJSONArray("schedules"); + ArrayList epgEvents = new ArrayList<>(); + for (int index = 0; index < events.length(); index++) { + JSONObject event = events.getJSONObject(index); + epgEvents.add(new EPGEvent(stringDateToLongDate(event.getString("start"), dateFormat), stringDateToLongDate(event.getString("end"), dateFormat), event.getString("title"))); + } + return epgEvents; + } + + private static long stringDateToLongDate(String string, SimpleDateFormat dateFormat) throws ParseException { + return dateFormat.parse(string).getTime() + hackyTimeRecenter(); + } + + /** WARNING: ONLY for demonstration purposes. Remove in any deployment version!

+ * There is no reason in showing an empty screen with no content. Thus I decided to, for the sake of demonstration, always make the programs center around the current day. This would of course never be part of a deployment version, but is necessary for the purpose of demonstration, at least with the input-data I have been provided. */ + @SuppressLint("LongLogTag") + private static long hackyTimeRecenter() { + // 1489791600000L is 2017-03-18T00:00:00+01:00 + return TimeUnit.DAYS.toMillis(TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - 1489791600000L)); + } + +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/Fetcher.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/Fetcher.java index 1a87375..5042c33 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/Fetcher.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/Fetcher.java @@ -1,42 +1,42 @@ -package rothschild.henning.jacob.noriginmedia.model; - -import android.content.Context; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; - -/** - * Short helper-class to reduce coupling between Model and Controller. - * Fetches data remotely (server) or locally (disk). - * Created by Jacob H. Rothschild on 22.07.2017. - */ - -public class Fetcher { - - // As this functions as a top-class of model, I feel it is ok to call static functions from here, instead of using dependency injection from our controller. - - // FIXME: Remote data is not stored (cached) locally - // FIXME: Missing comparison functionality to assure remote content is new, before giving it to dumb UI - - // NOTE: For this example, filename will always be "epg" - @Deprecated - public static JSONObject local(Context context, String filename) throws JSONException, IOException { - return fetch(ReaderCreator.localReader(context, filename)); - } - - // NOTE: For this example, urlString will always be "http://localhost:1337/epg" - @Deprecated - public static JSONObject remote(String urlString) throws JSONException, IOException { - return fetch(ReaderCreator.remoteReader(urlString)); - } - - // NOTE: This method will grow once we implement the actual epg, so don't worry about it being too short. It'll grow because we'll have to convert from JSON to epg-format - @Deprecated - private static JSONObject fetch(BufferedReader reader) throws JSONException, IOException { - return new JSONObject(StringFetcher.fromBufferedReader(reader)); - } - -} +package rothschild.henning.jacob.noriginmedia.model; + +import android.content.Context; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; + +/** + * Short helper-class to reduce coupling between Model and Controller. + * Fetches data remotely (server) or locally (disk). + * Created by Jacob H. Rothschild on 22.07.2017. + */ + +public class Fetcher { + + // As this functions as a top-class of model, I feel it is ok to call static functions from here, instead of using dependency injection from our controller. + + // FIXME: Remote data is not stored (cached) locally + // FIXME: Missing comparison functionality to assure remote content is new, before giving it to dumb UI + + // NOTE: For this example, filename will always be "epg" + @Deprecated + public static JSONObject local(Context context, String filename) throws JSONException, IOException { + return fetch(ReaderCreator.localReader(context, filename)); + } + + // NOTE: For this example, urlString will always be "http://localhost:1337/epg" + @Deprecated + public static JSONObject remote(String urlString) throws JSONException, IOException { + return fetch(ReaderCreator.remoteReader(urlString)); + } + + // NOTE: This method will grow once we implement the actual epg, so don't worry about it being too short. It'll grow because we'll have to convert from JSON to epg-format + @Deprecated + private static JSONObject fetch(BufferedReader reader) throws JSONException, IOException { + return new JSONObject(StringFetcher.fromBufferedReader(reader)); + } + +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/ReaderCreator.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/ReaderCreator.java index e772de2..d6ae377 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/ReaderCreator.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/ReaderCreator.java @@ -1,33 +1,33 @@ -package rothschild.henning.jacob.noriginmedia.model; - -import android.content.Context; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; - -/** - * Static functions for creating and returning BufferedReaders for local files and remote urls - * Created by Jacob H. Rothschild on 22.07.2017. - */ - -public class ReaderCreator { - - /** @return A BufferedReader for fetching the content of the url 'urlString' */ - public static BufferedReader remoteReader(String urlString) throws IOException { - return inputStreamToBufferedReader(new URL(urlString).openStream()); - } - - public static BufferedReader localReader(Context context, String filename) throws IOException { - return inputStreamToBufferedReader(context.openFileInput(filename)); - } - - /** @return A BufferedReader for reading the InputStream 'stream' */ - private static BufferedReader inputStreamToBufferedReader(InputStream stream) throws IOException { - // Settings "UTF-8" is necessary to read "æøå". This is NOT reproducible in normal java, but it is in GAE - return new BufferedReader(new InputStreamReader(stream, "UTF-8")); - } - -} +package rothschild.henning.jacob.noriginmedia.model; + +import android.content.Context; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; + +/** + * Static functions for creating and returning BufferedReaders for local files and remote urls + * Created by Jacob H. Rothschild on 22.07.2017. + */ + +public class ReaderCreator { + + /** @return A BufferedReader for fetching the content of the url 'urlString' */ + public static BufferedReader remoteReader(String urlString) throws IOException { + return inputStreamToBufferedReader(new URL(urlString).openStream()); + } + + public static BufferedReader localReader(Context context, String filename) throws IOException { + return inputStreamToBufferedReader(context.openFileInput(filename)); + } + + /** @return A BufferedReader for reading the InputStream 'stream' */ + private static BufferedReader inputStreamToBufferedReader(InputStream stream) throws IOException { + // Settings "UTF-8" is necessary to read "æøå". This is NOT reproducible in normal java, but it is in GAE + return new BufferedReader(new InputStreamReader(stream, "UTF-8")); + } + +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StorageWriter.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StorageWriter.java index 97a43b3..7f523cc 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StorageWriter.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StorageWriter.java @@ -1,26 +1,26 @@ -package rothschild.henning.jacob.noriginmedia.model; - -import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.IOException; -import java.io.OutputStreamWriter; - -/** - * Created by Jacob H. Rothschild on 24.07.2017. - */ - -public class StorageWriter { - - public static void writeIfFilenameNotNull(Context appContext, @Nullable String filename, String content) throws IOException { - if (filename != null) write(appContext, filename, content); - } - - public static void write(Context appContext, @NonNull String filename, String content) throws IOException { - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(appContext.openFileOutput(filename, Context.MODE_PRIVATE)); - outputStreamWriter.write(content); - outputStreamWriter.close(); - } - -} +package rothschild.henning.jacob.noriginmedia.model; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.io.IOException; +import java.io.OutputStreamWriter; + +/** + * Created by Jacob H. Rothschild on 24.07.2017. + */ + +public class StorageWriter { + + public static void writeIfFilenameNotNull(Context appContext, @Nullable String filename, String content) throws IOException { + if (filename != null) write(appContext, filename, content); + } + + public static void write(Context appContext, @NonNull String filename, String content) throws IOException { + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(appContext.openFileOutput(filename, Context.MODE_PRIVATE)); + outputStreamWriter.write(content); + outputStreamWriter.close(); + } + +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StringFetcher.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StringFetcher.java index 2e70152..1148f1f 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StringFetcher.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/model/StringFetcher.java @@ -1,34 +1,34 @@ -package rothschild.henning.jacob.noriginmedia.model; - -import org.json.JSONException; - -import java.io.BufferedReader; -import java.io.IOException; - -/** - * Created by Jacob H. Rothschild on 22.07.2017. - */ - -public class StringFetcher { - - /** @return All the content from 'reader', in one, large String */ - public static String fromBufferedReader(BufferedReader reader) throws JSONException, IOException { - return bufferedReaderToContentString(reader); - } - - /** @return All the content from 'reader', in one, large String */ - private static String bufferedReaderToContentString(BufferedReader reader) throws IOException { - StringBuilder builder = bufferedReaderToContentStringBuilder(reader); - reader.close(); - return builder.toString(); - } - - /** @return A StringBuilder made up of all the contents of 'reader' */ - private static StringBuilder bufferedReaderToContentStringBuilder(BufferedReader reader) throws IOException { - StringBuilder stringBuilder = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) stringBuilder.append(line); - return stringBuilder; - } - -} +package rothschild.henning.jacob.noriginmedia.model; + +import org.json.JSONException; + +import java.io.BufferedReader; +import java.io.IOException; + +/** + * Created by Jacob H. Rothschild on 22.07.2017. + */ + +public class StringFetcher { + + /** @return All the content from 'reader', in one, large String */ + public static String fromBufferedReader(BufferedReader reader) throws JSONException, IOException { + return bufferedReaderToContentString(reader); + } + + /** @return All the content from 'reader', in one, large String */ + private static String bufferedReaderToContentString(BufferedReader reader) throws IOException { + StringBuilder builder = bufferedReaderToContentStringBuilder(reader); + reader.close(); + return builder.toString(); + } + + /** @return A StringBuilder made up of all the contents of 'reader' */ + private static StringBuilder bufferedReaderToContentStringBuilder(BufferedReader reader) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) stringBuilder.append(line); + return stringBuilder; + } + +} diff --git a/app/src/main/java/rothschild/henning/jacob/noriginmedia/view/UnswipeableViewPager.java b/app/src/main/java/rothschild/henning/jacob/noriginmedia/view/UnswipeableViewPager.java index 246455d..2aeee1f 100644 --- a/app/src/main/java/rothschild/henning/jacob/noriginmedia/view/UnswipeableViewPager.java +++ b/app/src/main/java/rothschild/henning/jacob/noriginmedia/view/UnswipeableViewPager.java @@ -1,39 +1,39 @@ -package rothschild.henning.jacob.noriginmedia.view; - -import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; - -/** - * See https://stackoverflow.com/a/36614266/1329733 - * Created by Jacob H. Rothschild on 24.07.2017. - */ - -public class UnswipeableViewPager extends ViewPager { - - public UnswipeableViewPager(Context context) { - super(context); - } - - public UnswipeableViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_MOVE) { - // ignore move action - } else if (super.onInterceptTouchEvent(ev)) { - super.onTouchEvent(ev); - } - return false; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return MotionEventCompat.getActionMasked(ev) != MotionEvent.ACTION_MOVE && super.onTouchEvent(ev); - } - -} +package rothschild.henning.jacob.noriginmedia.view; + +import android.content.Context; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; + +/** + * See https://stackoverflow.com/a/36614266/1329733 + * Created by Jacob H. Rothschild on 24.07.2017. + */ + +public class UnswipeableViewPager extends ViewPager { + + public UnswipeableViewPager(Context context) { + super(context); + } + + public UnswipeableViewPager(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (MotionEventCompat.getActionMasked(ev) == MotionEvent.ACTION_MOVE) { + // ignore move action + } else if (super.onInterceptTouchEvent(ev)) { + super.onTouchEvent(ev); + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return MotionEventCompat.getActionMasked(ev) != MotionEvent.ACTION_MOVE && super.onTouchEvent(ev); + } + +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b7cff3a..4d9cfd6 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,41 +1,41 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_epg.xml b/app/src/main/res/layout/fragment_epg.xml index 15008a9..e1839fa 100644 --- a/app/src/main/res/layout/fragment_epg.xml +++ b/app/src/main/res/layout/fragment_epg.xml @@ -1,14 +1,14 @@ - - - - - - + + + + + + diff --git a/app/src/main/res/layout/fragment_placeholder.xml b/app/src/main/res/layout/fragment_placeholder.xml index ab8eda2..d03aa5b 100644 --- a/app/src/main/res/layout/fragment_placeholder.xml +++ b/app/src/main/res/layout/fragment_placeholder.xml @@ -1,16 +1,16 @@ - - - - - + + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 3e6b75c..2a281d1 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -1,15 +1,15 @@ -

- - - + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 03b7fbb..f5a6609 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,6 @@ - - - #202020 - #000000 - #E1A21E - + + + #202020 + #000000 + #E1A21E + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index cef3abc..88f81e2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,7 +1,7 @@ - - - 16dp - 16dp - 16dp - 8dp - + + + 16dp + 16dp + 16dp + 8dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 042c319..b464a4e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ - - NoriginMedia - Profile - Search - Hello World from section: %1$d + + NoriginMedia + Profile + Search + Hello World from section: %1$d \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 177cefc..06e89df 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,20 +1,20 @@ - - - - - - - - + + + +