0
0
mirror of https://github.com/mediathekview/zapp.git synced 2024-09-19 20:02:17 +02:00

Heavily simplified show info api by using retrofit and removing separate module

This commit is contained in:
Christine Emrich 2017-08-03 17:41:26 +02:00
parent 0e6b2262a2
commit d3cec03c94
24 changed files with 232 additions and 472 deletions

View File

@ -10,7 +10,6 @@
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/programguide" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />

View File

@ -4,7 +4,6 @@
<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>

View File

@ -37,7 +37,6 @@ android {
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile project(':programguide')
// tests
testCompile 'junit:junit:4.12'
@ -82,4 +81,7 @@ dependencies {
// timber logging
compile 'com.jakewharton.timber:timber:4.5.1'
// joda time
compile 'joda-time:joda-time:2.9.4'
}

View File

@ -1,42 +1,38 @@
package de.christinecoenen.code.programguide;
package de.christinecoenen.code.zapp.app.livestream.api;
import java.util.HashMap;
import java.util.Map;
import de.christinecoenen.code.programguide.model.Channel;
import de.christinecoenen.code.programguide.model.Show;
import de.christinecoenen.code.zapp.app.livestream.api.model.Channel;
import de.christinecoenen.code.zapp.app.livestream.model.LiveShow;
import timber.log.Timber;
/**
* Cache singleton for currently running show on any channel.
*/
public class Cache {
class Cache {
private static Cache instance = null;
private final Map<Channel, Show> shows = new HashMap<>();
private final Map<Channel, LiveShow> shows = new HashMap<>();
public static Cache getInstance() {
static Cache getInstance() {
if (instance == null) {
instance = new Cache();
}
return instance;
}
public void save(Channel channel, Show show) {
public void save(Channel channel, LiveShow 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);
LiveShow getShow(Channel channel) {
LiveShow show = shows.get(channel);
if (show == null) {
Timber.d("cache miss: " + channel);

View File

@ -0,0 +1,100 @@
package de.christinecoenen.code.zapp.app.livestream.api;
import de.christinecoenen.code.zapp.app.livestream.api.model.Channel;
import de.christinecoenen.code.zapp.app.livestream.api.model.ShowResponse;
import de.christinecoenen.code.zapp.app.livestream.model.LiveShow;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import timber.log.Timber;
public class ProgramGuideRequest implements Callback<ShowResponse> {
private final static ProgramInfoService service = new Retrofit.Builder()
.baseUrl("https://zappbackend.herokuapp.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ProgramInfoService.class);
private Listener listener;
private Channel channelId;
private Call<ShowResponse> showCall;
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) {
Timber.w(channelId + " is no valid channel id");
}
return setChannelId(newChannel);
}
public ProgramGuideRequest execute() {
if (listener == null) {
throw new RuntimeException("listener not set");
}
if (channelId == null) {
Timber.w("no valid channel id set");
listener.onRequestError();
return this;
}
LiveShow cachedShow = Cache.getInstance().getShow(channelId);
if (cachedShow != null) {
listener.onRequestSuccess(cachedShow);
} else {
showCall = service.getShows(channelId.toString());
showCall.enqueue(this);
}
return this;
}
public void cancel() {
if (showCall != null) {
showCall.cancel();
}
}
@Override
public void onResponse(Call<ShowResponse> call, Response<ShowResponse> response) {
//noinspection ConstantConditions
if (response.body() == null || !response.body().isSuccess()) {
listener.onRequestError();
} else {
//noinspection ConstantConditions
LiveShow liveShow = response.body().getShow().toLiveShow();
Cache.getInstance().save(channelId, liveShow);
listener.onRequestSuccess(liveShow);
}
}
@Override
public void onFailure(Call<ShowResponse> call, Throwable t) {
listener.onRequestError();
}
public interface Listener {
void onRequestError();
void onRequestSuccess(LiveShow currentShow);
}
}

View File

@ -0,0 +1,14 @@
package de.christinecoenen.code.zapp.app.livestream.api;
import de.christinecoenen.code.zapp.app.livestream.api.model.ShowResponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
interface ProgramInfoService {
@GET("shows/{channelName}")
Call<ShowResponse> getShows(@Path("channelName") String channelName);
}

View File

@ -1,4 +1,4 @@
package de.christinecoenen.code.programguide.model;
package de.christinecoenen.code.zapp.app.livestream.api.model;
public enum Channel {

View File

@ -0,0 +1,43 @@
package de.christinecoenen.code.zapp.app.livestream.api.model;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import de.christinecoenen.code.zapp.app.livestream.model.LiveShow;
@SuppressWarnings({"unused", "CanBeFinal"})
public class Show {
private static final DateTimeFormatter formatter = ISODateTimeFormat.dateTimeParser();
private String title;
private String subtitle;
private String description;
private String startTime;
private String endTime;
public LiveShow toLiveShow() {
LiveShow liveShow = new LiveShow();
liveShow.setTitle(title);
liveShow.setSubtitle(subtitle);
liveShow.setDescription(description);
if (startTime != null && endTime != null) {
liveShow.setStartTime(formatter.parseDateTime(startTime));
liveShow.setEndTime(formatter.parseDateTime(endTime));
}
return liveShow;
}
@Override
public String toString() {
return "Show{" +
"title='" + title + '\'' +
", subtitle='" + subtitle + '\'' +
", description='" + description + '\'' +
", startTime=" + startTime +
", endTime=" + endTime +
'}';
}
}

View File

@ -0,0 +1,25 @@
package de.christinecoenen.code.zapp.app.livestream.api.model;
import java.util.List;
@SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "unused", "CanBeFinal"})
public class ShowResponse {
private List<Show> shows;
public Show getShow() {
return shows.get(0);
}
public boolean isSuccess() {
return shows != null && !shows.isEmpty();
}
@Override
public String toString() {
return "ShowResponse{" +
"shows=" + shows +
'}';
}
}

View File

@ -1,9 +1,9 @@
package de.christinecoenen.code.programguide.model;
package de.christinecoenen.code.zapp.app.livestream.model;
import org.joda.time.DateTime;
import org.joda.time.Duration;
public class Show {
public class LiveShow {
private String title;
private String subtitle;

View File

@ -20,24 +20,36 @@ import java.util.concurrent.TimeUnit;
import butterknife.BindInt;
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.app.livestream.api.ProgramGuideRequest;
import de.christinecoenen.code.zapp.app.livestream.model.LiveShow;
import de.christinecoenen.code.zapp.model.ChannelModel;
import timber.log.Timber;
public abstract class ProgramInfoViewBase extends LinearLayout {
protected @BindView(R.id.text_show_title) TextView showTitleView;
protected @BindView(R.id.text_show_subtitle) TextView showSubtitleView;
protected @BindView(R.id.text_show_time) TextView showTimeView;
protected @BindView(R.id.progressbar_show_progress) ProgressBar progressBarView;
protected
@BindView(R.id.text_show_title)
TextView showTitleView;
protected
@BindView(R.id.text_show_subtitle)
TextView showSubtitleView;
protected
@BindView(R.id.text_show_time)
TextView showTimeView;
protected
@BindView(R.id.progressbar_show_progress)
ProgressBar progressBarView;
protected @BindInt(R.integer.view_program_info_update_show_info_interval_seconds) int updateShowInfoIntervalSeconds;
protected @BindInt(R.integer.view_program_info_update_show_time_interval_seconds) int updateShowTimeIntervalSeconds;
protected
@BindInt(R.integer.view_program_info_update_show_info_interval_seconds)
int updateShowInfoIntervalSeconds;
protected
@BindInt(R.integer.view_program_info_update_show_time_interval_seconds)
int updateShowTimeIntervalSeconds;
private ProgramGuideRequest currentShowInfoRequest;
private Show currentShow = null;
private LiveShow currentShow = null;
private ChannelModel currentChannel;
private final Handler handler = new Handler();
@ -56,7 +68,7 @@ public abstract class ProgramInfoViewBase extends LinearLayout {
}
@Override
public void onRequestSuccess(Show currentShow) {
public void onRequestSuccess(LiveShow currentShow) {
logMessage("show info loaded: " + currentShow);
ProgramInfoViewBase.this.currentShow = currentShow;
@ -188,7 +200,7 @@ public abstract class ProgramInfoViewBase extends LinearLayout {
private void loadProgramGuide() {
progressBarView.setEnabled(true);
progressBarView.setIndeterminate(true);
currentShowInfoRequest = new ProgramGuideRequest(getContext())
currentShowInfoRequest = new ProgramGuideRequest()
.setChannelId(currentChannel.getId())
.setListener(programGuideListener)
.execute();

View File

@ -1 +0,0 @@
/build

View File

@ -1,36 +0,0 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 26
buildToolsVersion '26.0.1'
defaultConfig {
minSdkVersion 19
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'])
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:26.0.0'
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'
compile 'com.jakewharton.timber:timber:4.5.1'
}

View File

@ -1,17 +0,0 @@
# 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 *;
#}

View File

@ -1,12 +0,0 @@
<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>

View File

@ -1,78 +0,0 @@
package de.christinecoenen.code.programguide;
import android.content.Context;
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;
import timber.log.Timber;
public class ProgramGuideRequest {
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) {
Timber.w(channelId + " is no valid channel id");
}
return setChannelId(newChannel);
}
public ProgramGuideRequest execute() {
if (listener == null) {
throw new RuntimeException("listener not set");
}
if (channelId == null) {
Timber.w("no valid channel id set");
listener.onRequestError();
return this;
}
downloader = PluginRegistry.getInstance(context).getDownloader(channelId, listener);
if (downloader == null) {
listener.onRequestError();
} else {
downloader.download();
}
return this;
}
public void cancel() {
if (downloader != null) {
downloader.cancel();
}
}
public interface Listener {
void onRequestError();
void onRequestSuccess(Show currentShow);
}
}

View File

@ -1,79 +0,0 @@
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) {
Cache.getInstance().save(shows);
onRequestSuccess(shows.get(channel));
}
@Override
public void onRequestSuccess(Show show) {
if (show == null) {
onRequestError();
return;
}
listener.onRequestSuccess(show);
Cache.getInstance().save(channel, show);
}
};
protected final RequestQueue queue;
protected Request<?> request;
protected final Channel channel;
private final ProgramGuideRequest.Listener listener;
protected BaseProgramGuideDownloader(RequestQueue queue, Channel channel, ProgramGuideRequest.Listener listener) {
this.queue = queue;
this.channel = channel;
this.listener = listener;
}
@Override
public void download() {
Show show = Cache.getInstance().getShow(this.channel);
if (show == null) {
downloadWithoutCache();
} else {
listener.onRequestSuccess(show);
}
}
@Override
public void cancel() {
if (request != null) {
request.cancel();
}
}
protected abstract void downloadWithoutCache();
@SuppressWarnings("unused")
protected interface DownloaderListener {
void onRequestError();
void onRequestSuccess(Map<Channel, Show> shows);
void onRequestSuccess(Show shows);
}
}

View File

@ -1,7 +0,0 @@
package de.christinecoenen.code.programguide.plugins;
public interface IProgramGuideDownloader {
void download();
void cancel();
}

View File

@ -1,71 +0,0 @@
package de.christinecoenen.code.programguide.plugins;
import android.content.Context;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import de.christinecoenen.code.programguide.ProgramGuideRequest;
import de.christinecoenen.code.programguide.model.Channel;
import de.christinecoenen.code.programguide.plugins.zappbackend.ZappBackendDownloader;
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 RequestQueue queue;
private PluginRegistry(Context context) {
queue = Volley.newRequestQueue(context);
}
public IProgramGuideDownloader getDownloader(Channel channel, ProgramGuideRequest.Listener listener) {
switch (channel) {
case DAS_ERSTE:
case BR_NORD:
case BR_SUED:
case HR:
case MDR_SACHSEN:
case MDR_SACHSEN_ANHALT:
case MDR_THUERINGEN:
case NDR_HAMBURG:
case NDR_MECKLENBURG_VORPOMMERN:
case NDR_NIEDERSACHSEN:
case NDR_SCHLESWIG_HOLSTEIN:
case RBB_BERLIN:
case RBB_BRANDENBURG:
case SR:
case SWR_BADEN_WUERTTEMBERG:
case SWR_RHEINLAND_PFALZ:
case WDR:
case ARD_ALPHA:
case TAGESSCHAU24:
case ONE:
case ZDF:
case KIKA:
case PHOENIX:
case ZDF_INFO:
case ZDF_NEO:
case DREISAT:
case ARTE:
case DEUTSCHE_WELLE:
case DEUTSCHE_WELLE_PLUS:
case PARLAMENTSFERNSEHEN_1:
case PARLAMENTSFERNSEHEN_2:
return new ZappBackendDownloader(queue, channel, listener);
}
return null;
}
}

View File

@ -1,60 +0,0 @@
package de.christinecoenen.code.programguide.plugins.zappbackend;
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.ProgramGuideRequest;
import de.christinecoenen.code.programguide.model.Channel;
import de.christinecoenen.code.programguide.model.Show;
import de.christinecoenen.code.programguide.plugins.BaseProgramGuideDownloader;
import timber.log.Timber;
public class ZappBackendDownloader extends BaseProgramGuideDownloader {
private static final String API_URL = "https://zappbackend.herokuapp.com/v1/shows/";
public ZappBackendDownloader(RequestQueue queue, Channel channel, ProgramGuideRequest.Listener listener) {
super(queue, channel, listener);
}
@Override
public void downloadWithoutCache() {
String url = API_URL + channel.toString();
request = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
Timber.d("json loaded");
parse(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Timber.d("error loading json: " + error.getMessage());
downloaderListener.onRequestError();
}
});
queue.add(request);
}
private void parse(JSONObject json) {
Show show;
try {
show = ZappBackendParser.parse(json);
} catch (JSONException e) {
Timber.d(e);
downloaderListener.onRequestError();
return;
}
downloaderListener.onRequestSuccess(show);
}
}

View File

@ -1,48 +0,0 @@
package de.christinecoenen.code.programguide.plugins.zappbackend;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import de.christinecoenen.code.programguide.model.Show;
import timber.log.Timber;
class ZappBackendParser {
// 2017-03-11T17:27:00+01:00
private static final DateTimeFormatter formatter = ISODateTimeFormat.dateTimeParser();
static Show parse(JSONObject json) throws JSONException {
Show show = new Show();
JSONArray shows = json.getJSONArray("shows");
JSONObject showJson = shows.getJSONObject(0);
String title = showJson.getString("title");
String subtitle = getOptionalString(showJson, "subtitle");
String startTime = getOptionalString(showJson, "startTime");
String endTime = getOptionalString(showJson, "endTime");
show.setTitle(title);
show.setSubtitle(subtitle);
if (startTime != null && endTime != null) {
show.setStartTime(formatter.parseDateTime(startTime));
show.setEndTime(formatter.parseDateTime(endTime));
}
return show;
}
private static String getOptionalString(JSONObject json, String key) {
try {
String value = json.getString(key);
return value.equals("null") ? null : value;
} catch (JSONException e) {
Timber.d("optional json key is not present: " + key);
}
return null;
}
}

View File

@ -1,3 +0,0 @@
<resources>
<string name="app_name">Program Guide</string>
</resources>

View File

@ -1,18 +0,0 @@
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>
*/
@SuppressWarnings("unused")
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -1 +1 @@
include ':app', ':programguide'
include ':app'