add gitlab-ci config

This commit is contained in:
Felix 2017-10-12 18:27:41 +02:00
parent a30d197c78
commit 62e77a59c6
14 changed files with 175 additions and 117 deletions

56
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,56 @@
image: inovex/gitlab-ci-android
#image: openjdk:8-jdk
variables:
ANDROID_COMPILE_SDK: "25"
ANDROID_BUILD_TOOLS: "25.0.2"
ANDROID_SDK_TOOLS: "3859397"
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
before_script:
- export GRADLE_USER_HOME=$(pwd)/.gradle
- chmod +x ./gradlew
- echo "ad8ad258047a00e2ee528be5b1a8a18bab7c4367" > $ANDROID_HOME/licenses/android-sdk-license
- echo "8933bad161af4178b1185d1a37fbf41ea5269c55" >> $ANDROID_HOME/licenses/android-sdk-license
- echo "d56f5187479451eabf01fb78af6dfcb131a6481e" >> $ANDROID_HOME/licenses/android-sdk-license
- cat $ANDROID_HOME/licenses/*
stages:
- build
- test
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
build:
stage: build
script:
- ./gradlew --info --stacktrace assembleDebug
artifacts:
paths:
- app/build/outputs/apk/*.apk
unitTests:
stage: test
script:
- ./gradlew --stacktrace test
artifacts:
paths:
- app/build/reports/tests/
functionalTests:
stage: test
script:
- env
- wget --quiet --output-document=android-wait-for-emulator https://raw.githubusercontent.com/travis-ci/travis-cookbooks/0f497eb71291b52a703143c5cd63a217c8766dc9/community-cookbooks/android-sdk/files/default/android-wait-for-emulator
- chmod +x android-wait-for-emulator
- echo y | tools/bin/sdkmanager "system-images;android-$ANDROID_COMPILE_SDK;google_apis;x86"
- echo no | tools/bin/avdmanager create avd -n test -k "system-images;android-$ANDROID_COMPILE_SDK;google_apis;x86" --abi google_apis/x86
- tools/emulator -force-32bit -avd test -no-window -no-audio &
- ./android-wait-for-emulator
- adb shell input keyevent 82
- ./gradlew --debug --stacktrace cAT

View file

@ -40,10 +40,9 @@ dependencies {
implementation "android.arch.lifecycle:common-java8:1.0.0-beta1"
implementation "android.arch.persistence.room:runtime:${rootProject.ext.archCompVersion}"
annotationProcessor "android.arch.persistence.room:compiler:${rootProject.ext.archCompVersion}"
kapt "android.arch.persistence.room:compiler:${rootProject.ext.archCompVersion}"
implementation "android.arch.persistence.room:rxjava2:${rootProject.ext.archCompVersion}"
compile 'com.github.satyan:sugar:1.4'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.1.3'

View file

@ -7,7 +7,8 @@ import android.arch.persistence.room.RoomDatabase
* Created by felix on 04.10.17.
*/
@Database(entities = arrayOf(PlaybackProgress::class), version = 5)
@Database(entities = arrayOf(PlaybackProgress::class,WatchlistItem::class), version = 1, exportSchema = false)
abstract class ChaosflixDatabase : RoomDatabase() {
abstract fun playbackProgressDao(): PlaybackProgressDao
abstract fun watchlistItemDao(): WatchlistItemDao
}

View file

@ -1,5 +1,6 @@
package de.nicidienase.chaosflix.common.entities
import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
@ -7,5 +8,8 @@ import android.arch.persistence.room.PrimaryKey
* Created by felix on 06.04.17.
*/
@Entity
class PlaybackProgress (@PrimaryKey var eventId: Int = 0, var progress: Long = 0){}
@Entity(tableName = "playback_progress")
class PlaybackProgress (@PrimaryKey
@ColumnInfo(name = "event_id")
var eventId: Int,
var progress: Long)

View file

@ -1,7 +1,9 @@
package de.nicidienase.chaosflix.common.entities
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import io.reactivex.Flowable
/**
* Created by felix on 04.10.17.
@ -10,11 +12,11 @@ import android.arch.persistence.room.Query
@Dao
interface PlaybackProgressDao{
@Query("SELECT * from playback_progress")
fun getAll(): List<PlaybackProgress>
fun getAll(): Flowable<List<PlaybackProgress>>
@Query("SELECT * from playback_progress WHERE id = (:id)")
fun getProgressForEvent(id:Int):PlaybackProgress
@Query("SELECT * from playback_progress WHERE event_id = :arg0 LIMIT 1")
fun getProgressForEvent(id:Int): Flowable<PlaybackProgress>
@Query("DELETE from playback_progress WHERE id = (:id)")
fun deleteProgress(id:Int)
@Insert
fun saveProgress(progress: PlaybackProgress)
}

View file

@ -1,6 +1,8 @@
package de.nicidienase.chaosflix.common.entities
import android.arch.persistence.room.Entity
import android.arch.persistence.room.Ignore
import android.arch.persistence.room.PrimaryKey
import org.joda.time.DateTime
@ -10,5 +12,7 @@ import java.util.Date
* Created by felix on 19.04.17.
*/
@Entity
class WatchlistItem (var id: Int = 0, var eventId: Int = id, var added: DateTime = DateTime(Date())) {}
@Entity(tableName = "watchlist_item")
data class WatchlistItem (@PrimaryKey
var id: Int,
var eventId: Int)

View file

@ -28,7 +28,6 @@ import de.nicidienase.chaosflix.leanback.PlaybackHelper;
import de.nicidienase.chaosflix.R;
import de.nicidienase.chaosflix.leanback.activities.LeanbackBaseActivity;
import de.nicidienase.chaosflix.leanback.activities.DetailsActivity;
import de.nicidienase.chaosflix.common.entities.PlaybackProgress;
import de.nicidienase.chaosflix.common.entities.recording.Event;
import de.nicidienase.chaosflix.common.entities.recording.Recording;
import de.nicidienase.chaosflix.common.entities.streaming.Room;

View file

@ -1,5 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
@ -49,17 +51,27 @@ android {
dependencies {
implementation project(':common')
compile fileTree(include: ['*.jar'], dir: 'libs')
implementation "com.android.support:support-v4:${rootProject.ext.supportLibraryVersion}"
implementation "com.android.support:recyclerview-v7:${rootProject.ext.supportLibraryVersion}"
implementation "com.android.support:cardview-v7:${rootProject.ext.supportLibraryVersion}"
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
compile "com.android.support:design:${rootProject.ext.supportLibraryVersion}"
compile fileTree(include: ['*.jar'], dir: 'libs')
implementation "android.arch.lifecycle:runtime:1.0.0"
implementation "android.arch.lifecycle:extensions:${rootProject.ext.archCompVersion}"
implementation "android.arch.lifecycle:common-java8:1.0.0-beta1"
implementation "android.arch.persistence.room:runtime:${rootProject.ext.archCompVersion}"
implementation "android.arch.persistence.room:rxjava2:${rootProject.ext.archCompVersion}"
kapt 'com.android.databinding:compiler:3.0.0-beta7'
kapt "android.arch.lifecycle:compiler:${rootProject.ext.archCompVersion}"
kapt "android.arch.persistence.room:compiler:${rootProject.ext.archCompVersion}"
compile 'com.google.android.exoplayer:exoplayer:r2.5.2'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.jakewharton:butterknife:8.5.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
kapt 'com.jakewharton:butterknife-compiler:8.5.1'
debugCompile 'com.facebook.stetho:stetho:1.4.2'
debugCompile 'com.facebook.stetho:stetho-okhttp:1.4.2'

View file

@ -1,15 +1,16 @@
package de.nicidienase.chaosflix.touch;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.ViewModel;
import android.arch.lifecycle.ViewModelProvider;
import android.content.Context;
import android.arch.persistence.room.Room;
import android.content.res.Resources;
import android.util.Log;
import java.util.List;
import de.nicidienase.chaosflix.R;
import de.nicidienase.chaosflix.common.entities.ChaosflixDatabase;
import de.nicidienase.chaosflix.common.entities.PlaybackProgress;
import de.nicidienase.chaosflix.common.entities.WatchlistItem;
import de.nicidienase.chaosflix.common.entities.recording.Conference;
@ -19,8 +20,10 @@ import de.nicidienase.chaosflix.common.entities.recording.Recording;
import de.nicidienase.chaosflix.common.entities.streaming.LiveConference;
import de.nicidienase.chaosflix.common.network.RecordingService;
import de.nicidienase.chaosflix.common.network.StreamingService;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
@ -31,25 +34,21 @@ import retrofit2.converter.gson.GsonConverterFactory;
* Created by felix on 24.09.17.
*/
public class ChaosflixViewModel extends ViewModel {
public class ChaosflixViewModel extends AndroidViewModel {
private static final String TAG = ChaosflixViewModel.class.getSimpleName();
private static Factory factory;
private final StreamingService mStreamingApi;
private final RecordingService mRecordingApi;
private final StreamingService streamingApi;
private final RecordingService recordingApi;
private final ChaosflixDatabase database;
CompositeDisposable disposable = new CompositeDisposable();
public static ChaosflixViewModel.Factory getFactory(Context context){
if(factory == null){
Resources res = context.getResources();
factory = new Factory(
res.getString(R.string.api_media_ccc_url),
res.getString(R.string.streaming_media_ccc_url));
}
return factory;
}
public ChaosflixViewModel(Application application){
super(application);
Resources res = application.getResources();
String recordingUrl = res.getString(R.string.api_media_ccc_url);
String streamingUrl = res.getString(R.string.streaming_media_ccc_url);
public ChaosflixViewModel(String recordingUrl, String streamingUrl){
OkHttpClient client = new OkHttpClient();
GsonConverterFactory gsonConverterFactory = GsonConverterFactory.create();
RxJava2CallAdapterFactory rxJava2CallAdapterFactory = RxJava2CallAdapterFactory.create();
@ -60,7 +59,7 @@ public class ChaosflixViewModel extends ViewModel {
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(rxJava2CallAdapterFactory)
.build();
mRecordingApi = retrofitRecordings.create(RecordingService.class);
recordingApi = retrofitRecordings.create(RecordingService.class);
Retrofit retrofigStreaming = new Retrofit.Builder()
.baseUrl(streamingUrl)
@ -68,7 +67,9 @@ public class ChaosflixViewModel extends ViewModel {
.addConverterFactory(gsonConverterFactory)
.addCallAdapterFactory(rxJava2CallAdapterFactory)
.build();
mStreamingApi = retrofigStreaming.create(StreamingService.class);
streamingApi = retrofigStreaming.create(StreamingService.class);
database = Room.databaseBuilder(getApplication().getApplicationContext(), ChaosflixDatabase.class, "mediaccc.db").build();
}
public LiveData<ConferencesWrapper> getConferencesWrapperAsLiveData(){
@ -76,7 +77,7 @@ public class ChaosflixViewModel extends ViewModel {
@Override
protected void onActive() {
super.onActive();
mRecordingApi.getConferences()
recordingApi.getConferences()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(conferencesWrapper -> setValue(conferencesWrapper));
@ -84,94 +85,73 @@ public class ChaosflixViewModel extends ViewModel {
};
}
public Observable<ConferencesWrapper> getConferencesWrapper() {
return mRecordingApi.getConferences()
return recordingApi.getConferences()
.doOnError(throwable -> Log.d(TAG, String.valueOf(throwable.getCause())))
.subscribeOn(Schedulers.io());
}
public Observable<List<Conference>> getConferencesByGroup(String group){
return mRecordingApi.getConferences().map(
return recordingApi.getConferences().map(
conferencesWrapper -> conferencesWrapper.getConferencesBySeries().get(group))
.subscribeOn(Schedulers.io());
}
public Observable<Conference> getConference(int mConferenceId) {
return mRecordingApi.getConference(mConferenceId)
return recordingApi.getConference(mConferenceId)
.subscribeOn(Schedulers.io());
}
public Observable<Event> getEvent(int apiID) {
return mRecordingApi.getEvent(apiID)
return recordingApi.getEvent(apiID)
.subscribeOn(Schedulers.io());
}
public Observable<Recording> getRecording(long id) {
return mRecordingApi.getRecording(id)
return recordingApi.getRecording(id)
.subscribeOn(Schedulers.io());
}
public Observable<List<LiveConference>> getStreamingConferences() {
return mStreamingApi.getStreamingConferences()
return streamingApi.getStreamingConferences()
.subscribeOn(Schedulers.io());
}
public void setPlaybackProgress(int apiId, long progress){
getPlaybackProgress(apiId)
.subscribe(p -> {
if(p == 0l){
PlaybackProgress playbackProgress = new PlaybackProgress(apiId, progress, 0);
playbackProgress.setId((long) apiId);
playbackProgress.save();
} else {
PlaybackProgress playbackProgress = PlaybackProgress.findById(PlaybackProgress.class, apiId);
database.playbackProgressDao().getProgressForEvent(apiId)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(playbackProgress -> {
if(playbackProgress != null){
playbackProgress.setProgress(progress);
playbackProgress.save();
} else {
playbackProgress = new PlaybackProgress(apiId,progress);
}
database.playbackProgressDao().saveProgress(playbackProgress);
});
}
public Flowable<PlaybackProgress> getPlaybackProgress(int apiID) {
return database.playbackProgressDao().getProgressForEvent(apiID);
}
public void createBookmark(int apiId){
database.watchlistItemDao().getItemForEvent(apiId)
.subscribe(watchlistItem -> {
if(watchlistItem == null){
watchlistItem = new WatchlistItem(apiId,apiId);
database.watchlistItemDao().saveItem(watchlistItem);
}
});
}
public Observable<Long> getPlaybackProgress(int apiID) {
return Observable.fromCallable(() -> {
PlaybackProgress progress = PlaybackProgress.findById(PlaybackProgress.class, apiID);
return progress != null ? progress.getProgress() : 0l;
}).subscribeOn(Schedulers.io());
public Flowable<WatchlistItem> getBookmark(int apiId){
return database.watchlistItemDao().getItemForEvent(apiId);
}
public void createBookmark(int apiId){
WatchlistItem bookmark = getBookmark(apiId);
if(bookmark != null){
bookmark = new WatchlistItem(apiId);
bookmark.save();
} else {
// Bookmark already exists
}
}
public WatchlistItem getBookmark(int apiId){
return WatchlistItem.findById(WatchlistItem.class, apiId);
}
public boolean bookmarkExists(int apiId) {
return getBookmark(apiId) != null;
}
public boolean removeBookmark(int apiID) {
return getBookmark(apiID).delete();
}
public static class Factory extends ViewModelProvider.NewInstanceFactory{
private final String recordingUrl;
private final String streamUrl;
public Factory(String recordingUrl, String streamUrl){
this.recordingUrl = recordingUrl;
this.streamUrl = streamUrl;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new ChaosflixViewModel(recordingUrl,streamUrl);
}
public void removeBookmark(int apiID) {
getBookmark(apiID).subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(watchlistItem ->
database.watchlistItemDao().deleteItem(watchlistItem));
}
}

View file

@ -50,8 +50,7 @@ public class BrowseActivity extends AppCompatActivity implements
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_container_layout);
mViewModel = ViewModelProviders.of(this,ChaosflixViewModel.getFactory(this))
.get(ChaosflixViewModel.class);
mViewModel = ViewModelProviders.of(this).get(ChaosflixViewModel.class);
if(savedInstanceState == null){
ConferencesTabBrowseFragment browseFragment

View file

@ -32,12 +32,7 @@ public class PlayerActivity extends AppCompatActivity implements ExoPlayerFragme
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_container_layout);
Resources res = getResources();
ChaosflixViewModel.Factory factory =
new ChaosflixViewModel.Factory(
res.getString(R.string.api_media_ccc_url),
res.getString(R.string.streaming_media_ccc_url));
viewModel = ViewModelProviders.of(this,factory).get(ChaosflixViewModel.class);
viewModel = ViewModelProviders.of(this).get(ChaosflixViewModel.class);
int eventId = getIntent().getExtras().getInt(EVENT_ID);
int recordingId = getIntent().getExtras().getInt(RECORDING_ID);

View file

@ -20,12 +20,7 @@ public class ChaosflixFragment extends Fragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Resources res = getResources();
ChaosflixViewModel.Factory factory =
new ChaosflixViewModel.Factory(
res.getString(R.string.api_media_ccc_url),
res.getString(R.string.streaming_media_ccc_url));
mViewModel = ViewModelProviders.of(getActivity(),factory).get(ChaosflixViewModel.class);
mViewModel = ViewModelProviders.of(getActivity()).get(ChaosflixViewModel.class);
}

View file

@ -24,6 +24,7 @@ import de.nicidienase.chaosflix.common.entities.recording.Event;
import de.nicidienase.chaosflix.common.entities.recording.Recording;
import de.nicidienase.chaosflix.databinding.FragmentEventDetailsNewBinding;
import de.nicidienase.chaosflix.touch.ChaosflixViewModel;
import io.reactivex.disposables.CompositeDisposable;
public class EventDetailsFragment extends Fragment {
private static final String TAG = EventDetailsFragment.class.getSimpleName();
@ -34,6 +35,7 @@ public class EventDetailsFragment extends Fragment {
private boolean appBarExpanded;
private ChaosflixViewModel viewModel;
private CompositeDisposable disposable = new CompositeDisposable();
public static EventDetailsFragment newInstance(Event event) {
EventDetailsFragment fragment = new EventDetailsFragment();
@ -56,7 +58,7 @@ public class EventDetailsFragment extends Fragment {
if (getArguments() != null) {
mEvent = getArguments().getParcelable(EVENT_PARAM);
}
viewModel = ViewModelProviders.of(this,ChaosflixViewModel.getFactory(getContext())).get(ChaosflixViewModel.class);
viewModel = ViewModelProviders.of(this).get(ChaosflixViewModel.class);
}
@Override
@ -122,6 +124,12 @@ public class EventDetailsFragment extends Fragment {
}
}
@Override
public void onStop() {
super.onStop();
disposable.clear();
}
@Override
public void onDetach() {
super.onDetach();
@ -131,10 +139,13 @@ public class EventDetailsFragment extends Fragment {
@Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if(viewModel.bookmarkExists(mEvent.getApiID())){
menu.findItem(R.id.action_bookmark).setVisible(false);
menu.findItem(R.id.action_unbookmark).setVisible(true);
}
disposable.add(viewModel.getBookmark(mEvent.getApiID())
.subscribe(watchlistItem -> {
if (watchlistItem != null) {
menu.findItem(R.id.action_bookmark).setVisible(false);
menu.findItem(R.id.action_unbookmark).setVisible(true);
}
}));
}
@Override
@ -154,10 +165,7 @@ public class EventDetailsFragment extends Fragment {
viewModel.createBookmark(mEvent.getApiID());
return true;
case R.id.action_unbookmark:
boolean success = viewModel.removeBookmark(mEvent.getApiID());
if(!success){
Snackbar.make(item.getActionView(),"Error removing Bookmark",Snackbar.LENGTH_LONG).show();
}
viewModel.removeBookmark(mEvent.getApiID());
return true;
case R.id.action_download:
Snackbar.make(item.getActionView(),"Start download",Snackbar.LENGTH_LONG).show();

View file

@ -127,9 +127,13 @@ public class ExoPlayerFragment extends ChaosflixFragment implements MyListener.P
super.onResume();
if(exoPlayer != null){
exoPlayer.setPlayWhenReady(mPlaybackState);
getViewModel().getPlaybackProgress(mEvent.getApiID())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aLong -> exoPlayer.seekTo(aLong));
// getViewModel().getPlaybackProgress(mEvent.getApiID())
// .observe(this,
// playbackProgress -> {
// if(playbackProgress != null) {
// exoPlayer.seekTo(playbackProgress.getProgress());
// }
// });
videoView.setPlayer(exoPlayer);
}
}