mirror of
https://github.com/mediathekview/zapp.git
synced 2024-09-19 20:02:17 +02:00
Implement and use basic program info module for most channels #5
This commit is contained in:
parent
ad39e520fd
commit
2a961adda6
@ -10,6 +10,7 @@
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/programguide" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
|
@ -4,6 +4,7 @@
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/Zapp.iml" filepath="$PROJECT_DIR$/Zapp.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/programguide/programguide.iml" filepath="$PROJECT_DIR$/programguide/programguide.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -26,19 +26,17 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile fileTree(include: ['*.jar'], dir: 'libs')
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.android.support:appcompat-v7:24.2.1'
|
||||
compile 'com.android.support:support-v4:24.2.1'
|
||||
compile 'com.android.support:design:24.2.1'
|
||||
|
||||
compile 'com.google.code.gson:gson:2.7'
|
||||
compile 'commons-io:commons-io:2.5'
|
||||
|
||||
// changelog
|
||||
compile 'com.github.porokoro.paperboy:paperboy:3.0.0'
|
||||
|
||||
// for butterknive:
|
||||
compile 'com.jakewharton:butterknife:8.4.0'
|
||||
apt 'com.jakewharton:butterknife-compiler:8.4.0'
|
||||
compile project(':programguide')
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import de.christinecoenen.code.zapp.model.IChannelList;
|
||||
import de.christinecoenen.code.zapp.model.json.JsonChannelList;
|
||||
import de.christinecoenen.code.zapp.utils.view.ClickableViewPager;
|
||||
import de.christinecoenen.code.zapp.utils.view.FullscreenActivity;
|
||||
import de.christinecoenen.code.zapp.views.ProgrammInfoView;
|
||||
|
||||
public class ChannelDetailActivity extends FullscreenActivity {
|
||||
|
||||
@ -35,6 +36,7 @@ public class ChannelDetailActivity extends FullscreenActivity {
|
||||
protected @BindView(R.id.viewpager_channels) ClickableViewPager viewPager;
|
||||
protected @BindView(R.id.video) VideoView videoView;
|
||||
protected @BindView(R.id.progressbar_video) ProgressBar progressView;
|
||||
protected @BindView(R.id.programm_info) ProgrammInfoView programmInfoView;
|
||||
|
||||
protected @BindDrawable(android.R.drawable.ic_media_pause) Drawable pauseIcon;
|
||||
protected @BindDrawable(android.R.drawable.ic_media_play) Drawable playIcon;
|
||||
@ -92,6 +94,7 @@ public class ChannelDetailActivity extends FullscreenActivity {
|
||||
currentChannel = channel;
|
||||
setTitle(channel.getName());
|
||||
playDelayed();
|
||||
programmInfoView.setChannel(channel);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4,10 +4,19 @@ import java.io.Serializable;
|
||||
|
||||
public class ChannelModel implements Serializable {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private String streamUrl;
|
||||
private int drawableId;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@ -35,7 +44,8 @@ public class ChannelModel implements Serializable {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChannelModel{" +
|
||||
"name='" + name + '\'' +
|
||||
"id='" + id + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", streamUrl='" + streamUrl + '\'' +
|
||||
", drawableId=" + drawableId +
|
||||
'}';
|
||||
|
@ -60,6 +60,7 @@ class JsonChannelsParser {
|
||||
getIdentifier(jsonModel.logoName, "drawable", context.getPackageName());
|
||||
|
||||
ChannelModel model = new ChannelModel();
|
||||
model.setId(jsonModel.id);
|
||||
model.setName(jsonModel.name);
|
||||
model.setStreamUrl(jsonModel.streamUrl);
|
||||
model.setDrawableId(logoResourceId);
|
||||
|
@ -0,0 +1,82 @@
|
||||
package de.christinecoenen.code.zapp.views;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import de.christinecoenen.code.programguide.ProgramGuideRequest;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
import de.christinecoenen.code.zapp.R;
|
||||
import de.christinecoenen.code.zapp.model.ChannelModel;
|
||||
|
||||
public class ProgrammInfoView extends LinearLayout {
|
||||
|
||||
private static final String TAG = ProgrammInfoView.class.getSimpleName();
|
||||
|
||||
protected @BindView(R.id.text_show_title) TextView showTitleView;
|
||||
protected @BindView(R.id.text_show_subtitle) TextView showSubtitleView;
|
||||
|
||||
private ProgramGuideRequest currentShowInfoRequest;
|
||||
|
||||
|
||||
private final ProgramGuideRequest.Listener programGuideListener = new ProgramGuideRequest.Listener() {
|
||||
@Override
|
||||
public void onRequestError() {
|
||||
Log.w(TAG, "could not load show info");
|
||||
showTitleView.setText(R.string.activity_channel_detail_info_error);
|
||||
showSubtitleView.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestSuccess(Show currentShow) {
|
||||
Log.w(TAG, "show info loaded: " + currentShow);
|
||||
showTitleView.setText(currentShow.getTitle());
|
||||
showSubtitleView.setText(currentShow.getSubtitle());
|
||||
}
|
||||
};
|
||||
|
||||
public ProgrammInfoView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
setOrientation(LinearLayout.VERTICAL);
|
||||
setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.view_programm_info, this, true);
|
||||
|
||||
ButterKnife.bind(this, this);
|
||||
}
|
||||
|
||||
public ProgrammInfoView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public void setChannel(ChannelModel channel) {
|
||||
chancelProgramGuideLoading();
|
||||
loadProgramGuide(channel.getId());
|
||||
}
|
||||
|
||||
|
||||
private void loadProgramGuide(String channelId) {
|
||||
currentShowInfoRequest = new ProgramGuideRequest(getContext())
|
||||
.setChannelId(channelId)
|
||||
.setListener(programGuideListener)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private void chancelProgramGuideLoading() {
|
||||
showTitleView.setText("");
|
||||
showSubtitleView.setText("");
|
||||
if (currentShowInfoRequest != null) {
|
||||
currentShowInfoRequest.cancel();
|
||||
}
|
||||
}
|
||||
}
|
@ -49,10 +49,18 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:orientation="horizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/activity_horizontal_margin"
|
||||
android:paddingEnd="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:background="@color/black_overlay"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<!-- add content here that should show up on click -->
|
||||
<de.christinecoenen.code.zapp.views.ProgrammInfoView
|
||||
android:id="@+id/programm_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
20
app/src/main/res/layout/view_programm_info.xml
Normal file
20
app/src/main/res/layout/view_programm_info.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_show_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.AppCompat.Large.Inverse"
|
||||
tools:text="Show Title"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_show_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.AppCompat.Medium.Inverse"
|
||||
tools:text="Show subtitle"/>
|
||||
|
||||
</merge>
|
@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"id": "das-erste",
|
||||
"id": "das_erste",
|
||||
"name": "Das Erste",
|
||||
"stream_url": "http://live-lh.daserste.de/i/daserste_de@91204/master.m3u8",
|
||||
"logo_name": "channel_logo_das_erste"
|
||||
@ -18,7 +18,7 @@
|
||||
"logo_name": "channel_logo_arte"
|
||||
},
|
||||
{
|
||||
"id": "3sat",
|
||||
"id": "dreisat",
|
||||
"name": "3sat",
|
||||
"stream_url": "http://zdf0910-lh.akamaihd.net/i/dach10_v1@392872/master.m3u8",
|
||||
"logo_name": "channel_logo_3sat"
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
<string name="activity_changelog_title">Changelog</string>
|
||||
|
||||
<string name="activity_channel_detail_info_error">Keine Programminfo</string>
|
||||
|
||||
<!-- menus -->
|
||||
<string name="action_share">Teilen</string>
|
||||
|
||||
|
1
programguide/.gitignore
vendored
Normal file
1
programguide/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
38
programguide/build.gradle
Normal file
38
programguide/build.gradle
Normal file
@ -0,0 +1,38 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 24
|
||||
buildToolsVersion "24.0.2"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 24
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// avoid warning for "Not all execution paths return a value"
|
||||
return void
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
|
||||
exclude group: 'com.android.support', module: 'support-annotations'
|
||||
})
|
||||
compile 'com.android.support:appcompat-v7:24.2.1'
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
||||
compile 'com.android.volley:volley:1.0.0'
|
||||
compile 'joda-time:joda-time:2.9.4'
|
||||
compile 'org.jsoup:jsoup:1.9.2'
|
||||
}
|
17
programguide/proguard-rules.pro
vendored
Normal file
17
programguide/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in C:\tools\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 *;
|
||||
#}
|
@ -0,0 +1,26 @@
|
||||
package de.christinecoenen.code.programguide;
|
||||
|
||||
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 static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumentation test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||
|
||||
assertEquals("de.christinecoenen.code.programmguide.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
12
programguide/src/main/AndroidManifest.xml
Normal file
12
programguide/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.christinecoenen.code.programguide">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application android:allowBackup="false"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="false">
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,64 @@
|
||||
package de.christinecoenen.code.programguide;
|
||||
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
|
||||
/**
|
||||
* Cache singleton for currently running show on any channel.
|
||||
*/
|
||||
public class Cache {
|
||||
|
||||
private static final String TAG = Cache.class.getSimpleName();
|
||||
|
||||
private static Cache instance = null;
|
||||
|
||||
private final Map<Channel, Show> shows = new HashMap<>();
|
||||
|
||||
public static Cache getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new Cache();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void save(Channel channel, Show show) {
|
||||
this.shows.put(channel, show);
|
||||
}
|
||||
|
||||
public void save(Map<Channel, Show> shows) {
|
||||
this.shows.putAll(shows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return currently running show or null in case of cache miss
|
||||
*/
|
||||
public Show getShow(Channel channel) {
|
||||
Show show = shows.get(channel);
|
||||
|
||||
if (show == null) {
|
||||
Log.d(TAG, "cache miss: " + channel);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (show.getEndTime() == null) {
|
||||
Log.d(TAG, "cache miss: " + channel + ", no show end time");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (show.getEndTime().isBeforeNow()) {
|
||||
Log.d(TAG, "cache miss: " + channel + ", show too old");
|
||||
shows.remove(channel);
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.d(TAG, "cache hit: " + channel + " - " + show);
|
||||
|
||||
return show;
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package de.christinecoenen.code.programguide;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
import de.christinecoenen.code.programguide.plugins.IProgramGuideDownloader;
|
||||
import de.christinecoenen.code.programguide.plugins.PluginRegistry;
|
||||
|
||||
public class ProgramGuideRequest {
|
||||
|
||||
private static final String TAG = ProgramGuideRequest.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private Listener listener;
|
||||
private Channel channelId;
|
||||
private IProgramGuideDownloader downloader;
|
||||
|
||||
public ProgramGuideRequest(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ProgramGuideRequest setListener(Listener listener) {
|
||||
this.listener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public ProgramGuideRequest setChannelId(Channel channelId) {
|
||||
this.channelId = channelId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ProgramGuideRequest setChannelId(String channelId) {
|
||||
Channel newChannel = null;
|
||||
try {
|
||||
newChannel = Channel.getById(channelId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, channelId + " is no valid channel id");
|
||||
}
|
||||
|
||||
return setChannelId(newChannel);
|
||||
}
|
||||
|
||||
public ProgramGuideRequest execute() {
|
||||
if (listener == null) {
|
||||
throw new RuntimeException("listener not set");
|
||||
}
|
||||
|
||||
if (channelId == null) {
|
||||
Log.w(TAG, "no valid channel id set");
|
||||
listener.onRequestError();
|
||||
return this;
|
||||
}
|
||||
|
||||
downloader = PluginRegistry.getInstance(context).getDownloader(channelId);
|
||||
|
||||
if (downloader == null) {
|
||||
listener.onRequestError();
|
||||
} else {
|
||||
downloader.download(listener);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (downloader != null) {
|
||||
downloader.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onRequestError();
|
||||
void onRequestSuccess(Show currentShow);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package de.christinecoenen.code.programguide.model;
|
||||
|
||||
|
||||
public enum Channel {
|
||||
DAS_ERSTE("das_erste"),
|
||||
BR_NORD("br_nord"),
|
||||
BR_SUED("br_sued"),
|
||||
HR("hr"),
|
||||
MDR_SACHSEN("mdr_sachsen"),
|
||||
MDR_SACHSEN_ANHALT("mdr_sachsen_anhalt"),
|
||||
MDR_THUERINGEN("mdr_thueringen"),
|
||||
NDR_HAMBURG("ndr_hh"),
|
||||
NDR_MECKLENBURG_VORPOMMERN("ndr_mv"),
|
||||
NDR_NIEDERSACHSEN("ndr_nds"),
|
||||
NDR_SCHLESWIG_HOLSTEIN("ndr_sh"),
|
||||
RBB_BERLIN("rbb_berlin"),
|
||||
RBB_BRANDENBURG("rbb_brandenburg"),
|
||||
SR("sr"),
|
||||
SWR_BADEN_WUERTTEMBERG("swr_bw"),
|
||||
SWR_RHEINLAND_PFALZ("swr_rp"),
|
||||
WDR("wdr"),
|
||||
ARD_ALPHA("ard_alpha"),
|
||||
TAGESSCHAU24("tagesschau24"),
|
||||
ONE("one"),
|
||||
|
||||
ARTE("arte"),
|
||||
|
||||
ZDF("zdf"),
|
||||
DREISAT("dreisat"),
|
||||
KIKA("kika"),
|
||||
PHOENIX("phoenix"),
|
||||
ZDF_KULTUR("zdf_kultur"),
|
||||
ZDF_INFO("zdf_info"),
|
||||
ZDF_NEO("zdf_neo");
|
||||
|
||||
public static Channel getById(String id) {
|
||||
for (Channel channel : Channel.values()) {
|
||||
if (channel.id.equals(id)) {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private final String id;
|
||||
|
||||
Channel(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package de.christinecoenen.code.programguide.model;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
public class Show {
|
||||
|
||||
private String title;
|
||||
private String subtitle;
|
||||
private String description;
|
||||
private DateTime startTime;
|
||||
private DateTime endTime;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle = subtitle;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public DateTime getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(DateTime startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public DateTime getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(DateTime endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Show{" +
|
||||
"title='" + title + '\'' +
|
||||
", subtitle='" + subtitle + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", startTime=" + startTime +
|
||||
", endTime=" + endTime +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package de.christinecoenen.code.programguide.plugins;
|
||||
|
||||
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.RequestQueue;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import de.christinecoenen.code.programguide.Cache;
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.ProgramGuideRequest;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
|
||||
public abstract class BaseProgramGuideDownloader implements IProgramGuideDownloader {
|
||||
|
||||
protected final DownloaderListener downloaderListener = new DownloaderListener() {
|
||||
@Override
|
||||
public void onRequestError() {
|
||||
listener.onRequestError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestSuccess(Map<Channel, Show> shows) {
|
||||
onRequestSuccess(shows.get(channel));
|
||||
Cache.getInstance().save(shows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestSuccess(Show show) {
|
||||
listener.onRequestSuccess(show);
|
||||
Cache.getInstance().save(channel, show);
|
||||
}
|
||||
};
|
||||
|
||||
protected final RequestQueue queue;
|
||||
protected Request<?> request;
|
||||
|
||||
private final Channel channel;
|
||||
private ProgramGuideRequest.Listener listener;
|
||||
|
||||
|
||||
protected BaseProgramGuideDownloader(RequestQueue queue, Channel channel) {
|
||||
this.queue = queue;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download(ProgramGuideRequest.Listener listener) {
|
||||
this.listener = listener;
|
||||
Show show = Cache.getInstance().getShow(this.channel);
|
||||
|
||||
if (show == null) {
|
||||
download();
|
||||
} else {
|
||||
listener.onRequestSuccess(show);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (request != null) {
|
||||
request.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void download();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected interface DownloaderListener {
|
||||
void onRequestError();
|
||||
void onRequestSuccess(Map<Channel, Show> shows);
|
||||
void onRequestSuccess(Show shows);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package de.christinecoenen.code.programguide.plugins;
|
||||
|
||||
|
||||
import de.christinecoenen.code.programguide.ProgramGuideRequest;
|
||||
|
||||
public interface IProgramGuideDownloader {
|
||||
void download(ProgramGuideRequest.Listener listener);
|
||||
void cancel();
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package de.christinecoenen.code.programguide.plugins;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.toolbox.Volley;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.plugins.ard.ArdDownloader;
|
||||
import de.christinecoenen.code.programguide.plugins.arte.ArteDownloader;
|
||||
import de.christinecoenen.code.programguide.plugins.zdf.ZdfDownloader;
|
||||
|
||||
public class PluginRegistry {
|
||||
|
||||
private static PluginRegistry instance = null;
|
||||
|
||||
public static PluginRegistry getInstance(Context context) {
|
||||
if (instance == null) {
|
||||
instance = new PluginRegistry(context);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
private final Map<Channel, IProgramGuideDownloader> downloaders;
|
||||
|
||||
private PluginRegistry(Context context) {
|
||||
RequestQueue queue = Volley.newRequestQueue(context);
|
||||
|
||||
downloaders = new HashMap<>(Channel.values().length);
|
||||
|
||||
for (Channel ardChannel : ArdDownloader.CHANNELS) {
|
||||
downloaders.put(ardChannel, new ArdDownloader(queue, ardChannel));
|
||||
}
|
||||
for (Channel zdfChannel : ZdfDownloader.CHANNELS) {
|
||||
downloaders.put(zdfChannel, new ZdfDownloader(queue, zdfChannel));
|
||||
}
|
||||
for (Channel arteChannel : ArteDownloader.CHANNELS) {
|
||||
downloaders.put(arteChannel, new ArteDownloader(queue, arteChannel));
|
||||
}
|
||||
}
|
||||
|
||||
public IProgramGuideDownloader getDownloader(Channel channel) {
|
||||
return downloaders.get(channel);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package de.christinecoenen.code.programguide.plugins.ard;
|
||||
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.StringRequest;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
import de.christinecoenen.code.programguide.plugins.BaseProgramGuideDownloader;
|
||||
|
||||
public class ArdDownloader extends BaseProgramGuideDownloader {
|
||||
|
||||
public static final Channel[] CHANNELS = new Channel[] {
|
||||
Channel.DAS_ERSTE,
|
||||
Channel.BR_NORD,
|
||||
Channel.BR_SUED,
|
||||
Channel.HR,
|
||||
Channel.MDR_SACHSEN,
|
||||
Channel.MDR_SACHSEN_ANHALT,
|
||||
Channel.MDR_THUERINGEN,
|
||||
Channel.NDR_HAMBURG,
|
||||
Channel.NDR_MECKLENBURG_VORPOMMERN,
|
||||
Channel.NDR_NIEDERSACHSEN,
|
||||
Channel.NDR_SCHLESWIG_HOLSTEIN,
|
||||
Channel.RBB_BERLIN,
|
||||
Channel.RBB_BRANDENBURG,
|
||||
Channel.SR,
|
||||
Channel.SWR_BADEN_WUERTTEMBERG,
|
||||
Channel.SWR_RHEINLAND_PFALZ,
|
||||
Channel.WDR,
|
||||
Channel.ARD_ALPHA,
|
||||
Channel.TAGESSCHAU24,
|
||||
Channel.ONE
|
||||
};
|
||||
|
||||
private static final String TAG = ArdDownloader.class.getSimpleName();
|
||||
private static final String HTML_URL = "http://programm.ard.de/TV/Programm/Load/NavJetztImTV35";
|
||||
|
||||
public ArdDownloader(RequestQueue queue, Channel channel) {
|
||||
super(queue, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download() {
|
||||
request = new StringRequest(HTML_URL,
|
||||
new Response.Listener<String>() {
|
||||
@Override
|
||||
public void onResponse(String response) {
|
||||
Log.d(TAG, "html loaded: " + response);
|
||||
parse(response);
|
||||
}
|
||||
}, new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
Log.d(TAG, "error loading html: " + error.getMessage());
|
||||
downloaderListener.onRequestError();
|
||||
}
|
||||
});
|
||||
|
||||
queue.add(request);
|
||||
}
|
||||
|
||||
private void parse(String html) {
|
||||
Map<Channel, Show> shows = ArdParser.parse(html);
|
||||
downloaderListener.onRequestSuccess(shows);
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
package de.christinecoenen.code.programguide.plugins.ard;
|
||||
|
||||
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.MutableDateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
|
||||
class ArdParser {
|
||||
|
||||
private static final String TAG = ArdParser.class.getSimpleName();
|
||||
|
||||
// 20:15
|
||||
private static final DateTimeFormatter TIME_FORMATTER =
|
||||
DateTimeFormat.forPattern("HH:mm");
|
||||
|
||||
private static final Pattern TIME_PATTERN = Pattern.compile("\\d+:\\d+");
|
||||
|
||||
static Map<Channel, Show> parse(String html) {
|
||||
Map<Channel, Show> shows = new ArrayMap<>();
|
||||
|
||||
Document document = Jsoup.parseBodyFragment(html);
|
||||
Elements items = document.select("li a");
|
||||
|
||||
for (Element item : items) {
|
||||
shows.putAll(parseItem(item));
|
||||
}
|
||||
|
||||
return shows;
|
||||
}
|
||||
|
||||
private static Map<Channel, Show> parseItem(Element item) {
|
||||
String title = item.select("b").text();
|
||||
String rawMetadata = item.select("span.small").text();
|
||||
|
||||
Show show = parseShow(title, rawMetadata);
|
||||
List<Channel> channels = getChannels(rawMetadata);
|
||||
|
||||
Map<Channel, Show> shows = new ArrayMap<>();
|
||||
for (Channel channel : channels) {
|
||||
shows.put(channel, show);
|
||||
}
|
||||
return shows;
|
||||
}
|
||||
|
||||
private static Show parseShow(String title, String rawMetadata) {
|
||||
Show show = new Show();
|
||||
show.setTitle(title);
|
||||
setTimes(show, rawMetadata);
|
||||
|
||||
Log.d(TAG, show.toString());
|
||||
return show;
|
||||
}
|
||||
|
||||
private static void setTimes(Show show, String rawMetadata) {
|
||||
String[] splitMetadata = rawMetadata.split("\\|"); // MDR Fernsehen | 19:30 - 19:50 Uhr
|
||||
String rawTime = splitMetadata[splitMetadata.length - 1]; // 19:30 - 19:50 Uhr
|
||||
|
||||
Matcher matcher = TIME_PATTERN.matcher(rawTime);
|
||||
|
||||
if (matcher.find()) {
|
||||
DateTime startTime = TIME_FORMATTER.parseDateTime(matcher.group());
|
||||
MutableDateTime startTimeDate = MutableDateTime.now();
|
||||
startTimeDate.setMillisOfDay(0);
|
||||
startTimeDate.setMinuteOfDay(startTime.getMinuteOfDay());
|
||||
show.setStartTime(startTimeDate.toDateTime());
|
||||
}
|
||||
|
||||
if (matcher.find()) {
|
||||
DateTime endTime = TIME_FORMATTER.parseDateTime(matcher.group());
|
||||
MutableDateTime endTimeDate = MutableDateTime.now();
|
||||
endTimeDate.setMillisOfDay(0);
|
||||
endTimeDate.setMinuteOfDay(endTime.getMinuteOfDay());
|
||||
show.setEndTime(endTimeDate.toDateTime());
|
||||
|
||||
if (show.getEndTime().isBefore(show.getStartTime())) {
|
||||
// show plays around midnight
|
||||
if (DateTime.now().getHourOfDay() < 12) {
|
||||
// noew is morning - startTime was yesterday
|
||||
DateTime newStartTime = show.getStartTime().minusDays(1);
|
||||
show.setStartTime(newStartTime);
|
||||
} else {
|
||||
// now is evening - end time is tomorrow
|
||||
DateTime newEndTime = show.getEndTime().plusDays(1);
|
||||
show.setEndTime(newEndTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Channel> getChannels(String rawMetadata) {
|
||||
// SR Fernsehen | SWR Ferns. BW | SWR Ferns. RP | 20:15 - 21:00 Uhr
|
||||
String[] splitMetadata = rawMetadata.split("\\|");
|
||||
|
||||
List<Channel> channels = new ArrayList<>();
|
||||
for (int i = 0; i < splitMetadata.length-1; i++) {
|
||||
Channel[] parsedChannels = getParsedChannels(splitMetadata[i]);
|
||||
if (parsedChannels != null) {
|
||||
channels.addAll(Arrays.asList(parsedChannels));
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
}
|
||||
|
||||
private static Channel[] getParsedChannels(String htmlName) {
|
||||
switch(htmlName.trim()) {
|
||||
case "ARD-alpha":
|
||||
return new Channel[] { Channel.ARD_ALPHA };
|
||||
case "Das Erste":
|
||||
return new Channel[] { Channel.DAS_ERSTE };
|
||||
case "SR Fernsehen":
|
||||
return new Channel[] { Channel.SR };
|
||||
case "tagesschau24":
|
||||
return new Channel[] { Channel.TAGESSCHAU24 };
|
||||
case "SWR Ferns. BW":
|
||||
return new Channel[] { Channel.SWR_BADEN_WUERTTEMBERG };
|
||||
case "SWR Ferns. RP":
|
||||
return new Channel[] { Channel.SWR_RHEINLAND_PFALZ };
|
||||
case "WDR Fernsehen":
|
||||
return new Channel[] { Channel.WDR };
|
||||
case "ONE":
|
||||
return new Channel[] { Channel.ONE };
|
||||
case "hr-fernsehen":
|
||||
return new Channel[] { Channel.HR };
|
||||
case "BR Fernsehen":
|
||||
return new Channel[] {
|
||||
Channel.BR_NORD,
|
||||
Channel.BR_SUED
|
||||
};
|
||||
case "MDR Fernsehen":
|
||||
return new Channel[] {
|
||||
Channel.MDR_SACHSEN,
|
||||
Channel.MDR_SACHSEN_ANHALT,
|
||||
Channel.MDR_THUERINGEN
|
||||
};
|
||||
case "NDR Fernsehen":
|
||||
return new Channel[] {
|
||||
Channel.NDR_HAMBURG,
|
||||
Channel.NDR_MECKLENBURG_VORPOMMERN,
|
||||
Channel.NDR_NIEDERSACHSEN,
|
||||
Channel.NDR_SCHLESWIG_HOLSTEIN
|
||||
};
|
||||
case "rbb Fernsehen":
|
||||
return new Channel[] {
|
||||
Channel.RBB_BERLIN,
|
||||
Channel.RBB_BRANDENBURG
|
||||
};
|
||||
case "EinsPlus": // ?
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package de.christinecoenen.code.programguide.plugins.arte;
|
||||
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
import de.christinecoenen.code.programguide.plugins.BaseProgramGuideDownloader;
|
||||
|
||||
public class ArteDownloader extends BaseProgramGuideDownloader {
|
||||
|
||||
public static final Channel[] CHANNELS = new Channel[] {
|
||||
Channel.ARTE
|
||||
};
|
||||
|
||||
private static final String TAG = ArteDownloader.class.getSimpleName();
|
||||
private static final String JSON_URL = "https://api.arte.tv/api/player/v1/livestream/de";
|
||||
|
||||
public ArteDownloader(RequestQueue queue, Channel channel) {
|
||||
super(queue, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download() {
|
||||
request = new JsonObjectRequest(Request.Method.GET, JSON_URL, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
Log.d(TAG, "json loaded: " + response.toString());
|
||||
parse(response);
|
||||
}
|
||||
}, new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
Log.d(TAG, "error loading json: " + error.getMessage());
|
||||
downloaderListener.onRequestError();
|
||||
}
|
||||
});
|
||||
|
||||
queue.add(request);
|
||||
}
|
||||
|
||||
private void parse(JSONObject json) {
|
||||
Show show;
|
||||
|
||||
try {
|
||||
show = ArteParser.parse(json);
|
||||
} catch (JSONException e) {
|
||||
Log.d(TAG, e.getMessage());
|
||||
downloaderListener.onRequestError();
|
||||
return;
|
||||
}
|
||||
|
||||
downloaderListener.onRequestSuccess(show);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package de.christinecoenen.code.programguide.plugins.arte;
|
||||
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
|
||||
/**
|
||||
* Parses ARTE program info json.
|
||||
* @see "https://api.arte.tv/api/player/v1/livestream/de"
|
||||
*/
|
||||
class ArteParser {
|
||||
|
||||
private static final String TAG = ArteParser.class.getSimpleName();
|
||||
|
||||
static Show parse(JSONObject json) throws JSONException {
|
||||
Show show = new Show();
|
||||
|
||||
JSONObject root = json.getJSONObject("videoJsonPlayer");
|
||||
String title = root.getString("VTI");
|
||||
String subtitle = getOptionalString(root, "subtitle");
|
||||
String description = getOptionalString(root, "V7T");
|
||||
|
||||
show.setTitle(title);
|
||||
show.setSubtitle(subtitle);
|
||||
show.setDescription(description);
|
||||
|
||||
return show;
|
||||
}
|
||||
|
||||
private static String getOptionalString(JSONObject json, String key) {
|
||||
try {
|
||||
return json.getString(key);
|
||||
} catch (JSONException e) {
|
||||
Log.d(TAG, "optional json key is not present: " + key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package de.christinecoenen.code.programguide.plugins.zdf;
|
||||
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.volley.Request;
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.Response;
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.JsonObjectRequest;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
import de.christinecoenen.code.programguide.plugins.BaseProgramGuideDownloader;
|
||||
|
||||
public class ZdfDownloader extends BaseProgramGuideDownloader {
|
||||
|
||||
public static final Channel[] CHANNELS = new Channel[] {
|
||||
Channel.ZDF,
|
||||
Channel.KIKA,
|
||||
Channel.PHOENIX,
|
||||
Channel.ZDF_INFO,
|
||||
Channel.ZDF_KULTUR,
|
||||
Channel.ZDF_NEO,
|
||||
Channel.DREISAT
|
||||
};
|
||||
|
||||
private static final String TAG = ZdfDownloader.class.getSimpleName();
|
||||
private static final String URL_ZDF_ALL = "http://sofa01.zdf.de/epgservice/v2/all/now/json";
|
||||
|
||||
public ZdfDownloader(RequestQueue queue, Channel channel) {
|
||||
super(queue, channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void download() {
|
||||
|
||||
request = new JsonObjectRequest(Request.Method.GET, URL_ZDF_ALL, null,
|
||||
new Response.Listener<JSONObject>() {
|
||||
@Override
|
||||
public void onResponse(JSONObject response) {
|
||||
Log.d(TAG, "json loaded: " + response.toString());
|
||||
parse(response);
|
||||
}
|
||||
}, new Response.ErrorListener() {
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
Log.d(TAG, "error loading json: " + error.getMessage());
|
||||
downloaderListener.onRequestError();
|
||||
}
|
||||
});
|
||||
|
||||
queue.add(request);
|
||||
}
|
||||
|
||||
private void parse(JSONObject json) {
|
||||
Map<Channel, Show> shows;
|
||||
|
||||
try {
|
||||
shows = ZdfParser.parse(json);
|
||||
} catch (JSONException e) {
|
||||
Log.d(TAG, e.getMessage());
|
||||
downloaderListener.onRequestError();
|
||||
return;
|
||||
}
|
||||
|
||||
downloaderListener.onRequestSuccess(shows);
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package de.christinecoenen.code.programguide.plugins.zdf;
|
||||
|
||||
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import de.christinecoenen.code.programguide.model.Channel;
|
||||
import de.christinecoenen.code.programguide.model.Show;
|
||||
|
||||
/**
|
||||
* Parses program info json of all ZDF related channels.
|
||||
* @see "http://sofa01.zdf.de/epgservice/v2/all/now/json"
|
||||
*/
|
||||
class ZdfParser {
|
||||
|
||||
private static final String TAG = ZdfParser.class.getSimpleName();
|
||||
|
||||
// 2016-09-22T16:10:00+02:00
|
||||
private static final DateTimeFormatter formatter =
|
||||
DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
|
||||
static Map<Channel, Show> parse(JSONObject json) throws JSONException {
|
||||
|
||||
JSONObject responseJson = json.getJSONObject("response");
|
||||
JSONObject channelsJson = responseJson.getJSONObject("sender");
|
||||
|
||||
Map<Channel, Show> shows = new ArrayMap<>();
|
||||
shows.put(Channel.ZDF, getChannelShow("ZDF", channelsJson));
|
||||
shows.put(Channel.KIKA, getChannelShow("KI.KA", channelsJson));
|
||||
shows.put(Channel.ZDF_INFO, getChannelShow("ZDFinfo", channelsJson));
|
||||
shows.put(Channel.ZDF_KULTUR, getChannelShow("ZDF.kultur", channelsJson));
|
||||
shows.put(Channel.ZDF_NEO, getChannelShow("ZDFneo", channelsJson));
|
||||
shows.put(Channel.PHOENIX, getChannelShow("phoenix", channelsJson));
|
||||
shows.put(Channel.DREISAT, getChannelShow("dreisat", channelsJson));
|
||||
|
||||
return shows;
|
||||
}
|
||||
|
||||
private static Show getChannelShow(String channelKey, JSONObject channelsJson) throws JSONException {
|
||||
JSONObject channelJson = channelsJson.getJSONObject(channelKey);
|
||||
JSONArray showsJson = channelJson.getJSONArray("sendungen");
|
||||
JSONObject showMetadataJson = ((JSONObject) showsJson.get(0)).getJSONObject("sendung");
|
||||
|
||||
return parseShow(showMetadataJson);
|
||||
}
|
||||
|
||||
private static Show parseShow(JSONObject showMetadata) throws JSONException {
|
||||
Show show = new Show();
|
||||
|
||||
JSONObject showJson = showMetadata.getJSONObject("value");
|
||||
|
||||
String title = showJson.getString("titel");
|
||||
String subtitle = getOptionalString(showJson, "untertitel");
|
||||
String description = getOptionalString(showJson, "beschreibung");
|
||||
DateTime startTime = formatter.parseDateTime(showJson.getString("time"));
|
||||
DateTime endTime = formatter.parseDateTime(showJson.getString("endTime"));
|
||||
|
||||
show.setTitle(title);
|
||||
show.setSubtitle(subtitle);
|
||||
show.setDescription(description);
|
||||
show.setStartTime(startTime);
|
||||
show.setEndTime(endTime);
|
||||
|
||||
return show;
|
||||
}
|
||||
|
||||
private static String getOptionalString(JSONObject json, String key) {
|
||||
try {
|
||||
return json.getString(key);
|
||||
} catch (JSONException e) {
|
||||
Log.d(TAG, "optional json key is not present: " + key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
3
programguide/src/main/res/values/strings.xml
Normal file
3
programguide/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Program Guide</string>
|
||||
</resources>
|
@ -0,0 +1,17 @@
|
||||
package de.christinecoenen.code.programguide;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
include ':app'
|
||||
include ':app', ':programguide'
|
||||
|
Loading…
Reference in New Issue
Block a user