2
.gitignore
vendored
|
@ -37,3 +37,5 @@ build/*
|
|||
*.ipr
|
||||
*~
|
||||
*.swp
|
||||
News-Android-App/out.map
|
||||
News-Android-App/extra/release/output.json
|
||||
|
|
10
CHANGELOG.md
|
@ -1,13 +1,13 @@
|
|||
1.0 (in development)
|
||||
---------------------
|
||||
- Single Sign on for all Nextcloud Android Apps!
|
||||
|
||||
|
||||
0.9.9.26
|
||||
---------------------
|
||||
- Fix - <a href="https://github.com/owncloud/News-Android-App/issues/726">#726 Add new feed fails</a>
|
||||
- Fix - <a href="https://github.com/owncloud/News-Android-App/issues/744">#744 Fix issues when adding feeds (Thanks @Unpublished)</a>
|
||||
- Feature - <a href="https://github.com/owncloud/News-Android-App/issues/747">#747 Add option to share article when using chrome-custom-tabs</a>
|
||||
- Fix - Reset database when account is stored
|
||||
- Fix - Workaround for app-crashes due to widget problems
|
||||
- Feature - Support for Android Auto (Podcast playback)
|
||||
- Feature - Use picture-in-picture mode for video podcasts
|
||||
- Fix - Fix restarts of app due to a bug in android compat library (when using dark mode)
|
||||
|
||||
|
||||
0.9.9.25
|
||||
|
|
|
@ -40,8 +40,8 @@ android {
|
|||
|
||||
buildTypes {
|
||||
debug {
|
||||
shrinkResources true
|
||||
minifyEnabled true
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
useProguard true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
testProguardFiles 'proguard-test.pro'
|
||||
|
@ -131,26 +131,27 @@ dependencies {
|
|||
// implementation 'com.google.android.gms:play-services:4.2.42'
|
||||
//implementation project(':Android-SingleSignOn')
|
||||
//implementation project(path: ':MaterialShowcaseView:library', configuration: 'default')
|
||||
//implementation 'com.github.nextcloud:Android-SingleSignOn:e781a33e01'
|
||||
implementation 'com.github.nextcloud:Android-SingleSignOn:unit-testing-SNAPSHOT'
|
||||
implementation 'com.github.nextcloud:Android-SingleSignOn:fix-account-not-found-SNAPSHOT'
|
||||
implementation 'com.github.David-Development:MaterialShowcaseView:bf6afa225d'
|
||||
|
||||
// https://mvnrepository.com/artifact/androidx.legacy/legacy-support-v4
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.core:core:1.1.0-alpha05"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0-alpha04"
|
||||
implementation "androidx.preference:preference:1.1.0-alpha04"
|
||||
implementation "androidx.core:core:1.2.0-alpha01"
|
||||
implementation "androidx.appcompat:appcompat:1.1.0-alpha05"
|
||||
implementation "androidx.preference:preference:1.1.0-alpha05"
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.android.material/material
|
||||
implementation "com.google.android.material:material:1.1.0-alpha05"
|
||||
implementation "com.google.android.material:material:1.1.0-alpha06"
|
||||
//implementation "com.google.android.material:material:1.0.0"
|
||||
implementation "androidx.palette:palette:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0-alpha04"
|
||||
implementation "androidx.recyclerview:recyclerview:1.1.0-alpha05"
|
||||
implementation "androidx.browser:browser:1.0.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
//implementation 'de.mrmaffen:holocircularprogressbar:1.0.1'
|
||||
implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'com.jakewharton:butterknife:10.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:10.1.0'
|
||||
|
||||
implementation 'com.sothree.slidinguppanel:library:3.2.1'
|
||||
|
@ -169,7 +170,6 @@ dependencies {
|
|||
transitive = true
|
||||
}
|
||||
|
||||
|
||||
implementation "com.google.dagger:dagger:${DAGGER_VERSION}"
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
|
||||
|
||||
|
@ -186,7 +186,7 @@ dependencies {
|
|||
|
||||
implementation 'com.nbsp:library:1.02' // MaterialFilePicker
|
||||
|
||||
extraImplementation 'com.github.tommus:youtube-android-player-api:1.2.2'
|
||||
//extraImplementation 'com.github.tommus:youtube-android-player-api:1.2.2'
|
||||
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
|
|
BIN
News-Android-App/src/androidTest/assets/OwncloudNewsReaderOrm.db
Normal file
|
@ -3,11 +3,18 @@ package de.luhmer.owncloudnewsreader.di;
|
|||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.nextcloud.android.sso.AccountImporter;
|
||||
import com.nextcloud.android.sso.model.SingleSignOnAccount;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListFragment;
|
||||
import de.luhmer.owncloudnewsreader.SettingsActivity;
|
||||
|
@ -17,6 +24,7 @@ import de.luhmer.owncloudnewsreader.ssl.MemorizingTrustManager;
|
|||
|
||||
public class TestApiModule extends ApiModule {
|
||||
|
||||
private static final String TAG = TestApiModule.class.getCanonicalName();
|
||||
private Application application;
|
||||
|
||||
public static String DUMMY_ACCOUNT_AccountName = "test-account";
|
||||
|
@ -81,9 +89,43 @@ public class TestApiModule extends ApiModule {
|
|||
return application.getPackageName() + "_preferences_test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String providesDatabaseFileName() {
|
||||
String filename = "OwncloudNewsReaderOrmTest.db";
|
||||
|
||||
try {
|
||||
|
||||
String dst = "/data/data/" + application.getApplicationContext().getPackageName() + "/databases/" + filename;
|
||||
File dstFile = new File(dst);
|
||||
dstFile.getParentFile().mkdirs();
|
||||
|
||||
// https://stackoverflow.com/a/35690692
|
||||
copy(InstrumentationRegistry.getContext().getAssets().open("OwncloudNewsReaderOrm.db"), dstFile);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed copying Test Database", e);
|
||||
}
|
||||
//return PreferenceManager.getDefaultSharedPreferencesName(mApplication);
|
||||
return filename;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ApiProvider provideAPI(MemorizingTrustManager mtm, SharedPreferences sp) {
|
||||
ApiProvider apiProvider = new TestApiProvider(mtm, sp, application);
|
||||
return apiProvider;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void copy(InputStream in, File dst) throws IOException {
|
||||
try (OutputStream out = new FileOutputStream(dst)) {
|
||||
// Transfer bytes from in to out
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
in.close();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,17 +82,36 @@ public class TestApiProvider extends ApiProvider {
|
|||
InputStream inputStream;
|
||||
switch (request.getUrl()) {
|
||||
case "/index.php/apps/news/api/v1-2/feeds":
|
||||
inputStream = handleCreateFeed(request);
|
||||
if("POST".equals(request.getMethod())) {
|
||||
inputStream = handleCreateFeed(request);
|
||||
} else {
|
||||
inputStream = stringToInputStream("{\"feeds\": []}");
|
||||
}
|
||||
break;
|
||||
case "/index.php/apps/news/api/v1-2/user":
|
||||
inputStream = handleUser();
|
||||
break;
|
||||
case "/index.php/apps/news/api/v1-2/folders":
|
||||
inputStream = handleFolders();
|
||||
break;
|
||||
case "/index.php/apps/news/api/v1-2/items":
|
||||
inputStream = stringToInputStream("{\"items\": []}");
|
||||
break;
|
||||
//case "index.php/apps/news/api/v1-2/feeds":
|
||||
case "/index.php/apps/news/api/v1-2/items/unread/multiple":
|
||||
inputStream = stringToInputStream("");
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, request.getUrl());
|
||||
throw new Error("Not implemented yet!");
|
||||
}
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
private InputStream handleFolders() {
|
||||
String folders = "{\"folders\":[{\"id\":2,\"name\":\"Comic\"},{\"id\":3,\"name\":\"Android\"}]}";
|
||||
return stringToInputStream(folders);
|
||||
}
|
||||
|
||||
|
||||
// https://github.com/nextcloud/news/blob/master/docs/externalapi/Legacy.md#create-a-feed
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package de.luhmer.owncloudnewsreader.helper;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static void initMaterialShowCaseView(Context context) {
|
||||
String PREFS_NAME = "material_showcaseview_prefs";
|
||||
SharedPreferences sp = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit()
|
||||
.putInt("status_SWIPE_LEFT_RIGHT_AND_PTR", -1)
|
||||
.putInt("status_LOGO_SYNC", -1)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static void clearFocus() {
|
||||
sleep(200);
|
||||
onView(withId(R.id.toolbar)).perform(click());
|
||||
sleep(200);
|
||||
}
|
||||
|
||||
public static void sleep(int millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void sleep(float seconds) {
|
||||
try {
|
||||
Thread.sleep((long) seconds * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
package de.luhmer.owncloudnewsreader.tests;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package de.luhmer.owncloudnewsreader.tests;
|
||||
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
|
||||
import com.nextcloud.android.sso.aidl.NextcloudRequest;
|
||||
|
||||
import org.junit.Before;
|
||||
|
@ -12,8 +15,6 @@ import org.mockito.runners.MockitoJUnitRunner;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import de.luhmer.owncloudnewsreader.NewFeedActivity;
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
import de.luhmer.owncloudnewsreader.TestApplication;
|
||||
|
|
|
@ -3,25 +3,39 @@ package de.luhmer.owncloudnewsreader.tests;
|
|||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.espresso.Espresso;
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.espresso.matcher.BoundedMatcher;
|
||||
import androidx.test.espresso.matcher.ViewMatchers;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.nextcloud.android.sso.aidl.NextcloudRequest;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.test.espresso.Espresso;
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.espresso.matcher.ViewMatchers;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import de.luhmer.owncloudnewsreader.Constants;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderDetailFragment;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
|
||||
|
@ -29,22 +43,39 @@ import de.luhmer.owncloudnewsreader.R;
|
|||
import de.luhmer.owncloudnewsreader.TestApplication;
|
||||
import de.luhmer.owncloudnewsreader.adapter.NewsListRecyclerAdapter;
|
||||
import de.luhmer.owncloudnewsreader.adapter.ViewHolder;
|
||||
import de.luhmer.owncloudnewsreader.di.ApiProvider;
|
||||
import de.luhmer.owncloudnewsreader.di.TestApiProvider;
|
||||
import de.luhmer.owncloudnewsreader.di.TestComponent;
|
||||
import helper.OrientationChangeAction;
|
||||
import helper.RecyclerViewAssertions;
|
||||
|
||||
import static androidx.core.util.Preconditions.checkNotNull;
|
||||
import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static androidx.test.InstrumentationRegistry.registerInstance;
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.typeText;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
|
||||
import static androidx.test.espresso.assertion.ViewAssertions.matches;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static de.luhmer.owncloudnewsreader.helper.Utils.clearFocus;
|
||||
import static de.luhmer.owncloudnewsreader.helper.Utils.initMaterialShowCaseView;
|
||||
import static de.luhmer.owncloudnewsreader.helper.Utils.sleep;
|
||||
import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
|
@ -55,7 +86,11 @@ public class NewsReaderListActivityUiTests {
|
|||
@Rule
|
||||
public ActivityTestRule<NewsReaderListActivity> mActivityRule = new ActivityTestRule<>(NewsReaderListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
protected @Inject SharedPreferences mPrefs;
|
||||
protected @Inject ApiProvider mApi;
|
||||
|
||||
private NewsReaderListActivity getActivity() {
|
||||
return mActivityRule.getActivity();
|
||||
|
@ -68,6 +103,10 @@ public class NewsReaderListActivityUiTests {
|
|||
|
||||
TestComponent ac = (TestComponent) ((TestApplication)(getActivity().getApplication())).getAppComponent();
|
||||
ac.inject(this);
|
||||
|
||||
clearFocus();
|
||||
|
||||
initMaterialShowCaseView(getActivity());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -80,13 +119,21 @@ public class NewsReaderListActivityUiTests {
|
|||
onView(isRoot()).perform(OrientationChangeAction.orientationLandscape(getActivity()));
|
||||
//onView(isRoot()).perform(OrientationChangeAction.orientationPortrait(getActivity()));
|
||||
|
||||
sleep(1.0f);
|
||||
sleep(2000);
|
||||
|
||||
LinearLayoutManager llm = (LinearLayoutManager) ndf.getRecyclerView().getLayoutManager();
|
||||
onView(withId(R.id.list)).check(new RecyclerViewAssertions(scrollPosition-(scrollPosition-llm.findFirstVisibleItemPosition())));
|
||||
int expectedPosition = scrollPosition-(scrollPosition-llm.findFirstVisibleItemPosition());
|
||||
|
||||
// As there is a little offset when rotating.. we need to add one here..
|
||||
onView(withId(R.id.list)).check(new RecyclerViewAssertions(expectedPosition+1));
|
||||
onView(withId(R.id.tv_no_items_available)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
|
||||
|
||||
//onView(isRoot()).perform(OrientationChangeAction.orientationLandscape(getActivity()));
|
||||
sleep(2000);
|
||||
|
||||
onView(isRoot()).perform(OrientationChangeAction.orientationPortrait(getActivity()));
|
||||
|
||||
onView(withId(R.id.list)).check(new RecyclerViewAssertions(expectedPosition));
|
||||
onView(withId(R.id.tv_no_items_available)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -95,7 +142,7 @@ public class NewsReaderListActivityUiTests {
|
|||
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.actionOnItemAtPosition(scrollPosition, click()));
|
||||
|
||||
sleep(2);
|
||||
sleep(2000);
|
||||
|
||||
Espresso.pressBack();
|
||||
|
||||
|
@ -110,22 +157,122 @@ public class NewsReaderListActivityUiTests {
|
|||
getActivity().runOnUiThread(() -> na.changeReadStateOfItem(vh, false));
|
||||
sleep(1.0f);
|
||||
|
||||
onView(withId(R.id.list)).check(new RecyclerViewAssertions(scrollPosition-(scrollPosition-llm.findFirstVisibleItemPosition())));
|
||||
int expectedPosition = scrollPosition-(scrollPosition-llm.findFirstVisibleItemPosition());
|
||||
onView(withId(R.id.list)).check(new RecyclerViewAssertions(expectedPosition));
|
||||
onView(withId(R.id.tv_no_items_available)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSyncFinishedRefreshRecycler_sameActivity() {
|
||||
syncResultTest(true);
|
||||
assertTrue(syncResultTest(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyncFinishedSnackbar_sameActivity() {
|
||||
syncResultTest(false);
|
||||
assertTrue(syncResultTest(false));
|
||||
}
|
||||
|
||||
private void syncResultTest(boolean testFirstPosition) {
|
||||
|
||||
@Test
|
||||
public void searchTest() {
|
||||
String firstItem = "Immer wieder sonntags KW 19";
|
||||
// String firstItem = "These are the best screen protectors for the Huawei P30 Pro";
|
||||
|
||||
// Check first item
|
||||
checkRecyclerViewFirstItemText(firstItem);
|
||||
|
||||
// Open search menu
|
||||
onView(allOf(withId(R.id.menu_search), withContentDescription(getString(R.string.action_search)), isDisplayed())).perform(click());
|
||||
|
||||
// Type in "test" into searchbar
|
||||
onView(allOf(withClassName(is("android.widget.SearchView$SearchAutoComplete")), isDisplayed())).perform(typeText("test"));
|
||||
sleep(1000);
|
||||
checkRecyclerViewFirstItemText("VR ohne Kabel: Die Oculus Quest im Test, definitiv der richtige Ansatz");
|
||||
// checkRecyclerViewFirstItemText("Testfahrt im Mercedes E 300 de mit 90-kW-Elektromotor und Vierzylinder-Diesel");
|
||||
|
||||
// Close search bar
|
||||
onView(withContentDescription("Collapse")).perform(click());
|
||||
|
||||
sleep(1000);
|
||||
|
||||
// Test if search reset was successful
|
||||
checkRecyclerViewFirstItemText(firstItem);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void syncTest() {
|
||||
// Open navigation drawer
|
||||
onView(allOf(withContentDescription(getString(R.string.news_list_drawer_text)), isDisplayed())).perform(click());
|
||||
|
||||
sleep(1500);
|
||||
|
||||
/*
|
||||
// Click on Got it
|
||||
onView(allOf(withText("GOT IT"), isDisplayed())).perform(click());
|
||||
|
||||
sleep(1000);
|
||||
*/
|
||||
|
||||
// Trigger refresh
|
||||
onView(allOf(withContentDescription(getString(R.string.content_desc_tap_to_refresh)), isDisplayed())).perform(click());
|
||||
|
||||
sleep(1000);
|
||||
try {
|
||||
verifySyncRequested();
|
||||
} catch (Exception e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the API was actually called
|
||||
private void verifySyncRequested() throws Exception {
|
||||
TestApiProvider.NewsTestNetworkRequest nr = ((TestApiProvider)mApi).networkRequestSpy;
|
||||
ArgumentCaptor<NextcloudRequest> argument = ArgumentCaptor.forClass(NextcloudRequest.class);
|
||||
verify(nr, times(6)).performNetworkRequest(argument.capture(), any());
|
||||
|
||||
List<String> requestedUrls = argument.getAllValues().stream().map(nextcloudRequest -> nextcloudRequest.getUrl()).collect(Collectors.toList());
|
||||
|
||||
assertTrue(requestedUrls.contains("/index.php/apps/news/api/v1-2/folders"));
|
||||
assertTrue(requestedUrls.contains("/index.php/apps/news/api/v1-2/feeds"));
|
||||
assertTrue(requestedUrls.contains("/index.php/apps/news/api/v1-2/items/unread/multiple"));
|
||||
assertTrue(requestedUrls.contains("/index.php/apps/news/api/v1-2/items")); // TODO Double check why /items is called twice... ?
|
||||
assertTrue(requestedUrls.contains("/index.php/apps/news/api/v1-2/user"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void checkRecyclerViewFirstItemText(String text) {
|
||||
onView(withId(R.id.list)).check(matches(atPosition(0, hasDescendant(withText(text)))));
|
||||
}
|
||||
|
||||
private String getString(@IdRes int resId) {
|
||||
return mActivityRule.getActivity().getString(resId);
|
||||
}
|
||||
|
||||
|
||||
public static Matcher<View> atPosition(final int position, @NonNull final Matcher<View> itemMatcher) {
|
||||
checkNotNull(itemMatcher);
|
||||
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("has item at position " + position + ": ");
|
||||
itemMatcher.describeTo(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(final RecyclerView view) {
|
||||
RecyclerView.ViewHolder viewHolder = view.findViewHolderForAdapterPosition(position);
|
||||
if (viewHolder == null) {
|
||||
// has no item on such position
|
||||
return false;
|
||||
}
|
||||
return itemMatcher.matches(viewHolder.itemView);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private boolean syncResultTest(boolean testFirstPosition) {
|
||||
if(!testFirstPosition) {
|
||||
onView(withId(R.id.list)).perform(RecyclerViewActions.scrollToPosition(scrollPosition));
|
||||
}
|
||||
|
@ -163,17 +310,9 @@ public class NewsReaderListActivityUiTests {
|
|||
e.printStackTrace();
|
||||
fail(e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sleep(float seconds) {
|
||||
try {
|
||||
Thread.sleep((long) seconds * 1000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Fragment waitForFragment(int id, int timeout) {
|
||||
long endTime = SystemClock.uptimeMillis() + timeout;
|
||||
while (SystemClock.uptimeMillis() <= endTime) {
|
||||
|
|
|
@ -4,6 +4,14 @@ import android.app.Activity;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
@ -15,12 +23,6 @@ import java.lang.reflect.Method;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
import de.luhmer.owncloudnewsreader.TestApplication;
|
||||
|
@ -55,6 +57,9 @@ public class NightModeTest {
|
|||
public ActivityTestRule<NewsReaderListActivity> mActivityRule = new ActivityTestRule<>(NewsReaderListActivity.class);
|
||||
//public ActivityTestRule<NewsReaderListActivity> mActivityRule = new ActivityTestRule<>(NewsReaderListActivity.class, true, false);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
|
||||
private Activity getActivity() {
|
||||
return mActivityRule.getActivity();
|
||||
}
|
||||
|
|
|
@ -13,14 +13,14 @@ import android.view.View;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.test.espresso.matcher.BoundedMatcher;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.test.espresso.matcher.BoundedMatcher;
|
||||
|
||||
public class CustomMatchers {
|
||||
private static final String TAG = CustomMatchers.class.getCanonicalName();
|
||||
|
||||
|
|
|
@ -29,15 +29,15 @@ import android.app.Activity;
|
|||
import android.content.pm.ActivityInfo;
|
||||
import android.view.View;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
|
||||
import androidx.test.runner.lifecycle.Stage;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
package screengrab;
|
||||
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.rule.GrantPermissionRule;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.test.rule.ActivityTestRule;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderDetailFragment;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListFragment;
|
||||
|
@ -17,12 +20,17 @@ import de.luhmer.owncloudnewsreader.adapter.ViewHolder;
|
|||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.model.PodcastItem;
|
||||
import tools.fastlane.screengrab.Screengrab;
|
||||
import tools.fastlane.screengrab.UiAutomatorScreenshotStrategy;
|
||||
import tools.fastlane.screengrab.locale.LocaleTestRule;
|
||||
|
||||
import static de.luhmer.owncloudnewsreader.helper.Utils.clearFocus;
|
||||
import static de.luhmer.owncloudnewsreader.helper.Utils.initMaterialShowCaseView;
|
||||
|
||||
/**
|
||||
* Created by David on 06.03.2016.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@RunWith(JUnit4.class)
|
||||
@LargeTest
|
||||
public class ScreenshotTest {
|
||||
|
||||
@ClassRule
|
||||
|
@ -31,19 +39,28 @@ public class ScreenshotTest {
|
|||
@Rule
|
||||
public ActivityTestRule<NewsReaderListActivity> mActivityRule = new ActivityTestRule<>(NewsReaderListActivity.class);
|
||||
|
||||
@Rule
|
||||
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
|
||||
private NewsReaderListActivity activity;
|
||||
|
||||
private NewsReaderListActivity mActivity;
|
||||
private NewsReaderListFragment nrlf;
|
||||
private NewsReaderDetailFragment nrdf;
|
||||
private int itemPos = 0;
|
||||
|
||||
private int podcastGroupPosition = 3;
|
||||
//private int podcastGroupPosition = 3;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
activity = mActivityRule.getActivity();
|
||||
nrlf = mActivityRule.getActivity().getSlidingListFragment();
|
||||
nrdf = mActivityRule.getActivity().getNewsReaderDetailFragment();
|
||||
Screengrab.setDefaultScreenshotStrategy(new UiAutomatorScreenshotStrategy());
|
||||
|
||||
mActivity = mActivityRule.getActivity();
|
||||
nrlf = mActivity.getSlidingListFragment();
|
||||
nrdf = mActivity.getNewsReaderDetailFragment();
|
||||
|
||||
clearFocus();
|
||||
|
||||
initMaterialShowCaseView(mActivity);
|
||||
}
|
||||
|
||||
|
||||
|
@ -52,9 +69,9 @@ public class ScreenshotTest {
|
|||
public void testTakeScreenshots() {
|
||||
Screengrab.screenshot("startup");
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
openDrawer();
|
||||
nrlf.getListView().expandGroup(podcastGroupPosition);
|
||||
//nrlf.getListView().expandGroup(podcastGroupPosition);
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -66,7 +83,7 @@ public class ScreenshotTest {
|
|||
Screengrab.screenshot("slider_open");
|
||||
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
closeDrawer();
|
||||
|
||||
try {
|
||||
|
@ -75,7 +92,7 @@ public class ScreenshotTest {
|
|||
e.printStackTrace();
|
||||
}
|
||||
|
||||
activity.onClick(null, itemPos); //Select item
|
||||
mActivity.onClick(null, itemPos); //Select item
|
||||
|
||||
});
|
||||
|
||||
|
@ -87,7 +104,7 @@ public class ScreenshotTest {
|
|||
|
||||
Screengrab.screenshot("detail_activity");
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
NewsListRecyclerAdapter na = (NewsListRecyclerAdapter) nrdf.getRecyclerView().getAdapter();
|
||||
ViewHolder vh = (ViewHolder) nrdf.getRecyclerView().getChildViewHolder(nrdf.getRecyclerView().getLayoutManager().findViewByPosition(itemPos));
|
||||
na.changeReadStateOfItem(vh, false);
|
||||
|
@ -98,11 +115,12 @@ public class ScreenshotTest {
|
|||
|
||||
|
||||
@Test
|
||||
public void testPodcast() {
|
||||
activity.runOnUiThread(() -> {
|
||||
public void testAudioPodcast() {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
openDrawer();
|
||||
nrlf.getListView().expandGroup(podcastGroupPosition);
|
||||
openFeed(podcastGroupPosition, 0);
|
||||
//nrlf.getListView().expandGroup(podcastGroupPosition);
|
||||
//openFeed(podcastGroupPosition, 0);
|
||||
openFeed(2, 1); // Open Android Podcast
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -111,17 +129,17 @@ public class ScreenshotTest {
|
|||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Screengrab.screenshot("podcast_list");
|
||||
//Screengrab.screenshot("podcast_list");
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
ViewHolder vh = (ViewHolder) nrdf.getRecyclerView().getChildViewHolder(nrdf.getRecyclerView().getLayoutManager().findViewByPosition(0));
|
||||
PodcastItem podcastItem = DatabaseConnectionOrm.ParsePodcastItemFromRssItem(activity, vh.getRssItem());
|
||||
activity.openMediaItem(podcastItem);
|
||||
PodcastItem podcastItem = DatabaseConnectionOrm.ParsePodcastItemFromRssItem(mActivity, vh.getRssItem());
|
||||
mActivity.openMediaItem(podcastItem);
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -129,7 +147,7 @@ public class ScreenshotTest {
|
|||
Screengrab.screenshot("podcast_running");
|
||||
|
||||
|
||||
activity.runOnUiThread(() -> activity.pausePodcast());
|
||||
mActivity.runOnUiThread(() -> mActivity.pausePodcast());
|
||||
|
||||
|
||||
try {
|
||||
|
@ -143,12 +161,13 @@ public class ScreenshotTest {
|
|||
|
||||
@Test
|
||||
public void testVideoPodcast() {
|
||||
activity.runOnUiThread(() -> {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
//Set url to mock
|
||||
nrlf.bindUserInfoToUI();
|
||||
|
||||
openDrawer();
|
||||
openFeed(0, 13); //Click on ARD Podcast
|
||||
//openFeed(0, 13); //Click on ARD Podcast
|
||||
openFeed(7, -1);
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -157,23 +176,23 @@ public class ScreenshotTest {
|
|||
e.printStackTrace();
|
||||
}
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
mActivity.runOnUiThread(() -> {
|
||||
ViewHolder vh = (ViewHolder) nrdf.getRecyclerView().getChildViewHolder(nrdf.getRecyclerView().getLayoutManager().findViewByPosition(1));
|
||||
PodcastItem podcastItem = DatabaseConnectionOrm.ParsePodcastItemFromRssItem(activity, vh.getRssItem());
|
||||
activity.openMediaItem(podcastItem);
|
||||
PodcastItem podcastItem = DatabaseConnectionOrm.ParsePodcastItemFromRssItem(mActivity, vh.getRssItem());
|
||||
mActivity.openMediaItem(podcastItem);
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
Thread.sleep(5000);
|
||||
Thread.sleep(15000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
Screengrab.screenshot("video_podcast_running");
|
||||
|
||||
|
||||
activity.runOnUiThread(() -> activity.pausePodcast());
|
||||
mActivity.runOnUiThread(() -> mActivity.pausePodcast());
|
||||
|
||||
|
||||
try {
|
||||
|
@ -188,14 +207,14 @@ public class ScreenshotTest {
|
|||
}
|
||||
|
||||
private void openDrawer() {
|
||||
if(activity.drawerLayout != null) {
|
||||
activity.drawerLayout.openDrawer(GravityCompat.START, true);
|
||||
if(mActivity.drawerLayout != null) {
|
||||
mActivity.drawerLayout.openDrawer(GravityCompat.START, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeDrawer() {
|
||||
if(activity.drawerLayout != null) {
|
||||
activity.drawerLayout.closeDrawer(GravityCompat.START, true);
|
||||
if(mActivity.drawerLayout != null) {
|
||||
mActivity.drawerLayout.closeDrawer(GravityCompat.START, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package de.luhmer.owncloudnewsreader;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.FragmentTransaction;
|
||||
|
||||
import com.google.android.youtube.player.YouTubeInitializationResult;
|
||||
import com.google.android.youtube.player.YouTubePlayer;
|
||||
import com.google.android.youtube.player.YouTubePlayerFragment;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.RegisterYoutubeOutput;
|
||||
|
||||
public class YoutubePlayerManager {
|
||||
|
||||
public static void StartYoutubePlayer(final Activity activity, int YOUTUBE_CONTENT_VIEW_ID, final EventBus eventBus, final Runnable onInitSuccess) {
|
||||
YouTubePlayerFragment youTubePlayerFragment = YouTubePlayerFragment.newInstance();
|
||||
FragmentTransaction ft = activity.getFragmentManager().beginTransaction();
|
||||
ft.add(YOUTUBE_CONTENT_VIEW_ID, youTubePlayerFragment).commit();
|
||||
youTubePlayerFragment.initialize("AIzaSyA2OHKWvF_hRVtPmLcwnO8yF6-iah2hjbk", new YouTubePlayer.OnInitializedListener() {
|
||||
@Override
|
||||
public void onInitializationSuccess(YouTubePlayer.Provider provider, YouTubePlayer youTubePlayer, boolean wasRestored) {
|
||||
eventBus.post(new RegisterYoutubeOutput(youTubePlayer, wasRestored));
|
||||
onInitSuccess.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializationFailure(YouTubePlayer.Provider provider, YouTubeInitializationResult youTubeInitializationResult) {
|
||||
youTubeInitializationResult.getErrorDialog(activity, 0).show();
|
||||
//Toast.makeText(activity, "Error while playing youtube video! (InitializationFailure)", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
package de.luhmer.owncloudnewsreader.services.podcast;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.youtube.player.YouTubePlayer;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.model.MediaItem;
|
||||
|
||||
/**
|
||||
* Created by david on 31.01.17.
|
||||
*/
|
||||
|
||||
public class YoutubePlaybackService extends PlaybackService {
|
||||
|
||||
private static final String TAG = YoutubePlaybackService.class.getCanonicalName();
|
||||
YouTubePlayer youTubePlayer;
|
||||
Context context;
|
||||
|
||||
public YoutubePlaybackService(Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
|
||||
super(podcastStatusListener, mediaItem);
|
||||
this.context = context;
|
||||
setStatus(Status.PREPARING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if(youTubePlayer != null) {
|
||||
youTubePlayer.pause();
|
||||
youTubePlayer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play() {
|
||||
if(youTubePlayer != null) {
|
||||
youTubePlayer.play();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
if(youTubePlayer != null) {
|
||||
youTubePlayer.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float currentPlaybackSpeed) {
|
||||
|
||||
}
|
||||
|
||||
public void seekTo(double percent) {
|
||||
if(youTubePlayer != null) {
|
||||
double totalDuration = getTotalDuration();
|
||||
int position = (int) ((totalDuration / 100d) * percent);
|
||||
youTubePlayer.seekToMillis(position);
|
||||
}
|
||||
}
|
||||
public int getCurrentDuration() {
|
||||
if(youTubePlayer != null) {
|
||||
return youTubePlayer.getCurrentTimeMillis();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getTotalDuration() {
|
||||
if(youTubePlayer != null) {
|
||||
return youTubePlayer.getDurationMillis();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoType getVideoType() {
|
||||
return VideoType.YouTube;
|
||||
}
|
||||
|
||||
public void setYoutubePlayer(Object youTubePlayer, boolean wasRestored) {
|
||||
this.youTubePlayer = (YouTubePlayer) youTubePlayer;
|
||||
this.youTubePlayer.setPlaybackEventListener(youtubePlaybackEventListener);
|
||||
this.youTubePlayer.setPlayerStateChangeListener(youtubePlayerStateChangeListener);
|
||||
|
||||
this.youTubePlayer.setPlayerStyle(YouTubePlayer.PlayerStyle.MINIMAL);
|
||||
|
||||
// Start buffering
|
||||
if (!wasRestored) {
|
||||
Pattern youtubeIdPattern = Pattern.compile(".*?v=([^&]*)");
|
||||
Matcher matcher = youtubeIdPattern.matcher(getMediaItem().link);
|
||||
if(matcher.matches()) {
|
||||
String youtubeId = matcher.group(1);
|
||||
this.youTubePlayer.cueVideo(youtubeId);
|
||||
} else {
|
||||
Toast.makeText(context, "Cannot find youtube video id", Toast.LENGTH_LONG).show();
|
||||
setStatus(Status.FAILED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
YouTubePlayer.PlayerStateChangeListener youtubePlayerStateChangeListener = new YouTubePlayer.PlayerStateChangeListener() {
|
||||
@Override
|
||||
public void onLoading() {
|
||||
Log.d(TAG, "onLoading() called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaded(String s) {
|
||||
Log.d(TAG, "onLoaded() called with: s = [" + s + "]");
|
||||
youTubePlayer.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdStarted() {
|
||||
Log.d(TAG, "onAdStarted() called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoStarted() {
|
||||
Log.d(TAG, "onVideoStarted() called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoEnded() {
|
||||
Log.d(TAG, "onVideoEnded() called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(YouTubePlayer.ErrorReason errorReason) {
|
||||
Log.d(TAG, "onError() called with: errorReason = [" + errorReason + "]");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
YouTubePlayer.PlaybackEventListener youtubePlaybackEventListener = new YouTubePlayer.PlaybackEventListener() {
|
||||
@Override
|
||||
public void onPlaying() {
|
||||
Log.d(TAG, "onPlaying() called");
|
||||
setStatus(Status.PLAYING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPaused() {
|
||||
Log.d(TAG, "onPaused() called");
|
||||
setStatus(Status.PAUSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopped() {
|
||||
Log.d(TAG, "onStopped() called");
|
||||
setStatus(Status.PAUSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuffering(boolean b) {
|
||||
Log.d(TAG, "onBuffering() called with: b = [" + b + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekTo(int i) {
|
||||
Log.d(TAG, "onSeekTo() called with: i = [" + i + "]");
|
||||
}
|
||||
};
|
||||
}
|
|
@ -3,8 +3,8 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="de.luhmer.owncloudnewsreader"
|
||||
android:installLocation="internalOnly"
|
||||
android:versionCode="141"
|
||||
android:versionName="0.9.9.25">
|
||||
android:versionCode="142"
|
||||
android:versionName="0.9.9.26">
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
@ -14,12 +14,14 @@
|
|||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
|
||||
<!-- <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<!-- <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> -->
|
||||
<!-- Required for TwilightManager -->
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:name=".NewsReaderApplication"
|
||||
android:allowBackup="true"
|
||||
|
@ -30,21 +32,42 @@
|
|||
android:theme="@style/SplashTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:replace="android:icon, android:label, android:theme, android:name">
|
||||
|
||||
<meta-data android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc"/>
|
||||
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
android:resource="@drawable/ic_notification" />
|
||||
|
||||
<activity
|
||||
android:name=".NewsReaderListActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTop">
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Use this intent filter to get voice searches -->
|
||||
<intent-filter>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity> <!-- android:configChanges="keyboardHidden|orientation|screenSize" -->
|
||||
|
||||
<activity
|
||||
android:name=".NewsDetailActivity"
|
||||
android:label="@string/title_activity_news_detail" />
|
||||
<!-- android:configChanges="keyboardHidden|orientation|screenSize" -->
|
||||
|
||||
<activity android:name=".PiPVideoPlaybackActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="de.luhmer.owncloudnewsreader.pip"
|
||||
android:autoRemoveFromRecents="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
tools:targetApi="n"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
|
|
|
@ -45,9 +45,13 @@ import android.widget.ImageView;
|
|||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.nextcloud.android.sso.AccountImporter;
|
||||
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||
import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted;
|
||||
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
|
||||
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
|
||||
import com.nextcloud.android.sso.helper.SingleAccountHelper;
|
||||
|
@ -60,8 +64,6 @@ import java.net.URL;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
@ -190,6 +192,8 @@ public class LoginDialogActivity extends AppCompatActivity {
|
|||
AccountImporter.pickNewAccount(LoginDialogActivity.this);
|
||||
} catch (NextcloudFilesAppNotInstalledException e) {
|
||||
UiExceptionManager.showDialogForException(LoginDialogActivity.this, e);
|
||||
} catch (AndroidGetAccountsPermissionNotGranted e) {
|
||||
AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,6 +256,8 @@ public class LoginDialogActivity extends AppCompatActivity {
|
|||
editor.putBoolean(SettingsActivity.SW_USE_SINGLE_SIGN_ON, true);
|
||||
editor.commit();
|
||||
|
||||
resetDatabase();
|
||||
|
||||
SingleAccountHelper.setCurrentAccount(this, importedAccount.name);
|
||||
|
||||
mApi.initApi(new NextcloudAPI.ApiConnectedListener() {
|
||||
|
@ -337,6 +343,8 @@ public class LoginDialogActivity extends AppCompatActivity {
|
|||
editor.putBoolean(SettingsActivity.SW_USE_SINGLE_SIGN_ON, false);
|
||||
editor.commit();
|
||||
|
||||
resetDatabase();
|
||||
|
||||
final ProgressDialog dialogLogin = buildPendingDialogWhileLoggingIn();
|
||||
dialogLogin.show();
|
||||
|
||||
|
@ -357,7 +365,13 @@ public class LoginDialogActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private void finishLogin(final ProgressDialog dialogLogin) {
|
||||
private void resetDatabase() {
|
||||
//Reset Database
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(LoginDialogActivity.this);
|
||||
dbConn.resetDatabase();
|
||||
}
|
||||
|
||||
private void finishLogin(final ProgressDialog dialogLogin) {
|
||||
mApi.getAPI().version()
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -409,10 +423,6 @@ public class LoginDialogActivity extends AppCompatActivity {
|
|||
Log.v(TAG, "onComplete() called");
|
||||
|
||||
if(loginSuccessful) {
|
||||
//Reset Database
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(LoginDialogActivity.this);
|
||||
dbConn.resetDatabase();
|
||||
|
||||
Intent returnIntent = new Intent();
|
||||
setResult(RESULT_OK, returnIntent);
|
||||
|
||||
|
@ -443,12 +453,16 @@ public class LoginDialogActivity extends AppCompatActivity {
|
|||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
AccountImporter.onActivityResult(requestCode, resultCode, data, LoginDialogActivity.this, new AccountImporter.IAccountAccessGranted() {
|
||||
@Override
|
||||
public void accountAccessGranted(SingleSignOnAccount account) {
|
||||
LoginDialogActivity.this.importedAccount = account;
|
||||
loginSingleSignOn();
|
||||
}
|
||||
AccountImporter.onActivityResult(requestCode, resultCode, data, LoginDialogActivity.this, account -> {
|
||||
LoginDialogActivity.this.importedAccount = account;
|
||||
loginSingleSignOn();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
AccountImporter.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,11 +27,6 @@ import android.content.pm.PackageManager;
|
|||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.text.Html;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
@ -41,6 +36,16 @@ import android.view.MenuItem;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -48,12 +53,6 @@ import java.util.Set;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
|
@ -320,7 +319,7 @@ public class NewsDetailActivity extends PodcastFragmentActivity {
|
|||
menuItem_Read.setIcon(R.drawable.ic_check_box_outline_blank_white);
|
||||
menuItem_Read.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
@ -377,17 +376,12 @@ public class NewsDetailActivity extends PodcastFragmentActivity {
|
|||
mPostDelayHandler.delayTimer();
|
||||
break;
|
||||
|
||||
case R.id.action_starred:
|
||||
Boolean curState = rssItem.getStarred_temp();
|
||||
rssItem.setStarred_temp(!curState);
|
||||
dbConn.updateRssItem(rssItem);
|
||||
|
||||
updateActionBarIcons();
|
||||
case R.id.action_starred:
|
||||
toggleRssItemStarredState();
|
||||
break;
|
||||
|
||||
mPostDelayHandler.delayTimer();
|
||||
break;
|
||||
|
||||
case R.id.action_openInBrowser:
|
||||
case R.id.action_openInBrowser:
|
||||
NewsDetailFragment newsDetailFragment = getNewsDetailFragmentAtPosition(currentPosition);
|
||||
String link = "about:blank";
|
||||
|
||||
|
@ -481,6 +475,17 @@ public class NewsDetailActivity extends PodcastFragmentActivity {
|
|||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public void toggleRssItemStarredState() {
|
||||
RssItem rssItem = rssItems.get(currentPosition);
|
||||
Boolean curState = rssItem.getStarred_temp();
|
||||
rssItem.setStarred_temp(!curState);
|
||||
dbConn.updateRssItem(rssItem);
|
||||
|
||||
updateActionBarIcons();
|
||||
|
||||
mPostDelayHandler.delayTimer();
|
||||
}
|
||||
|
||||
private boolean isChromeDefaultBrowser() {
|
||||
Intent browserIntent = new Intent("android.intent.action.VIEW", Uri.parse("http://"));
|
||||
ResolveInfo resolveInfo = getPackageManager().resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
|
|
|
@ -21,15 +21,16 @@
|
|||
|
||||
package de.luhmer.owncloudnewsreader;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
@ -43,6 +44,13 @@ import android.webkit.WebViewClient;
|
|||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.select.Elements;
|
||||
|
@ -55,12 +63,6 @@ import java.util.Map;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import de.luhmer.owncloudnewsreader.adapter.ProgressBarWebChromeClient;
|
||||
|
@ -87,7 +89,7 @@ public class NewsDetailFragment extends Fragment implements RssItemToHtmlTask.Li
|
|||
|
||||
private int section_number;
|
||||
protected String html;
|
||||
boolean changedUrl = false;
|
||||
private GestureDetector mGestureDetector;
|
||||
|
||||
|
||||
public NewsDetailFragment() { }
|
||||
|
@ -180,6 +182,8 @@ public class NewsDetailFragment extends Fragment implements RssItemToHtmlTask.Li
|
|||
mProgressBarLoading.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setUpGestureDetector();
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
|
@ -188,6 +192,38 @@ public class NewsDetailFragment extends Fragment implements RssItemToHtmlTask.Li
|
|||
mWebView.saveState(outState);
|
||||
}
|
||||
|
||||
private void setUpGestureDetector() {
|
||||
mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener());
|
||||
|
||||
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener()
|
||||
{
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
Log.v(TAG, "onDoubleTap() called with: e = [" + e + "]");
|
||||
NewsDetailActivity ndActivity = ((NewsDetailActivity)getActivity());
|
||||
if(ndActivity != null) {
|
||||
((NewsDetailActivity) getActivity()).toggleRssItemStarredState();
|
||||
|
||||
// Star has 5 corners. So we can rotate it by 2/5
|
||||
View view = getActivity().findViewById(R.id.action_starred);
|
||||
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotation", view.getRotation() + (2*(360f/5f)));
|
||||
animator.start();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTapEvent(MotionEvent e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startLoadRssItemToWebViewTask() {
|
||||
Log.d(TAG, "startLoadRssItemToWebViewTask() called");
|
||||
mWebView.setVisibility(View.GONE);
|
||||
|
@ -319,16 +355,16 @@ public class NewsDetailFragment extends Fragment implements RssItemToHtmlTask.Li
|
|||
|
||||
});
|
||||
|
||||
mWebView.setOnTouchListener(new View.OnTouchListener() {
|
||||
mWebView.setOnTouchListener((v, event) -> {
|
||||
mGestureDetector.onTouchEvent(event);
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (v.getId() == R.id.webview && event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
changedUrl = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
/*
|
||||
if (v.getId() == R.id.webview && event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
changedUrl = true;
|
||||
}
|
||||
*/
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -31,13 +31,6 @@ import android.os.AsyncTask;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
|
@ -48,13 +41,19 @@ import android.view.ViewGroup;
|
|||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import de.luhmer.owncloudnewsreader.adapter.NewsListRecyclerAdapter;
|
||||
|
@ -64,6 +63,7 @@ import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm.SORT_DIRECTIO
|
|||
import de.luhmer.owncloudnewsreader.database.model.RssItem;
|
||||
import de.luhmer.owncloudnewsreader.database.model.RssItemDao;
|
||||
import de.luhmer.owncloudnewsreader.helper.AsyncTaskHelper;
|
||||
import de.luhmer.owncloudnewsreader.helper.PostDelayHandler;
|
||||
import de.luhmer.owncloudnewsreader.helper.Search;
|
||||
import de.luhmer.owncloudnewsreader.helper.StopWatch;
|
||||
import io.reactivex.observers.DisposableObserver;
|
||||
|
@ -107,8 +107,30 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
private RecyclerView.OnItemTouchListener itemTouchListener;
|
||||
|
||||
protected @Inject SharedPreferences mPrefs;
|
||||
protected @Inject PostDelayHandler mPostDelayHandler;
|
||||
|
||||
protected DisposableObserver<List<RssItem>> SearchResultObserver = new DisposableObserver<List<RssItem>>() {
|
||||
private PodcastFragmentActivity mActivity;
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public NewsReaderDetailFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
this.mActivity = (PodcastFragmentActivity) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
this.mActivity = null;
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
protected DisposableObserver<List<RssItem>> searchResultObserver = new DisposableObserver<List<RssItem>>() {
|
||||
@Override
|
||||
public void onNext(List<RssItem> rssItems) {
|
||||
loadRssItemsIntoView(rssItems);
|
||||
|
@ -117,7 +139,7 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
@Override
|
||||
public void onError(Throwable e) {
|
||||
pbLoading.setVisibility(View.GONE);
|
||||
Toast.makeText(getActivity(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(mActivity, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -126,12 +148,6 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public NewsReaderDetailFragment() {
|
||||
}
|
||||
|
||||
public static SORT_DIRECTION getSortDirection(SharedPreferences prefs) {
|
||||
return NewsDetailActivity.getSortDirectionFromSettings(prefs);
|
||||
|
@ -158,16 +174,16 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
return titel;
|
||||
}
|
||||
|
||||
public void setData(Long idFeed, Long idFolder, String titel, boolean updateListView) {
|
||||
protected void setData(Long idFeed, Long idFolder, String title, boolean updateListView) {
|
||||
Log.v(TAG, "Creating new itstance");
|
||||
|
||||
this.idFeed = idFeed;
|
||||
this.idFolder = idFolder;
|
||||
this.titel = titel;
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle(titel);
|
||||
this.titel = title;
|
||||
mActivity.getSupportActionBar().setTitle(title);
|
||||
|
||||
if (updateListView) {
|
||||
updateCurrentRssView(getActivity());
|
||||
updateCurrentRssView();
|
||||
} else {
|
||||
refreshCurrentRssView();
|
||||
}
|
||||
|
@ -188,9 +204,9 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
super.onResume();
|
||||
}
|
||||
|
||||
public void updateMenuItemsState() {
|
||||
NewsReaderListActivity nla = (NewsReaderListActivity) getActivity();
|
||||
if (nla.getMenuItemDownloadMoreItems() != null) {
|
||||
protected void updateMenuItemsState() {
|
||||
NewsReaderListActivity nla = (NewsReaderListActivity) mActivity;
|
||||
if(nla != null && nla.getMenuItemDownloadMoreItems() != null) {
|
||||
if (idFolder != null && idFolder == ALL_UNREAD_ITEMS.getValue()) {
|
||||
nla.getMenuItemDownloadMoreItems().setEnabled(false);
|
||||
} else {
|
||||
|
@ -199,7 +215,7 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
public void notifyDataSetChangedOnAdapter() {
|
||||
protected void notifyDataSetChangedOnAdapter() {
|
||||
NewsListRecyclerAdapter nca = (NewsListRecyclerAdapter) recyclerView.getAdapter();
|
||||
if (nca != null) {
|
||||
nca.notifyDataSetChanged();
|
||||
|
@ -209,20 +225,17 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
/**
|
||||
* Refreshes the current RSS-View
|
||||
*/
|
||||
public void refreshCurrentRssView() {
|
||||
protected void refreshCurrentRssView() {
|
||||
Log.v(TAG, "refreshCurrentRssView");
|
||||
NewsListRecyclerAdapter nra = ((NewsListRecyclerAdapter) recyclerView.getAdapter());
|
||||
|
||||
if (nra != null) {
|
||||
nra.refreshAdapterDataAsync(new NewsListRecyclerAdapter.IOnRefreshFinished() {
|
||||
@Override
|
||||
public void OnRefreshFinished() {
|
||||
pbLoading.setVisibility(View.GONE);
|
||||
nra.refreshAdapterDataAsync(() -> {
|
||||
pbLoading.setVisibility(View.GONE);
|
||||
|
||||
if (layoutManagerSavedState != null) {
|
||||
recyclerView.getLayoutManager().onRestoreInstanceState(layoutManagerSavedState);
|
||||
layoutManagerSavedState = null;
|
||||
}
|
||||
if (layoutManagerSavedState != null) {
|
||||
recyclerView.getLayoutManager().onRestoreInstanceState(layoutManagerSavedState);
|
||||
layoutManagerSavedState = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -230,11 +243,10 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
|
||||
/**
|
||||
* Updates the current RSS-View
|
||||
* @param context
|
||||
*/
|
||||
public void updateCurrentRssView(Context context) {
|
||||
public void updateCurrentRssView() {
|
||||
Log.v(TAG, "updateCurrentRssView");
|
||||
AsyncTaskHelper.StartAsyncTask(new UpdateCurrentRssViewTask(context));
|
||||
AsyncTaskHelper.StartAsyncTask(new UpdateCurrentRssViewTask());
|
||||
}
|
||||
|
||||
public RecyclerView getRecyclerView() {
|
||||
|
@ -247,26 +259,22 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
}
|
||||
|
||||
protected List<RssItem> performSearch(String searchString) {
|
||||
Handler mainHandler = new Handler(getActivity().getMainLooper());
|
||||
Handler mainHandler = new Handler(mActivity.getMainLooper());
|
||||
|
||||
Runnable myRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pbLoading.setVisibility(View.VISIBLE);
|
||||
tvNoItemsAvailable.setVisibility(View.GONE);
|
||||
}
|
||||
Runnable myRunnable = () -> {
|
||||
pbLoading.setVisibility(View.VISIBLE);
|
||||
tvNoItemsAvailable.setVisibility(View.GONE);
|
||||
};
|
||||
mainHandler.post(myRunnable);
|
||||
|
||||
return Search.PerformSearch(getActivity(), idFolder, idFeed, searchString, mPrefs);
|
||||
return Search.PerformSearch(mActivity, idFolder, idFeed, searchString, mPrefs);
|
||||
}
|
||||
|
||||
void loadRssItemsIntoView(List<RssItem> rssItems) {
|
||||
try {
|
||||
NewsListRecyclerAdapter nra = ((NewsListRecyclerAdapter) recyclerView.getAdapter());
|
||||
if (nra == null) {
|
||||
nra = new NewsListRecyclerAdapter(getActivity(), recyclerView, (PodcastFragmentActivity) getActivity(), ((PodcastFragmentActivity) getActivity()).mPostDelayHandler, mPrefs);
|
||||
|
||||
nra = new NewsListRecyclerAdapter(mActivity, recyclerView, mActivity, mPostDelayHandler, mPrefs);
|
||||
recyclerView.setAdapter(nra);
|
||||
}
|
||||
nra.updateAdapterData(rssItems);
|
||||
|
@ -293,25 +301,25 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
ButterKnife.bind(this, rootView);
|
||||
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), RecyclerView.VERTICAL, false));
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(mActivity, RecyclerView.VERTICAL, false));
|
||||
recyclerView.setItemAnimator(new DefaultItemAnimator());
|
||||
|
||||
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new NewsReaderItemTouchHelperCallback());
|
||||
itemTouchHelper.attachToRecyclerView(recyclerView);
|
||||
//recyclerView.addItemDecoration(new DividerItemDecoration(getActivity())); // Enable divider line
|
||||
//recyclerView.addItemDecoration(new DividerItemDecoration(mActivity)); // Enable divider line
|
||||
|
||||
/*
|
||||
recyclerView.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
((NewsReaderListActivity) getActivity()).clearSearchViewFocus();
|
||||
((NewsReaderListActivity) mActivity).clearSearchViewFocus();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
swipeRefresh.setColorSchemeColors(accentColor);
|
||||
swipeRefresh.setOnRefreshListener((SwipeRefreshLayout.OnRefreshListener) getActivity());
|
||||
swipeRefresh.setOnRefreshListener((SwipeRefreshLayout.OnRefreshListener) mActivity);
|
||||
|
||||
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
|
@ -326,7 +334,7 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
});
|
||||
|
||||
itemTouchListener = new RecyclerView.OnItemTouchListener() {
|
||||
GestureDetectorCompat detector = new GestureDetectorCompat(getActivity(), new RecyclerViewOnGestureListener());
|
||||
GestureDetectorCompat detector = new GestureDetectorCompat(mActivity, new RecyclerViewOnGestureListener());
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
|
||||
|
@ -433,11 +441,6 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
}
|
||||
|
||||
private class UpdateCurrentRssViewTask extends AsyncTask<Void, Void, List<RssItem>> {
|
||||
private Context context;
|
||||
|
||||
UpdateCurrentRssViewTask(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
|
@ -448,7 +451,7 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
|
||||
@Override
|
||||
protected List<RssItem> doInBackground(Void... voids) {
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(context);
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(NewsReaderDetailFragment.this.getContext());
|
||||
SORT_DIRECTION sortDirection = getSortDirection(mPrefs);
|
||||
boolean onlyUnreadItems = mPrefs.getBoolean(SettingsActivity.CB_SHOWONLYUNREAD_STRING, false);
|
||||
boolean onlyStarredItems = false;
|
||||
|
@ -508,7 +511,7 @@ public class NewsReaderDetailFragment extends Fragment {
|
|||
minLeftEdgeDistance = 0;
|
||||
} else {
|
||||
// otherwise, have left-edge offset to avoid mark-read gesture when user is pulling to open drawer
|
||||
minLeftEdgeDistance = ((NewsReaderListActivity) getActivity()).getEdgeSizeOfDrawer();
|
||||
minLeftEdgeDistance = ((NewsReaderListActivity) mActivity).getEdgeSizeOfDrawer();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,9 +40,25 @@ import android.view.Menu;
|
|||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.customview.widget.ViewDragHelper;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.nextcloud.android.sso.AccountImporter;
|
||||
import com.nextcloud.android.sso.api.NextcloudAPI;
|
||||
|
@ -68,22 +84,6 @@ import java.util.concurrent.TimeUnit;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import androidx.customview.widget.ViewDragHelper;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter;
|
||||
|
@ -97,6 +97,7 @@ import de.luhmer.owncloudnewsreader.database.model.RssItem;
|
|||
import de.luhmer.owncloudnewsreader.events.podcast.FeedPanelSlideEvent;
|
||||
import de.luhmer.owncloudnewsreader.helper.DatabaseUtils;
|
||||
import de.luhmer.owncloudnewsreader.helper.ThemeChooser;
|
||||
import de.luhmer.owncloudnewsreader.helper.ThemeUtils;
|
||||
import de.luhmer.owncloudnewsreader.reader.nextcloud.RssItemObservable;
|
||||
import de.luhmer.owncloudnewsreader.services.DownloadImagesService;
|
||||
import de.luhmer.owncloudnewsreader.services.DownloadWebPageService;
|
||||
|
@ -109,12 +110,12 @@ import io.reactivex.Completable;
|
|||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.functions.Action;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Function;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence;
|
||||
import uk.co.deanwild.materialshowcaseview.ShowcaseConfig;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static de.luhmer.owncloudnewsreader.LoginDialogActivity.RESULT_LOGIN;
|
||||
import static de.luhmer.owncloudnewsreader.LoginDialogActivity.ShowAlertDialog;
|
||||
|
||||
|
@ -151,7 +152,9 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
@VisibleForTesting @Nullable @BindView(R.id.drawer_layout) public DrawerLayout drawerLayout;
|
||||
|
||||
private ActionBarDrawerToggle drawerToggle;
|
||||
private SearchView searchView;
|
||||
private SearchView mSearchView;
|
||||
private String mSearchString;
|
||||
private static final String SEARCH_KEY = "SEARCH_KEY";
|
||||
|
||||
private PublishSubject<String> searchPublishSubject;
|
||||
private static final int REQUEST_CODE_PERMISSION_DOWNLOAD_WEB_ARCHIVE = 1;
|
||||
|
@ -207,11 +210,10 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
.commit();
|
||||
|
||||
if (drawerLayout != null) {
|
||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.empty_view_content, R.string.empty_view_content) {
|
||||
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.news_list_drawer_text, R.string.news_list_drawer_text) {
|
||||
@Override
|
||||
public void onDrawerClosed(View drawerView) {
|
||||
super.onDrawerClosed(drawerView);
|
||||
togglePodcastVideoViewAnimation();
|
||||
|
||||
syncState();
|
||||
EventBus.getDefault().post(new FeedPanelSlideEvent(false));
|
||||
|
@ -220,7 +222,6 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
@Override
|
||||
public void onDrawerOpened(View drawerView) {
|
||||
super.onDrawerOpened(drawerView);
|
||||
togglePodcastVideoViewAnimation();
|
||||
reloadCountNumbersOfSlidingPaneAdapter();
|
||||
|
||||
syncState();
|
||||
|
@ -239,17 +240,18 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
drawerToggle.syncState();
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {//When the app starts (no orientation change)
|
||||
startDetailFragment(SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS.getValue(), true, null, true);
|
||||
}
|
||||
|
||||
//AppRater.app_launched(this);
|
||||
//AppRater.rateNow(this);
|
||||
|
||||
if (savedInstanceState == null) { //When the app starts (no orientation change)
|
||||
updateDetailFragment(SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS.getValue(), true, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
if (drawerToggle != null) {
|
||||
drawerToggle.syncState();
|
||||
}
|
||||
|
@ -266,20 +268,27 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
if (tabletSize) {
|
||||
showTapLogoToSyncShowcaseView();
|
||||
}
|
||||
|
||||
|
||||
if (ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle(getString(R.string.permission_req_location_twilight_title))
|
||||
.setMessage(getString(R.string.permission_req_location_twilight_text))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||
//ActivityCompat.requestPermissions(this, new String[]{ACCESS_COARSE_LOCATION}, 1349);
|
||||
ActivityCompat.requestPermissions(this, new String[]{ACCESS_FINE_LOCATION}, 139);
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.actionbarsherlock.app.SherlockFragmentActivity#onSaveInstanceState(android.os.Bundle)
|
||||
*/
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
saveInstanceState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.actionbarsherlock.app.SherlockFragmentActivity#onRestoreInstanceState(android.os.Bundle)
|
||||
*/
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
restoreInstanceState(savedInstanceState);
|
||||
|
@ -289,8 +298,9 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (drawerToggle != null)
|
||||
if (drawerToggle != null) {
|
||||
drawerToggle.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveInstanceState(Bundle outState) {
|
||||
|
@ -306,6 +316,10 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
outState.putInt(LIST_ADAPTER_PAGE_COUNT, adapter.getCachedPages());
|
||||
}
|
||||
}
|
||||
if(mSearchView != null) {
|
||||
mSearchString = mSearchView.getQuery().toString();
|
||||
outState.putString(SEARCH_KEY, mSearchString);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreInstanceState(Bundle savedInstanceState) {
|
||||
|
@ -322,11 +336,12 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
.getRecyclerView()
|
||||
.setAdapter(adapter);
|
||||
|
||||
startDetailFragment(savedInstanceState.getLong(ID_FEED_STRING),
|
||||
updateDetailFragment(savedInstanceState.getLong(ID_FEED_STRING),
|
||||
savedInstanceState.getBoolean(IS_FOLDER_BOOLEAN),
|
||||
savedInstanceState.getLong(OPTIONAL_FOLDER_ID),
|
||||
false);
|
||||
}
|
||||
mSearchString = savedInstanceState.getString(SEARCH_KEY, null);
|
||||
}
|
||||
|
||||
|
||||
|
@ -414,12 +429,12 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
NewsReaderDetailFragment ndf = getNewsReaderDetailFragment();
|
||||
if (ndf != null) {
|
||||
//ndf.reloadAdapterFromScratch();
|
||||
ndf.updateCurrentRssView(NewsReaderListActivity.this);
|
||||
ndf.updateCurrentRssView();
|
||||
}
|
||||
}
|
||||
|
||||
public void switchToAllUnreadItemsFolder() {
|
||||
startDetailFragment(SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS.getValue(), true, null, true);
|
||||
updateDetailFragment(SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS.getValue(), true, null, true);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
|
@ -507,24 +522,13 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
if (firstVisiblePosition == 0 || firstVisiblePosition == -1) {
|
||||
updateCurrentRssView();
|
||||
} else {
|
||||
Snackbar snackbar = Snackbar.make(findViewById(R.id.coordinator_layout),
|
||||
getResources().getQuantityString(R.plurals.message_bar_new_articles_available, newItemsCount, newItemsCount),
|
||||
Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(getString(R.string.message_bar_reload), mSnackbarListener);
|
||||
snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.accent_material_dark));
|
||||
// Setting android:TextColor to #000 in the light theme results in black on black
|
||||
// text on the Snackbar, set the text back to white,
|
||||
// TODO: find a cleaner way to do this
|
||||
TextView textView = snackbar.getView().findViewById(com.google.android.material.R.id.snackbar_text);
|
||||
textView.setTextColor(Color.WHITE);
|
||||
snackbar.show();
|
||||
showSnackbar(newItemsCount);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
NewsReaderListFragment newsReaderListFragment = getSlidingListFragment();
|
||||
|
@ -541,6 +545,19 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
startSync();
|
||||
}
|
||||
|
||||
private void showSnackbar(int newItemsCount) {
|
||||
Snackbar snackbar = Snackbar.make(findViewById(R.id.coordinator_layout),
|
||||
getResources().getQuantityString(R.plurals.message_bar_new_articles_available, newItemsCount, newItemsCount),
|
||||
Snackbar.LENGTH_LONG);
|
||||
snackbar.setAction(getString(R.string.message_bar_reload), mSnackbarListener);
|
||||
//snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.accent_material_dark));
|
||||
// Setting android:TextColor to #000 in the light theme results in black on black
|
||||
// text on the Snackbar, set the text back to white,
|
||||
//TextView textView = snackbar.getView().findViewById(com.google.android.material.R.id.snackbar_text);
|
||||
//textView.setTextColor(Color.WHITE);
|
||||
snackbar.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method from {@link NewsReaderListFragment.Callbacks} indicating
|
||||
* that the item with the given ID was selected.
|
||||
|
@ -550,7 +567,7 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
if (drawerLayout != null)
|
||||
drawerLayout.closeDrawer(GravityCompat.START);
|
||||
|
||||
startDetailFragment(idFeed, isFolder, optional_folder_id, true);
|
||||
updateDetailFragment(idFeed, isFolder, optional_folder_id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -558,7 +575,7 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
if (drawerLayout != null)
|
||||
drawerLayout.closeDrawer(GravityCompat.START);
|
||||
|
||||
startDetailFragment(idFeed, false, optional_folder_id, true);
|
||||
updateDetailFragment(idFeed, false, optional_folder_id, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -600,41 +617,37 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
}
|
||||
|
||||
|
||||
private NewsReaderDetailFragment startDetailFragment(long id, Boolean folder, Long optional_folder_id, boolean updateListView)
|
||||
{
|
||||
if(menuItemDownloadMoreItems != null) {
|
||||
menuItemDownloadMoreItems.setEnabled(true);
|
||||
}
|
||||
private NewsReaderDetailFragment updateDetailFragment(long id, Boolean folder, Long optional_folder_id, boolean updateListView) {
|
||||
if(menuItemDownloadMoreItems != null) {
|
||||
menuItemDownloadMoreItems.setEnabled(true);
|
||||
}
|
||||
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(getApplicationContext());
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(getApplicationContext());
|
||||
|
||||
Long feedId = null;
|
||||
Long folderId;
|
||||
String title = null;
|
||||
Long feedId = null;
|
||||
Long folderId;
|
||||
String title = null;
|
||||
|
||||
if(!folder)
|
||||
{
|
||||
feedId = id;
|
||||
folderId = optional_folder_id;
|
||||
title = dbConn.getFeedById(id).getFeedTitle();
|
||||
}
|
||||
else
|
||||
{
|
||||
folderId = id;
|
||||
int idFolder = (int) id;
|
||||
if(idFolder >= 0)
|
||||
title = dbConn.getFolderById(id).getLabel();
|
||||
else if(idFolder == -10)
|
||||
title = getString(R.string.allUnreadFeeds);
|
||||
else if(idFolder == -11)
|
||||
title = getString(R.string.starredFeeds);
|
||||
if(!folder) {
|
||||
feedId = id;
|
||||
folderId = optional_folder_id;
|
||||
title = dbConn.getFeedById(id).getFeedTitle();
|
||||
} else {
|
||||
folderId = id;
|
||||
int idFolder = (int) id;
|
||||
if(idFolder >= 0) {
|
||||
title = dbConn.getFolderById(id).getLabel();
|
||||
} else if(idFolder == -10) {
|
||||
title = getString(R.string.allUnreadFeeds);
|
||||
} else if(idFolder == -11) {
|
||||
title = getString(R.string.starredFeeds);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NewsReaderDetailFragment fragment = getNewsReaderDetailFragment();
|
||||
fragment.setData(feedId, folderId, title, updateListView);
|
||||
return fragment;
|
||||
}
|
||||
NewsReaderDetailFragment fragment = getNewsReaderDetailFragment();
|
||||
fragment.setData(feedId, folderId, title, updateListView);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
|
||||
public void UpdateItemList()
|
||||
|
@ -698,39 +711,48 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_search);
|
||||
|
||||
//Set expand listener to close keyboard
|
||||
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
clearSearchViewFocus();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchView.setOnQueryTextListener(this);
|
||||
searchView.setOnQueryTextFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
//Set expand listener to close keyboard
|
||||
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if(!hasFocus) {
|
||||
clearSearchViewFocus();
|
||||
}
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
//onQueryTextChange(""); // Reset search
|
||||
mSearchView.setQuery("", true);
|
||||
clearSearchViewFocus();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
NewsReaderDetailFragment ndf = getNewsReaderDetailFragment();
|
||||
if(ndf != null)
|
||||
ndf.updateMenuItemsState();
|
||||
mSearchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
|
||||
mSearchView.setIconifiedByDefault(false);
|
||||
mSearchView.setOnQueryTextListener(this);
|
||||
mSearchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> {
|
||||
if(!hasFocus) {
|
||||
clearSearchViewFocus();
|
||||
}
|
||||
});
|
||||
|
||||
ThemeUtils.colorSearchViewCursorColor(mSearchView, Color.WHITE);
|
||||
|
||||
NewsReaderDetailFragment ndf = getNewsReaderDetailFragment();
|
||||
if(ndf != null) {
|
||||
ndf.updateMenuItemsState();
|
||||
}
|
||||
|
||||
updateButtonLayout();
|
||||
|
||||
return true;
|
||||
// focus the SearchView (if search view was active before orientation change)
|
||||
if (mSearchString != null && !mSearchString.isEmpty()) {
|
||||
searchItem.expandActionView();
|
||||
mSearchView.setQuery(mSearchString, true);
|
||||
mSearchView.clearFocus();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public MenuItem getMenuItemDownloadMoreItems() {
|
||||
|
@ -826,9 +848,9 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
return true;
|
||||
|
||||
case R.id.menu_search:
|
||||
searchView.setIconified(false);
|
||||
searchView.setFocusable(true);
|
||||
searchView.requestFocusFromTouch();
|
||||
mSearchView.setIconified(false);
|
||||
mSearchView.setFocusable(true);
|
||||
mSearchView.requestFocusFromTouch();
|
||||
return true;
|
||||
|
||||
case R.id.menu_download_web_archive:
|
||||
|
@ -1071,17 +1093,10 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
searchPublishSubject
|
||||
.debounce(400, TimeUnit.MILLISECONDS)
|
||||
.distinctUntilChanged()
|
||||
.map(new Function<String, List<RssItem>>() {
|
||||
|
||||
@Override
|
||||
public List<RssItem> apply(String s) throws Exception {
|
||||
return getNewsReaderDetailFragment().performSearch(s);
|
||||
}
|
||||
|
||||
})
|
||||
.map(s -> getNewsReaderDetailFragment().performSearch(s))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeWith(getNewsReaderDetailFragment().SearchResultObserver)
|
||||
.subscribeWith(getNewsReaderDetailFragment().searchResultObserver)
|
||||
.isDisposed();
|
||||
|
||||
}
|
||||
|
@ -1090,6 +1105,6 @@ public class NewsReaderListActivity extends PodcastFragmentActivity implements
|
|||
}
|
||||
|
||||
public void clearSearchViewFocus() {
|
||||
searchView.clearFocus();
|
||||
mSearchView.clearFocus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ import java.io.Serializable;
|
|||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
@ -56,6 +57,8 @@ import de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter;
|
|||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.di.ApiProvider;
|
||||
import de.luhmer.owncloudnewsreader.interfaces.ExpListTextClicked;
|
||||
import de.luhmer.owncloudnewsreader.model.AbstractItem;
|
||||
import de.luhmer.owncloudnewsreader.model.ConcreteFeedItem;
|
||||
import de.luhmer.owncloudnewsreader.model.FolderSubscribtionItem;
|
||||
import de.luhmer.owncloudnewsreader.model.UserInfo;
|
||||
import io.reactivex.Observer;
|
||||
|
@ -221,17 +224,27 @@ public class NewsReaderListFragment extends Fragment implements OnCreateContextM
|
|||
|
||||
};
|
||||
|
||||
// Code below is only used for unit tests
|
||||
@VisibleForTesting
|
||||
public OnChildClickListener onChildClickListener = new OnChildClickListener() {
|
||||
|
||||
@Override
|
||||
public boolean onChildClick(ExpandableListView parent, View v,
|
||||
int groupPosition, int childPosition, long id) {
|
||||
|
||||
long idItem = lvAdapter.getChildId(groupPosition, childPosition);
|
||||
long idItem;
|
||||
if(childPosition != -1) {
|
||||
idItem = lvAdapter.getChildId(groupPosition, childPosition);
|
||||
} else {
|
||||
idItem = groupPosition;
|
||||
}
|
||||
Long optional_id_folder = null;
|
||||
FolderSubscribtionItem groupItem = (FolderSubscribtionItem) lvAdapter.getGroup(groupPosition);
|
||||
AbstractItem groupItem = (AbstractItem) lvAdapter.getGroup(groupPosition);
|
||||
if(groupItem != null)
|
||||
optional_id_folder = groupItem.id_database;
|
||||
if(groupItem instanceof ConcreteFeedItem) {
|
||||
idItem = ((ConcreteFeedItem)groupItem).feedId;
|
||||
}
|
||||
|
||||
mCallbacks.onChildItemClicked(idItem, optional_id_folder);
|
||||
|
||||
|
|
|
@ -0,0 +1,276 @@
|
|||
package de.luhmer.owncloudnewsreader;
|
||||
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Point;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.SurfaceView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.RegisterVideoOutput;
|
||||
import de.luhmer.owncloudnewsreader.helper.ThemeChooser;
|
||||
import de.luhmer.owncloudnewsreader.services.PodcastPlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.services.podcast.PlaybackService;
|
||||
|
||||
import static de.luhmer.owncloudnewsreader.services.PodcastPlaybackService.CURRENT_PODCAST_MEDIA_TYPE;
|
||||
|
||||
public class PiPVideoPlaybackActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = PiPVideoPlaybackActivity.class.getCanonicalName();
|
||||
private EventBus mEventBus;
|
||||
|
||||
private MediaBrowserCompat mMediaBrowser;
|
||||
|
||||
protected static boolean activityIsRunning = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
ThemeChooser.chooseTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeChooser.afterOnCreate(this);
|
||||
|
||||
setContentView(R.layout.activity_pip_video_playback);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
//moveTaskToBack(false);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PictureInPictureParams.Builder pictureInPictureParamsBuilder = new PictureInPictureParams.Builder();
|
||||
//Rational aspectRatio = new Rational(vv.getWidth(), vv.getHeight());
|
||||
//pictureInPictureParamsBuilder.setAspectRatio(aspectRatio).build();
|
||||
enterPictureInPictureMode(pictureInPictureParamsBuilder.build());
|
||||
} else {
|
||||
enterPictureInPictureMode();
|
||||
}
|
||||
|
||||
|
||||
enterPictureInPictureMode();
|
||||
} else {
|
||||
Toast.makeText(this, "This device does not support video playback.", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureModeChanged (boolean isInPictureInPictureMode, Configuration newConfig) {
|
||||
Log.d(TAG, "onPictureInPictureModeChanged() called with: isInPictureInPictureMode = [" + isInPictureInPictureMode + "], newConfig = [" + newConfig + "]");
|
||||
|
||||
RelativeLayout surfaceViewWrapper = findViewById(R.id.layout_activity_pip);
|
||||
SurfaceView surfaceView = (SurfaceView) surfaceViewWrapper.getChildAt(0);
|
||||
|
||||
if(surfaceView != null) {
|
||||
if (isInPictureInPictureMode) {
|
||||
surfaceView.setLayoutParams(new RelativeLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams.MATCH_PARENT,
|
||||
RelativeLayout.LayoutParams.MATCH_PARENT));
|
||||
} else {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
Point size = new Point();
|
||||
display.getSize(size);
|
||||
float width = size.x;
|
||||
//int height = size.y;
|
||||
//int newWidth = (int) (width * (9f/16f));
|
||||
int newWidth = (int) (width * (3f/4f));
|
||||
|
||||
surfaceView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, newWidth));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (isInPictureInPictureMode) {
|
||||
// Hide the full-screen UI (controls, etc.) while in picture-in-picture mode.
|
||||
|
||||
} else {
|
||||
// Restore the full-screen UI.
|
||||
//Intent intent = new Intent(this, NewsReaderListActivity.class);
|
||||
//intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
//startActivity(intent);
|
||||
|
||||
// Finish PiP Activity
|
||||
//finish();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
// When dismissing
|
||||
if(!isInPictureInPictureMode) {
|
||||
finish();
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Log.d(TAG, "onStart() called");
|
||||
super.onStart();
|
||||
|
||||
mEventBus = EventBus.getDefault();
|
||||
//mEventBus.register(this);
|
||||
|
||||
mMediaBrowser = new MediaBrowserCompat(this,
|
||||
new ComponentName(this, PodcastPlaybackService.class),
|
||||
mConnectionCallbacks,
|
||||
null); // optional Bundle
|
||||
mMediaBrowser.connect();
|
||||
|
||||
activityIsRunning = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
Log.d(TAG, "onStop() called");
|
||||
|
||||
unregisterVideoViews();
|
||||
|
||||
//mEventBus.unregister(this);
|
||||
|
||||
// (see "stay in sync with the MediaSession")
|
||||
if (MediaControllerCompat.getMediaController(this) != null) {
|
||||
MediaControllerCompat.getMediaController(this).unregisterCallback(controllerCallback);
|
||||
}
|
||||
|
||||
mMediaBrowser.disconnect();
|
||||
|
||||
activityIsRunning = false;
|
||||
|
||||
super.onStop();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
|
||||
finishAndRemoveTask();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterVideoViews() {
|
||||
mEventBus.post(new RegisterVideoOutput(null, null));
|
||||
}
|
||||
|
||||
/*
|
||||
@Subscribe
|
||||
public void onEvent(CollapsePodcastView event) {
|
||||
Log.d(TAG, "onEvent() called with: event = [" + event + "]");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
finishAndRemoveTask();
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
Log.d(TAG, "onBackPressed() called");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
enterPictureInPictureMode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
|
||||
new MediaBrowserCompat.ConnectionCallback() {
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.d(TAG, "onConnected() called");
|
||||
|
||||
// Get the token for the MediaSession
|
||||
MediaSessionCompat.Token token = mMediaBrowser.getSessionToken();
|
||||
|
||||
try {
|
||||
// Create a MediaControllerCompat
|
||||
MediaControllerCompat mediaController = new MediaControllerCompat(PiPVideoPlaybackActivity.this, token);
|
||||
|
||||
// Save the controller
|
||||
MediaControllerCompat.setMediaController(PiPVideoPlaybackActivity.this, mediaController);
|
||||
|
||||
// Register a Callback to stay in sync
|
||||
mediaController.registerCallback(controllerCallback);
|
||||
|
||||
// Display the initial state
|
||||
MediaMetadataCompat metadata = mediaController.getMetadata();
|
||||
handleMetadataChange(metadata);
|
||||
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Connecting to podcast service failed!", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MediaControllerCompat.Callback controllerCallback =
|
||||
new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onMetadataChanged(MediaMetadataCompat metadata) {
|
||||
Log.v(TAG, "onMetadataChanged() called with: metadata = [" + metadata + "]");
|
||||
handleMetadataChange(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat stateCompat) {
|
||||
Log.v(TAG, "onPlaybackStateChanged() called with: state = [" + stateCompat + "]");
|
||||
}
|
||||
};
|
||||
|
||||
private void handleMetadataChange(MediaMetadataCompat metadata) {
|
||||
Log.d(TAG, "handleMetadataChange() called with: metadata = [" + metadata + "]");
|
||||
|
||||
unregisterVideoViews();
|
||||
RelativeLayout surfaceViewWrapper = findViewById(R.id.layout_activity_pip);
|
||||
surfaceViewWrapper.removeAllViews();
|
||||
|
||||
PlaybackService.VideoType mediaType = PlaybackService.VideoType.valueOf(metadata.getString(CURRENT_PODCAST_MEDIA_TYPE));
|
||||
Log.d(TAG, "handleMetadataChange() called with: mediaType = [" + mediaType + "]");
|
||||
|
||||
switch (mediaType) {
|
||||
case None:
|
||||
finish();
|
||||
break;
|
||||
case Video:
|
||||
// default
|
||||
SurfaceView surfaceView = createSurfaceView();
|
||||
surfaceViewWrapper.addView(surfaceView);
|
||||
mEventBus.post(new RegisterVideoOutput(surfaceView, surfaceViewWrapper));
|
||||
break;
|
||||
/*
|
||||
case YouTube:
|
||||
final int YOUTUBE_CONTENT_VIEW_ID = 10101010;
|
||||
FrameLayout frame = new FrameLayout(this);
|
||||
frame.setId(YOUTUBE_CONTENT_VIEW_ID);
|
||||
surfaceViewWrapper.addView(frame);
|
||||
YoutubePlayerManager.StartYoutubePlayer(this, YOUTUBE_CONTENT_VIEW_ID, mEventBus, () -> Log.d(TAG, "onInit Success()"));
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private SurfaceView createSurfaceView() {
|
||||
SurfaceView surfaceView = new SurfaceView(this);
|
||||
surfaceView.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT));
|
||||
return surfaceView;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,25 @@
|
|||
package de.luhmer.owncloudnewsreader;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.text.InputFilter;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -22,7 +34,6 @@ import android.widget.ProgressBar;
|
|||
import android.widget.RelativeLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewSwitcher;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
|
@ -43,12 +54,12 @@ import butterknife.ButterKnife;
|
|||
import butterknife.OnClick;
|
||||
import de.luhmer.owncloudnewsreader.ListView.PodcastArrayAdapter;
|
||||
import de.luhmer.owncloudnewsreader.ListView.PodcastFeedArrayAdapter;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.CollapsePodcastView;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.ExpandPodcastView;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.SpeedPodcast;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.StartDownloadPodcast;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.TogglePlayerStateEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.WindPodcast;
|
||||
import de.luhmer.owncloudnewsreader.model.MediaItem;
|
||||
import de.luhmer.owncloudnewsreader.model.PodcastFeedItem;
|
||||
import de.luhmer.owncloudnewsreader.model.PodcastItem;
|
||||
import de.luhmer.owncloudnewsreader.services.PodcastDownloadService;
|
||||
|
@ -56,12 +67,12 @@ import de.luhmer.owncloudnewsreader.services.PodcastPlaybackService;
|
|||
import de.luhmer.owncloudnewsreader.services.podcast.PlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout;
|
||||
|
||||
import static android.media.MediaMetadata.METADATA_KEY_MEDIA_ID;
|
||||
import static de.luhmer.owncloudnewsreader.services.PodcastPlaybackService.CURRENT_PODCAST_MEDIA_TYPE;
|
||||
import static de.luhmer.owncloudnewsreader.services.PodcastPlaybackService.PLAYBACK_SPEED_FLOAT;
|
||||
|
||||
|
||||
/**
|
||||
* A simple {@link Fragment} subclass.
|
||||
* Activities that contain this fragment must implement the
|
||||
* {@link PodcastFragment.OnFragmentInteractionListener} interface
|
||||
* to StartYoutubePlayer interaction events.
|
||||
* Use the {@link PodcastFragment#newInstance} factory method to
|
||||
* create an instance of this fragment.
|
||||
*
|
||||
|
@ -69,12 +80,43 @@ import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout;
|
|||
public class PodcastFragment extends Fragment {
|
||||
|
||||
private static final String TAG = PodcastFragment.class.getCanonicalName();
|
||||
//private static UpdatePodcastStatusEvent podcast; // Retain over different instances
|
||||
|
||||
private UpdatePodcastStatusEvent podcast;
|
||||
private int lastDrawableId;
|
||||
|
||||
private OnFragmentInteractionListener mListener;
|
||||
private PodcastSlidingUpPanelLayout sliding_layout;
|
||||
private EventBus eventBus;
|
||||
private MediaBrowserCompat mMediaBrowser;
|
||||
private Activity mActivity;
|
||||
|
||||
private long currentPositionInMillis = 0;
|
||||
private long maxPositionInMillis = 100000;
|
||||
|
||||
protected @BindView(R.id.btn_playPausePodcast) ImageButton btnPlayPausePodcast;
|
||||
protected @BindView(R.id.btn_playPausePodcastSlider) ImageButton btnPlayPausePodcastSlider;
|
||||
protected @BindView(R.id.btn_nextPodcastSlider) ImageButton btnNextPodcastSlider;
|
||||
protected @BindView(R.id.btn_previousPodcastSlider) ImageButton btnPreviousPodcastSlider;
|
||||
protected @BindView(R.id.btn_podcastSpeed) ImageButton btnPlaybackSpeed;
|
||||
|
||||
protected @BindView(R.id.img_feed_favicon) ImageView imgFavIcon;
|
||||
|
||||
protected @BindView(R.id.tv_title) TextView tvTitle;
|
||||
protected @BindView(R.id.tv_titleSlider) TextView tvTitleSlider;
|
||||
|
||||
protected @BindView(R.id.tv_from) TextView tvFrom;
|
||||
protected @BindView(R.id.tv_to) TextView tvTo;
|
||||
protected @BindView(R.id.tv_fromSlider) TextView tvFromSlider;
|
||||
protected @BindView(R.id.tv_ToSlider) TextView tvToSlider;
|
||||
|
||||
protected @BindView(R.id.sb_progress) SeekBar sb_progress;
|
||||
protected @BindView(R.id.pb_progress) ProgressBar pb_progress;
|
||||
protected @BindView(R.id.pb_progress2) ProgressBar pb_progress2;
|
||||
|
||||
protected @BindView(R.id.podcastFeedList) ListView /* CardGridView CardListView*/ podcastFeedList;
|
||||
protected @BindView(R.id.rlPodcast) RelativeLayout rlPodcast;
|
||||
protected @BindView(R.id.ll_podcast_header) LinearLayout rlPodcastHeader;
|
||||
protected @BindView(R.id.fl_playPausePodcastWrapper) FrameLayout playPausePodcastWrapper;
|
||||
protected @BindView(R.id.podcastTitleGrid) ListView /*CardGridView*/ podcastTitleGrid;
|
||||
|
||||
protected @BindView(R.id.viewSwitcherProgress) ViewSwitcher /*CardGridView*/ viewSwitcherProgress;
|
||||
|
||||
/**
|
||||
* Use this factory method to create a new instance of
|
||||
|
@ -85,42 +127,76 @@ public class PodcastFragment extends Fragment {
|
|||
public static PodcastFragment newInstance() {
|
||||
return new PodcastFragment();
|
||||
}
|
||||
public PodcastFragment() {
|
||||
// Required empty public constructor
|
||||
}
|
||||
|
||||
//Your created method
|
||||
public boolean onBackPressed() //returns if the event was handled
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
EventBus eventBus;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setRetainInstance(true);
|
||||
|
||||
//setRetainInstance(true);
|
||||
eventBus = EventBus.getDefault();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
eventBus.register(this);
|
||||
|
||||
super.onResume();
|
||||
//mActivity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
eventBus.unregister(this);
|
||||
|
||||
super.onPause();
|
||||
eventBus.unregister(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
mMediaBrowser = new MediaBrowserCompat(mActivity,
|
||||
new ComponentName(mActivity, PodcastPlaybackService.class),
|
||||
mConnectionCallbacks,
|
||||
null); // optional Bundle
|
||||
mMediaBrowser.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// (see "stay in sync with the MediaSession")
|
||||
if (MediaControllerCompat.getMediaController(mActivity) != null) {
|
||||
MediaControllerCompat.getMediaController(mActivity).unregisterCallback(mediaControllerCallback);
|
||||
MediaControllerCompat.getMediaController(mActivity).unregisterCallback(controllerCallback);
|
||||
}
|
||||
mMediaBrowser.disconnect();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
mActivity = getActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mActivity = null;
|
||||
}
|
||||
|
||||
protected void tryOpeningPictureinPictureMode() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
//moveTaskToBack(false /* nonRoot */);
|
||||
|
||||
if(!PiPVideoPlaybackActivity.activityIsRunning) {
|
||||
Intent intent = new Intent(getActivity(), PiPVideoPlaybackActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
//intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
@ -152,164 +228,46 @@ public class PodcastFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
long lastPodcastRssItemId = -1;
|
||||
@Subscribe
|
||||
public void onEvent(UpdatePodcastStatusEvent podcast) {
|
||||
this.podcast = podcast;
|
||||
|
||||
hasTitleInCache = true;
|
||||
|
||||
int drawableId = podcast.isPlaying() ? R.drawable.ic_action_pause : R.drawable.ic_action_play_arrow;
|
||||
int contentDescriptionId = podcast.isPlaying() ? R.string.content_desc_pause : R.string.content_desc_play;
|
||||
|
||||
if(lastDrawableId != drawableId) {
|
||||
lastDrawableId = drawableId;
|
||||
btnPlayPausePodcast.setImageResource(drawableId);
|
||||
btnPlayPausePodcast.setContentDescription(getString(contentDescriptionId));
|
||||
btnPlayPausePodcastSlider.setImageResource(drawableId);
|
||||
}
|
||||
|
||||
if(lastPodcastRssItemId != podcast.getRssItemId() && imgFavIcon != null) {
|
||||
if(loadPodcastFavIcon()) {
|
||||
lastPodcastRssItemId = podcast.getRssItemId();
|
||||
}
|
||||
}
|
||||
|
||||
int hours = (int)(podcast.getCurrent() / (1000*60*60));
|
||||
int minutes = (int)(podcast.getCurrent() % (1000*60*60)) / (1000*60);
|
||||
int seconds = (int) ((podcast.getCurrent() % (1000*60*60)) % (1000*60) / 1000);
|
||||
minutes += hours * 60;
|
||||
tvFrom.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
tvFromSlider.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
|
||||
hours = (int)( podcast.getMax() / (1000*60*60));
|
||||
minutes = (int)(podcast.getMax() % (1000*60*60)) / (1000*60);
|
||||
seconds = (int) ((podcast.getMax() % (1000*60*60)) % (1000*60) / 1000);
|
||||
minutes += hours * 60;
|
||||
tvTo.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
tvToSlider.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
|
||||
tvTitle.setText(podcast.getTitle());
|
||||
tvTitleSlider.setText(podcast.getTitle());
|
||||
|
||||
if(podcast.getStatus() == PlaybackService.Status.PREPARING) {
|
||||
sb_progress.setVisibility(View.INVISIBLE);
|
||||
pb_progress2.setVisibility(View.VISIBLE);
|
||||
|
||||
pb_progress.setIndeterminate(true);
|
||||
} else {
|
||||
double progress = ((double) podcast.getCurrent() / (double) podcast.getMax()) * 100d;
|
||||
|
||||
if(!blockSeekbarUpdate) {
|
||||
sb_progress.setVisibility(View.VISIBLE);
|
||||
pb_progress2.setVisibility(View.INVISIBLE);
|
||||
sb_progress.setProgress((int) progress);
|
||||
}
|
||||
|
||||
pb_progress.setIndeterminate(false);
|
||||
pb_progress.setProgress((int) progress);
|
||||
}
|
||||
@OnClick(R.id.fl_playPausePodcastWrapper)
|
||||
protected void playPause() {
|
||||
eventBus.post(new TogglePlayerStateEvent());
|
||||
}
|
||||
|
||||
private boolean loadPodcastFavIcon() {
|
||||
return ((PodcastFragmentActivity) getActivity()).getCurrentPlayingPodcast(
|
||||
new PodcastFragmentActivity.OnCurrentPlayingPodcastCallback() {
|
||||
@Override
|
||||
public void currentPlayingPodcastReceived(MediaItem mediaItem) {
|
||||
Log.d(TAG, "currentPlayingPodcastReceived() called with: mediaItem = [" + mediaItem + "]");
|
||||
if(mediaItem != null) {
|
||||
String favIconUrl = mediaItem.favIcon;
|
||||
Log.d(TAG, "currentPlayingPodcastReceived: " + favIconUrl);
|
||||
DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().
|
||||
showImageOnLoading(R.drawable.default_feed_icon_light).
|
||||
showImageForEmptyUri(R.drawable.default_feed_icon_light).
|
||||
showImageOnFail(R.drawable.default_feed_icon_light).
|
||||
build();
|
||||
ImageLoader.getInstance().displayImage(favIconUrl, imgFavIcon, displayImageOptions);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@BindView(R.id.btn_playPausePodcast) ImageButton btnPlayPausePodcast;
|
||||
@BindView(R.id.btn_playPausePodcastSlider) ImageButton btnPlayPausePodcastSlider;
|
||||
@BindView(R.id.btn_nextPodcastSlider) ImageButton btnNextPodcastSlider;
|
||||
@BindView(R.id.btn_previousPodcastSlider) ImageButton btnPreviousPodcastSlider;
|
||||
@BindView(R.id.btn_podcastSpeed) ImageButton btnPlaybackSpeed;
|
||||
|
||||
@BindView(R.id.img_feed_favicon) ImageView imgFavIcon;
|
||||
|
||||
@BindView(R.id.tv_title) TextView tvTitle;
|
||||
@BindView(R.id.tv_titleSlider) TextView tvTitleSlider;
|
||||
|
||||
|
||||
@BindView(R.id.tv_from) TextView tvFrom;
|
||||
@BindView(R.id.tv_to) TextView tvTo;
|
||||
@BindView(R.id.tv_fromSlider) TextView tvFromSlider;
|
||||
@BindView(R.id.tv_ToSlider) TextView tvToSlider;
|
||||
|
||||
@BindView(R.id.sb_progress) SeekBar sb_progress;
|
||||
@BindView(R.id.pb_progress) ProgressBar pb_progress;
|
||||
@BindView(R.id.pb_progress2) ProgressBar pb_progress2;
|
||||
|
||||
|
||||
@BindView(R.id.podcastFeedList) ListView /* CardGridView CardListView*/ podcastFeedList;
|
||||
@BindView(R.id.rlPodcast) RelativeLayout rlPodcast;
|
||||
@BindView(R.id.ll_podcast_header) LinearLayout rlPodcastHeader;
|
||||
@BindView(R.id.fl_playPausePodcastWrapper) FrameLayout playPausePodcastWrapper;
|
||||
@BindView(R.id.podcastTitleGrid) ListView /*CardGridView*/ podcastTitleGrid;
|
||||
|
||||
@BindView(R.id.viewSwitcherProgress) ViewSwitcher /*CardGridView*/ viewSwitcherProgress;
|
||||
|
||||
|
||||
boolean hasTitleInCache = false;
|
||||
@OnClick(R.id.fl_playPausePodcastWrapper) void playPause() {
|
||||
if(!hasTitleInCache) {
|
||||
Toast.makeText(getActivity(), "Please select a title first", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
eventBus.post(new TogglePlayerStateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.btn_playPausePodcastSlider) void playPauseSlider() {
|
||||
@OnClick(R.id.btn_playPausePodcastSlider)
|
||||
protected void playPauseSlider() {
|
||||
playPause();
|
||||
}
|
||||
|
||||
@OnClick(R.id.btn_nextPodcastSlider) void nextChapter() {
|
||||
eventBus.post(new WindPodcast() {{
|
||||
long position = podcast.getCurrent() + 30000;
|
||||
toPositionInPercent = ((double) position / (double) podcast.getMax()) * 100d;
|
||||
}});
|
||||
@OnClick(R.id.btn_nextPodcastSlider)
|
||||
protected void windForward() {
|
||||
eventBus.post(new WindPodcast(30000));
|
||||
|
||||
//Toast.makeText(getActivity(), "This feature is not supported yet :(", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@OnClick(R.id.btn_previousPodcastSlider) void previousChapter() {
|
||||
eventBus.post(new WindPodcast() {{
|
||||
long position = podcast.getCurrent() - 10000;
|
||||
toPositionInPercent = ((double) position / (double) podcast.getMax()) * 100d;
|
||||
}});
|
||||
@OnClick(R.id.btn_previousPodcastSlider)
|
||||
protected void windBack() {
|
||||
eventBus.post(new WindPodcast(-10000));
|
||||
}
|
||||
|
||||
@OnClick(R.id.btn_podcastSpeed) void openSpeedMenu() {
|
||||
@OnClick(R.id.btn_podcastSpeed)
|
||||
protected void openSpeedMenu() {
|
||||
showPlaybackSpeedPicker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
// create ContextThemeWrapper from the original Activity Context with the custom theme
|
||||
Context context = new ContextThemeWrapper(getActivity(), R.style.Theme_MaterialComponents_Light_DarkActionBar);
|
||||
//Context context = new ContextThemeWrapper(getActivity(), R.style.Theme_MaterialComponents_Light_DarkActionBar);
|
||||
// clone the inflater using the ContextThemeWrapper
|
||||
LayoutInflater localInflater = inflater.cloneInContext(context);
|
||||
//LayoutInflater localInflater = inflater.cloneInContext(context);
|
||||
// inflate using the cloned inflater, not the passed in default
|
||||
View view = localInflater.inflate(R.layout.fragment_podcast, container, false);
|
||||
|
||||
//View view = localInflater.inflate(R.layout.fragment_podcast, container, false);
|
||||
View view = inflater.inflate(R.layout.fragment_podcast, container, false);
|
||||
|
||||
//View view = inflater.inflate(R.layout.fragment_podcast, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
|
||||
|
||||
if(getActivity() instanceof PodcastFragmentActivity) {
|
||||
sliding_layout = ((PodcastFragmentActivity) getActivity()).getSlidingLayout();
|
||||
}
|
||||
|
@ -322,8 +280,6 @@ public class PodcastFragment extends Fragment {
|
|||
sliding_layout.setPanelSlideListener(onPanelSlideListener);
|
||||
}
|
||||
|
||||
|
||||
|
||||
PodcastFeedArrayAdapter mArrayAdapter = new PodcastFeedArrayAdapter(getActivity(), new PodcastFeedItem[0]);
|
||||
|
||||
if(mArrayAdapter.getCount() > 0) {
|
||||
|
@ -339,49 +295,16 @@ public class PodcastFragment extends Fragment {
|
|||
}
|
||||
|
||||
|
||||
public void onButtonPressed(Uri uri) {
|
||||
if (mListener != null) {
|
||||
mListener.onFragmentInteraction(uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
mListener = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This interface must be implemented by activities that contain this
|
||||
* fragment to allow an interaction in this fragment to be communicated
|
||||
* to the activity and potentially other fragments contained in that
|
||||
* activity.
|
||||
* <p>
|
||||
* See the Android Training lesson <a href=
|
||||
* "http://developer.android.com/training/basics/fragments/communicating.html"
|
||||
* >Communicating with Other Fragments</a> for more information.
|
||||
*/
|
||||
public interface OnFragmentInteractionListener {
|
||||
void onFragmentInteraction(Uri uri);
|
||||
}
|
||||
|
||||
|
||||
private SlidingUpPanelLayout.PanelSlideListener onPanelSlideListener = new SlidingUpPanelLayout.PanelSlideListener() {
|
||||
@Override
|
||||
public void onPanelSlide(View view, float v) {
|
||||
|
||||
}
|
||||
public void onPanelSlide(View view, float v) { }
|
||||
|
||||
@Override
|
||||
public void onPanelCollapsed(View view) {
|
||||
if(sliding_layout != null)
|
||||
sliding_layout.setDragView(rlPodcastHeader);
|
||||
viewSwitcherProgress.setDisplayedChild(0);
|
||||
|
||||
if(getActivity() instanceof PodcastFragmentActivity)
|
||||
((PodcastFragmentActivity)getActivity()).togglePodcastVideoViewAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -389,47 +312,45 @@ public class PodcastFragment extends Fragment {
|
|||
if(sliding_layout != null)
|
||||
sliding_layout.setDragView(viewSwitcherProgress);
|
||||
viewSwitcherProgress.setDisplayedChild(1);
|
||||
|
||||
if(getActivity() instanceof PodcastFragmentActivity)
|
||||
((PodcastFragmentActivity)getActivity()).togglePodcastVideoViewAnimation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelAnchored(View view) {
|
||||
@Override public void onPanelAnchored(View view) { }
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPanelHidden(View view) {
|
||||
|
||||
}
|
||||
@Override public void onPanelHidden(View view) { }
|
||||
};
|
||||
|
||||
|
||||
boolean blockSeekbarUpdate = false;
|
||||
private SeekBar.OnSeekBarChangeListener onSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() {
|
||||
int before;
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
|
||||
//Log.v(TAG, "onProgressChanged");
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
|
||||
/*
|
||||
if(fromUser) {
|
||||
Log.v(TAG, "onProgressChanged: " + progress + "%");
|
||||
before = progress;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
blockSeekbarUpdate = true;
|
||||
Log.v(TAG, "onStartTrackingTouch");
|
||||
before = seekBar.getProgress();
|
||||
blockSeekbarUpdate = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
if(hasTitleInCache) {
|
||||
eventBus.post(new WindPodcast() {{
|
||||
toPositionInPercent = seekBar.getProgress();
|
||||
}});
|
||||
blockSeekbarUpdate = false;
|
||||
}
|
||||
Log.v(TAG, "onStopTrackingTouch");
|
||||
int diffInSeconds = seekBar.getProgress() - before;
|
||||
eventBus.post(new WindPodcast(diffInSeconds));
|
||||
blockSeekbarUpdate = false;
|
||||
}
|
||||
};
|
||||
// TODO SEEK DOES NOT WORK PROPERLY!!!!
|
||||
|
||||
|
||||
private void showPlaybackSpeedPicker() {
|
||||
|
@ -437,20 +358,12 @@ public class PodcastFragment extends Fragment {
|
|||
numberPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
|
||||
numberPicker.setMinValue(0);
|
||||
numberPicker.setMaxValue(PodcastPlaybackService.PLAYBACK_SPEEDS.length-1);
|
||||
numberPicker.setFormatter(new NumberPicker.Formatter() {
|
||||
@Override
|
||||
public String format(int i) {
|
||||
return String.valueOf(PodcastPlaybackService.PLAYBACK_SPEEDS[i]);
|
||||
}
|
||||
});
|
||||
numberPicker.setFormatter(i -> String.valueOf(PodcastPlaybackService.PLAYBACK_SPEEDS[i]));
|
||||
|
||||
if(getActivity() instanceof PodcastFragmentActivity) {
|
||||
((PodcastFragmentActivity) getActivity()).getCurrentPlaybackSpeed(new PodcastFragmentActivity.OnPlaybackSpeedCallback() {
|
||||
@Override
|
||||
public void currentPlaybackReceived(float playbackSpeed) {
|
||||
int position = Arrays.binarySearch(PodcastPlaybackService.PLAYBACK_SPEEDS, playbackSpeed);
|
||||
numberPicker.setValue(position);
|
||||
}
|
||||
getCurrentPlaybackSpeed(playbackSpeed -> {
|
||||
int position = Arrays.binarySearch(PodcastPlaybackService.PLAYBACK_SPEEDS, playbackSpeed);
|
||||
numberPicker.setValue(position);
|
||||
});
|
||||
|
||||
} else {
|
||||
|
@ -467,19 +380,12 @@ public class PodcastFragment extends Fragment {
|
|||
// set dialog message
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
float speed = PodcastPlaybackService.PLAYBACK_SPEEDS[numberPicker.getValue()];
|
||||
//Toast.makeText(getActivity(), String.valueOf(speed]), Toast.LENGTH_SHORT).show();
|
||||
eventBus.post(new SpeedPodcast(speed));
|
||||
dialog.cancel();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(getString(android.R.string.cancel), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.cancel();
|
||||
}
|
||||
.setPositiveButton(getString(android.R.string.ok), (dialog, id) -> {
|
||||
float speed = PodcastPlaybackService.PLAYBACK_SPEEDS[numberPicker.getValue()];
|
||||
eventBus.post(new SpeedPodcast(speed));
|
||||
dialog.cancel();
|
||||
})
|
||||
.setNegativeButton(getString(android.R.string.cancel), (dialog, id) -> dialog.cancel())
|
||||
.setView(numberPicker);
|
||||
|
||||
// create alert dialog
|
||||
|
@ -501,4 +407,232 @@ public class PodcastFragment extends Fragment {
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private MediaControllerCompat.Callback controllerCallback =
|
||||
new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onMetadataChanged(MediaMetadataCompat metadata) {
|
||||
Log.v(TAG, "onMetadataChanged() called with: metadata = [" + metadata + "]");
|
||||
displayMetadata(metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat stateCompat) {
|
||||
Log.v(TAG, "onPlaybackStateChanged() called with: state = [" + stateCompat + "]");
|
||||
displayPlaybackState(stateCompat);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private void displayMetadata(MediaMetadataCompat metadata) {
|
||||
String title = metadata.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
|
||||
String author = metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
|
||||
if(author != null) {
|
||||
title += " - " + author;
|
||||
}
|
||||
tvTitle.setText(title);
|
||||
tvTitleSlider.setText(title);
|
||||
|
||||
String favIconUrl = metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI);
|
||||
if(favIconUrl != null) {
|
||||
Log.d(TAG, "currentPlayingPodcastReceived: " + favIconUrl);
|
||||
DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().
|
||||
showImageOnLoading(R.drawable.default_feed_icon_light).
|
||||
showImageForEmptyUri(R.drawable.default_feed_icon_light).
|
||||
showImageOnFail(R.drawable.default_feed_icon_light).
|
||||
build();
|
||||
ImageLoader.getInstance().displayImage(favIconUrl, imgFavIcon, displayImageOptions);
|
||||
}
|
||||
|
||||
PlaybackService.VideoType mediaType = PlaybackService.VideoType.valueOf(metadata.getString(CURRENT_PODCAST_MEDIA_TYPE));
|
||||
|
||||
if("-1".equals(metadata.getString(METADATA_KEY_MEDIA_ID))) {
|
||||
// Collapse if no podcast is loaded
|
||||
eventBus.post(new CollapsePodcastView());
|
||||
} else {
|
||||
// Expand if podcast is loaded
|
||||
eventBus.post(new ExpandPodcastView());
|
||||
|
||||
if (mediaType == PlaybackService.VideoType.Video) {
|
||||
Log.v(TAG, "init regular video");
|
||||
tryOpeningPictureinPictureMode();
|
||||
}
|
||||
}
|
||||
|
||||
maxPositionInMillis = metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
|
||||
}
|
||||
|
||||
private void displayPlaybackState(PlaybackStateCompat stateCompat) {
|
||||
boolean showPlayingButton = false;
|
||||
|
||||
int state = stateCompat.getState();
|
||||
if(PlaybackStateCompat.STATE_PLAYING == state ||
|
||||
PlaybackStateCompat.STATE_BUFFERING == state ||
|
||||
PlaybackStateCompat.STATE_CONNECTING == state ||
|
||||
PlaybackStateCompat.STATE_PAUSED == state) {
|
||||
//Log.v(TAG, "State is: " + state);
|
||||
|
||||
if (PlaybackStateCompat.STATE_PAUSED != state) {
|
||||
showPlayingButton = true;
|
||||
}
|
||||
}
|
||||
|
||||
int drawableId = showPlayingButton ? R.drawable.ic_action_pause : R.drawable.ic_action_play;
|
||||
int contentDescriptionId = showPlayingButton ? R.string.content_desc_pause : R.string.content_desc_play;
|
||||
|
||||
// If attached to context..
|
||||
if(mActivity != null) {
|
||||
btnPlayPausePodcast.setImageResource(drawableId);
|
||||
btnPlayPausePodcast.setContentDescription(getString(contentDescriptionId));
|
||||
btnPlayPausePodcastSlider.setImageResource(drawableId);
|
||||
}
|
||||
|
||||
currentPositionInMillis = stateCompat.getPosition();
|
||||
|
||||
updateProgressBar(state);
|
||||
}
|
||||
|
||||
private void updateProgressBar(@PlaybackStateCompat.State int state) {
|
||||
int hours = (int)(currentPositionInMillis / (1000*60*60));
|
||||
int minutes = (int)(currentPositionInMillis % (1000*60*60)) / (1000*60);
|
||||
int seconds = (int) ((currentPositionInMillis % (1000*60*60)) % (1000*60) / 1000);
|
||||
minutes += hours * 60;
|
||||
tvFrom.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
tvFromSlider.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
|
||||
hours = (int)(maxPositionInMillis / (1000*60*60));
|
||||
minutes = (int)(maxPositionInMillis % (1000*60*60)) / (1000*60);
|
||||
seconds = (int) ((maxPositionInMillis % (1000*60*60)) % (1000*60) / 1000);
|
||||
minutes += hours * 60;
|
||||
tvTo.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
tvToSlider.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
|
||||
if(state == PlaybackStateCompat.STATE_CONNECTING) {
|
||||
sb_progress.setVisibility(View.INVISIBLE);
|
||||
pb_progress2.setVisibility(View.VISIBLE);
|
||||
|
||||
pb_progress.setIndeterminate(true);
|
||||
} else {
|
||||
double progress = ((double) currentPositionInMillis / (double) maxPositionInMillis) * 100d;
|
||||
|
||||
if(!blockSeekbarUpdate) {
|
||||
sb_progress.setVisibility(View.VISIBLE);
|
||||
pb_progress2.setVisibility(View.INVISIBLE);
|
||||
sb_progress.setProgress((int) progress);
|
||||
}
|
||||
|
||||
pb_progress.setIndeterminate(false);
|
||||
pb_progress.setProgress((int) progress);
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.android.com/guide/topics/media-apps/audio-app/building-a-mediabrowser-client#customize-mediabrowser-connectioncallback
|
||||
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
|
||||
new MediaBrowserCompat.ConnectionCallback() {
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.d(TAG, "onConnected() called");
|
||||
|
||||
// Get the token for the MediaSession
|
||||
MediaSessionCompat.Token token = mMediaBrowser.getSessionToken();
|
||||
|
||||
try {
|
||||
// Create a MediaControllerCompat
|
||||
MediaControllerCompat mediaController = new MediaControllerCompat(mActivity, token);
|
||||
|
||||
// Save the controller
|
||||
MediaControllerCompat.setMediaController(mActivity, mediaController);
|
||||
|
||||
// Register a Callback to stay in sync
|
||||
mediaController.registerCallback(controllerCallback);
|
||||
|
||||
// Display the initial state
|
||||
MediaMetadataCompat metadata = mediaController.getMetadata();
|
||||
PlaybackStateCompat pbState = mediaController.getPlaybackState();
|
||||
displayMetadata(metadata);
|
||||
displayPlaybackState(pbState);
|
||||
|
||||
// Finish building the UI
|
||||
//buildTransportControls();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Connecting to podcast service failed!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended() {
|
||||
Log.d(TAG, "onConnectionSuspended() called");
|
||||
// The Service has crashed. Disable transport controls until it automatically reconnects
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed() {
|
||||
Log.e(TAG, "onConnectionFailed() called");
|
||||
// The Service has refused our connection
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
public void getCurrentPlaybackSpeed(final OnPlaybackSpeedCallback callback) {
|
||||
MediaControllerCompat.getMediaController(mActivity)
|
||||
.sendCommand(PLAYBACK_SPEED_FLOAT,
|
||||
null,
|
||||
new ResultReceiver(new Handler()) {
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
callback.currentPlaybackReceived(resultData.getFloat(PLAYBACK_SPEED_FLOAT));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public boolean getCurrentPlayingPodcast(final OnCurrentPlayingPodcastCallback callback) {
|
||||
if(mMediaBrowser != null && mMediaBrowser.isConnected()) {
|
||||
MediaControllerCompat.getMediaController(mActivity)
|
||||
.sendCommand(CURRENT_PODCAST_ITEM_MEDIA_ITEM,
|
||||
null,
|
||||
new ResultReceiver(new Handler()) {
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
callback.currentPlayingPodcastReceived((MediaItem) resultData.getSerializable(CURRENT_PODCAST_ITEM_MEDIA_ITEM));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private MediaControllerCompat.Callback mediaControllerCallback = new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onSessionReady() {
|
||||
Log.d(TAG, "onSessionReady() called");
|
||||
super.onSessionReady();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionDestroyed() {
|
||||
Log.d(TAG, "onSessionDestroyed() called");
|
||||
super.onSessionDestroyed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionEvent(String event, Bundle extras) {
|
||||
Log.d(TAG, "onSessionEvent() called with: event = [" + event + "], extras = [" + extras + "]");
|
||||
super.onSessionEvent(event, extras);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public interface OnPlaybackSpeedCallback {
|
||||
void currentPlaybackReceived(float playbackSpeed);
|
||||
}
|
||||
|
||||
/*
|
||||
public interface OnCurrentPlayingPodcastCallback {
|
||||
void currentPlayingPodcastReceived(MediaItem mediaItem);
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
package de.luhmer.owncloudnewsreader;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.content.ComponentName;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.nextcloud.android.sso.helper.VersionCheckHelper;
|
||||
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
|
||||
|
||||
|
@ -31,27 +24,21 @@ import org.greenrobot.eventbus.Subscribe;
|
|||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import de.luhmer.owncloudnewsreader.ListView.SubscriptionExpandableListAdapter;
|
||||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.database.model.RssItem;
|
||||
import de.luhmer.owncloudnewsreader.di.ApiProvider;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.CollapsePodcastView;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.ExpandPodcastView;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.PodcastCompletedEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.RegisterVideoOutput;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.RegisterYoutubeOutput;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.VideoDoubleClicked;
|
||||
import de.luhmer.owncloudnewsreader.helper.PostDelayHandler;
|
||||
import de.luhmer.owncloudnewsreader.helper.SizeAnimator;
|
||||
import de.luhmer.owncloudnewsreader.helper.ThemeChooser;
|
||||
import de.luhmer.owncloudnewsreader.interfaces.IPlayPausePodcastClicked;
|
||||
import de.luhmer.owncloudnewsreader.model.MediaItem;
|
||||
|
@ -59,15 +46,11 @@ import de.luhmer.owncloudnewsreader.model.PodcastItem;
|
|||
import de.luhmer.owncloudnewsreader.notification.NextcloudNotificationManager;
|
||||
import de.luhmer.owncloudnewsreader.services.PodcastDownloadService;
|
||||
import de.luhmer.owncloudnewsreader.services.PodcastPlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.services.podcast.PlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.ssl.MemorizingTrustManager;
|
||||
import de.luhmer.owncloudnewsreader.view.PodcastSlidingUpPanelLayout;
|
||||
import de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout;
|
||||
import de.luhmer.owncloudnewsreader.widget.WidgetProvider;
|
||||
|
||||
import static de.luhmer.owncloudnewsreader.Constants.MIN_NEXTCLOUD_FILES_APP_VERSION_CODE;
|
||||
import static de.luhmer.owncloudnewsreader.services.PodcastPlaybackService.CURRENT_PODCAST_ITEM_MEDIA_ITEM;
|
||||
import static de.luhmer.owncloudnewsreader.services.PodcastPlaybackService.PLAYBACK_SPEED_FLOAT;
|
||||
|
||||
public class PodcastFragmentActivity extends AppCompatActivity implements IPlayPausePodcastClicked {
|
||||
|
||||
|
@ -78,32 +61,17 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
@Inject protected MemorizingTrustManager mMTM;
|
||||
@Inject protected PostDelayHandler mPostDelayHandler;
|
||||
|
||||
private MediaBrowserCompat mMediaBrowser;
|
||||
private EventBus eventBus;
|
||||
private PodcastFragment mPodcastFragment;
|
||||
private int appHeight;
|
||||
private int appWidth;
|
||||
|
||||
@BindView(R.id.videoPodcastSurfaceWrapper)
|
||||
protected ZoomableRelativeLayout rlVideoPodcastSurfaceWrapper;
|
||||
@BindView(R.id.sliding_layout)
|
||||
protected PodcastSlidingUpPanelLayout sliding_layout;
|
||||
//YouTubePlayerFragment youtubeplayerfragment;
|
||||
|
||||
private boolean currentlyPlaying = false;
|
||||
private boolean showedYoutubeFeatureNotAvailableDialog = false;
|
||||
private boolean videoViewInitialized = false;
|
||||
private boolean isVideoViewVisible = true;
|
||||
|
||||
|
||||
private static final int animationTime = 300; //Milliseconds
|
||||
private boolean isFullScreen = false;
|
||||
private float scaleFactor = 1;
|
||||
private boolean useAnimation = false;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
//Log.v(TAG, "onCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
((NewsReaderApplication) getApplication()).getAppComponent().injectActivity(this);
|
||||
|
||||
ThemeChooser.chooseTheme(this);
|
||||
|
@ -117,6 +85,18 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
mPostDelayHandler.stopRunningPostDelayHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
//Log.v(TAG, "onPostCreate() called with: savedInstanceState = [" + savedInstanceState + "]");
|
||||
super.onPostCreate(savedInstanceState);
|
||||
|
||||
eventBus = EventBus.getDefault();
|
||||
|
||||
ButterKnife.bind(this);
|
||||
|
||||
updatePodcastView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
@ -124,50 +104,11 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
mMTM.bindDisplayActivity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
eventBus = EventBus.getDefault();
|
||||
|
||||
ButterKnife.bind(this);
|
||||
|
||||
//youtubeplayerfragment = (YouTubePlayerFragment)getFragmentManager().findFragmentById(R.id.youtubeplayerfragment);
|
||||
|
||||
|
||||
ViewTreeObserver vto = rlVideoPodcastSurfaceWrapper.getViewTreeObserver();
|
||||
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
rlVideoPodcastSurfaceWrapper.readVideoPosition();
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
}
|
||||
});
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.setVisibility(View.INVISIBLE);
|
||||
|
||||
updatePodcastView();
|
||||
|
||||
/*
|
||||
if (isMyServiceRunning(PodcastPlaybackService.class, this)) {
|
||||
Intent intent = new Intent(this, PodcastPlaybackService.class);
|
||||
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
*/
|
||||
mMediaBrowser = new MediaBrowserCompat(this,
|
||||
new ComponentName(this, PodcastPlaybackService.class),
|
||||
mConnectionCallbacks,
|
||||
null); // optional Bundle
|
||||
|
||||
super.onPostCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
mMTM.unbindDisplayActivity(this);
|
||||
|
||||
super.onStop();
|
||||
|
||||
mMediaBrowser.disconnect();
|
||||
}
|
||||
|
||||
|
||||
|
@ -197,33 +138,25 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
protected void onResume() {
|
||||
eventBus.register(this);
|
||||
|
||||
if (mMediaBrowser != null && !mMediaBrowser.isConnected()) {
|
||||
sliding_layout.setPanelHeight(0);
|
||||
sliding_layout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
}
|
||||
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
Log.d(TAG, "onPause");
|
||||
eventBus.unregister(this);
|
||||
|
||||
|
||||
//TODO THIS IS NEVER REACHED!
|
||||
/*
|
||||
isVideoViewVisible = false;
|
||||
videoViewInitialized = false;
|
||||
|
||||
eventBus.post(new RegisterVideoOutput(null, null));
|
||||
eventBus.post(new RegisterYoutubeOutput(null, false));
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.setVisibility(View.GONE);
|
||||
rlVideoPodcastSurfaceWrapper.removeAllViews();
|
||||
*/
|
||||
|
||||
WidgetProvider.UpdateWidget(this);
|
||||
|
||||
|
||||
if (NextcloudNotificationManager.isUnreadRssCountNotificationVisible(this)) {
|
||||
DatabaseConnectionOrm dbConn = new DatabaseConnectionOrm(this);
|
||||
int count = Integer.parseInt(dbConn.getUnreadItemsCountForSpecificFolder(SubscriptionExpandableListAdapter.SPECIAL_FOLDERS.ALL_UNREAD_ITEMS));
|
||||
|
@ -251,41 +184,7 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
}
|
||||
*/
|
||||
|
||||
private final MediaBrowserCompat.ConnectionCallback mConnectionCallbacks =
|
||||
new MediaBrowserCompat.ConnectionCallback() {
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.d(TAG, "onConnected() called");
|
||||
|
||||
// Get the token for the MediaSession
|
||||
MediaSessionCompat.Token token = mMediaBrowser.getSessionToken();
|
||||
|
||||
try {
|
||||
// Create a MediaControllerCompat
|
||||
MediaControllerCompat mediaController = new MediaControllerCompat(PodcastFragmentActivity.this, token);
|
||||
|
||||
// Save the controller
|
||||
MediaControllerCompat.setMediaController(PodcastFragmentActivity.this, mediaController);
|
||||
|
||||
// Finish building the UI
|
||||
//buildTransportControls();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Connecting to podcast service failed!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionSuspended() {
|
||||
Log.d(TAG, "onConnectionSuspended() called");
|
||||
// The Service has crashed. Disable transport controls until it automatically reconnects
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionFailed() {
|
||||
Log.e(TAG, "onConnectionFailed() called");
|
||||
// The Service has refused our connection
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
private void buildTransportControls() {
|
||||
|
@ -306,15 +205,6 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
// Register a Callback to stay in sync
|
||||
mediaController.registerCallback(controllerCallback);
|
||||
}
|
||||
|
||||
MediaControllerCompat.Callback controllerCallback =
|
||||
new MediaControllerCompat.Callback() {
|
||||
@Override
|
||||
public void onMetadataChanged(MediaMetadataCompat metadata) {}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(PlaybackStateCompat state) {}
|
||||
};
|
||||
*/
|
||||
|
||||
public PodcastSlidingUpPanelLayout getSlidingLayout() {
|
||||
|
@ -323,301 +213,62 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
|
||||
public boolean handlePodcastBackPressed() {
|
||||
if(mPodcastFragment != null && sliding_layout.getPanelState().equals(SlidingUpPanelLayout.PanelState.EXPANDED)) {
|
||||
if (!mPodcastFragment.onBackPressed())
|
||||
sliding_layout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
sliding_layout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void updatePodcastView() {
|
||||
|
||||
if(mPodcastFragment == null) {
|
||||
mPodcastFragment = PodcastFragment.newInstance();
|
||||
}
|
||||
/*
|
||||
if(mPodcastFragment != null) {
|
||||
getSupportFragmentManager().beginTransaction().remove(mPodcastFragment).commitAllowingStateLoss();
|
||||
}
|
||||
*/
|
||||
|
||||
mPodcastFragment = PodcastFragment.newInstance();
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.podcast_frame, mPodcastFragment)
|
||||
.commitAllowingStateLoss();
|
||||
|
||||
if(!currentlyPlaying)
|
||||
sliding_layout.setPanelHeight(0);
|
||||
collapsePodcastView();
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(CollapsePodcastView event) {
|
||||
Log.v(TAG, "onEvent(CollapsePodcastView) called with: event = [" + event + "]");
|
||||
collapsePodcastView();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(UpdatePodcastStatusEvent podcast) {
|
||||
boolean playStateChanged = currentlyPlaying;
|
||||
//If file is loaded or preparing and podcast is paused/not running expand the view
|
||||
currentlyPlaying = podcast.getStatus() == PlaybackService.Status.PLAYING
|
||||
|| podcast.getStatus() == PlaybackService.Status.PAUSED;
|
||||
|
||||
//Check if state was changed
|
||||
playStateChanged = playStateChanged != currentlyPlaying;
|
||||
|
||||
// If preparing or state changed and is now playing or paused
|
||||
if(podcast.getStatus() == PlaybackService.Status.PREPARING
|
||||
|| (playStateChanged
|
||||
&& (podcast.getStatus() == PlaybackService.Status.PLAYING
|
||||
|| podcast.getStatus() == PlaybackService.Status.PAUSED
|
||||
|| podcast.getStatus() == PlaybackService.Status.STOPPED))) {
|
||||
//Expand view
|
||||
sliding_layout.setPanelHeight((int) dipToPx(68));
|
||||
Log.v(TAG, "expanding podcast view!");
|
||||
} else if(playStateChanged) {
|
||||
//Hide view
|
||||
sliding_layout.setPanelHeight(0);
|
||||
currentlyPlaying = false;
|
||||
Log.v(TAG, "collapsing podcast view!");
|
||||
}
|
||||
|
||||
if (podcast.isVideoFile() && podcast.getVideoType() == PlaybackService.VideoType.Video) {
|
||||
if ((!isVideoViewVisible || !videoViewInitialized) && rlVideoPodcastSurfaceWrapper.isPositionReady()) {
|
||||
rlVideoPodcastSurfaceWrapper.removeAllViews();
|
||||
videoViewInitialized = true;
|
||||
isVideoViewVisible = true;
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.setVisibility(View.VISIBLE);
|
||||
//AlphaAnimator.AnimateVisibilityChange(rlVideoPodcastSurfaceWrapper, View.VISIBLE);
|
||||
public void onEvent(ExpandPodcastView event) {
|
||||
Log.v(TAG, "onEvent(ExpandPodcastView) called with: event = [" + event + "]");
|
||||
expandPodcastView();
|
||||
}
|
||||
|
||||
|
||||
SurfaceView surfaceView = new SurfaceView(this);
|
||||
surfaceView.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT));
|
||||
rlVideoPodcastSurfaceWrapper.addView(surfaceView);
|
||||
|
||||
eventBus.post(new RegisterVideoOutput(surfaceView, rlVideoPodcastSurfaceWrapper));
|
||||
togglePodcastVideoViewAnimation();
|
||||
}
|
||||
} else if(podcast.getVideoType() == PlaybackService.VideoType.YouTube) {
|
||||
if(BuildConfig.FLAVOR.equals("extra")) {
|
||||
if (!videoViewInitialized) {
|
||||
isVideoViewVisible = true;
|
||||
videoViewInitialized = true;
|
||||
rlVideoPodcastSurfaceWrapper.removeAllViews();
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.setVisibility(View.VISIBLE);
|
||||
|
||||
togglePodcastVideoViewAnimation();
|
||||
|
||||
final int YOUTUBE_CONTENT_VIEW_ID = 10101010;
|
||||
FrameLayout frame = new FrameLayout(this);
|
||||
frame.setId(YOUTUBE_CONTENT_VIEW_ID);
|
||||
rlVideoPodcastSurfaceWrapper.addView(frame);
|
||||
//setContentView(frame, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
||||
|
||||
YoutubePlayerManager.StartYoutubePlayer(this, YOUTUBE_CONTENT_VIEW_ID, eventBus, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
togglePodcastVideoViewAnimation();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if(!showedYoutubeFeatureNotAvailableDialog) {
|
||||
showedYoutubeFeatureNotAvailableDialog = true;
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.warning))
|
||||
.setMessage(R.string.dialog_feature_not_available)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(getString(android.R.string.ok), null)
|
||||
.show();
|
||||
}
|
||||
} else {
|
||||
isVideoViewVisible = false;
|
||||
videoViewInitialized = false;
|
||||
|
||||
eventBus.post(new RegisterVideoOutput(null, null));
|
||||
eventBus.post(new RegisterYoutubeOutput(null, false));
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.setVisibility(View.GONE);
|
||||
//AlphaAnimator.AnimateVisibilityChange(rlVideoPodcastSurfaceWrapper, View.GONE);
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.removeAllViews();
|
||||
}
|
||||
private void collapsePodcastView() {
|
||||
sliding_layout.setPanelHeight(0);
|
||||
}
|
||||
|
||||
private void expandPodcastView() {
|
||||
sliding_layout.setPanelHeight((int) dipToPx(68));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(PodcastCompletedEvent podcastCompletedEvent) {
|
||||
sliding_layout.setPanelHeight(0);
|
||||
collapsePodcastView();
|
||||
sliding_layout.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
|
||||
currentlyPlaying = false;
|
||||
//currentlyPlaying = false;
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(VideoDoubleClicked videoDoubleClicked) {
|
||||
appHeight = getWindow().getDecorView().findViewById(android.R.id.content).getHeight();
|
||||
appWidth = getWindow().getDecorView().findViewById(android.R.id.content).getWidth();
|
||||
|
||||
if(isFullScreen) {
|
||||
rlVideoPodcastSurfaceWrapper.setDisableScale(false);
|
||||
togglePodcastVideoViewAnimation();
|
||||
|
||||
//showSystemUI();
|
||||
} else {
|
||||
//hideSystemUI();
|
||||
|
||||
rlVideoPodcastSurfaceWrapper.setDisableScale(true);
|
||||
//oldScaleFactor = rlVideoPodcastSurfaceWrapper.getScaleFactor();
|
||||
|
||||
final View view = rlVideoPodcastSurfaceWrapper;
|
||||
|
||||
final float oldHeight = view.getLayoutParams().height;
|
||||
final float oldWidth = view.getLayoutParams().width;
|
||||
|
||||
|
||||
//view.setPivotX(oldWidth/2);
|
||||
//view.setPivotY(oldHeight/2);
|
||||
|
||||
/*
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
float width = display.getWidth(); // deprecated
|
||||
float height = display.getHeight(); // deprecated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
scaleFactor = appWidth / (float) view.getWidth();
|
||||
float newHeightTemp = oldHeight * scaleFactor;
|
||||
float newWidthTemp = oldWidth * scaleFactor;
|
||||
|
||||
//view.animate().scaleX(scaleFactor).scaleY(scaleFactor).setDuration(100);
|
||||
//scaleView(view, 1f, scaleFactor, 1f, scaleFactor);
|
||||
|
||||
|
||||
if(newHeightTemp > appHeight) { //Could happen on Tablets or in Landscape Mode
|
||||
scaleFactor = appHeight / (float) view.getHeight();
|
||||
newHeightTemp = oldHeight * scaleFactor;
|
||||
newWidthTemp = oldWidth * scaleFactor;
|
||||
}
|
||||
|
||||
|
||||
final float newHeight = newHeightTemp;
|
||||
final float newWidth = newWidthTemp;
|
||||
float newXPosition = rlVideoPodcastSurfaceWrapper.getVideoXPosition() + (int) getResources().getDimension(R.dimen.activity_vertical_margin);// (appWidth / 2) + dipToPx(10);
|
||||
float newYPosition = (appHeight/2) + ((newHeight/2) - oldHeight);
|
||||
|
||||
useAnimation = true;
|
||||
|
||||
view.animate().x(newXPosition).y(newYPosition).setDuration(animationTime).setListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animator) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animator) {
|
||||
if(useAnimation) {
|
||||
view.startAnimation(new SizeAnimator(view, newWidth, newHeight, oldWidth, oldHeight, animationTime).sizeAnimator);
|
||||
}
|
||||
useAnimation = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animator) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animator) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//rlVideoPodcastSurfaceWrapper.animate().scaleX(scaleFactor).scaleY(scaleFactor).x(newXPosition).y(newYPosition).setDuration(500).setListener(onResizeListener);
|
||||
//surfaceView.animate().scaleX(scaleFactor).scaleY(scaleFactor).setDuration(500);
|
||||
|
||||
//oldScaleFactor
|
||||
//scaleFactor = dipToPx(oldWidth) / newWidth;
|
||||
//scaleFactor = (1/oldScaleFactor) * dipToPx(oldWidth) / newWidth;
|
||||
//scaleFactor = oldWidth / newWidth;
|
||||
|
||||
scaleFactor = 1/scaleFactor;
|
||||
}
|
||||
|
||||
isFullScreen = !isFullScreen;
|
||||
}
|
||||
|
||||
|
||||
public void togglePodcastVideoViewAnimation() {
|
||||
boolean isLeftSliderOpen = false;
|
||||
|
||||
if(this instanceof NewsReaderListActivity && ((NewsReaderListActivity) this).drawerLayout != null) {
|
||||
isLeftSliderOpen = ((NewsReaderListActivity) this).drawerLayout.isDrawerOpen(GravityCompat.START);
|
||||
}
|
||||
|
||||
int podcastMediaControlHeightDp = pxToDp((int) getResources().getDimension(R.dimen.podcast_media_control_height));
|
||||
|
||||
if(sliding_layout.getPanelState().equals(SlidingUpPanelLayout.PanelState.EXPANDED)) { //On Tablets
|
||||
animateToPosition(podcastMediaControlHeightDp);
|
||||
} else if(isLeftSliderOpen) {
|
||||
animateToPosition(0);
|
||||
} else {
|
||||
animateToPosition(64);
|
||||
}
|
||||
}
|
||||
|
||||
public static int pxToDp(int px)
|
||||
{
|
||||
public static int pxToDp(int px) {
|
||||
return (int) (px / Resources.getSystem().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
public void animateToPosition(final int yPosition) {
|
||||
appHeight = getWindow().getDecorView().findViewById(android.R.id.content).getHeight();
|
||||
appWidth = getWindow().getDecorView().findViewById(android.R.id.content).getWidth();
|
||||
|
||||
final View view = rlVideoPodcastSurfaceWrapper; //surfaceView
|
||||
|
||||
if(scaleFactor != 1) {
|
||||
int oldHeight = view.getLayoutParams().height;
|
||||
int oldWidth = view.getLayoutParams().width;
|
||||
int newHeight = view.getLayoutParams().height *= scaleFactor;
|
||||
int newWidth = view.getLayoutParams().width *= scaleFactor;
|
||||
scaleFactor = 1;
|
||||
|
||||
Animation animator = new SizeAnimator(view, newWidth, newHeight, oldWidth, oldHeight, animationTime).sizeAnimator;
|
||||
animator.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
animateToPosition(yPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
|
||||
}
|
||||
});
|
||||
view.startAnimation(animator);
|
||||
} else {
|
||||
int absoluteYPosition = appHeight - view.getHeight() - (int) getResources().getDimension(R.dimen.activity_vertical_margin) - (int) dipToPx(yPosition);
|
||||
float xPosition = rlVideoPodcastSurfaceWrapper.getVideoXPosition();
|
||||
|
||||
//TODO podcast video is only working for newer android versions
|
||||
view.animate().x(xPosition).y(absoluteYPosition).setDuration(animationTime);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
int height = (int)(view.getHeight() * scaleFactor);
|
||||
int width = (int)(view.getWidth() * scaleFactor);
|
||||
view.setScaleX(oldScaleFactor);
|
||||
view.setScaleY(oldScaleFactor);
|
||||
view.getLayoutParams().height = height;
|
||||
view.getLayoutParams().width = width;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
private float dipToPx(float dip) {
|
||||
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, getResources().getDisplayMetrics());
|
||||
}
|
||||
|
@ -628,9 +279,13 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
intent.putExtra(PodcastPlaybackService.MEDIA_ITEM, mediaItem);
|
||||
startService(intent);
|
||||
|
||||
/*
|
||||
if(!mMediaBrowser.isConnected()) {
|
||||
mMediaBrowser.connect();
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
//bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||
|
||||
}
|
||||
|
@ -642,76 +297,58 @@ public class PodcastFragmentActivity extends AppCompatActivity implements IPlayP
|
|||
File file = new File(PodcastDownloadService.getUrlToPodcastFile(this, podcastItem.link, false));
|
||||
if(file.exists()) {
|
||||
podcastItem.link = file.getAbsolutePath();
|
||||
|
||||
openMediaItem(podcastItem);
|
||||
} else if(!podcastItem.offlineCached) {
|
||||
|
||||
AlertDialog.Builder alertDialog = new AlertDialog.Builder(this)
|
||||
.setNegativeButton("Abort", null)
|
||||
.setNeutralButton("Download", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
PodcastDownloadService.startPodcastDownload(PodcastFragmentActivity.this, podcastItem);
|
||||
.setTitle("Podcast");
|
||||
|
||||
Toast.makeText(PodcastFragmentActivity.this, "Starting download of podcast. Please wait..", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
})
|
||||
.setTitle("Podcast")
|
||||
.setMessage("Choose if you want to download or stream the selected podcast");
|
||||
|
||||
|
||||
|
||||
alertDialog.setPositiveButton("Stream", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
openMediaItem(podcastItem);
|
||||
}
|
||||
});
|
||||
if("youtube".equals(podcastItem.mimeType)) {
|
||||
alertDialog.setPositiveButton("Open Youtube", (dialogInterface, i) -> openYoutube(podcastItem));
|
||||
} else {
|
||||
alertDialog.setNeutralButton("Download", (dialogInterface, i) -> {
|
||||
PodcastDownloadService.startPodcastDownload(PodcastFragmentActivity.this, podcastItem);
|
||||
Toast.makeText(PodcastFragmentActivity.this, "Starting download of podcast. Please wait..", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
alertDialog.setPositiveButton("Stream", (dialogInterface, i) -> openMediaItem(podcastItem));
|
||||
alertDialog.setMessage("Choose if you want to download or stream the selected podcast");
|
||||
}
|
||||
|
||||
alertDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void pausePodcast() {
|
||||
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this).getTransportControls().pause();
|
||||
}
|
||||
|
||||
public void getCurrentPlaybackSpeed(final OnPlaybackSpeedCallback callback) {
|
||||
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this)
|
||||
.sendCommand(PLAYBACK_SPEED_FLOAT,
|
||||
null,
|
||||
new ResultReceiver(new Handler()) {
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
callback.currentPlaybackReceived(resultData.getFloat(PLAYBACK_SPEED_FLOAT));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean getCurrentPlayingPodcast(final OnCurrentPlayingPodcastCallback callback) {
|
||||
if(mMediaBrowser != null && mMediaBrowser.isConnected()) {
|
||||
MediaControllerCompat.getMediaController(PodcastFragmentActivity.this)
|
||||
.sendCommand(CURRENT_PODCAST_ITEM_MEDIA_ITEM,
|
||||
null,
|
||||
new ResultReceiver(new Handler()) {
|
||||
@Override
|
||||
protected void onReceiveResult(int resultCode, Bundle resultData) {
|
||||
callback.currentPlayingPodcastReceived((MediaItem) resultData.getSerializable(CURRENT_PODCAST_ITEM_MEDIA_ITEM));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
private void openYoutube(PodcastItem podcastItem) {
|
||||
Log.e(TAG, podcastItem.link);
|
||||
String youtubeVideoID = getVideoIdFromYoutubeUrl(podcastItem.link);
|
||||
if(youtubeVideoID == null) {
|
||||
Toast.makeText(this, "Failed to extract youtube video id for url: " + podcastItem.link + ". Please report this issue.", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
Intent appIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:" + youtubeVideoID));
|
||||
Intent webIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.youtube.com/watch?v=" + podcastItem.link));
|
||||
try {
|
||||
startActivity(appIntent);
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
startActivity(webIntent);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnPlaybackSpeedCallback {
|
||||
void currentPlaybackReceived(float playbackSpeed);
|
||||
public String getVideoIdFromYoutubeUrl(String url){
|
||||
String videoId = null;
|
||||
String regex = "http(?:s)?:\\/\\/(?:m.)?(?:www\\.)?youtu(?:\\.be\\/|be\\.com\\/(?:watch\\?(?:feature=youtu.be\\&)?v=|v\\/|embed\\/|user\\/(?:[\\w#]+\\/)+))([^&#?\\n]+)";
|
||||
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(url);
|
||||
if(matcher.find()){
|
||||
videoId = matcher.group(1);
|
||||
}
|
||||
return videoId;
|
||||
}
|
||||
|
||||
public interface OnCurrentPlayingPodcastCallback {
|
||||
void currentPlayingPodcastReceived(MediaItem mediaItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,29 +2,27 @@ package de.luhmer.owncloudnewsreader.adapter;
|
|||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
import de.luhmer.owncloudnewsreader.SettingsActivity;
|
||||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.database.model.RssItem;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.PodcastCompletedEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent;
|
||||
import de.luhmer.owncloudnewsreader.helper.AsyncTaskHelper;
|
||||
import de.luhmer.owncloudnewsreader.helper.PostDelayHandler;
|
||||
import de.luhmer.owncloudnewsreader.helper.StopWatch;
|
||||
|
@ -122,6 +120,9 @@ public class NewsListRecyclerAdapter extends RecyclerView.Adapter {
|
|||
this.cachedPages = cachedPages;
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO right now this is not working anymore.. We need to use the MediaSession here..
|
||||
// Not sure if this is the cleanest solution though..
|
||||
@Subscribe
|
||||
public void onEvent(UpdatePodcastStatusEvent podcast) {
|
||||
if (podcast.isPlaying()) {
|
||||
|
@ -138,6 +139,7 @@ public class NewsListRecyclerAdapter extends RecyclerView.Adapter {
|
|||
Log.v(TAG, "Updating Listview - Podcast paused");
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(PodcastCompletedEvent podcastCompletedEvent) {
|
||||
|
@ -182,26 +184,33 @@ public class NewsListRecyclerAdapter extends RecyclerView.Adapter {
|
|||
|
||||
final ViewHolder holder = new ViewHolder(view, mPrefs);
|
||||
|
||||
holder.starImageView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
toggleStarredStateOfItem(holder);
|
||||
}
|
||||
});
|
||||
holder.starImageView.setOnClickListener(view1 -> toggleStarredStateOfItem(holder));
|
||||
|
||||
holder.setClickListener((RecyclerItemClickListener) activity);
|
||||
|
||||
holder.flPlayPausePodcastWrapper.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (holder.isPlaying()) {
|
||||
playPausePodcastClicked.pausePodcast();
|
||||
} else {
|
||||
playPausePodcastClicked.openPodcast(holder.getRssItem());
|
||||
}
|
||||
holder.flPlayPausePodcastWrapper.setOnClickListener(v -> {
|
||||
if (holder.isPlaying()) {
|
||||
playPausePodcastClicked.pausePodcast();
|
||||
} else {
|
||||
playPausePodcastClicked.openPodcast(holder.getRssItem());
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
// TODO implement option to delete cached podcasts (https://github.com/nextcloud/news-android/issues/742)
|
||||
holder.flPlayPausePodcastWrapper.setOnLongClickListener(v -> {
|
||||
// TODO check if cached..
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle("")
|
||||
.setMessage("")
|
||||
.setPositiveButton("", (dialog, which) -> {})
|
||||
.setNegativeButton("", (dialog, which) -> {})
|
||||
.create()
|
||||
.show();
|
||||
return false;
|
||||
});
|
||||
*/
|
||||
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,7 +226,7 @@ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickL
|
|||
public void setPlaying(boolean playing) {
|
||||
this.playing = playing;
|
||||
|
||||
int imageId = playing ? R.drawable.ic_action_pause : R.drawable.ic_action_play_arrow;
|
||||
int imageId = playing ? R.drawable.ic_action_pause : R.drawable.ic_action_play;
|
||||
int contentDescriptionId = playing ? R.string.content_desc_pause : R.string.content_desc_play;
|
||||
|
||||
String contentDescription = btnPlayPausePodcast.getContext().getString(contentDescriptionId);
|
||||
|
|
|
@ -13,9 +13,13 @@ import java.util.Collection;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import de.greenrobot.dao.query.LazyList;
|
||||
import de.greenrobot.dao.query.WhereCondition;
|
||||
import de.luhmer.owncloudnewsreader.Constants;
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderApplication;
|
||||
import de.luhmer.owncloudnewsreader.database.model.CurrentRssItemViewDao;
|
||||
import de.luhmer.owncloudnewsreader.database.model.DaoSession;
|
||||
import de.luhmer.owncloudnewsreader.database.model.Feed;
|
||||
|
@ -52,13 +56,16 @@ public class DatabaseConnectionOrm {
|
|||
};
|
||||
|
||||
private final String TAG = getClass().getCanonicalName();
|
||||
private static final String[] VIDEO_FORMATS = { "youtube", "video/mp4" };
|
||||
//private static final String[] VIDEO_FORMATS = { "youtube", "video/mp4" };
|
||||
private static final String[] VIDEO_FORMATS = { "video/mp4" };
|
||||
public enum SORT_DIRECTION { asc, desc }
|
||||
|
||||
private DaoSession daoSession;
|
||||
|
||||
private final static int PageSize = 100;
|
||||
|
||||
protected @Inject @Named("databaseFileName") String databasePath;
|
||||
|
||||
public void resetDatabase() {
|
||||
daoSession.getRssItemDao().deleteAll();
|
||||
daoSession.getFeedDao().deleteAll();
|
||||
|
@ -67,7 +74,10 @@ public class DatabaseConnectionOrm {
|
|||
}
|
||||
|
||||
public DatabaseConnectionOrm(Context context) {
|
||||
daoSession = DatabaseHelperOrm.getDaoSession(context);
|
||||
if(databasePath == null) {
|
||||
((NewsReaderApplication) context.getApplicationContext()).getAppComponent().injectDatabaseConnection(this);
|
||||
}
|
||||
daoSession = DatabaseHelperOrm.getDaoSession(context, databasePath);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -463,14 +473,15 @@ public class DatabaseConnectionOrm {
|
|||
|
||||
public static PodcastItem ParsePodcastItemFromRssItem(Context context, RssItem rssItem) {
|
||||
PodcastItem podcastItem = new PodcastItem();
|
||||
Feed feed = rssItem.getFeed();
|
||||
podcastItem.author = feed.getFeedTitle();// rssItem.getAuthor();
|
||||
podcastItem.itemId = rssItem.getId();
|
||||
podcastItem.title = rssItem.getTitle();
|
||||
podcastItem.link = rssItem.getEnclosureLink();
|
||||
podcastItem.mimeType = rssItem.getEnclosureMime();
|
||||
podcastItem.favIcon = rssItem.getFeed().getFaviconUrl();
|
||||
podcastItem.favIcon = feed.getFaviconUrl();
|
||||
|
||||
boolean isVideo = Arrays.asList(DatabaseConnectionOrm.VIDEO_FORMATS).contains(podcastItem.mimeType);
|
||||
podcastItem.isVideoPodcast = isVideo;
|
||||
podcastItem.isVideoPodcast = Arrays.asList(DatabaseConnectionOrm.VIDEO_FORMATS).contains(podcastItem.mimeType);
|
||||
|
||||
File file = new File(PodcastDownloadService.getUrlToPodcastFile(context, podcastItem.link, false));
|
||||
podcastItem.offlineCached = file.exists();
|
||||
|
|
|
@ -28,11 +28,9 @@ import de.luhmer.owncloudnewsreader.database.model.DaoMaster;
|
|||
import de.luhmer.owncloudnewsreader.database.model.DaoSession;
|
||||
|
||||
public class DatabaseHelperOrm {
|
||||
public static final String DATABASE_NAME_ORM = "OwncloudNewsReaderOrm.db";
|
||||
|
||||
private volatile static DaoSession daoSession;
|
||||
|
||||
public static DaoSession getDaoSession(Context context) {
|
||||
public static DaoSession getDaoSession(Context context, String DATABASE_NAME_ORM) {
|
||||
if(daoSession == null) {
|
||||
synchronized (DatabaseHelperOrm.class) {
|
||||
if(daoSession == null) {
|
||||
|
|
|
@ -51,6 +51,14 @@ public class ApiModule {
|
|||
return mApplication.getPackageName() + "_preferences";
|
||||
}
|
||||
|
||||
// Dagger will only look for methods annotated with @Provides
|
||||
@Provides
|
||||
@Named("databaseFileName")
|
||||
public String providesDatabaseFileName() {
|
||||
//return PreferenceManager.getDefaultSharedPreferencesName(mApplication);
|
||||
return "OwncloudNewsReaderOrm.db";
|
||||
}
|
||||
|
||||
/*
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
@ -16,6 +16,7 @@ import de.luhmer.owncloudnewsreader.SettingsActivity;
|
|||
import de.luhmer.owncloudnewsreader.SettingsFragment;
|
||||
import de.luhmer.owncloudnewsreader.SyncIntervalSelectorActivity;
|
||||
import de.luhmer.owncloudnewsreader.authentication.OwnCloudSyncAdapter;
|
||||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.services.SyncItemStateService;
|
||||
import de.luhmer.owncloudnewsreader.widget.WidgetProvider;
|
||||
|
||||
|
@ -46,4 +47,6 @@ public interface AppComponent {
|
|||
void injectService(OwnCloudSyncAdapter ownCloudSyncAdapter);
|
||||
|
||||
void injectWidget(WidgetProvider widgetProvider);
|
||||
|
||||
void injectDatabaseConnection(DatabaseConnectionOrm databaseConnectionOrm);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package de.luhmer.owncloudnewsreader.events.podcast;
|
||||
|
||||
public class CollapsePodcastView {
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package de.luhmer.owncloudnewsreader.events.podcast;
|
||||
|
||||
public class ExpandPodcastView {
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package de.luhmer.owncloudnewsreader.events.podcast;
|
||||
|
||||
public class RegisterYoutubeOutput {
|
||||
|
||||
public RegisterYoutubeOutput(Object youTubePlayer, boolean wasRestored) {
|
||||
this.youTubePlayer = youTubePlayer;
|
||||
this.wasRestored = wasRestored;
|
||||
}
|
||||
|
||||
public Object youTubePlayer; // (Type: com.google.android.youtube.player.YouTubePlayer;)
|
||||
public boolean wasRestored;
|
||||
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package de.luhmer.owncloudnewsreader.events.podcast;
|
||||
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.services.podcast.PlaybackService;
|
||||
|
||||
public class UpdatePodcastStatusEvent {
|
||||
|
@ -8,10 +10,10 @@ public class UpdatePodcastStatusEvent {
|
|||
private long max;
|
||||
private String author;
|
||||
private String title;
|
||||
private PlaybackService.Status status;
|
||||
private @PlaybackStateCompat.State int status;
|
||||
private PlaybackService.VideoType videoType;
|
||||
private long rssItemId;
|
||||
private float speed = -1;
|
||||
private float speed;
|
||||
|
||||
public long getRssItemId() {
|
||||
return rssItemId;
|
||||
|
@ -25,12 +27,12 @@ public class UpdatePodcastStatusEvent {
|
|||
return title;
|
||||
}
|
||||
|
||||
public PlaybackService.Status getStatus() {
|
||||
public @PlaybackStateCompat.State int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return status == PlaybackService.Status.PLAYING;
|
||||
return status == PlaybackStateCompat.STATE_PLAYING;
|
||||
}
|
||||
|
||||
public long getCurrent() {
|
||||
|
@ -47,7 +49,7 @@ public class UpdatePodcastStatusEvent {
|
|||
|
||||
public boolean isVideoFile() { return !(videoType == PlaybackService.VideoType.None); }
|
||||
|
||||
public UpdatePodcastStatusEvent(long current, long max, PlaybackService.Status status, String author, String title, PlaybackService.VideoType videoType, long rssItemId, float speed) {
|
||||
public UpdatePodcastStatusEvent(long current, long max, @PlaybackStateCompat.State int status, String author, String title, PlaybackService.VideoType videoType, long rssItemId, float speed) {
|
||||
this.current = current;
|
||||
this.max = max;
|
||||
this.status = status;
|
||||
|
|
|
@ -2,6 +2,10 @@ package de.luhmer.owncloudnewsreader.events.podcast;
|
|||
|
||||
public class WindPodcast {
|
||||
|
||||
public double toPositionInPercent;
|
||||
public double milliSeconds;
|
||||
|
||||
public WindPodcast(double milliSeconds) {
|
||||
this.milliSeconds = milliSeconds;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
package de.luhmer.owncloudnewsreader.helper;
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
|
||||
public class ThemeUtils {
|
||||
|
||||
private static final String TAG = ThemeUtils.class.getCanonicalName();
|
||||
|
||||
private ThemeUtils() {}
|
||||
|
||||
/**
|
||||
* Sets the color of the SearchView to {@code color} (cursor.
|
||||
* @param searchView
|
||||
*/
|
||||
public static void colorSearchViewCursorColor(SearchView searchView, @ColorInt int color) {
|
||||
try {
|
||||
Field searchTextViewRef = SearchView.class.getDeclaredField("mSearchSrcTextView");
|
||||
searchTextViewRef.setAccessible(true);
|
||||
Object searchAutoComplete = searchTextViewRef.get(searchView);
|
||||
|
||||
Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes");
|
||||
mCursorDrawableRes.setAccessible(true);
|
||||
mCursorDrawableRes.set(searchAutoComplete, R.drawable.cursor);
|
||||
|
||||
|
||||
// Set color of handle
|
||||
// https://stackoverflow.com/a/49555923
|
||||
|
||||
//get the pointer resource id
|
||||
Field textSelectHandleRef = TextView.class.getDeclaredField("mTextSelectHandleRes");
|
||||
textSelectHandleRef.setAccessible(true);
|
||||
int drawableResId = textSelectHandleRef.getInt(searchAutoComplete);
|
||||
|
||||
//get the editor
|
||||
Field editorRef = TextView.class.getDeclaredField("mEditor");
|
||||
editorRef.setAccessible(true);
|
||||
Object editor = editorRef.get(searchAutoComplete);
|
||||
|
||||
//tint drawable
|
||||
Drawable drawable = ContextCompat.getDrawable(searchView.getContext(), drawableResId);
|
||||
drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
|
||||
//set the drawable
|
||||
Field mSelectHandleCenter = editor.getClass().getDeclaredField("mSelectHandleCenter");
|
||||
mSelectHandleCenter.setAccessible(true);
|
||||
mSelectHandleCenter.set(editor, drawable);
|
||||
|
||||
Field mSelectHandleLeft = editor.getClass().getDeclaredField("mSelectHandleLeft");
|
||||
mSelectHandleLeft.setAccessible(true);
|
||||
mSelectHandleLeft.set(editor, drawable);
|
||||
|
||||
Field mSelectHandleRight = editor.getClass().getDeclaredField("mSelectHandleRight");
|
||||
mSelectHandleRight.setAccessible(true);
|
||||
mSelectHandleRight.set(editor, drawable);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Couldn't apply color to search view cursor", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -27,7 +27,9 @@ public class PodcastItem extends MediaItem {
|
|||
public static Integer DOWNLOAD_NOT_STARTED = -2;
|
||||
|
||||
|
||||
/*
|
||||
public boolean isYoutubeVideo() {
|
||||
return link.matches("^https?://(www.)?youtube.com/.*");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ public class NextcloudNotificationManager {
|
|||
PendingIntent pIntent = PendingIntent.getActivity(context, 0, intentNewsReader, 0);
|
||||
NotificationCompat.Builder mNotificationDownloadImages = new NotificationCompat.Builder(context, channelId)
|
||||
.setContentTitle(context.getResources().getString(R.string.app_name))
|
||||
.setContentText("Downloading images for offline usage")
|
||||
.setContentText(context.getString(R.string.notification_download_images_offline))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentIntent(pIntent)
|
||||
.setAutoCancel(true)
|
||||
|
@ -109,7 +109,7 @@ public class NextcloudNotificationManager {
|
|||
PendingIntent pIntent = PendingIntent.getActivity(context, 0, intentNewsReader, 0);
|
||||
NotificationCompat.Builder mNotificationWebPages = new NotificationCompat.Builder(context, channelId)
|
||||
.setContentTitle(context.getResources().getString(R.string.app_name))
|
||||
.setContentText("Downloading webpages for offline usage")
|
||||
.setContentText(context.getString(R.string.notification_download_articles_offline))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentIntent(pIntent)
|
||||
.setAutoCancel(true)
|
||||
|
@ -187,6 +187,8 @@ public class NextcloudNotificationManager {
|
|||
*/
|
||||
//.setUsesChronometer(true)
|
||||
.setContentTitle(description.getTitle())
|
||||
.setContentText(description.getSubtitle())
|
||||
.setSubText(description.getDescription())
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
//.setContentText(description.getSubtitle())
|
||||
//.setContentText(mediaMetadata.getText(MediaMetadataCompat.METADATA_KEY_ARTIST))
|
||||
|
@ -195,15 +197,18 @@ public class NextcloudNotificationManager {
|
|||
.setLargeIcon(bitmapIcon)
|
||||
.setContentIntent(controller.getSessionActivity())
|
||||
.setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setOnlyAlertOnce(true);
|
||||
|
||||
boolean isPlaying = controller.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
|
||||
builder.addAction(getPlayPauseAction(context, isPlaying));
|
||||
|
||||
// Make the transport controls visible on the lockscreen
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||
|
||||
builder.setStyle(new MediaStyle()
|
||||
//.setShowActionsInCompactView(0) // show only play/pause in compact view
|
||||
.setMediaSession(mediaSession.getSessionToken())
|
||||
.setShowActionsInCompactView(0)
|
||||
.setShowCancelButton(true)
|
||||
.setCancelButtonIntent(
|
||||
MediaButtonReceiver.buildMediaButtonPendingIntent(
|
||||
|
@ -214,7 +219,7 @@ public class NextcloudNotificationManager {
|
|||
}
|
||||
|
||||
private static NotificationCompat.Action getPlayPauseAction(Context context, boolean isPlaying) {
|
||||
int drawableId = isPlaying ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play;
|
||||
int drawableId = isPlaying ? R.drawable.ic_action_pause : R.drawable.ic_action_play;
|
||||
String actionText = isPlaying ? "Pause" : "Play"; // TODO extract as string resource
|
||||
|
||||
PendingIntent pendingIntent = MediaButtonReceiver.buildMediaButtonPendingIntent(context,
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.List;
|
|||
import java.util.Random;
|
||||
|
||||
import de.greenrobot.dao.query.LazyList;
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
import de.luhmer.owncloudnewsreader.async_tasks.DownloadImageHandler;
|
||||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.database.model.Feed;
|
||||
|
@ -170,7 +171,7 @@ public class DownloadImagesService extends JobIntentService {
|
|||
//RemoveOldImages();
|
||||
} else {
|
||||
mNotificationDownloadImages
|
||||
.setContentText((count + 1) + "/" + maxCount + " - Downloading Images for offline usage")
|
||||
.setContentText((count + 1) + "/" + maxCount + " - " + getString(R.string.notification_download_images_offline))
|
||||
.setProgress(maxCount, count + 1, false);
|
||||
|
||||
mNotificationManager.notify(NOTIFICATION_ID, mNotificationDownloadImages.build());
|
||||
|
|
|
@ -117,13 +117,10 @@ public class DownloadWebPageService extends Service {
|
|||
private void runOnMainThreadAndWait(final Runnable runnable) throws InterruptedException {
|
||||
synchronized(runnable) {
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runnable.run();
|
||||
synchronized (runnable) {
|
||||
runnable.notifyAll();
|
||||
}
|
||||
handler.post(() -> {
|
||||
runnable.run();
|
||||
synchronized (runnable) {
|
||||
runnable.notifyAll();
|
||||
}
|
||||
});
|
||||
runnable.wait(); // unlocks runnable while waiting
|
||||
|
@ -210,22 +207,19 @@ public class DownloadWebPageService extends Service {
|
|||
//Log.v(TAG, "Loading page:");
|
||||
initWebView();
|
||||
loadUrlInWebViewAndWait();
|
||||
} /* else {
|
||||
} else {
|
||||
Log.v(TAG, "Already cached article: " + url);
|
||||
} */
|
||||
}
|
||||
}
|
||||
updateNotificationProgress();
|
||||
}
|
||||
|
||||
private void initWebView() {
|
||||
try {
|
||||
runOnMainThreadAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView = new WebView(DownloadWebPageService.this);
|
||||
webView.setWebViewClient(new DownloadImageWebViewClient(lock));
|
||||
webView.setWebChromeClient(new DownloadImageWebViewChromeClient());
|
||||
}
|
||||
runOnMainThreadAndWait(() -> {
|
||||
webView = new WebView(DownloadWebPageService.this);
|
||||
webView.setWebViewClient(new DownloadImageWebViewClient(lock));
|
||||
webView.setWebChromeClient(new DownloadImageWebViewChromeClient());
|
||||
});
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Error while setting up WebView", e);
|
||||
|
@ -234,11 +228,9 @@ public class DownloadWebPageService extends Service {
|
|||
|
||||
private void loadUrlInWebViewAndWait() {
|
||||
try {
|
||||
runOnMainThreadAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
runOnMainThreadAndWait(() -> {
|
||||
Log.d(TAG, "downloading website for url: " + url);
|
||||
webView.loadUrl(url);
|
||||
});
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
|
@ -287,28 +279,17 @@ public class DownloadWebPageService extends Service {
|
|||
}
|
||||
|
||||
private void saveWebArchive(final WebView view, final String url) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delayedRunOnMainThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Can't store directly on external dir.. (workaround -> store on internal storage first and move then))
|
||||
final File webArchive = getWebPageArchiveFileForUrl(DownloadWebPageService.this, url);
|
||||
final File webArchiveExternalStorage = getWebPageArchiveFileForUrl(DownloadWebPageService.this, url);
|
||||
view.saveWebArchive(webArchive.getAbsolutePath(), false, new ValueCallback<String>() {
|
||||
@Override
|
||||
public void onReceiveValue(String value) {
|
||||
// Move file to external storage once done writing
|
||||
webArchive.renameTo(webArchiveExternalStorage);
|
||||
//boolean success = webArchive.renameTo(webArchiveExternalStorage);
|
||||
//Log.v(TAG, "Move succeeded: " + success);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
}).start();
|
||||
new Thread(() -> delayedRunOnMainThread(() -> {
|
||||
// Can't store directly on external dir.. (workaround -> store on internal storage first and move then))
|
||||
final File webArchive = getWebPageArchiveFileForUrl(DownloadWebPageService.this, url);
|
||||
final File webArchiveExternalStorage = getWebPageArchiveFileForUrl(DownloadWebPageService.this, url);
|
||||
view.saveWebArchive(webArchive.getAbsolutePath(), false, value -> {
|
||||
// Move file to external storage once done writing
|
||||
webArchive.renameTo(webArchiveExternalStorage);
|
||||
//boolean success = webArchive.renameTo(webArchiveExternalStorage);
|
||||
//Log.v(TAG, "Move succeeded: " + success);
|
||||
});
|
||||
}, 2000)).start();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,7 +305,7 @@ public class DownloadWebPageService extends Service {
|
|||
EventBus.getDefault().post(new StopWebArchiveDownloadEvent());
|
||||
} else {
|
||||
mNotificationWebPages
|
||||
.setContentText((current) + "/" + totalCount + " - Downloading Images for offline usage")
|
||||
.setContentText((current) + "/" + totalCount + " - " + getString(R.string.notification_download_articles_offline))
|
||||
.setProgress(totalCount, current, false);
|
||||
|
||||
mNotificationManager.notify(NOTIFICATION_ID, mNotificationWebPages.build());
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.ComponentName;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ResultReceiver;
|
||||
|
@ -26,16 +27,18 @@ import org.greenrobot.eventbus.Subscribe;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.NewsReaderListActivity;
|
||||
import de.luhmer.owncloudnewsreader.R;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.NewPodcastPlaybackListener;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.PodcastCompletedEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.RegisterVideoOutput;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.RegisterYoutubeOutput;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.SpeedPodcast;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.TogglePlayerStateEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent;
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.WindPodcast;
|
||||
import de.luhmer.owncloudnewsreader.model.MediaItem;
|
||||
import de.luhmer.owncloudnewsreader.model.PodcastItem;
|
||||
|
@ -43,7 +46,6 @@ import de.luhmer.owncloudnewsreader.model.TTSItem;
|
|||
import de.luhmer.owncloudnewsreader.services.podcast.MediaPlayerPlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.services.podcast.PlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.services.podcast.TTSPlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.services.podcast.YoutubePlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.view.PodcastNotification;
|
||||
|
||||
import static android.view.KeyEvent.KEYCODE_MEDIA_STOP;
|
||||
|
@ -55,7 +57,13 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
private static final String TAG = "PodcastPlaybackService";
|
||||
|
||||
public static final String PLAYBACK_SPEED_FLOAT = "PLAYBACK_SPEED";
|
||||
public static final String CURRENT_PODCAST_ITEM_MEDIA_ITEM= "CURRENT_PODCAST_ITEM";
|
||||
public static final String CURRENT_PODCAST_ITEM_MEDIA_ITEM = "CURRENT_PODCAST_ITEM";
|
||||
|
||||
public static final String CURRENT_PODCAST_MEDIA_TYPE = "CURRENT_PODCAST_MEDIA_TYPE";
|
||||
|
||||
private static final long PROGRESS_UPDATE_INTERNAL = 1000;
|
||||
private static final long PROGRESS_UPDATE_INITIAL_INTERVAL = 100;
|
||||
|
||||
private PodcastNotification podcastNotification;
|
||||
|
||||
private EventBus eventBus;
|
||||
|
@ -68,6 +76,12 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
private float currentPlaybackSpeed = 1;
|
||||
|
||||
|
||||
public static final int delay = 500; //In milliseconds
|
||||
private final ScheduledExecutorService mExecutorService =
|
||||
Executors.newSingleThreadScheduledExecutor();
|
||||
private ScheduledFuture<?> mScheduleFuture;
|
||||
|
||||
|
||||
public MediaItem getCurrentlyPlayingPodcast() {
|
||||
if(mPlaybackService != null) {
|
||||
return mPlaybackService.getMediaItem();
|
||||
|
@ -90,20 +104,20 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
@Override
|
||||
public void onLoadChildren(@NonNull String s, @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
|
||||
Log.d(TAG, "onLoadChildren() called with: s = [" + s + "], result = [" + result + "]");
|
||||
result.sendResult(new ArrayList<MediaBrowserCompat.MediaItem>());
|
||||
result.sendResult(new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onUnbind(Intent intent) {
|
||||
Log.d(TAG, "onUnbind() called with: intent = [" + intent + "]");
|
||||
if (!isActive()) {
|
||||
Log.v(TAG, "Stopping PodcastPlaybackService because of inactivity");
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
if(podcastNotification != null) {
|
||||
podcastNotification.unbind();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mSession != null) {
|
||||
mSession.release();
|
||||
}
|
||||
}
|
||||
|
||||
return super.onUnbind(intent);
|
||||
}
|
||||
|
||||
|
@ -125,9 +139,6 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
eventBus.register(this);
|
||||
//eventBus.post(new PodcastPlaybackServiceStarted());
|
||||
|
||||
mHandler.postDelayed(mUpdateTimeTask, 0);
|
||||
|
||||
|
||||
setSessionToken(mSession.getSessionToken());
|
||||
|
||||
Intent intent = new Intent(this, NewsReaderListActivity.class);
|
||||
|
@ -153,7 +164,7 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
|
||||
}
|
||||
|
||||
mHandler.removeCallbacks(mUpdateTimeTask);
|
||||
mExecutorService.shutdown();
|
||||
podcastNotification.cancel();
|
||||
|
||||
super.onDestroy();
|
||||
|
@ -169,62 +180,110 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
mPlaybackService.destroy();
|
||||
mPlaybackService = null;
|
||||
}
|
||||
mHandler.removeCallbacks(mUpdateTimeTask);
|
||||
|
||||
stopProgressUpdates();
|
||||
|
||||
if(intent.hasExtra(MEDIA_ITEM)) {
|
||||
MediaItem mediaItem = (MediaItem) intent.getSerializableExtra(MEDIA_ITEM);
|
||||
|
||||
if (mediaItem instanceof PodcastItem) {
|
||||
if (((PodcastItem) mediaItem).isYoutubeVideo()) {
|
||||
mPlaybackService = new YoutubePlaybackService(this, podcastStatusListener, mediaItem);
|
||||
} else {
|
||||
//if (((PodcastItem) mediaItem).isYoutubeVideo()) {
|
||||
// mPlaybackService = new YoutubePlaybackService(this, podcastStatusListener, mediaItem);
|
||||
//} else {
|
||||
mPlaybackService = new MediaPlayerPlaybackService(this, podcastStatusListener, mediaItem);
|
||||
}
|
||||
//}
|
||||
} else if (mediaItem instanceof TTSItem) {
|
||||
mPlaybackService = new TTSPlaybackService(this, podcastStatusListener, mediaItem);
|
||||
}
|
||||
|
||||
podcastNotification.podcastChanged();
|
||||
sendMediaStatus();
|
||||
updateMetadata(mediaItem);
|
||||
|
||||
// Update notification after setting metadata (notification uses metadata information)
|
||||
podcastNotification.createPodcastNotification();
|
||||
|
||||
mPlaybackService.playbackSpeedChanged(currentPlaybackSpeed);
|
||||
|
||||
startProgressUpdates();
|
||||
|
||||
requestAudioFocus();
|
||||
}
|
||||
}
|
||||
|
||||
return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
private void updateMetadata(MediaItem mediaItem) {
|
||||
MediaItem mi = mediaItem;
|
||||
if(mi == null) {
|
||||
mi = new PodcastItem(-1, "", "", "", "", false, null, false);
|
||||
}
|
||||
|
||||
int totalDuration = 0;
|
||||
if(mPlaybackService != null) {
|
||||
totalDuration = mPlaybackService.getTotalDuration();
|
||||
}
|
||||
|
||||
mSession.setMetadata(new MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, mi.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, mi.title)
|
||||
//.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, mediaItem.author) // Android Auto
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, mi.favIcon)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, String.valueOf(mi.itemId))
|
||||
.putString(CURRENT_PODCAST_MEDIA_TYPE, getCurrentlyPlayedMediaType().toString())
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, totalDuration)
|
||||
//.putLong(EXTRA_IS_EXPLICIT, EXTRA_METADATA_ENABLED_VALUE) // Android Auto
|
||||
//.putLong(EXTRA_IS_DOWNLOADED, EXTRA_METADATA_ENABLED_VALUE) // Android Auto
|
||||
.build());
|
||||
}
|
||||
|
||||
/*
|
||||
private Long getVideoWidth() {
|
||||
if(mPlaybackService instanceof MediaPlayerPlaybackService) {
|
||||
return ((MediaPlayerPlaybackService)mPlaybackService).getVideoWidth();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
|
||||
private PlaybackService.PodcastStatusListener podcastStatusListener = new PlaybackService.PodcastStatusListener() {
|
||||
@Override
|
||||
public void podcastStatusUpdated() {
|
||||
sendMediaStatus();
|
||||
syncMediaAndPlaybackStatus();
|
||||
if(mPlaybackService != null) {
|
||||
updateMetadata(mPlaybackService.getMediaItem());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void podcastCompleted() {
|
||||
Log.d(TAG, "Podcast completed, cleaning up");
|
||||
mHandler.removeCallbacks(mUpdateTimeTask);
|
||||
podcastNotification.cancel();
|
||||
|
||||
mPlaybackService.destroy();
|
||||
mPlaybackService = null;
|
||||
endCurrentMediaPlayback();
|
||||
|
||||
EventBus.getDefault().post(new PodcastCompletedEvent());
|
||||
}
|
||||
};
|
||||
|
||||
public static final int delay = 500; //In milliseconds
|
||||
private void endCurrentMediaPlayback() {
|
||||
Log.d(TAG, "endCurrentMediaPlayback() called");
|
||||
stopProgressUpdates();
|
||||
|
||||
// Set metadata
|
||||
updateMetadata(null);
|
||||
|
||||
/**
|
||||
* Background Runnable thread
|
||||
* */
|
||||
private Runnable mUpdateTimeTask = new Runnable() {
|
||||
public void run() {
|
||||
sendMediaStatus();
|
||||
mHandler.postDelayed(this, delay);
|
||||
if(mPlaybackService != null) {
|
||||
mPlaybackService.destroy();
|
||||
mPlaybackService = null;
|
||||
}
|
||||
};
|
||||
|
||||
syncMediaAndPlaybackStatus();
|
||||
|
||||
Log.d(TAG, "cancel notification");
|
||||
podcastNotification.cancel();
|
||||
|
||||
abandonAudioFocus();
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(TogglePlayerStateEvent event) {
|
||||
|
@ -247,13 +306,17 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
|
||||
private boolean isPlaying() {
|
||||
return (mPlaybackService != null && mPlaybackService.getStatus() == PlaybackService.Status.PLAYING);
|
||||
return (mPlaybackService != null && mPlaybackService.getStatus() == PlaybackStateCompat.STATE_PLAYING);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(WindPodcast event) {
|
||||
if(mPlaybackService != null) {
|
||||
mPlaybackService.seekTo(event.toPositionInPercent);
|
||||
int seekTo = (int) (mPlaybackService.getCurrentPosition() + event.milliSeconds);
|
||||
if(seekTo < 0) {
|
||||
seekTo = 0;
|
||||
}
|
||||
mPlaybackService.seekTo(seekTo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,20 +327,9 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(RegisterYoutubeOutput videoOutput) {
|
||||
if(mPlaybackService != null && mPlaybackService instanceof YoutubePlaybackService) {
|
||||
if(videoOutput.youTubePlayer == null) {
|
||||
mPlaybackService.destroy();
|
||||
} else {
|
||||
((YoutubePlaybackService) mPlaybackService).setYoutubePlayer(videoOutput.youTubePlayer, videoOutput.wasRestored);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(NewPodcastPlaybackListener newListener) {
|
||||
sendMediaStatus();
|
||||
syncMediaAndPlaybackStatus();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
|
@ -291,20 +343,73 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
|
||||
public void play() {
|
||||
if(mPlaybackService != null) {
|
||||
// Start playback
|
||||
mPlaybackService.play();
|
||||
}
|
||||
startProgressUpdates();
|
||||
|
||||
mHandler.removeCallbacks(mUpdateTimeTask);
|
||||
mHandler.postDelayed(mUpdateTimeTask, 0);
|
||||
requestAudioFocus();
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if(mPlaybackService != null) {
|
||||
mPlaybackService.pause();
|
||||
}
|
||||
stopProgressUpdates();
|
||||
|
||||
mHandler.removeCallbacks(mUpdateTimeTask);
|
||||
sendMediaStatus();
|
||||
abandonAudioFocus();
|
||||
}
|
||||
|
||||
|
||||
private void requestAudioFocus() {
|
||||
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
|
||||
// Request audio focus for playback
|
||||
int result = audioManager.requestAudioFocus(
|
||||
audioFocusChangeListener,
|
||||
// Use the music stream.
|
||||
AudioManager.STREAM_MUSIC,
|
||||
// Request permanent focus.
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
Log.d(TAG, "AUDIOFOCUS_REQUEST_GRANTED");
|
||||
}
|
||||
}
|
||||
|
||||
private void abandonAudioFocus() {
|
||||
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
// Abandon audio focus when playback complete
|
||||
audioManager.abandonAudioFocus(audioFocusChangeListener);
|
||||
}
|
||||
|
||||
private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener = focusChange -> {
|
||||
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
|
||||
// Permanent loss of audio focus
|
||||
// Pause playback immediately
|
||||
mSession.getController().getTransportControls().pause();
|
||||
}
|
||||
else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
|
||||
// Pause playback
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
|
||||
// Lower the volume, keep playing
|
||||
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
|
||||
// Your app has been granted audio focus again
|
||||
// Raise volume to normal, restart playback if necessary
|
||||
}
|
||||
};
|
||||
|
||||
private void startProgressUpdates() {
|
||||
mScheduleFuture = mExecutorService.scheduleAtFixedRate(
|
||||
() -> mHandler.post(PodcastPlaybackService.this::syncMediaAndPlaybackStatus), PROGRESS_UPDATE_INITIAL_INTERVAL,
|
||||
PROGRESS_UPDATE_INTERNAL, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void stopProgressUpdates() {
|
||||
if (mScheduleFuture != null) {
|
||||
mScheduleFuture.cancel(false);
|
||||
}
|
||||
syncMediaAndPlaybackStatus(); // Send one last update
|
||||
}
|
||||
|
||||
|
||||
|
@ -312,22 +417,22 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
return currentPlaybackSpeed;
|
||||
}
|
||||
|
||||
public void sendMediaStatus() {
|
||||
UpdatePodcastStatusEvent audioPodcastEvent;
|
||||
|
||||
public void syncMediaAndPlaybackStatus() {
|
||||
/*
|
||||
if(mPlaybackService == null) {
|
||||
audioPodcastEvent = new UpdatePodcastStatusEvent(0, 0, PlaybackService.Status.NOT_INITIALIZED, "", "", PlaybackService.VideoType.None, -1, -1);
|
||||
} else {
|
||||
audioPodcastEvent = new UpdatePodcastStatusEvent(
|
||||
mPlaybackService.getCurrentDuration(),
|
||||
mPlaybackService.getCurrentPosition(),
|
||||
mPlaybackService.getTotalDuration(),
|
||||
mPlaybackService.getStatus(),
|
||||
mPlaybackService.getMediaItem().link,
|
||||
mPlaybackService.getMediaItem().title,
|
||||
"NOT SUPPORTED ANYMORE!!!",
|
||||
mPlaybackService.getVideoType(),
|
||||
mPlaybackService.getMediaItem().itemId,
|
||||
getPlaybackSpeed());
|
||||
}
|
||||
|
||||
eventBus.post(audioPodcastEvent);
|
||||
|
||||
if(audioPodcastEvent.isPlaying()) {
|
||||
|
@ -335,10 +440,58 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
} else {
|
||||
stopForeground(false);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
@PlaybackStateCompat.State int playbackState;
|
||||
int currentPosition = 0;
|
||||
int totalDuration = 0;
|
||||
if(mPlaybackService == null || mPlaybackService.getMediaItem().itemId == -1) {
|
||||
// When podcast is not initialized or playback is finished
|
||||
playbackState = PlaybackStateCompat.STATE_NONE;
|
||||
|
||||
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
|
||||
.setState(playbackState, currentPosition, 1.0f)
|
||||
.setActions(buildPlaybackActions(playbackState, false))
|
||||
.build());
|
||||
stopForeground(false);
|
||||
} else {
|
||||
currentPosition = mPlaybackService.getCurrentPosition();
|
||||
totalDuration = mPlaybackService.getTotalDuration();
|
||||
playbackState = mPlaybackService.getStatus();
|
||||
|
||||
if (playbackState== PlaybackStateCompat.STATE_PLAYING) {
|
||||
startForeground(PodcastNotification.NOTIFICATION_ID, podcastNotification.getNotification());
|
||||
} else {
|
||||
stopForeground(false);
|
||||
}
|
||||
|
||||
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
|
||||
.setState(playbackState, currentPosition, 1.0f)
|
||||
.setActions(buildPlaybackActions(playbackState, true))
|
||||
.build());
|
||||
}
|
||||
|
||||
if(playbackState == PlaybackStateCompat.STATE_PLAYING) {
|
||||
mSession.setActive(true);
|
||||
} else {
|
||||
mSession.setActive(false);
|
||||
}
|
||||
|
||||
podcastNotification.updateStateOfNotification(playbackState, currentPosition, totalDuration);
|
||||
}
|
||||
|
||||
private long buildPlaybackActions(int playbackState, boolean mediaLoaded) {
|
||||
long actions = playbackState == PlaybackStateCompat.STATE_PLAYING ? PlaybackStateCompat.ACTION_PAUSE : PlaybackStateCompat.ACTION_PLAY;
|
||||
actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS |
|
||||
PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
|
||||
//PlaybackStateCompat.ACTION_STOP;
|
||||
|
||||
//public class PodcastPlaybackServiceStarted { }
|
||||
if(mediaLoaded) {
|
||||
actions |= PlaybackStateCompat.ACTION_SEEK_TO;
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
PhoneStateListener phoneStateListener = new PhoneStateListener() {
|
||||
@Override
|
||||
|
@ -346,11 +499,14 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
if (state == TelephonyManager.CALL_STATE_RINGING) {
|
||||
//Incoming call: Pause music
|
||||
pause();
|
||||
} else if(state == TelephonyManager.CALL_STATE_IDLE) {
|
||||
}
|
||||
/*
|
||||
else if(state == TelephonyManager.CALL_STATE_IDLE) {
|
||||
//Not in call: Play music
|
||||
} else if(state == TelephonyManager.CALL_STATE_OFFHOOK) {
|
||||
//A call is dialing, active or on hold
|
||||
}
|
||||
*/
|
||||
super.onCallStateChanged(state, incomingNumber);
|
||||
}
|
||||
};
|
||||
|
@ -359,12 +515,21 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
private final class MediaSessionCallback extends MediaSessionCompat.Callback {
|
||||
@Override
|
||||
public void onPlay() {
|
||||
EventBus.getDefault().post(new TogglePlayerStateEvent());
|
||||
Log.d(TAG, "onPlay() called");
|
||||
play();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
EventBus.getDefault().post(new TogglePlayerStateEvent());
|
||||
Log.d(TAG, "onPause() called");
|
||||
pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayFromSearch(String query, Bundle extras) {
|
||||
Log.d(TAG, "onPlayFromSearch() called with: query = [" + query + "], extras = [" + extras + "]");
|
||||
// TODO Implement this
|
||||
super.onPlayFromSearch(query, extras);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -375,12 +540,34 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
cb.send(0, b);
|
||||
} else if(command.equals(CURRENT_PODCAST_ITEM_MEDIA_ITEM)) {
|
||||
Bundle b = new Bundle();
|
||||
b.putSerializable(CURRENT_PODCAST_ITEM_MEDIA_ITEM, mPlaybackService.getMediaItem());
|
||||
if(mPlaybackService != null) {
|
||||
b.putSerializable(CURRENT_PODCAST_ITEM_MEDIA_ITEM, mPlaybackService.getMediaItem());
|
||||
} else {
|
||||
b.putSerializable(CURRENT_PODCAST_ITEM_MEDIA_ITEM, null);
|
||||
}
|
||||
cb.send(0, b);
|
||||
}
|
||||
super.onCommand(command, extras, cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSeekTo(long pos) {
|
||||
Log.d(TAG, "onSeekTo() called with: pos = [" + pos + "]");
|
||||
super.onSeekTo(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToNext() {
|
||||
Log.d(TAG, "onSkipToNext() called");
|
||||
super.onSkipToNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSkipToPrevious() {
|
||||
Log.d(TAG, "onSkipToPrevious() called");
|
||||
super.onSkipToPrevious();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
|
||||
Log.d(TAG, mediaButtonEvent.getAction());
|
||||
|
@ -392,6 +579,7 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
// Stop requested (e.g. notification was swiped away)
|
||||
if(keyEvent.getKeyCode() == KEYCODE_MEDIA_STOP) {
|
||||
pause();
|
||||
endCurrentMediaPlayback();
|
||||
stopSelf();
|
||||
/*
|
||||
boolean isPlaying = mSession.getController().getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING;
|
||||
|
@ -406,6 +594,7 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
}
|
||||
|
||||
private void initMediaSessions() {
|
||||
|
||||
//String packageName = PodcastNotificationToggle.class.getPackage().getName();
|
||||
//ComponentName receiver = new ComponentName(packageName, PodcastNotificationToggle.class.getName());
|
||||
ComponentName mediaButtonReceiver = new ComponentName(this, MediaButtonReceiver.class);
|
||||
|
@ -413,8 +602,8 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
|
||||
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
|
||||
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PAUSED, 0, 0)
|
||||
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE).build());
|
||||
.setState(PlaybackStateCompat.STATE_NONE, 0, 0)
|
||||
.setActions(buildPlaybackActions(PlaybackStateCompat.STATE_PAUSED, false)).build());
|
||||
|
||||
mSession.setCallback(new MediaSessionCallback());
|
||||
|
||||
|
@ -424,21 +613,14 @@ public class PodcastPlaybackService extends MediaBrowserServiceCompat {
|
|||
//mSession.setMediaButtonReceiver(pendingIntent);
|
||||
|
||||
|
||||
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
|
||||
@Override
|
||||
public void onAudioFocusChange(int focusChange) {
|
||||
// Ignore
|
||||
}
|
||||
}, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
||||
updateMetadata(null);
|
||||
}
|
||||
|
||||
//MediaControllerCompat controller = mSession.getController();
|
||||
|
||||
//mSession.setActive(true);
|
||||
|
||||
mSession.setMetadata(new MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "")
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "")
|
||||
.build());
|
||||
private PlaybackService.VideoType getCurrentlyPlayedMediaType() {
|
||||
if(mPlaybackService != null) {
|
||||
return mPlaybackService.getVideoType();
|
||||
} else {
|
||||
return PlaybackService.VideoType.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package de.luhmer.owncloudnewsreader.services.podcast;
|
|||
import android.content.Context;
|
||||
import android.media.MediaPlayer;
|
||||
import android.os.Build;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
@ -21,46 +22,41 @@ import de.luhmer.owncloudnewsreader.model.PodcastItem;
|
|||
public class MediaPlayerPlaybackService extends PlaybackService {
|
||||
private static final String TAG = MediaPlayerPlaybackService.class.getCanonicalName();
|
||||
private MediaPlayer mMediaPlayer;
|
||||
private View parentResizableView;
|
||||
//private View parentView;
|
||||
|
||||
public MediaPlayerPlaybackService(final Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
|
||||
super(podcastStatusListener, mediaItem);
|
||||
|
||||
mMediaPlayer = new MediaPlayer();
|
||||
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mediaPlayer, int i, int i2) {
|
||||
setStatus(Status.FAILED);
|
||||
Toast.makeText(context, "Failed to open podcast", Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
//mMediaPlayer.setOnVideoSizeChangedListener((mp, width, height) -> configureVideo(width, height));
|
||||
|
||||
mMediaPlayer.setOnErrorListener((mediaPlayer, i, i2) -> {
|
||||
setStatus(PlaybackStateCompat.STATE_ERROR);
|
||||
Toast.makeText(context, "Failed to open podcast", Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
});
|
||||
|
||||
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mediaPlayer) {
|
||||
setStatus(Status.PAUSED);
|
||||
play();
|
||||
}
|
||||
mMediaPlayer.setOnPreparedListener(mediaPlayer -> {
|
||||
podcastStatusListener.podcastStatusUpdated();
|
||||
setStatus(PlaybackStateCompat.STATE_PAUSED);
|
||||
play();
|
||||
});
|
||||
|
||||
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mediaPlayer) {
|
||||
pause();//Send the over signal
|
||||
podcastCompleted();
|
||||
}
|
||||
mMediaPlayer.setOnCompletionListener(mediaPlayer -> {
|
||||
pause();//Send the over signal
|
||||
podcastCompleted();
|
||||
});
|
||||
|
||||
|
||||
try {
|
||||
setStatus(Status.PREPARING);
|
||||
setStatus(PlaybackStateCompat.STATE_CONNECTING);
|
||||
|
||||
mMediaPlayer.setDataSource(((PodcastItem) mediaItem).link);
|
||||
mMediaPlayer.prepareAsync();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
setStatus(Status.FAILED);
|
||||
setStatus(PlaybackStateCompat.STATE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,14 +74,14 @@ public class MediaPlayerPlaybackService extends PlaybackService {
|
|||
if (progress >= 1) {
|
||||
mMediaPlayer.seekTo(0);
|
||||
}
|
||||
setStatus(Status.PLAYING);
|
||||
setStatus(PlaybackStateCompat.STATE_PLAYING);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
Log.e(TAG, "Error while playing", ex);
|
||||
}
|
||||
|
||||
mMediaPlayer.start();
|
||||
|
||||
populateVideo();
|
||||
//populateVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -93,7 +89,7 @@ public class MediaPlayerPlaybackService extends PlaybackService {
|
|||
if (mMediaPlayer.isPlaying()) {
|
||||
mMediaPlayer.pause();
|
||||
}
|
||||
setStatus(Status.PAUSED);
|
||||
setStatus(PlaybackStateCompat.STATE_PAUSED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -103,16 +99,15 @@ public class MediaPlayerPlaybackService extends PlaybackService {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void seekTo(double percent) {
|
||||
double totalDuration = mMediaPlayer.getDuration();
|
||||
int position = (int) ((totalDuration / 100d) * percent);
|
||||
public void seekTo(int position) {
|
||||
//double totalDuration = mMediaPlayer.getDuration();
|
||||
//int position = (int) ((totalDuration / 100d) * percent);
|
||||
mMediaPlayer.seekTo(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentDuration() {
|
||||
public int getCurrentPosition() {
|
||||
if (mMediaPlayer != null && isMediaLoaded()) {
|
||||
return mMediaPlayer.getCurrentPosition();
|
||||
}
|
||||
|
@ -133,6 +128,7 @@ public class MediaPlayerPlaybackService extends PlaybackService {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
private void populateVideo() {
|
||||
double videoHeightRel = (double) mSurfaceWidth / (double) mMediaPlayer.getVideoWidth();
|
||||
int videoHeight = (int) (mMediaPlayer.getVideoHeight() * videoHeightRel);
|
||||
|
@ -140,9 +136,13 @@ public class MediaPlayerPlaybackService extends PlaybackService {
|
|||
if (mSurfaceWidth != 0 && videoHeight != 0 && mSurfaceHolder != null) {
|
||||
//mSurfaceHolder.setFixedSize(mSurfaceWidth, videoHeight);
|
||||
|
||||
parentResizableView.getLayoutParams().height = videoHeight;
|
||||
parentResizableView.setLayoutParams(parentResizableView.getLayoutParams());
|
||||
parentView.getLayoutParams().height = videoHeight;
|
||||
parentView.setLayoutParams(parentView.getLayoutParams());
|
||||
}
|
||||
}*/
|
||||
|
||||
public long getVideoWidth() {
|
||||
return mMediaPlayer.getVideoWidth();
|
||||
}
|
||||
|
||||
public void setVideoView(SurfaceView surfaceView, View parentResizableView) {
|
||||
|
@ -153,37 +153,32 @@ public class MediaPlayerPlaybackService extends PlaybackService {
|
|||
mMediaPlayer.setScreenOnWhilePlaying(false);
|
||||
} else {
|
||||
if (surfaceView.getHolder() != mSurfaceHolder) {
|
||||
this.parentResizableView = parentResizableView;
|
||||
//this.parentView = parentResizableView;
|
||||
|
||||
surfaceView.getHolder().addCallback(mSHCallback);
|
||||
//videoOutput.surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
|
||||
|
||||
populateVideo();
|
||||
|
||||
//Log.v(TAG, "Enable Screen output!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private int mSurfaceWidth;
|
||||
private int mSurfaceHeight;
|
||||
//private int mSurfaceWidth;
|
||||
//private int mSurfaceHeight;
|
||||
private SurfaceHolder mSurfaceHolder;
|
||||
SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
|
||||
{
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int surfaceWidth, int surfaceHeight)
|
||||
{
|
||||
mSurfaceWidth = surfaceWidth;
|
||||
mSurfaceHeight = surfaceHeight;
|
||||
private SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() {
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int surfaceWidth, int surfaceHeight) {
|
||||
Log.v(TAG, "surfaceChanged() called with: holder = [" + holder + "], format = [" + format + "], surfaceWidth = [" + surfaceWidth + "], surfaceHeight = [" + surfaceHeight + "]");
|
||||
//mSurfaceWidth = surfaceWidth;
|
||||
//mSurfaceHeight = surfaceHeight;
|
||||
//populateVideo();
|
||||
}
|
||||
|
||||
public void surfaceCreated(SurfaceHolder holder)
|
||||
{
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v(TAG, "surfaceCreated() called with: holder = [" + holder + "]");
|
||||
mSurfaceHolder = holder;
|
||||
mMediaPlayer.setDisplay(mSurfaceHolder); //TODO required
|
||||
mMediaPlayer.setScreenOnWhilePlaying(true); //TODO required
|
||||
|
||||
Log.d(TAG, "surfaceCreated");
|
||||
mMediaPlayer.setDisplay(mSurfaceHolder);
|
||||
mMediaPlayer.setScreenOnWhilePlaying(true);
|
||||
}
|
||||
|
||||
public void surfaceDestroyed(SurfaceHolder holder)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package de.luhmer.owncloudnewsreader.services.podcast;
|
||||
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.model.MediaItem;
|
||||
|
||||
/**
|
||||
|
@ -8,18 +10,17 @@ import de.luhmer.owncloudnewsreader.model.MediaItem;
|
|||
|
||||
public abstract class PlaybackService {
|
||||
|
||||
public enum VideoType { None, Video, VideoType, YouTube }
|
||||
|
||||
private @PlaybackStateCompat.State int mStatus = PlaybackStateCompat.STATE_NONE;
|
||||
private PodcastStatusListener podcastStatusListener;
|
||||
private MediaItem mediaItem;
|
||||
|
||||
public interface PodcastStatusListener {
|
||||
void podcastStatusUpdated();
|
||||
void podcastCompleted();
|
||||
}
|
||||
|
||||
public enum Status { NOT_INITIALIZED, FAILED, PREPARING, PLAYING, PAUSED, STOPPED };
|
||||
public enum VideoType { None, Video, VideoType, YouTube }
|
||||
|
||||
private Status mStatus = Status.NOT_INITIALIZED;
|
||||
private PodcastStatusListener podcastStatusListener;
|
||||
private MediaItem mediaItem;
|
||||
|
||||
public PlaybackService(PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
|
||||
this.podcastStatusListener = podcastStatusListener;
|
||||
this.mediaItem = mediaItem;
|
||||
|
@ -31,8 +32,8 @@ public abstract class PlaybackService {
|
|||
public abstract void playbackSpeedChanged(float currentPlaybackSpeed);
|
||||
|
||||
|
||||
public void seekTo(double percent) { }
|
||||
public int getCurrentDuration() { return 0; }
|
||||
public void seekTo(int position) { }
|
||||
public int getCurrentPosition() { return 0; }
|
||||
public int getTotalDuration() { return 0; }
|
||||
public VideoType getVideoType() { return VideoType.None; }
|
||||
|
||||
|
@ -40,11 +41,11 @@ public abstract class PlaybackService {
|
|||
return mediaItem;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
public @PlaybackStateCompat.State int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
protected void setStatus(Status status) {
|
||||
protected void setStatus(@PlaybackStateCompat.State int status) {
|
||||
this.mStatus = status;
|
||||
podcastStatusListener.podcastStatusUpdated();
|
||||
}
|
||||
|
@ -54,9 +55,8 @@ public abstract class PlaybackService {
|
|||
}
|
||||
|
||||
public boolean isMediaLoaded() {
|
||||
return getStatus() != Status.NOT_INITIALIZED
|
||||
&& getStatus() != Status.PREPARING
|
||||
&& getStatus() != Status.FAILED;
|
||||
return getStatus() != PlaybackStateCompat.STATE_NONE
|
||||
&& getStatus() != PlaybackStateCompat.STATE_CONNECTING
|
||||
&& getStatus() != PlaybackStateCompat.STATE_ERROR;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package de.luhmer.owncloudnewsreader.services.podcast;
|
|||
import android.content.Context;
|
||||
import android.speech.tts.TextToSpeech;
|
||||
import android.speech.tts.UtteranceProgressListener;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -22,10 +23,9 @@ public class TTSPlaybackService extends PlaybackService implements TextToSpeech.
|
|||
|
||||
try {
|
||||
ttsController = new TextToSpeech(context, this);
|
||||
setStatus(Status.PREPARING);
|
||||
|
||||
if(ttsController == null) {
|
||||
setStatus(PlaybackStateCompat.STATE_CONNECTING);
|
||||
|
||||
if(ttsController != null) {
|
||||
ttsController.setOnUtteranceProgressListener(new UtteranceProgressListener() {
|
||||
@Override
|
||||
public void onDone(String utteranceId) {
|
||||
|
@ -35,9 +35,9 @@ public class TTSPlaybackService extends PlaybackService implements TextToSpeech.
|
|||
@Override public void onStart(String utteranceId) {}
|
||||
@Override public void onError(String utteranceId) {}
|
||||
});
|
||||
}
|
||||
else
|
||||
} else {
|
||||
onInit(TextToSpeech.SUCCESS);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public class TTSPlaybackService extends PlaybackService implements TextToSpeech.
|
|||
public void pause() {
|
||||
if (ttsController.isSpeaking()) {
|
||||
ttsController.stop();
|
||||
setStatus(Status.PAUSED);
|
||||
setStatus(PlaybackStateCompat.STATE_PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,9 +84,9 @@ public class TTSPlaybackService extends PlaybackService implements TextToSpeech.
|
|||
HashMap<String,String> ttsParams = new HashMap<>();
|
||||
ttsParams.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,"dummyId");
|
||||
ttsController.speak(((TTSItem)getMediaItem()).text, TextToSpeech.QUEUE_FLUSH, ttsParams);
|
||||
setStatus(Status.PLAYING);
|
||||
setStatus(PlaybackStateCompat.STATE_PLAYING);
|
||||
} else {
|
||||
Log.e("TTS", "Initilization Failed!");
|
||||
Log.e("TTS", "Initialization Failed!");
|
||||
ttsController = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,23 +3,17 @@ package de.luhmer.owncloudnewsreader.view;
|
|||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.UpdatePodcastStatusEvent;
|
||||
import de.luhmer.owncloudnewsreader.model.MediaItem;
|
||||
import de.luhmer.owncloudnewsreader.notification.NextcloudNotificationManager;
|
||||
import de.luhmer.owncloudnewsreader.services.PodcastPlaybackService;
|
||||
import de.luhmer.owncloudnewsreader.services.podcast.PlaybackService;
|
||||
|
||||
public class PodcastNotification {
|
||||
|
||||
|
@ -36,7 +30,7 @@ public class PodcastNotification {
|
|||
private final String CHANNEL_ID = "Podcast Notification";
|
||||
|
||||
private MediaSessionCompat mSession;
|
||||
private PlaybackService.Status lastStatus = PlaybackService.Status.NOT_INITIALIZED;
|
||||
private @PlaybackStateCompat.State int lastStatus = PlaybackStateCompat.STATE_NONE;
|
||||
|
||||
public final static int NOTIFICATION_ID = 1111;
|
||||
|
||||
|
@ -44,41 +38,22 @@ public class PodcastNotification {
|
|||
this.mContext = context;
|
||||
this.mSession = session;
|
||||
this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
this.notificationBuilder = NextcloudNotificationManager.buildPodcastNotification(mContext, CHANNEL_ID, mSession);
|
||||
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
public void unbind() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mSession != null) {
|
||||
mSession.release();
|
||||
}
|
||||
//EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onEvent(UpdatePodcastStatusEvent podcast) {
|
||||
public void updateStateOfNotification(@PlaybackStateCompat.State int status, long currentPosition, long totalDuration) {
|
||||
if(mSession == null) {
|
||||
Log.v(TAG, "Session null.. ignore UpdatePodcastStatusEvent");
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != lastStatus) {
|
||||
lastStatus = status;
|
||||
|
||||
|
||||
if (podcast.getStatus() != lastStatus) {
|
||||
lastStatus = podcast.getStatus();
|
||||
|
||||
/*
|
||||
notificationBuilder.setContentTitle(podcast.getTitle());
|
||||
notificationBuilder.mActions.clear();
|
||||
notificationBuilder.addAction(
|
||||
drawableId,
|
||||
actionText,
|
||||
PendingIntent.getBroadcast(mContext, 0, new Intent(mContext, PodcastNotificationToggle.class),
|
||||
PendingIntent.FLAG_ONE_SHOT));
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
if(podcast.isPlaying()) {
|
||||
//Prevent the Podcast Player from getting killed because of low memory
|
||||
|
@ -100,53 +75,36 @@ public class PodcastNotification {
|
|||
.build());
|
||||
*/
|
||||
|
||||
|
||||
mSession.setActive(true);
|
||||
if (podcast.isPlaying()) {
|
||||
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PLAYING, podcast.getCurrent(), 1.0f)
|
||||
.setActions(PlaybackStateCompat.ACTION_PAUSE).build());
|
||||
} else {
|
||||
mSession.setPlaybackState(new PlaybackStateCompat.Builder()
|
||||
.setState(PlaybackStateCompat.STATE_PAUSED, podcast.getCurrent(), 0.0f)
|
||||
.setActions(PlaybackStateCompat.ACTION_PLAY).build());
|
||||
}
|
||||
|
||||
//mSession.setActive(podcast.isPlaying());
|
||||
|
||||
|
||||
notificationBuilder = NextcloudNotificationManager.buildPodcastNotification(mContext, CHANNEL_ID, mSession);
|
||||
|
||||
//int drawableId = podcast.isPlaying() ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play;
|
||||
//String actionText = podcast.isPlaying() ? "Pause" : "Play";
|
||||
//notificationBuilder.addAction(new NotificationCompat.Action(drawableId, actionText, intent));
|
||||
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
|
||||
|
||||
int hours = (int)( podcast.getCurrent() / (1000*60*60));
|
||||
int minutes = (int)(podcast.getCurrent() % (1000*60*60)) / (1000*60);
|
||||
int seconds = (int) ((podcast.getCurrent() % (1000*60*60)) % (1000*60) / 1000);
|
||||
int hours = (int) (currentPosition / (1000*60*60));
|
||||
int minutes = (int) ((currentPosition % (1000*60*60)) / (1000*60));
|
||||
int seconds = (int) ((currentPosition % (1000*60*60)) % (1000*60) / 1000);
|
||||
minutes += hours * 60;
|
||||
String fromText = (String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
|
||||
|
||||
hours = (int)( podcast.getMax() / (1000*60*60));
|
||||
minutes = (int)(podcast.getMax() % (1000*60*60)) / (1000*60);
|
||||
seconds = (int) ((podcast.getMax() % (1000*60*60)) % (1000*60) / 1000);
|
||||
hours = (int) (totalDuration / (1000*60*60));
|
||||
minutes = (int) ((totalDuration % (1000*60*60)) / (1000*60));
|
||||
seconds = (int) ((totalDuration % (1000*60*60)) % (1000*60) / 1000);
|
||||
minutes += hours * 60;
|
||||
String toText = (String.format(Locale.getDefault(),"%02d:%02d", minutes, seconds));
|
||||
|
||||
|
||||
|
||||
double progressDouble = ((double)podcast.getCurrent() / (double)podcast.getMax()) * 100d;
|
||||
double progressDouble = ((double)currentPosition / (double)totalDuration) * 100d;
|
||||
int progress = ((int) progressDouble);
|
||||
|
||||
|
||||
notificationBuilder
|
||||
.setContentText(fromText + " - " + toText)
|
||||
.setProgress(100, progress, podcast.getStatus() == PlaybackService.Status.PREPARING);
|
||||
.setProgress(100, progress, status == PlaybackStateCompat.STATE_CONNECTING); // TODO IMPLEMENT THIS!!!!
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
|
@ -156,14 +114,17 @@ public class PodcastNotification {
|
|||
if(notificationManager != null) {
|
||||
notificationManager.cancel(NOTIFICATION_ID);
|
||||
}
|
||||
/*
|
||||
if(mSession != null) {
|
||||
mSession.setActive(false);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public void podcastChanged() {
|
||||
public void createPodcastNotification() {
|
||||
/*
|
||||
MediaItem podcastItem = ((PodcastPlaybackService)mContext).getCurrentlyPlayingPodcast();
|
||||
|
||||
*/
|
||||
/*
|
||||
String favIconUrl = podcastItem.favIcon;
|
||||
DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().
|
||||
|
@ -173,13 +134,14 @@ public class PodcastNotification {
|
|||
build();
|
||||
*/
|
||||
|
||||
//TODO networkOnMainThreadExceptionHere!
|
||||
//Bitmap bmpAlbumArt = ImageLoader.getInstance().loadImageSync(favIconUrl, displayImageOptions);
|
||||
|
||||
/*
|
||||
mSession.setMetadata(new MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, podcastItem.author)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, podcastItem.title)
|
||||
.build());
|
||||
*/
|
||||
|
||||
/*
|
||||
mSession.setMetadata(new MediaMetadataCompat.Builder()
|
||||
|
|
|
@ -1,248 +0,0 @@
|
|||
package de.luhmer.owncloudnewsreader.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.events.podcast.VideoDoubleClicked;
|
||||
|
||||
//http://stackoverflow.com/questions/10013906/android-zoom-in-out-relativelayout-with-spread-pinch
|
||||
public class ZoomableRelativeLayout extends RelativeLayout {
|
||||
private static final int INVALID_POINTER_ID = -1;
|
||||
private static final int INVALID_SIZE = -1;
|
||||
|
||||
|
||||
public boolean disableScale = false;
|
||||
public void setDisableScale(boolean disableScale) {
|
||||
this.disableScale = disableScale;
|
||||
}
|
||||
|
||||
private static final String TAG = "ZoomableRelativeLayout";
|
||||
|
||||
private GestureDetector mDoubleTapDetector;
|
||||
private ScaleGestureDetector mScaleDetector;
|
||||
float mScaleFactor = 1;
|
||||
public float getScaleFactor() {
|
||||
return mScaleFactor;
|
||||
}
|
||||
|
||||
float mPosX;
|
||||
float mPosY;
|
||||
private float mLastTouchX;
|
||||
private float mLastTouchY;
|
||||
private int mActivePointerId;
|
||||
|
||||
private float mInitHeight = INVALID_SIZE;
|
||||
private float mInitWidth = INVALID_SIZE;
|
||||
|
||||
public ZoomableRelativeLayout(Context context) {
|
||||
super(context);
|
||||
initZoomView(context);
|
||||
}
|
||||
|
||||
public ZoomableRelativeLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initZoomView(context);
|
||||
}
|
||||
|
||||
public ZoomableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initZoomView(context);
|
||||
}
|
||||
|
||||
|
||||
boolean mPositionReady = false;
|
||||
public boolean isPositionReady() {
|
||||
return mPositionReady;
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus) {
|
||||
if(hasWindowFocus) {
|
||||
readVideoPosition();
|
||||
mPositionReady = true;
|
||||
}
|
||||
super.onWindowFocusChanged(hasWindowFocus);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
public void readVideoPosition() {
|
||||
int position[] = new int[2];
|
||||
getLocationOnScreen(position);
|
||||
mVideoXPosition = position[0];
|
||||
mVideoYPosition = position[1];
|
||||
|
||||
mPositionReady = true;
|
||||
|
||||
Log.d(TAG, "Grabbing new Video Wrapper Position. X:" + mVideoXPosition + " - Y:" + mVideoYPosition);
|
||||
|
||||
//mVideoXPosition = getX();
|
||||
//mVideoYPosition = getY();
|
||||
}
|
||||
|
||||
private void initZoomView(Context context) {
|
||||
// Create our ScaleGestureDetector
|
||||
mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
|
||||
mDoubleTapDetector = new GestureDetector(context, new DoubleTapListener());
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
canvas.save(Canvas.MATRIX_SAVE_FLAG);
|
||||
canvas.scale(mScaleFactor, mScaleFactor, mPosX, mPosY);
|
||||
super.dispatchDraw(canvas);
|
||||
canvas.restore();
|
||||
}*/
|
||||
|
||||
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
|
||||
|
||||
// event when double tap occurs
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
float x = e.getX();
|
||||
float y = e.getY();
|
||||
|
||||
Log.d("Double Tap", "Tapped at: (" + x + "," + y + ")");
|
||||
|
||||
EventBus.getDefault().post(new VideoDoubleClicked());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private float mVideoXPosition;
|
||||
private float mVideoYPosition;
|
||||
public float getVideoXPosition() {return mVideoXPosition;}
|
||||
public float getVideoYPosition() {return mVideoYPosition;}
|
||||
|
||||
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
|
||||
@Override
|
||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
||||
if(!disableScale) {
|
||||
readVideoPosition();
|
||||
}
|
||||
|
||||
super.onScaleEnd(detector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onScale(ScaleGestureDetector detector) {
|
||||
if(disableScale)
|
||||
return true;
|
||||
|
||||
if(mInitWidth == INVALID_SIZE) {
|
||||
mInitWidth = getWidth();
|
||||
mInitHeight = getHeight();
|
||||
}
|
||||
|
||||
mScaleFactor *= detector.getScaleFactor();
|
||||
|
||||
// Don't let the object get too small or too large.
|
||||
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
|
||||
|
||||
if(mScaleFactor < 1)
|
||||
mScaleFactor = 1;
|
||||
|
||||
|
||||
Log.d(TAG, "Scale:" + mScaleFactor);
|
||||
|
||||
|
||||
getLayoutParams().width = (int)(mInitWidth * mScaleFactor);
|
||||
getLayoutParams().height = (int)(mInitHeight * mScaleFactor);
|
||||
setLayoutParams(getLayoutParams());
|
||||
|
||||
//invalidate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public void restore() {
|
||||
mScaleFactor = 1;
|
||||
this.invalidate();
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
// Let the ScaleGestureDetector inspect all events.
|
||||
mScaleDetector.onTouchEvent(ev);
|
||||
mDoubleTapDetector.onTouchEvent(ev);
|
||||
|
||||
final int action = ev.getAction();
|
||||
switch (action & MotionEvent.ACTION_MASK) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
final float x = ev.getX();
|
||||
final float y = ev.getY();
|
||||
|
||||
mLastTouchX = x;
|
||||
mLastTouchY = y;
|
||||
mActivePointerId = ev.getPointerId(0);
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
|
||||
final float x = ev.getX(pointerIndex);
|
||||
final float y = ev.getY(pointerIndex);
|
||||
|
||||
// Only move if the ScaleGestureDetector isn't processing a gesture.
|
||||
if (!mScaleDetector.isInProgress()) {
|
||||
final float dx = x - mLastTouchX;
|
||||
final float dy = y - mLastTouchY;
|
||||
|
||||
mPosX += dx;
|
||||
mPosY += dy;
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
mLastTouchX = x;
|
||||
mLastTouchY = y;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_UP: {
|
||||
mActivePointerId = INVALID_POINTER_ID;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
mActivePointerId = INVALID_POINTER_ID;
|
||||
break;
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_POINTER_UP: {
|
||||
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
||||
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
||||
final int pointerId = ev.getPointerId(pointerIndex);
|
||||
if (pointerId == mActivePointerId) {
|
||||
// This was our active pointer going up. Choose a new
|
||||
// active pointer and adjust accordingly.
|
||||
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
|
||||
mLastTouchX = ev.getX(newPointerIndex);
|
||||
mLastTouchY = ev.getY(newPointerIndex);
|
||||
mActivePointerId = ev.getPointerId(newPointerIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -39,16 +39,16 @@ import de.luhmer.owncloudnewsreader.R;
|
|||
import de.luhmer.owncloudnewsreader.database.DatabaseConnectionOrm;
|
||||
import de.luhmer.owncloudnewsreader.database.model.RssItem;
|
||||
|
||||
public class WidgetTodoViewsFactory implements RemoteViewsService.RemoteViewsFactory {
|
||||
private static final String TAG = WidgetTodoViewsFactory.class.getCanonicalName();
|
||||
public class WidgetNewsViewsFactory implements RemoteViewsService.RemoteViewsFactory {
|
||||
private static final String TAG = WidgetNewsViewsFactory.class.getCanonicalName();
|
||||
|
||||
private DatabaseConnectionOrm dbConn;
|
||||
private List<RssItem> rssItems;
|
||||
private Context context = null;
|
||||
private Context context;
|
||||
|
||||
private int appWidgetId;
|
||||
|
||||
public WidgetTodoViewsFactory(Context context, Intent intent) {
|
||||
public WidgetNewsViewsFactory(Context context, Intent intent) {
|
||||
this.context = context;
|
||||
appWidgetId = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
|
||||
|
||||
|
@ -79,14 +79,14 @@ public class WidgetTodoViewsFactory implements RemoteViewsService.RemoteViewsFac
|
|||
// combination with the app widget item XML file to construct a RemoteViews object.
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
public RemoteViews getViewAt(int position) {
|
||||
if(Constants.debugModeWidget)
|
||||
if(Constants.debugModeWidget) {
|
||||
Log.d(TAG, "getViewAt: " + position);
|
||||
}
|
||||
|
||||
RssItem rssItem = rssItems.get(position);
|
||||
|
||||
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
|
||||
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
|
||||
|
||||
try {
|
||||
RssItem rssItem = rssItems.get(position);
|
||||
String header = rssItem.getFeed().getFeedTitle();
|
||||
String colorString = rssItem.getFeed().getAvgColour();
|
||||
|
||||
|
@ -140,7 +140,7 @@ public class WidgetTodoViewsFactory implements RemoteViewsService.RemoteViewsFac
|
|||
iCheck.putExtra(WidgetProvider.ACTION_CHECKED_CLICK, true);
|
||||
rv.setOnClickFillInIntent(R.id.cb_lv_item_read, iCheck);
|
||||
} catch(Exception ex) {
|
||||
Log.e(TAG, "Error: " + ex.getLocalizedMessage());
|
||||
Log.e(TAG, "Error while getting view for widget at position: " + position, ex);
|
||||
}
|
||||
|
||||
// Return the RemoteViews object.
|
|
@ -28,6 +28,6 @@ public class WidgetService extends RemoteViewsService {
|
|||
|
||||
@Override
|
||||
public RemoteViewsFactory onGetViewFactory(Intent intent) {
|
||||
return new WidgetTodoViewsFactory(this.getApplicationContext(), intent);
|
||||
return new WidgetNewsViewsFactory(this.getApplicationContext(), intent);
|
||||
}
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 155 B After Width: | Height: | Size: 155 B |
Before Width: | Height: | Size: 226 B After Width: | Height: | Size: 226 B |
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
Before Width: | Height: | Size: 388 B After Width: | Height: | Size: 388 B |
6
News-Android-App/src/main/res/drawable/cursor.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle" >
|
||||
<solid android:color="#ffffff" />
|
||||
<size android:width="2dp" />
|
||||
</shape>
|
|
@ -41,18 +41,6 @@
|
|||
android:id="@+id/toolbar_layout"
|
||||
layout="@layout/toolbar_layout" />
|
||||
|
||||
<de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout
|
||||
android:id="@+id/videoPodcastSurfaceWrapper"
|
||||
android:layout_width="@dimen/podcast_video_player_width"
|
||||
android:layout_height="100dp"
|
||||
android:background="#ff7c7c7c"
|
||||
android:padding="2dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="@dimen/podcast_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin" >
|
||||
|
||||
</de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<FrameLayout
|
||||
|
|
|
@ -52,19 +52,6 @@
|
|||
android:id="@+id/toolbar_layout"
|
||||
layout="@layout/toolbar_layout" />
|
||||
|
||||
<de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout
|
||||
android:id="@+id/videoPodcastSurfaceWrapper"
|
||||
android:layout_width="@dimen/podcast_video_player_width"
|
||||
android:layout_height="@dimen/podcast_video_player_height"
|
||||
android:background="#ff7c7c7c"
|
||||
android:padding="2dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginRight="@dimen/podcast_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/podcast_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin" >
|
||||
|
||||
</de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout>
|
||||
|
||||
<de.luhmer.owncloudnewsreader.view.AnimatingProgressBar
|
||||
android:id="@+id/progressIndicator"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -34,35 +34,6 @@
|
|||
layout="@layout/toolbar_layout" />
|
||||
|
||||
|
||||
<de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout
|
||||
android:id="@+id/videoPodcastSurfaceWrapper"
|
||||
android:layout_width="@dimen/podcast_video_player_width"
|
||||
android:layout_height="@dimen/podcast_video_player_height"
|
||||
android:background="#ff7c7c7c"
|
||||
android:padding="2dp"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginRight="@dimen/podcast_horizontal_margin"
|
||||
android:layout_marginEnd="@dimen/podcast_horizontal_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin" >
|
||||
|
||||
<!--
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#ff7c7c7c">
|
||||
<fragment
|
||||
android:name="com.google.android.youtube.player.YouTubePlayerFragment"
|
||||
android:id="@+id/youtubeplayerfragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</FrameLayout>
|
||||
-->
|
||||
|
||||
|
||||
</de.luhmer.owncloudnewsreader.view.ZoomableRelativeLayout>
|
||||
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<FrameLayout
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/layout_activity_pip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".PiPVideoPlaybackActivity">
|
||||
|
||||
|
||||
|
||||
</RelativeLayout>
|
|
@ -18,7 +18,6 @@
|
|||
android:outAnimation="@android:anim/fade_out">
|
||||
|
||||
|
||||
|
||||
<!-- Default Header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -64,6 +63,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:progress="0"
|
||||
android:progressTint="@color/colorAccent"
|
||||
android:max="100"
|
||||
android:layoutDirection="ltr" />
|
||||
|
||||
|
@ -105,7 +105,7 @@
|
|||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_action_play_arrow"
|
||||
android:src="@drawable/ic_action_play"
|
||||
android:tint="@color/tintColor"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:scaleType="fitXY"
|
||||
|
@ -274,11 +274,14 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:progress="0"
|
||||
android:progressTint="@color/colorAccent"
|
||||
android:thumbTint="@color/colorAccent"
|
||||
android:max="100" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pb_progress2"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:progressTint="@color/colorAccent"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -326,7 +329,7 @@
|
|||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginRight="20dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/ic_action_play_arrow"
|
||||
android:src="@drawable/ic_action_play"
|
||||
android:tint="@color/tintColor"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:scaleType="fitXY"
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/ic_action_play_arrow"
|
||||
android:background="@drawable/ic_action_play"
|
||||
android:contentDescription="@string/content_desc_play"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:theme="@style/ToolbarTheme" />
|
||||
android:theme="@style/ToolbarTheme"
|
||||
app:popupTheme="@style/ToolbarOptionMenuBackgroundTheme"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
|
@ -28,6 +28,9 @@
|
|||
<string name="img_view_thumbnail" translatable="false">Thumbnail</string>
|
||||
<string name="tv_showing_cached_version">Showing cached version</string>
|
||||
|
||||
<string name="permission_req_location_twilight_title">Automated Light/Dark Theme</string>
|
||||
<string name="permission_req_location_twilight_text">In order to automatically switch between the light and dark theme, it is required to provide the devices location in order to determine the time for sunrise and sunset.</string>
|
||||
|
||||
<!-- Action Bar Items -->
|
||||
<string name="action_starred">Starred</string>
|
||||
<string name="action_read">Read</string>
|
||||
|
@ -42,6 +45,13 @@
|
|||
<string name="action_textToSpeech">Read out</string>
|
||||
<string name="action_search">Search</string>
|
||||
<string name="action_download_articles_offline">Download articles offline</string>
|
||||
<string name="news_list_drawer_text" translatable="false">Feed-list</string>
|
||||
|
||||
|
||||
|
||||
<!-- notifications -->
|
||||
<string name="notification_download_articles_offline">Downloading articles for offline usage</string>
|
||||
<string name="notification_download_images_offline">Downloading images for offline usage</string>
|
||||
<plurals name="notification_new_items_ticker">
|
||||
<item quantity="one">You have %d new unread item</item>
|
||||
<item quantity="other">You have %d new unread items</item>
|
||||
|
@ -51,7 +61,6 @@
|
|||
<item quantity="other">%d new unread items available</item>
|
||||
</plurals>
|
||||
|
||||
|
||||
<!-- Add new feed -->
|
||||
<string name="hint_feed_url">Feed URL</string>
|
||||
<string name="action_add_feed">Add feed</string>
|
||||
|
|
|
@ -46,6 +46,10 @@
|
|||
<item name="alertDialogTheme">@style/AlertDialogTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="ToolbarOptionMenuBackgroundTheme" parent="Theme.MaterialComponents.DayNight">
|
||||
<item name="android:background">@color/rss_item_list_background</item>
|
||||
<item name="android:itemBackground">@color/rss_item_list_background</item>
|
||||
</style>
|
||||
|
||||
|
||||
<!-- https://stackoverflow.com/a/54751236 -->
|
||||
|
@ -61,6 +65,7 @@
|
|||
|
||||
|
||||
<style name="ToolbarTheme" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar">
|
||||
<item name="android:background">@color/colorPrimary</item>
|
||||
<item name="android:textColor">@color/options_menu_item_text</item>
|
||||
</style>
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<automotiveApp>
|
||||
<uses name="media"/>
|
||||
</automotiveApp>
|
|
@ -1,12 +0,0 @@
|
|||
package de.luhmer.owncloudnewsreader;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
public class YoutubePlayerManager {
|
||||
|
||||
public static void StartYoutubePlayer(final Activity activity, int YOUTUBE_CONTENT_VIEW_ID, final EventBus eventBus, final Runnable onInitSuccess) {
|
||||
// Dummy
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package de.luhmer.owncloudnewsreader.services.podcast;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import de.luhmer.owncloudnewsreader.model.MediaItem;
|
||||
|
||||
/**
|
||||
* Created by david on 31.01.17.
|
||||
*/
|
||||
|
||||
public class YoutubePlaybackService extends PlaybackService {
|
||||
|
||||
public YoutubePlaybackService(Context context, PodcastStatusListener podcastStatusListener, MediaItem mediaItem) {
|
||||
super(podcastStatusListener, mediaItem);
|
||||
setStatus(Status.FAILED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() { }
|
||||
|
||||
@Override
|
||||
public void play() { }
|
||||
|
||||
@Override
|
||||
public void pause() { }
|
||||
|
||||
@Override
|
||||
public void playbackSpeedChanged(float currentPlaybackSpeed) { }
|
||||
|
||||
public void seekTo(double percent) { }
|
||||
public int getCurrentDuration() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getTotalDuration() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VideoType getVideoType() {
|
||||
return VideoType.YouTube;
|
||||
}
|
||||
|
||||
public void setYoutubePlayer(Object youTubePlayer, boolean wasRestored) { }
|
||||
}
|
|
@ -47,6 +47,14 @@ Download and install:
|
|||
4. Import the Project in Android Studio and start coding!
|
||||
|
||||
|
||||
Testing with Android Auto:
|
||||
-----------------------
|
||||
1. Open Android Studio, click on "Tools" -> "SDK Manager"
|
||||
2. Select and install "Android Auto API Simulators"
|
||||
3. Open terminal, go to <android-sdk>/extras/google/simulators
|
||||
4. Install apk using adb (`../../../platform-tools/adb install media-browser-simulator.apk`)
|
||||
5. Install apk using adb (`../../../platform-tools/adb install messaging-simulator.apk`)
|
||||
|
||||
That's all. I hope it works for you! If something is not working, please send me an email to david-dev@live.de
|
||||
|
||||
|
||||
|
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,5 +1,6 @@
|
|||
#Sat May 11 20:04:11 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip
|
||||
|
|