Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
florianmski committed Mar 10, 2015
2 parents 70a30f9 + ce2f526 commit bf79db6
Show file tree
Hide file tree
Showing 33 changed files with 704 additions and 77 deletions.
34 changes: 9 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,21 @@
Traktoid
===================

An awesome android app that works with the [Trakt v2 API](http://trakt.tv/)

![](http://i.imgur.com/YQSpK7R.jpg?1)
![](http://i.imgur.com/mVJtWv1.jpg?1)
![](http://i.imgur.com/uA79fN8.jpg?1)

[Sign up for testing](https://plus.google.com/communities/113346133721976635769)

An Android app powered by the [Trakt v2 API](http://trakt.tv/), [RxJava](https://github.com/ReactiveX/RxJava) and [Material Design](http://www.google.com/design/spec/material-design/introduction.html).
Unfortunately, Traktoid isn't available on the Playstore anymore as Google decided I didn't have the rights to display tv show and movie posters. Decision is final, I might try to resubmit it to the Playstore in the future but for now you can grab the [latest apk](https://github.com/florianmski/Traktoid/releases/latest) and [share your thoughts](https://plus.google.com/communities/113346133721976635769).

![](http://i.imgur.com/4BA20yx.png)

Developed By
============

* Florian Mierzejewski - <[email protected]>



License
=======

Copyright 2011 Florian Mierzejewski

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-------

```
"THE BEER-WARE LICENSE" (Revision 42):
You can do whatever you want with this stuff.
If we meet some day, and you think this stuff is worth it, you can buy me a beer in return.
```
5 changes: 3 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.florianmski.tracktoid"
android:versionCode="14"
android:versionName="0.8"
android:versionCode="15"
android:versionName="0.8.2"
android:installLocation="preferExternal">

<uses-permission android:name="android.permission.INTERNET"/>
Expand Down Expand Up @@ -45,6 +45,7 @@
/>

<service android:name=".services.TraktoidService" />
<service android:name=".services.CheckinService" />

<service
android:name=".services.sync.AuthenticatorService">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public abstract class CursorObservable<T> implements Observable.OnSubscribe<T>

protected abstract T toObject(Cursor cursor);

public CursorObservable(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
public CursorObservable(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, boolean listenToUpdates)
{
this.contentObserver = new ForceLoadContentObserver();

Expand All @@ -46,7 +46,13 @@ public CursorObservable(Context context, Uri uri, String[] projection, String se
this.selectionArgs = selectionArgs;
this.sortOrder = sortOrder;

context.getContentResolver().registerContentObserver(uri, false, contentObserver);
if(listenToUpdates)
context.getContentResolver().registerContentObserver(uri, false, contentObserver);
}

public CursorObservable(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
this(context, uri, projection, selection, selectionArgs, sortOrder, true);
}

private void closeCursor()
Expand Down Expand Up @@ -126,7 +132,8 @@ public void onChange(boolean selfChange, Uri uri)
else
Timber.d("updating : " + uri.toString().replace("content://com.florianmski.tracktoid.data.provider.TraktoidProvider/", ""));

call(subscriber);
if(subscriber != null)
call(subscriber);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.florianmski.tracktoid.rx.observables;

import com.uwetrottmann.trakt.v2.exceptions.CheckinInProgressException;
import com.uwetrottmann.trakt.v2.exceptions.OAuthUnauthorizedException;

import rx.Observable;
import rx.Subscriber;

public abstract class TraktObservable<T> implements Observable.OnSubscribe<T>
{
public abstract T fire() throws OAuthUnauthorizedException;
public abstract T fire() throws OAuthUnauthorizedException, CheckinInProgressException;

@Override
public void call(Subscriber<? super T> subscriber)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package com.florianmski.tracktoid.services;

import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;

import com.florianmski.tracktoid.R;
import com.florianmski.tracktoid.TraktoidConstants;
import com.florianmski.tracktoid.TraktoidTheme;
import com.florianmski.tracktoid.data.database.columns.SyncColumns;
import com.florianmski.tracktoid.rx.observables.TraktObservable;
import com.florianmski.tracktoid.trakt.TraktManager;
import com.florianmski.tracktoid.utils.CVHelper;
import com.florianmski.tracktoid.utils.DateHelper;
import com.florianmski.tracktoid.utils.DbHelper;
import com.uwetrottmann.trakt.v2.exceptions.CheckinInProgressException;
import com.uwetrottmann.trakt.v2.exceptions.OAuthUnauthorizedException;

import java.util.concurrent.TimeUnit;

import retrofit.client.Response;
import rx.Observable;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action0;
import rx.functions.Func1;
import timber.log.Timber;

public class CheckinService extends IntentService
{
private final static int NOTIFICATION_ID = 1337;

private final static String BUNDLE_CHECKIN = "checkin";
private final static String BUNDLE_DURATION = "duration";
private final static int BUNDLE_STOP_CHECKIN = 0;
private final static int BUNDLE_CHECKIN_EPISODE = 1;
private final static int BUNDLE_CHECKIN_MOVIE = 2;

private static Subscription subscription;

private NotificationManager notificationManager;
private NotificationCompat.Builder notificationBuilder;

public CheckinService()
{
super("CheckinService");
}

@Override
protected void onHandleIntent(Intent intent)
{
int checkin = intent.getExtras().getInt(BUNDLE_CHECKIN);

notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

if(checkin == BUNDLE_STOP_CHECKIN)
{
if(subscription != null)
subscription.unsubscribe();
}
else
{
final int id = intent.getExtras().getInt(TraktoidConstants.BUNDLE_ID);
final String title = intent.getExtras().getString(TraktoidConstants.BUNDLE_TITLE);
final int duration = intent.getExtras().getInt(BUNDLE_DURATION);

Intent i = new Intent(this, CheckinService.class);
i.putExtra(BUNDLE_CHECKIN, BUNDLE_STOP_CHECKIN);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, i, 0);
notificationBuilder = new NotificationCompat.Builder(this)
.setContentTitle(String.format("Watching %s", title))
.setColor(TraktoidTheme.DEFAULT.getColorDark(this))
.setSmallIcon(R.drawable.ic_check_white_24dp)
.setOngoing(true)
.addAction(new NotificationCompat.Action(R.drawable.ic_clear_white_24dp, "Cancel", pendingIntent));

startTimer(checkin, id, duration);
}
}

private void startTimer(final int checkin, final int id, int duration)
{
subscription = Observable
.interval((duration * 60) / 100, TimeUnit.SECONDS)
.takeWhile(new Func1<Long, Boolean>()
{
@Override
public Boolean call(Long tick)
{
return tick < 100;
}
})
.doOnSubscribe(new Action0()
{
@Override
public void call()
{
updateProgress(0);
}
})
.doOnUnsubscribe(new Action0()
{
@Override
public void call()
{
cancelCheckin();
}
})
.subscribe(new Subscriber<Long>()
{
@Override
public void onCompleted()
{
cancelNotification();

// mark as seen in db
CVHelper cv = new CVHelper()
.put(SyncColumns.WATCHED, true)
.put(SyncColumns.LAST_WATCHED_AT, DateHelper.now());

if(checkin == BUNDLE_CHECKIN_MOVIE)
DbHelper.updateMovie(CheckinService.this, cv.get(), String.valueOf(id));
else if(checkin == BUNDLE_CHECKIN_EPISODE)
DbHelper.updateEpisode(CheckinService.this, cv.get(), String.valueOf(id));
}

@Override
public void onError(Throwable e)
{
cancelNotification();
Timber.i("Error during checkin", e);
}

@Override
public void onNext(Long ticks)
{
updateProgress(ticks.intValue()+1);
}
});
}

private void cancelNotification()
{
notificationManager.cancel(NOTIFICATION_ID);
subscription = null;
}

private void cancelCheckin()
{
Observable.create(new TraktObservable<Response>()
{
@Override
public Response fire() throws OAuthUnauthorizedException, CheckinInProgressException
{
return TraktManager.getInstance().checkin().deleteActiveCheckin();
}
}).doOnCompleted(new Action0()
{
@Override
public void call()
{
cancelNotification();
}
}).subscribe();
}

private void updateProgress(int progress)
{
notificationBuilder
.setProgress(100, progress, false)
.setContentText(String.format("%d%%", progress));
updateNotification();
}

private void updateNotification()
{
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}

public static void checkinEpisode(Context context, int id, String title, int duration)
{
checkin(context, BUNDLE_CHECKIN_EPISODE, id, title, duration);
}

public static void checkinMovie(Context context, int id, String title, int duration)
{
checkin(context, BUNDLE_CHECKIN_MOVIE, id, title, duration);
}

private static void checkin(Context context, int checkin, int id, String title, int duration)
{
Intent i = new Intent(context, CheckinService.class);
i.putExtra(BUNDLE_CHECKIN, checkin);
i.putExtra(TraktoidConstants.BUNDLE_ID, id);
i.putExtra(TraktoidConstants.BUNDLE_TITLE, title);
i.putExtra(BUNDLE_DURATION, duration);
context.startService(i);
}

public static void stopCheckin(Context context)
{
Intent i = new Intent(context, CheckinService.class);
i.putExtra(BUNDLE_CHECKIN, BUNDLE_STOP_CHECKIN);
context.startService(i);
}

public static boolean isCheckinInProgress()
{
return subscription != null;
}

@Override
public void onTaskRemoved(Intent rootIntent)
{
super.onTaskRemoved(rootIntent);

// clean notification if app is killed
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFICATION_ID);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,14 @@ public Integer call(T t1, T t2)
{
DateTime syncTime1 = funcGetItemSyncTime.call(t1);
DateTime syncTime2 = funcGetItemSyncTime.call(t2);

// apparently sometimes API send back null values
// in this case take "now" time
if(syncTime1 == null)
syncTime1 = DateHelper.now();
if(syncTime2 == null)
syncTime2 = DateHelper.now();

return -syncTime1.compareTo(syncTime2);
}
})
Expand All @@ -426,7 +434,11 @@ public Observable<T> call(List<T> ts)
@Override
public Boolean call(T t)
{
return funcGetItemSyncTime.call(t).isAfter(lastLocalSyncTime);
DateTime syncTime = funcGetItemSyncTime.call(t);
if(syncTime == null)
syncTime = DateHelper.now();

return syncTime.isAfter(lastLocalSyncTime);
}
})
.collect(new ArrayList<T>(), new Action2<ArrayList<T>, T>()
Expand Down
Loading

0 comments on commit bf79db6

Please sign in to comment.