diff --git a/Dockerfile b/Dockerfile index 72d6992..1a3a9a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ENV ANDROID_SDK_CHECKSUM 124f2d5115eee365df6cf3228ffbca6fc3911d16f8025bebd5b1c6e # higher version casues Warning: Failed to find package ENV ANDROID_BUILD_TOOLS_VERSION 30.0.2 ENV ANDROID_SDK_ROOT /usr/local/android-sdk-linux -ENV ANDROID_VERSION 30 +ENV ANDROID_VERSION 32 # ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools ENV PATH ${PATH}:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:${ANDROID_SDK_ROOT}/cmdline-tools/tools/bin diff --git a/app/build.gradle b/app/build.gradle index fa8d0c6..84d3bf2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -39,13 +39,13 @@ else { apply plugin: 'kotlin-kapt' android { - compileSdkVersion 30 + compileSdkVersion 32 buildToolsVersion "30.0.2" defaultConfig { applicationId "net.schueller.peertube" minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 32 versionCode 1069 versionName "1.8.3" buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' @@ -94,10 +94,10 @@ android { } -def room_version = "2.3.0" -def lifecycleVersion = '2.3.1' -def exoplayer = '2.12.3' -def fragment_version = "1.3.6" +def room_version = "2.4.0" +def lifecycleVersion = '2.4.0' +def exoplayer = '2.16.1' +def fragment_version = "1.4.0" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -105,8 +105,8 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // Layouts and design - implementation 'androidx.constraintlayout:constraintlayout:2.1.1' - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'com.google.android.material:material:1.4.0' implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" implementation "androidx.fragment:fragment-ktx:$fragment_version" @@ -118,7 +118,7 @@ dependencies { implementation 'com.mikepenz:fontawesome-typeface:5.9.0.2-kotlin@aar' // http client / REST - implementation 'com.squareup.okhttp3:okhttp:4.9.1' + implementation 'com.squareup.okhttp3:okhttp:4.9.2' implementation 'com.squareup.retrofit2:retrofit:2.9.0' // image downloading and caching library diff --git a/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.kt b/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.kt index 3c55cfa..59d3560 100644 --- a/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.kt +++ b/app/src/main/java/net/schueller/peertube/activity/ServerAddressBookActivity.kt @@ -41,7 +41,7 @@ import java.util.* class ServerAddressBookActivity : CommonActivity() { - private val TAG = "ServerAddressBookActivity" + private val TAG = "ServerAddBookAct" private val mServerViewModel: ServerViewModel by viewModels() private var addServerFragment: AddServerFragment? = null @@ -133,15 +133,15 @@ class ServerAddressBookActivity : CommonActivity() { AlertDialog.Builder(this@ServerAddressBookActivity) .setTitle(getString(R.string.server_book_del_alert_title)) .setMessage(getString(R.string.server_book_del_alert_msg)) - .setPositiveButton(android.R.string.yes) { _: DialogInterface?, _: Int -> - val position = viewHolder.adapterPosition + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + val position = viewHolder.bindingAdapterPosition val server = adapter.getServerAtPosition(position) // Toast.makeText(ServerAddressBookActivity.this, "Deleting " + // server.getServerName(), Toast.LENGTH_LONG).show(); // Delete the server mServerViewModel.delete(server) } - .setNegativeButton(android.R.string.no) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.adapterPosition) } + .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) } .setIcon(android.R.drawable.ic_dialog_alert) .show() } diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.kt b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.kt index e2787e6..8a8304f 100644 --- a/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.kt +++ b/app/src/main/java/net/schueller/peertube/activity/VideoListActivity.kt @@ -19,6 +19,7 @@ package net.schueller.peertube.activity import android.Manifest.permission import android.R.drawable import android.R.string +import android.app.Activity import android.app.AlertDialog.Builder import android.app.SearchManager import android.content.Context @@ -33,6 +34,7 @@ import android.view.MenuItem import android.view.MenuItem.OnActionExpandListener import android.view.View import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView.OnSuggestionListener import androidx.appcompat.widget.Toolbar @@ -116,7 +118,7 @@ class VideoListActivity : CommonActivity() { Builder(this@VideoListActivity) .setTitle(getString(R.string.clear_search_history)) .setMessage(getString(R.string.clear_search_history_prompt)) - .setPositiveButton(string.yes) { _, _ -> + .setPositiveButton(string.ok) { _, _ -> val suggestions = SearchRecentSuggestions( applicationContext, SearchSuggestionsProvider.AUTHORITY, @@ -124,7 +126,7 @@ class VideoListActivity : CommonActivity() { ) suggestions.clearHistory() } - .setNegativeButton(string.no, null) + .setNegativeButton(string.cancel, null) .setIcon(drawable.ic_dialog_alert) .show() true @@ -160,8 +162,7 @@ class VideoListActivity : CommonActivity() { position ) as Cursor return cursor.getString( - cursor - .getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1) + cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1) ) } @@ -178,15 +179,26 @@ class VideoListActivity : CommonActivity() { stopService(Intent(this, VideoPlayerService::class.java)) } - public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == SWITCH_INSTANCE) { - if (resultCode == RESULT_OK) { - loadVideos(currentStart, count, sort, filter) - } +// public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { +// super.onActivityResult(requestCode, resultCode, data) +// if (requestCode == SWITCH_INSTANCE) { +// if (resultCode == RESULT_OK) { +// loadVideos(currentStart, count, sort, filter) +// } +// } +// } + + private var resultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + loadVideos(currentStart, count, sort, filter) } } + private fun openActivityForResult(intent: Intent) { + resultLauncher.launch(intent) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long @@ -213,7 +225,7 @@ class VideoListActivity : CommonActivity() { } id.action_server_address_book -> { val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java) - this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE) + openActivityForResult(addressBookActivityIntent) return false } else -> { @@ -461,7 +473,7 @@ class VideoListActivity : CommonActivity() { // new IconicsDrawable(this, FontAwesome.Icon.faw_user_circle)); // Click Listener - navigation.setOnNavigationItemSelectedListener { menuItem: MenuItem -> + navigation.setOnItemSelectedListener { menuItem: MenuItem -> when (menuItem.itemId) { id.navigation_overview -> { // TODO @@ -470,7 +482,7 @@ class VideoListActivity : CommonActivity() { loadOverview(currentPage) overViewActive = true } - return@setOnNavigationItemSelectedListener true + return@setOnItemSelectedListener true } id.navigation_trending -> { //Log.v(TAG, "navigation_trending"); @@ -482,7 +494,7 @@ class VideoListActivity : CommonActivity() { subscriptions = false loadVideos(currentStart, count, sort, filter) } - return@setOnNavigationItemSelectedListener true + return@setOnItemSelectedListener true } id.navigation_recent -> { if (!isLoading) { @@ -493,7 +505,7 @@ class VideoListActivity : CommonActivity() { subscriptions = false loadVideos(currentStart, count, sort, filter) } - return@setOnNavigationItemSelectedListener true + return@setOnItemSelectedListener true } id.navigation_local -> { //Log.v(TAG, "navigation_trending"); @@ -505,15 +517,15 @@ class VideoListActivity : CommonActivity() { subscriptions = false loadVideos(currentStart, count, sort, filter) } - return@setOnNavigationItemSelectedListener true + return@setOnItemSelectedListener true } id.navigation_subscriptions -> //Log.v(TAG, "navigation_subscriptions"); if (!Session.getInstance().isLoggedIn) { // Intent intent = new Intent(this, LoginActivity.class); // this.startActivity(intent); val addressBookActivityIntent = Intent(this, ServerAddressBookActivity::class.java) - this.startActivityForResult(addressBookActivityIntent, SWITCH_INSTANCE) - return@setOnNavigationItemSelectedListener false + openActivityForResult(addressBookActivityIntent) + return@setOnItemSelectedListener false } else { if (!isLoading) { overViewActive = false @@ -523,7 +535,7 @@ class VideoListActivity : CommonActivity() { subscriptions = true loadVideos(currentStart, count, sort, filter) } - return@setOnNavigationItemSelectedListener true + return@setOnItemSelectedListener true } } false @@ -574,6 +586,5 @@ class VideoListActivity : CommonActivity() { const val EXTRA_VIDEOID = "VIDEOID" const val EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST" - const val SWITCH_INSTANCE = 2 } } \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java deleted file mode 100644 index f73041c..0000000 --- a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.java +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.schueller.peertube.activity; - - -import android.annotation.SuppressLint; -import android.app.AppOpsManager; -import android.app.PendingIntent; -import android.app.PictureInPictureParams; -import android.app.RemoteAction; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.drawable.Icon; -import android.os.Build; -import android.os.Bundle; - -import android.preference.PreferenceManager; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatActivity; - -import android.text.TextUtils; -import android.util.Log; -import android.util.Rational; -import android.util.TypedValue; - -import android.view.WindowManager; -import android.widget.FrameLayout; - -import android.widget.RelativeLayout; - -import net.schueller.peertube.R; -import net.schueller.peertube.fragment.VideoMetaDataFragment; -import net.schueller.peertube.fragment.VideoPlayerFragment; -import net.schueller.peertube.service.VideoPlayerService; - - -import java.util.ArrayList; - -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; - - -import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PAUSE; -import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_PLAY; -import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP; -import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode; - -public class VideoPlayActivity extends AppCompatActivity { - - private static final String TAG = "VideoPlayActivity"; - - static boolean floatMode = false; - - private static final int REQUEST_CODE = 101; - private BroadcastReceiver receiver; - - //This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode. - @SuppressLint("NewApi") - public void makePipControls() { - FragmentManager fragmentManager = getSupportFragmentManager(); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); - - ArrayList actions = new ArrayList<>(); - - Intent actionIntent = new Intent(getString(R.string.app_background_audio)); - PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0); - @SuppressLint({"NewApi", "LocalSuppress"}) Icon icon = Icon.createWithResource(getApplicationContext(), android.R.drawable.stat_sys_speakerphone); - @SuppressLint({"NewApi", "LocalSuppress"}) RemoteAction remoteAction = new RemoteAction(icon, "close pip", "from pip window custom command", pendingIntent); - actions.add(remoteAction); - - actionIntent = new Intent(ACTION_STOP); - pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0); - icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_stop); - remoteAction = new RemoteAction(icon, "play", "stop the media", pendingIntent); - actions.add(remoteAction); - - assert videoPlayerFragment != null; - if (videoPlayerFragment.isPaused()) { - Log.e(TAG, "setting actions with play button"); - actionIntent = new Intent(ACTION_PLAY); - pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0); - icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_play); - remoteAction = new RemoteAction(icon, "play", "play the media", pendingIntent); - } else { - Log.e(TAG, "setting actions with pause button"); - actionIntent = new Intent(ACTION_PAUSE); - pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), REQUEST_CODE, actionIntent, 0); - icon = Icon.createWithResource(getApplicationContext(), com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause); - remoteAction = new RemoteAction(icon, "pause", "pause the media", pendingIntent); - } - actions.add(remoteAction); - - - //add custom actions to pip window - PictureInPictureParams params = - new PictureInPictureParams.Builder() - .setActions(actions) - .build(); - setPictureInPictureParams(params); - } - - public void changedToPipMode() { - FragmentManager fragmentManager = getSupportFragmentManager(); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); - - assert videoPlayerFragment != null; - videoPlayerFragment.showControls(false); - //create custom actions - makePipControls(); - - //setup receiver to handle customer actions - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_STOP); - filter.addAction(ACTION_PAUSE); - filter.addAction(ACTION_PLAY); - filter.addAction((getString(R.string.app_background_audio))); - receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - assert action != null; - if (action.equals(ACTION_PAUSE)) { - videoPlayerFragment.pauseVideo(); - makePipControls(); - } - if (action.equals(ACTION_PLAY)) { - videoPlayerFragment.unPauseVideo(); - makePipControls(); - } - - if (action.equals(getString(R.string.app_background_audio))) { - unregisterReceiver(receiver); - finish(); - } - if (action.equals(ACTION_STOP)) { - unregisterReceiver(receiver); - finishAndRemoveTask(); - } - } - }; - registerReceiver(receiver, filter); - - Log.v(TAG, "switched to pip "); - floatMode = true; - videoPlayerFragment.showControls(false); - } - - public void changedToNormalMode() { - FragmentManager fragmentManager = getSupportFragmentManager(); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); - - assert videoPlayerFragment != null; - videoPlayerFragment.showControls(true); - if (receiver != null) { - unregisterReceiver(receiver); - } - Log.v(TAG, "switched to normal"); - floatMode = false; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // Set theme - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); - setTheme(getResources().getIdentifier( - sharedPref.getString( - getString(R.string.pref_theme_key), - getString(R.string.app_default_theme) - ), - "style", - getPackageName()) - ); - - setContentView(R.layout.activity_video_play); - - // get video ID - Intent intent = getIntent(); - String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) - getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); - - assert videoPlayerFragment != null; - String playingVideo = videoPlayerFragment.getVideoUuid(); - Log.v(TAG, "oncreate click: " + videoUuid + " is trying to replace: " + playingVideo); - - if (TextUtils.isEmpty(playingVideo)) { - Log.v(TAG, "oncreate no video currently playing"); - videoPlayerFragment.start(videoUuid); - } else if (!playingVideo.equals(videoUuid)) { - Log.v(TAG, "oncreate different video playing currently"); - videoPlayerFragment.stopVideo(); - videoPlayerFragment.start(videoUuid); - } else { - Log.v(TAG, "oncreate same video playing currently"); - } - - // if we are in landscape set the video to fullscreen - int orientation = this.getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - setOrientation(true); - } - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) - getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); - assert videoPlayerFragment != null; - String videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID); - Log.v(TAG, "new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.getVideoUuid()); - String playingVideo = videoPlayerFragment.getVideoUuid(); - - if (TextUtils.isEmpty(playingVideo)) { - Log.v(TAG, "new intent no video currently playing"); - videoPlayerFragment.start(videoUuid); - } else if (!playingVideo.equals(videoUuid)) { - Log.v(TAG, "new intent different video playing currently"); - videoPlayerFragment.stopVideo(); - videoPlayerFragment.start(videoUuid); - } else { - Log.v(TAG, "new intent same video playing currently"); - } - - // if we are in landscape set the video to fullscreen - int orientation = this.getResources().getConfiguration().orientation; - if (orientation == Configuration.ORIENTATION_LANDSCAPE) { - setOrientation(true); - } - } - - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - Log.v(TAG, "onConfigurationChanged()..."); - - super.onConfigurationChanged(newConfig); - - // Checking the orientation changes of the screen - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { - setOrientation(true); - } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { - setOrientation(false); - } - } - - private void setOrientation(Boolean isLandscape) { - FragmentManager fragmentManager = getSupportFragmentManager(); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); - VideoMetaDataFragment videoMetaFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment); - - assert videoPlayerFragment != null; - RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) videoPlayerFragment.requireView().getLayoutParams(); - params.width = FrameLayout.LayoutParams.MATCH_PARENT; - params.height = isLandscape ? FrameLayout.LayoutParams.MATCH_PARENT : (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 250, getResources().getDisplayMetrics()); - - videoPlayerFragment.requireView().setLayoutParams(params); - - if (videoMetaFragment != null) { - FragmentTransaction transaction = fragmentManager.beginTransaction() - .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out); - - if (isLandscape) { - transaction.hide(videoMetaFragment); - } else { - transaction.show(videoMetaFragment); - } - - transaction.commit(); - } - - videoPlayerFragment.setIsFullscreen(isLandscape); - - if ( isLandscape ) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - } - - @Override - protected void onDestroy() { - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) - getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); - - assert videoPlayerFragment != null; - videoPlayerFragment.destroyVideo(); - - super.onDestroy(); - Log.v(TAG, "onDestroy..."); - } - - @Override - protected void onPause() { - super.onPause(); - Log.v(TAG, "onPause()..."); - } - - @Override - protected void onResume() { - super.onResume(); - Log.v(TAG, "onResume()..."); - } - - @Override - protected void onStop() { - super.onStop(); - - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) - getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); - - assert videoPlayerFragment != null; - videoPlayerFragment.stopVideo(); - - Log.v(TAG, "onStop()..."); - } - - @Override - protected void onStart() { - super.onStart(); - - Log.v(TAG, "onStart()..."); - } - - @SuppressLint("NewApi") - @Override - public void onUserLeaveHint() { - - Log.v(TAG, "onUserLeaveHint()..."); - - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); - FragmentManager fragmentManager = getSupportFragmentManager(); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); - VideoMetaDataFragment videoMetaDataFragment = (VideoMetaDataFragment) fragmentManager.findFragmentById(R.id.video_meta_data_fragment); - - String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key)); - - assert videoPlayerFragment != null; - assert backgroundBehavior != null; - if ( videoMetaDataFragment.isLeaveAppExpected() ) - { - super.onUserLeaveHint(); - return; - } - - if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) { - Log.v(TAG, "stop the video"); - - videoPlayerFragment.pauseVideo(); - stopService(new Intent(this, VideoPlayerService.class)); - super.onBackPressed(); - - } else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) { - Log.v(TAG, "play the Audio"); - super.onBackPressed(); - - } else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) { - Log.v(TAG, "play in floating video"); - //canEnterPIPMode makes sure API level is high enough - if (canEnterPipMode(this)) { - Log.v(TAG, "enabling pip"); - enterPipMode(); - } else { - Log.v(TAG, "unable to use pip"); - } - - } else { - // Deal with bad entries from older version - Log.v(TAG, "No setting, fallback"); - super.onBackPressed(); - - } - - - } - - // @RequiresApi(api = Build.VERSION_CODES.O) - @SuppressLint("NewApi") - public void onBackPressed() { - - Log.v(TAG, "onBackPressed()..."); - - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) - getSupportFragmentManager().findFragmentById(R.id.video_player_fragment); - - assert videoPlayerFragment != null; - - // copying Youtube behavior to have back button exit full screen. - if (videoPlayerFragment.getIsFullscreen()) { - Log.v(TAG, "exiting full screen"); - videoPlayerFragment.fullScreenToggle(); - return; - } - // pause video if pref is enabled - if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) { - videoPlayerFragment.pauseVideo(); - } - - String backgroundBehavior = sharedPref.getString(getString(R.string.pref_background_behavior_key), getString(R.string.pref_background_stop_key)); - - assert backgroundBehavior != null; - - if (backgroundBehavior.equals(getString(R.string.pref_background_stop_key))) { - Log.v(TAG, "stop the video"); - videoPlayerFragment.pauseVideo(); - stopService(new Intent(this, VideoPlayerService.class)); - super.onBackPressed(); - - } else if (backgroundBehavior.equals(getString(R.string.pref_background_audio_key))) { - Log.v(TAG, "play the Audio"); - super.onBackPressed(); - - } else if (backgroundBehavior.equals(getString(R.string.pref_background_float_key))) { - Log.v(TAG, "play in floating video"); - //canEnterPIPMode makes sure API level is high enough - if (canEnterPipMode(this)) { - Log.v(TAG, "enabling pip"); - enterPipMode(); - //fixes problem where back press doesn't bring up video list after returning from PIP mode - Intent intentSettings = new Intent(this, VideoListActivity.class); - this.startActivity(intentSettings); - } else { - Log.v(TAG, "Unable to enter PIP mode"); - super.onBackPressed(); - } - - } else { - // Deal with bad entries from older version - Log.v(TAG, "No setting, fallback"); - super.onBackPressed(); - - } - - - } - - @RequiresApi(api = Build.VERSION_CODES.O) - public void enterPipMode() { - final FragmentManager fragmentManager = getSupportFragmentManager(); - final VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById( R.id.video_player_fragment ); - - if ( videoPlayerFragment.getVideoAspectRatio() == 0 ) { - Log.i( TAG, "impossible to switch to pip" ); - } else { - Rational rational = new Rational( (int) ( videoPlayerFragment.getVideoAspectRatio() * 100 ), 100 ); - PictureInPictureParams mParams = - new PictureInPictureParams.Builder() - .setAspectRatio( rational ) -// .setSourceRectHint(new Rect(0,500,400,600)) - .build(); - - enterPictureInPictureMode( mParams ); - } - } - - @Override - public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, Configuration newConfig) { - FragmentManager fragmentManager = getSupportFragmentManager(); - VideoPlayerFragment videoPlayerFragment = (VideoPlayerFragment) fragmentManager.findFragmentById(R.id.video_player_fragment); - - if (videoPlayerFragment != null) { - - if (isInPictureInPictureMode) { - changedToPipMode(); - Log.v(TAG, "switched to pip "); - videoPlayerFragment.useController(false); - } else { - changedToNormalMode(); - Log.v(TAG, "switched to normal"); - videoPlayerFragment.useController(true); - } - - } else { - Log.e(TAG, "videoPlayerFragment is NULL"); - } - } - -} diff --git a/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.kt b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.kt new file mode 100644 index 0000000..bdb4ae0 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/activity/VideoPlayActivity.kt @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.activity + +import androidx.appcompat.app.AppCompatActivity +import android.annotation.SuppressLint +import net.schueller.peertube.fragment.VideoPlayerFragment +import net.schueller.peertube.R +import android.app.RemoteAction +import android.app.PendingIntent +import com.google.android.exoplayer2.ui.PlayerNotificationManager +import android.app.PictureInPictureParams +import android.content.* +import android.content.res.Configuration +import android.graphics.drawable.Icon +import android.os.Bundle +import android.text.TextUtils +import net.schueller.peertube.fragment.VideoMetaDataFragment +import android.widget.RelativeLayout +import android.widget.FrameLayout +import android.util.TypedValue +import android.view.WindowManager +import net.schueller.peertube.service.VideoPlayerService +import net.schueller.peertube.helper.VideoHelper +import androidx.annotation.RequiresApi +import android.os.Build +import android.util.Log +import android.util.Rational +import androidx.fragment.app.Fragment +import net.schueller.peertube.fragment.VideoDescriptionFragment +import java.util.ArrayList + +class VideoPlayActivity : CommonActivity() { + private var receiver: BroadcastReceiver? = null + + //This can only be called when in entering pip mode which can't happen if the device doesn't support pip mode. + @SuppressLint("NewApi") + fun makePipControls() { + val fragmentManager = supportFragmentManager + val videoPlayerFragment = + fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment? + val actions = ArrayList() + var actionIntent = Intent(getString(R.string.app_background_audio)) + var pendingIntent = + PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0) + @SuppressLint("NewApi", "LocalSuppress") var icon = Icon.createWithResource( + applicationContext, android.R.drawable.stat_sys_speakerphone + ) + @SuppressLint("NewApi", "LocalSuppress") var remoteAction = + RemoteAction(icon!!, "close pip", "from pip window custom command", pendingIntent!!) + actions.add(remoteAction) + actionIntent = Intent(PlayerNotificationManager.ACTION_STOP) + pendingIntent = + PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0) + icon = Icon.createWithResource( + applicationContext, + com.google.android.exoplayer2.ui.R.drawable.exo_notification_stop + ) + remoteAction = RemoteAction(icon, "play", "stop the media", pendingIntent) + actions.add(remoteAction) + assert(videoPlayerFragment != null) + if (videoPlayerFragment!!.isPaused) { + Log.e(TAG, "setting actions with play button") + actionIntent = Intent(PlayerNotificationManager.ACTION_PLAY) + pendingIntent = + PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0) + icon = Icon.createWithResource( + applicationContext, + com.google.android.exoplayer2.ui.R.drawable.exo_notification_play + ) + remoteAction = RemoteAction(icon, "play", "play the media", pendingIntent) + } else { + Log.e(TAG, "setting actions with pause button") + actionIntent = Intent(PlayerNotificationManager.ACTION_PAUSE) + pendingIntent = + PendingIntent.getBroadcast(applicationContext, REQUEST_CODE, actionIntent, 0) + icon = Icon.createWithResource( + applicationContext, + com.google.android.exoplayer2.ui.R.drawable.exo_notification_pause + ) + remoteAction = RemoteAction(icon, "pause", "pause the media", pendingIntent) + } + actions.add(remoteAction) + + + //add custom actions to pip window + val params = PictureInPictureParams.Builder() + .setActions(actions) + .build() + setPictureInPictureParams(params) + } + + private fun changedToPipMode() { + val fragmentManager = supportFragmentManager + val videoPlayerFragment = + (fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!! + videoPlayerFragment.showControls(false) + //create custom actions + makePipControls() + + //setup receiver to handle customer actions + val filter = IntentFilter() + filter.addAction(PlayerNotificationManager.ACTION_STOP) + filter.addAction(PlayerNotificationManager.ACTION_PAUSE) + filter.addAction(PlayerNotificationManager.ACTION_PLAY) + filter.addAction(getString(R.string.app_background_audio)) + receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action!! + if (action == PlayerNotificationManager.ACTION_PAUSE) { + videoPlayerFragment.pauseVideo() + makePipControls() + } + if (action == PlayerNotificationManager.ACTION_PLAY) { + videoPlayerFragment.unPauseVideo() + makePipControls() + } + if (action == getString(R.string.app_background_audio)) { + unregisterReceiver(receiver) + finish() + } + if (action == PlayerNotificationManager.ACTION_STOP) { + unregisterReceiver(receiver) + finishAndRemoveTask() + } + } + } + registerReceiver(receiver, filter) + Log.v(TAG, "switched to pip ") + floatMode = true + videoPlayerFragment.showControls(false) + } + + private fun changedToNormalMode() { + val fragmentManager = supportFragmentManager + val videoPlayerFragment = + (fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!! + videoPlayerFragment.showControls(true) + if (receiver != null) { + unregisterReceiver(receiver) + } + Log.v(TAG, "switched to normal") + floatMode = false + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Set theme + val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE) + setTheme( + resources.getIdentifier( + sharedPref.getString( + getString(R.string.pref_theme_key), + getString(R.string.app_default_theme) + ), + "style", + packageName + ) + ) + setContentView(R.layout.activity_video_play) + + // get video ID + val intent = intent + val videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID) + val videoPlayerFragment = + (supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!! + val playingVideo = videoPlayerFragment.videoUuid + Log.v(TAG, "oncreate click: $videoUuid is trying to replace: $playingVideo") + when { + TextUtils.isEmpty(playingVideo) -> { + Log.v(TAG, "oncreate no video currently playing") + videoPlayerFragment.start(videoUuid) + } + playingVideo != videoUuid -> { + Log.v(TAG, "oncreate different video playing currently") + videoPlayerFragment.stopVideo() + videoPlayerFragment.start(videoUuid) + } + else -> { + Log.v(TAG, "oncreate same video playing currently") + } + } + + // if we are in landscape set the video to fullscreen + val orientation = this.resources.configuration.orientation + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + setOrientation(true) + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + val videoPlayerFragment = + (supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!! + val videoUuid = intent.getStringExtra(VideoListActivity.EXTRA_VIDEOID) + Log.v( + TAG, + "new intent click: " + videoUuid + " is trying to replace: " + videoPlayerFragment.videoUuid + ) + val playingVideo = videoPlayerFragment.videoUuid + when { + TextUtils.isEmpty(playingVideo) -> { + Log.v(TAG, "new intent no video currently playing") + videoPlayerFragment.start(videoUuid) + } + playingVideo != videoUuid -> { + Log.v(TAG, "new intent different video playing currently") + videoPlayerFragment.stopVideo() + videoPlayerFragment.start(videoUuid) + } + else -> { + Log.v(TAG, "new intent same video playing currently") + } + } + + // if we are in landscape set the video to fullscreen + val orientation = this.resources.configuration.orientation + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + setOrientation(true) + } + } + + override fun onConfigurationChanged(newConfig: Configuration) { + Log.v(TAG, "onConfigurationChanged()...") + super.onConfigurationChanged(newConfig) + + // Checking the orientation changes of the screen + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + setOrientation(true) + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + setOrientation(false) + } + } + + private fun setOrientation(isLandscape: Boolean) { + val fragmentManager = supportFragmentManager + val videoPlayerFragment = + fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment? + val videoMetaFragment = + fragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment? + assert(videoPlayerFragment != null) + val params = videoPlayerFragment!!.requireView().layoutParams as RelativeLayout.LayoutParams + params.width = FrameLayout.LayoutParams.MATCH_PARENT + params.height = + if (isLandscape) FrameLayout.LayoutParams.MATCH_PARENT else TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 250f, + resources.displayMetrics + ) + .toInt() + videoPlayerFragment.requireView().layoutParams = params + if (videoMetaFragment != null) { + val transaction = fragmentManager.beginTransaction() + .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out) + if (isLandscape) { + transaction.hide(videoMetaFragment) + } else { + transaction.show(videoMetaFragment) + } + transaction.commit() + } + videoPlayerFragment.setIsFullscreen(isLandscape) + if (isLandscape) { + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } else { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) + } + } + + override fun onDestroy() { + val videoPlayerFragment = + (supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!! + videoPlayerFragment.destroyVideo() + super.onDestroy() + Log.v(TAG, "onDestroy...") + } + + override fun onPause() { + super.onPause() + Log.v(TAG, "onPause()...") + } + + override fun onResume() { + super.onResume() + Log.v(TAG, "onResume()...") + } + + override fun onStop() { + super.onStop() + val videoPlayerFragment = + (supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!! + videoPlayerFragment.stopVideo() + + // TODO: doesn't remove fragment?? + val fragment: Fragment? = supportFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG) + if (fragment != null) { + Log.v(TAG, "remove VideoDescriptionFragment") + supportFragmentManager.beginTransaction().remove(fragment).commit() + } + + Log.v(TAG, "onStop()...") + } + + override fun onStart() { + super.onStart() + Log.v(TAG, "onStart()...") + } + + @SuppressLint("NewApi") + public override fun onUserLeaveHint() { + Log.v(TAG, "onUserLeaveHint()...") + val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE) + val fragmentManager = supportFragmentManager + val videoPlayerFragment = + fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment? + val videoMetaDataFragment = + fragmentManager.findFragmentById(R.id.video_meta_data_fragment) as VideoMetaDataFragment? + val backgroundBehavior = sharedPref.getString( + getString(R.string.pref_background_behavior_key), + getString(R.string.pref_background_stop_key) + ) + assert(videoPlayerFragment != null) + assert(backgroundBehavior != null) + if (videoMetaDataFragment!!.isLeaveAppExpected) { + super.onUserLeaveHint() + return + } + if (backgroundBehavior == getString(R.string.pref_background_stop_key)) { + Log.v(TAG, "stop the video") + videoPlayerFragment!!.pauseVideo() + stopService(Intent(this, VideoPlayerService::class.java)) + super.onBackPressed() + } else if (backgroundBehavior == getString(R.string.pref_background_audio_key)) { + Log.v(TAG, "play the Audio") + super.onBackPressed() + } else if (backgroundBehavior == getString(R.string.pref_background_float_key)) { + Log.v(TAG, "play in floating video") + //canEnterPIPMode makes sure API level is high enough + if (VideoHelper.canEnterPipMode(this)) { + Log.v(TAG, "enabling pip") + enterPipMode() + } else { + Log.v(TAG, "unable to use pip") + } + } else { + // Deal with bad entries from older version + Log.v(TAG, "No setting, fallback") + super.onBackPressed() + } + } + + // @RequiresApi(api = Build.VERSION_CODES.O) + @SuppressLint("NewApi") + override fun onBackPressed() { + Log.v(TAG, "onBackPressed()...") + val sharedPref = getSharedPreferences(packageName + "_preferences", Context.MODE_PRIVATE) + val videoPlayerFragment = + (supportFragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment?)!! + + // copying Youtube behavior to have back button exit full screen. + if (videoPlayerFragment.getIsFullscreen()) { + Log.v(TAG, "exiting full screen") + videoPlayerFragment.fullScreenToggle() + return + } + // pause video if pref is enabled + if (sharedPref.getBoolean(getString(R.string.pref_back_pause_key), true)) { + videoPlayerFragment.pauseVideo() + } + val backgroundBehavior = sharedPref.getString( + getString(R.string.pref_background_behavior_key), + getString(R.string.pref_background_stop_key) + )!! + if (backgroundBehavior == getString(R.string.pref_background_stop_key)) { + Log.v(TAG, "stop the video") + videoPlayerFragment.pauseVideo() + stopService(Intent(this, VideoPlayerService::class.java)) + super.onBackPressed() + } else if (backgroundBehavior == getString(R.string.pref_background_audio_key)) { + Log.v(TAG, "play the Audio") + super.onBackPressed() + } else if (backgroundBehavior == getString(R.string.pref_background_float_key)) { + Log.v(TAG, "play in floating video") + //canEnterPIPMode makes sure API level is high enough + if (VideoHelper.canEnterPipMode(this)) { + Log.v(TAG, "enabling pip") + enterPipMode() + //fixes problem where back press doesn't bring up video list after returning from PIP mode + val intentSettings = Intent(this, VideoListActivity::class.java) + this.startActivity(intentSettings) + } else { + Log.v(TAG, "Unable to enter PIP mode") + super.onBackPressed() + } + } else { + // Deal with bad entries from older version + Log.v(TAG, "No setting, fallback") + super.onBackPressed() + } + } + + @RequiresApi(api = Build.VERSION_CODES.O) + fun enterPipMode() { + val fragmentManager = supportFragmentManager + val videoPlayerFragment = + fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment? + if (videoPlayerFragment!!.videoAspectRatio == 0.toFloat()) { + Log.i(TAG, "impossible to switch to pip") + } else { + val rational = Rational((videoPlayerFragment.videoAspectRatio * 100).toInt(), 100) + val mParams = PictureInPictureParams.Builder() + .setAspectRatio(rational) // .setSourceRectHint(new Rect(0,500,400,600)) + .build() + enterPictureInPictureMode(mParams) + } + } + + override fun onPictureInPictureModeChanged( + isInPictureInPictureMode: Boolean, + newConfig: Configuration + ) { + val fragmentManager = supportFragmentManager + val videoPlayerFragment = + fragmentManager.findFragmentById(R.id.video_player_fragment) as VideoPlayerFragment? + if (videoPlayerFragment != null) { + if (isInPictureInPictureMode) { + changedToPipMode() + Log.v(TAG, "switched to pip ") + videoPlayerFragment.useController(false) + } else { + changedToNormalMode() + Log.v(TAG, "switched to normal") + videoPlayerFragment.useController(true) + } + } else { + Log.e(TAG, "videoPlayerFragment is NULL") + } + } + + companion object { + private const val TAG = "VideoPlayActivity" + var floatMode = false + private const val REQUEST_CODE = 101 + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecycleViewAdapter.kt b/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecycleViewAdapter.kt index c55309a..6b21a64 100644 --- a/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecycleViewAdapter.kt +++ b/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecycleViewAdapter.kt @@ -4,19 +4,14 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import net.schueller.peertube.R -import net.schueller.peertube.databinding.ItemCategoryTitleBinding -import net.schueller.peertube.databinding.ItemChannelTitleBinding -import net.schueller.peertube.databinding.ItemTagTitleBinding -import net.schueller.peertube.databinding.RowVideoListBinding -import net.schueller.peertube.model.Category -import net.schueller.peertube.model.Channel -import net.schueller.peertube.model.TagVideo -import net.schueller.peertube.model.Video -import net.schueller.peertube.model.VideoList +import net.schueller.peertube.databinding.* +import net.schueller.peertube.fragment.VideoMetaDataFragment +import net.schueller.peertube.model.* import net.schueller.peertube.model.ui.OverviewRecycleViewItem +import net.schueller.peertube.model.ui.VideoMetaViewItem import java.util.ArrayList -class MultiViewRecycleViewAdapter : RecyclerView.Adapter() { +class MultiViewRecycleViewAdapter(private val videoMetaDataFragment: VideoMetaDataFragment? = null) : RecyclerView.Adapter() { private var items = ArrayList() set(value) { @@ -34,6 +29,11 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter MultiViewRecyclerViewHolder.VideoMetaViewHolder( + ItemVideoMetaBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + videoMetaDataFragment + ) + R.layout.item_video_comments_overview -> MultiViewRecyclerViewHolder.VideoCommentsViewHolder( + ItemVideoCommentsOverviewBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) else -> throw IllegalArgumentException("Invalid ViewType Provided") } } @@ -93,6 +113,8 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter holder.bind(items[position] as Category) is MultiViewRecyclerViewHolder.ChannelViewHolder -> holder.bind(items[position] as Channel) is MultiViewRecyclerViewHolder.TagViewHolder -> holder.bind(items[position] as TagVideo) + is MultiViewRecyclerViewHolder.VideoMetaViewHolder -> holder.bind(items[position] as VideoMetaViewItem) + is MultiViewRecyclerViewHolder.VideoCommentsViewHolder -> holder.bind(items[position] as CommentThread) } } @@ -104,6 +126,8 @@ class MultiViewRecycleViewAdapter : RecyclerView.Adapter R.layout.item_channel_title is Category -> R.layout.item_category_title is TagVideo -> R.layout.item_tag_title + is VideoMetaViewItem -> R.layout.item_video_meta + is CommentThread -> R.layout.item_video_comments_overview else -> { return 0} } } diff --git a/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt b/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt index 6fc4a5a..b8b19d5 100644 --- a/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt +++ b/app/src/main/java/net/schueller/peertube/adapter/MultiViewRecyclerViewHolder.kt @@ -16,47 +16,286 @@ */ package net.schueller.peertube.adapter +import android.content.Context import android.content.Intent +import android.util.Log import android.view.MenuItem import android.view.View +import android.view.View.GONE +import android.widget.Toast import androidx.appcompat.widget.PopupMenu import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding +import com.google.gson.JsonObject import com.squareup.picasso.Picasso -import net.schueller.peertube.R import net.schueller.peertube.R.color import net.schueller.peertube.R.string import net.schueller.peertube.activity.AccountActivity import net.schueller.peertube.activity.VideoListActivity -import net.schueller.peertube.activity.VideoListActivity.Companion import net.schueller.peertube.activity.VideoPlayActivity -import net.schueller.peertube.databinding.ItemCategoryTitleBinding -import net.schueller.peertube.databinding.ItemChannelTitleBinding -import net.schueller.peertube.databinding.RowVideoListBinding import net.schueller.peertube.helper.APIUrlHelper import net.schueller.peertube.helper.MetaDataHelper.getDuration import net.schueller.peertube.helper.MetaDataHelper.getMetaString import net.schueller.peertube.helper.MetaDataHelper.getOwnerString -import net.schueller.peertube.model.Avatar -import net.schueller.peertube.model.Category -import net.schueller.peertube.model.Channel -import net.schueller.peertube.model.Video import com.mikepenz.iconics.Iconics.Builder import net.schueller.peertube.R.id import net.schueller.peertube.R.menu -import net.schueller.peertube.databinding.ItemTagTitleBinding +import net.schueller.peertube.databinding.* +import net.schueller.peertube.fragment.VideoMetaDataFragment import net.schueller.peertube.intents.Intents -import net.schueller.peertube.model.TagVideo +import net.schueller.peertube.model.* +import net.schueller.peertube.model.ui.VideoMetaViewItem +import net.schueller.peertube.network.GetVideoDataService +import net.schueller.peertube.network.RetrofitInstance +import net.schueller.peertube.network.Session +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import net.schueller.peertube.R +import net.schueller.peertube.network.GetUserService + sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) { + var videoRating: Rating? = null + var isLeaveAppExpected = false + class CategoryViewHolder(private val binding: ItemCategoryTitleBinding): MultiViewRecyclerViewHolder(binding) { fun bind(category: Category) { binding.textViewTitle.text = category.label } } + class VideoCommentsViewHolder(private val binding: ItemVideoCommentsOverviewBinding): MultiViewRecyclerViewHolder(binding) { + fun bind(commentThread: CommentThread) { + + binding.videoCommentsTotalCount.text = commentThread.total.toString() + + if (commentThread.comments.isNotEmpty()) { + val highlightedComment: Comment = commentThread.comments[0] + + // owner / creator Avatar + val avatar = highlightedComment.account.avatar + if (avatar != null) { + val baseUrl = APIUrlHelper.getUrl(binding.videoHighlightedAvatar.context) + val avatarPath = avatar.path + Picasso.get() + .load(baseUrl + avatarPath) + .into(binding.videoHighlightedAvatar) + } + binding.videoHighlightedComment.text = highlightedComment.text + } + } + } + + + class VideoMetaViewHolder(private val binding: ItemVideoMetaBinding, private val videoMetaDataFragment: VideoMetaDataFragment?): MultiViewRecyclerViewHolder(binding) { + fun bind(videoMetaViewItem: VideoMetaViewItem) { + + val video = videoMetaViewItem.video + + if (video != null && videoMetaDataFragment != null) { + + val context = binding.avatar.context + val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) + val videoDataService = RetrofitInstance.getRetrofitInstance( + apiBaseURL, + APIUrlHelper.useInsecureConnection(context) + ).create( + GetVideoDataService::class.java + ) + val userService = RetrofitInstance.getRetrofitInstance( + apiBaseURL, + APIUrlHelper.useInsecureConnection(context) + ).create( + GetUserService::class.java + ) + + // Title + binding.videoName.text = video.name + binding.videoOpenDescription.setOnClickListener { + videoMetaDataFragment.showDescriptionFragment(video) + } + + // Thumbs up + binding.videoThumbsUpWrapper.setOnClickListener { + rateVideo(true, video, context, binding) + } + + // Thumbs Down + binding.videoThumbsDownWrapper.setOnClickListener { + rateVideo(false, video, context, binding) + } + + binding.videoAddToPlaylistWrapper.setOnClickListener { + Toast.makeText( + context, + context.getString(string.video_feature_not_yet_implemented), + Toast.LENGTH_SHORT + ).show() + } + + binding.videoBlockWrapper.setOnClickListener { + Toast.makeText( + context, + context.getString(string.video_feature_not_yet_implemented), + Toast.LENGTH_SHORT + ).show() + } + + binding.videoFlagWrapper.setOnClickListener { + Toast.makeText( + context, + context.getString(string.video_feature_not_yet_implemented), + Toast.LENGTH_SHORT + ).show() + } + + // video rating + videoRating = Rating() + videoRating!!.rating = RATING_NONE // default + updateVideoRating(video, binding) + + // Retrieve which rating the user gave to this video + if (Session.getInstance().isLoggedIn) { + val call = videoDataService.getVideoRating(video.id) + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + videoRating = response.body() + updateVideoRating(video, binding) + } + + override fun onFailure(call: Call, t: Throwable) { + // Do nothing. + } + }) + } + + // Share + binding.videoShare.setOnClickListener { + isLeaveAppExpected = true + Intents.Share(context, video) + } + + // hide download if not supported by video + if (video.downloadEnabled) { + binding.videoDownloadWrapper.setOnClickListener { + Intents.Download(context, video) + } + } else { + binding.videoDownloadWrapper.visibility = GONE + } + + val account = video.account + + // owner / creator Avatar + val avatar = account.avatar + if (avatar != null) { + val baseUrl = APIUrlHelper.getUrl(context) + val avatarPath = avatar.path + Picasso.get() + .load(baseUrl + avatarPath) + .into(binding.avatar) + } + // created at / views + binding.videoMeta.text = getMetaString( + video.createdAt, + video.views, + context!! + ) + + // owner / creator + binding.videoOwner.text = getOwnerString( + video.account.name, + video.account.host, + context + ) + + // videoOwnerSubscribers + binding.videoOwnerSubscribers.text = video.account.followersCount.toString() + + + // get subscription status + var isSubscribed = false + + if (Session.getInstance().isLoggedIn) { + val subChannel = video.channel.name + "@" + video.channel.host + val call = userService.subscriptionsExist(subChannel) + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + // {"video.channel.name + "@" + video.channel.host":true} + if (response.body()?.get(video.channel.name + "@" + video.channel.host)!!.asBoolean) { + binding.videoOwnerSubscribeButton.setText(string.unsubscribe) + isSubscribed = true; + } else { + binding.videoOwnerSubscribeButton.setText(string.subscribe) + } + } + } + override fun onFailure(call: Call, t: Throwable) { + // Do nothing. + } + }) + + } + + binding.videoOwnerSubscribeButton.setOnClickListener { + if (Session.getInstance().isLoggedIn) { + if (!isSubscribed) { + val payload = video.channel.name + "@" + video.channel.host + val body = "{\"uri\":\"$payload\"}".toRequestBody("application/json".toMediaType()) + val call = userService.subscribe(body) + call.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + binding.videoOwnerSubscribeButton.setText(string.unsubscribe) + isSubscribed = true + } + } + override fun onFailure(call: Call, t: Throwable) { + // Do nothing. + } + }) + } else { + val payload = video.channel.name + "@" + video.channel.host + val call = userService.unsubscribe(payload) + call.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + binding.videoOwnerSubscribeButton.setText(string.subscribe) + isSubscribed = false + } + } + override fun onFailure(call: Call, t: Throwable) { + // Do nothing. + } + }) + } + } else { + Toast.makeText( + context, + context.getString(string.video_login_required_for_service), + Toast.LENGTH_SHORT + ).show() + } + } + } + + + } + } + class ChannelViewHolder(private val binding: ItemChannelTitleBinding): MultiViewRecyclerViewHolder(binding) { fun bind(channel: Channel) { @@ -178,4 +417,117 @@ sealed class MultiViewRecyclerViewHolder(binding: ViewBinding) : RecyclerView.Vi } + fun updateVideoRating(video: Video?, binding: ItemVideoMetaBinding) { + + when (videoRating!!.rating) { + RATING_NONE -> { + Log.v("MWCVH", "RATING_NONE") + binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up) + binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down) + } + RATING_LIKE -> { + Log.v("MWCVH", "RATING_LIKE") + binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up_filled) + binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down) + } + RATING_DISLIKE -> { + Log.v("MWCVH", "RATING_DISLIKE") + binding.videoThumbsUp.setImageResource(R.drawable.ic_thumbs_up) + binding.videoThumbsDown.setImageResource(R.drawable.ic_thumbs_down_filled) + } + } + + // Update the texts + binding.videoThumbsUpTotal.text = video?.likes.toString() + binding.videoThumbsDownTotal.text = video?.dislikes.toString() + + } + + /** + * TODO: move this out and get update when rating changes + */ + fun rateVideo(like: Boolean, video: Video, context: Context, binding: ItemVideoMetaBinding) { + if (Session.getInstance().isLoggedIn) { + val ratePayload: String = when (videoRating!!.rating) { + RATING_LIKE -> if (like) RATING_NONE else RATING_DISLIKE + RATING_DISLIKE -> if (like) RATING_LIKE else RATING_NONE + RATING_NONE -> if (like) RATING_LIKE else RATING_DISLIKE + else -> if (like) RATING_LIKE else RATING_DISLIKE + } + + val body = "{\"rating\":\"$ratePayload\"}".toRequestBody("application/json".toMediaType()) + + val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) + val videoDataService = RetrofitInstance.getRetrofitInstance( + apiBaseURL, APIUrlHelper.useInsecureConnection( + context + ) + ).create( + GetVideoDataService::class.java + ) + val call = videoDataService.rateVideo(video.id, body) + call.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + // if 20x, update likes/dislikes + if (response.isSuccessful) { + val previousRating = videoRating!!.rating + + // Update the likes/dislikes count of the video, if needed. + // This is only a visual trick, as the actual like/dislike count has + // already been modified on the PeerTube instance. + if (previousRating != ratePayload) { + when (previousRating) { + RATING_NONE -> if (ratePayload == RATING_LIKE) { + video.likes = video.likes + 1 + } else { + video.dislikes = video.dislikes + 1 + } + RATING_LIKE -> { + video.likes = video.likes - 1 + if (ratePayload == RATING_DISLIKE) { + video.dislikes = video.dislikes + 1 + } + } + RATING_DISLIKE -> { + video.dislikes = video.dislikes - 1 + if (ratePayload == RATING_LIKE) { + video.likes = video.likes + 1 + } + } + } + } + videoRating!!.rating = ratePayload + updateVideoRating(video, binding) + } + } + + override fun onFailure(call: Call, t: Throwable) { + Toast.makeText( + context, + context.getString(string.video_rating_failed), + Toast.LENGTH_SHORT + ).show() + } + }) + } else { + Toast.makeText( + context, + context.getString(string.video_login_required_for_service), + Toast.LENGTH_SHORT + ).show() + } + } + + companion object { + private const val RATING_NONE = "none" + private const val RATING_LIKE = "like" + private const val RATING_DISLIKE = "dislike" + const val EXTRA_VIDEOID = "VIDEOID" + const val EXTRA_ACCOUNTDISPLAYNAME = "ACCOUNTDISPLAYNAMEANDHOST" + + } + } diff --git a/app/src/main/java/net/schueller/peertube/database/Server.kt b/app/src/main/java/net/schueller/peertube/database/Server.kt index 19f9d7f..fda5781 100644 --- a/app/src/main/java/net/schueller/peertube/database/Server.kt +++ b/app/src/main/java/net/schueller/peertube/database/Server.kt @@ -20,7 +20,7 @@ import android.os.Parcelable import androidx.room.PrimaryKey import androidx.room.ColumnInfo import androidx.room.Entity -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize @Entity(tableName = "server_table") diff --git a/app/src/main/java/net/schueller/peertube/database/ServerRepository.kt b/app/src/main/java/net/schueller/peertube/database/ServerRepository.kt index 871b9a9..c91d351 100644 --- a/app/src/main/java/net/schueller/peertube/database/ServerRepository.kt +++ b/app/src/main/java/net/schueller/peertube/database/ServerRepository.kt @@ -17,7 +17,6 @@ package net.schueller.peertube.database import android.app.Application -import android.os.AsyncTask import androidx.lifecycle.LiveData import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.kt b/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.kt index 2bfc3be..40886fd 100644 --- a/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.kt +++ b/app/src/main/java/net/schueller/peertube/fragment/AddServerFragment.kt @@ -19,12 +19,13 @@ package net.schueller.peertube.fragment import android.app.Activity import android.content.Intent import android.os.Bundle -import android.util.Log import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import net.schueller.peertube.R @@ -52,7 +53,7 @@ class AddServerFragment : Fragment() { } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { mBinding = FragmentAddServerBinding.inflate(inflater, container, false) return mBinding.root } @@ -115,7 +116,7 @@ class AddServerFragment : Fragment() { mBinding.pickServerUrl.setOnClickListener { val intentServer = Intent(activity, SearchServerActivity::class.java) - this.startActivityForResult(intentServer, PICK_SERVER) + openActivityForResult(intentServer) } } @@ -132,35 +133,24 @@ class AddServerFragment : Fragment() { } } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode != PICK_SERVER) { - return - } - - if (resultCode != Activity.RESULT_OK) { - return - } - - val serverUrlTest = data?.getStringExtra("serverUrl") - //Log.d(TAG, "serverUrl " + serverUrlTest); - - mBinding.serverUrl.setText(serverUrlTest) - - mBinding.serverLabel.apply { - if (text.toString().isBlank()) { - setText(data?.getStringExtra("serverName")) + private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> + if (result.resultCode == Activity.RESULT_OK) { + val intent = result.data + val serverUrlTest = intent?.getStringExtra("serverUrl") + mBinding.serverUrl.setText(serverUrlTest) + mBinding.serverLabel.apply { + if (text.toString().isBlank()) { + setText(intent?.getStringExtra("serverName")) + } } } + } + private fun openActivityForResult(intent: Intent) { + resultLauncher.launch(intent) } companion object { - private const val TAG = "AddServerFragment" - private const val PICK_SERVER = 1 - private const val SERVER_ARG = "server" fun newInstance(server: Server) = AddServerFragment().apply { diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoDescriptionFragment.kt b/app/src/main/java/net/schueller/peertube/fragment/VideoDescriptionFragment.kt new file mode 100644 index 0000000..7eaa58f --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/fragment/VideoDescriptionFragment.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.fragment + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.ImageButton +import net.schueller.peertube.R +import android.widget.TextView +import androidx.fragment.app.Fragment +import net.schueller.peertube.helper.APIUrlHelper +import net.schueller.peertube.helper.ErrorHelper +import net.schueller.peertube.model.Description +import net.schueller.peertube.model.Video +import net.schueller.peertube.network.GetVideoDataService +import net.schueller.peertube.network.RetrofitInstance +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + +class VideoDescriptionFragment : Fragment () { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate( + R.layout.fragment_video_description, container, + false + ) + + val video = video + + if (video != null) { + + val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) + val videoDataService = RetrofitInstance.getRetrofitInstance( + apiBaseURL, + APIUrlHelper.useInsecureConnection(context) + ).create( + GetVideoDataService::class.java + ) + + // description, get extended if available + val videoDescription = view.findViewById(R.id.description) + val shortDescription = video.description + if (shortDescription != null && shortDescription.length > 237) { + val call = videoDataService.getVideoFullDescription(video.uuid); + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + val videoFullDescription: Description? = response.body(); + + videoDescription.text = videoFullDescription?.description + } + override fun onFailure(call: Call, t: Throwable) { + Log.wtf(TAG, t.fillInStackTrace()) + ErrorHelper.showToastFromCommunicationError(activity, t) + } + }) + } + videoDescription.text = shortDescription; + + val closeButton = view.findViewById(R.id.video_description_close_button) + closeButton.setOnClickListener { + videoMetaDataFragment!!.hideDescriptionFragment() + } + + // video privacy + val videoPrivacy = view.findViewById(R.id.video_privacy); + videoPrivacy.text = video!!.privacy.label; + + // video category + val videoCategory = view.findViewById(R.id.video_category); + videoCategory.text = video!!.category.label; + + // video privacy + val videoLicense = view.findViewById(R.id.video_license); + videoLicense.text = video!!.licence.label; + + // video language + val videoLanguage = view.findViewById(R.id.video_language); + videoLanguage.text = video!!.language.label; + + // video privacy + val videoTags = view.findViewById(R.id.video_tags); + videoTags.text = android.text.TextUtils.join(", ", video!!.tags); + } + + + return view + } + + companion object { + private var video: Video? = null + private var videoMetaDataFragment: VideoMetaDataFragment? = null + const val TAG = "VideoDescr" + fun newInstance(mVideo: Video?, mVideoMetaDataFragment: VideoMetaDataFragment): VideoDescriptionFragment { + video = mVideo + videoMetaDataFragment = mVideoMetaDataFragment + return VideoDescriptionFragment() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.java b/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.java deleted file mode 100644 index b51e304..0000000 --- a/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package net.schueller.peertube.fragment; - -import android.Manifest; -import android.app.Activity; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.os.Bundle; -import android.util.Log; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.mikepenz.iconics.Iconics; -import com.squareup.picasso.Picasso; - -import java.util.Objects; -import net.schueller.peertube.R; -import net.schueller.peertube.helper.APIUrlHelper; -import net.schueller.peertube.helper.ErrorHelper; -import net.schueller.peertube.helper.MetaDataHelper; -import net.schueller.peertube.intents.Intents; -import net.schueller.peertube.model.Account; -import net.schueller.peertube.model.Avatar; -import net.schueller.peertube.model.Description; -import net.schueller.peertube.model.Rating; -import net.schueller.peertube.model.Video; -import net.schueller.peertube.network.GetVideoDataService; -import net.schueller.peertube.network.RetrofitInstance; -import net.schueller.peertube.network.Session; -import net.schueller.peertube.service.VideoPlayerService; - - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.app.ActivityCompat; -import androidx.fragment.app.Fragment; -import okhttp3.RequestBody; -import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -public class VideoMetaDataFragment extends Fragment { - - private static final String TAG = "VideoMetaDataFragment"; - - private static final String RATING_NONE = "none"; - private static final String RATING_LIKE = "like"; - private static final String RATING_DISLIKE = "dislike"; - - private Rating videoRating; - private ColorStateList defaultTextColor; - - private boolean leaveAppExpected = false; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_video_meta, container, false); - } - - @Override - public void onPause() - { - leaveAppExpected = false; - super.onPause(); - } - - public boolean isLeaveAppExpected() - { - return leaveAppExpected; - } - - public void updateVideoMeta(Video video, VideoPlayerService mService) { - Context context = getContext(); - Activity activity = getActivity(); - - String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); - GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class); - - // Thumbs up - Button thumbsUpButton = activity.findViewById(R.id.video_thumbs_up); - defaultTextColor = thumbsUpButton.getTextColors(); - thumbsUpButton.setText(R.string.video_thumbs_up_icon); - new Iconics.Builder().on(thumbsUpButton).build(); - thumbsUpButton.setOnClickListener(v -> { - rateVideo(true, video); - }); - - // Thumbs Down - Button thumbsDownButton = activity.findViewById(R.id.video_thumbs_down); - thumbsDownButton.setText(R.string.video_thumbs_down_icon); - new Iconics.Builder().on(thumbsDownButton).build(); - thumbsDownButton.setOnClickListener(v -> { - rateVideo(false, video); - }); - - // video rating - videoRating = new Rating(); - videoRating.setRating(RATING_NONE); // default - updateVideoRating(video); - - // Retrieve which rating the user gave to this video - if (Session.getInstance().isLoggedIn()) { - Call call = videoDataService.getVideoRating(video.getId()); - call.enqueue(new Callback() { - - @Override - public void onResponse(Call call, Response response) { - videoRating = response.body(); - updateVideoRating(video); - } - - @Override - public void onFailure(Call call, Throwable t) { - ErrorHelper.showToastFromCommunicationError( getActivity(), t ); - // Do nothing. - } - }); - } - - // Share - Button videoShareButton = activity.findViewById(R.id.video_share); - videoShareButton.setText(R.string.video_share_icon); - new Iconics.Builder().on(videoShareButton).build(); - videoShareButton.setOnClickListener(v -> - { - leaveAppExpected = true; - Intents.Share( context, video ); - } ); - - // Download - Button videoDownloadButton = activity.findViewById(R.id.video_download); - videoDownloadButton.setText(R.string.video_download_icon); - new Iconics.Builder().on(videoDownloadButton).build(); - videoDownloadButton.setOnClickListener(v -> { - // get permission to store file - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - leaveAppExpected = true; - ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - Intents.Download(context, video); - } else { - Toast.makeText(context, getString(R.string.video_download_permission_error), Toast.LENGTH_LONG).show(); - } - } else { - Intents.Download(context, video); - } - }); - - Account account = video.getAccount(); - - // owner / creator Avatar - Avatar avatar = account.getAvatar(); - if (avatar != null) { - ImageView avatarView = activity.findViewById(R.id.avatar); - String baseUrl = APIUrlHelper.getUrl(context); - String avatarPath = avatar.getPath(); - Picasso.get() - .load(baseUrl + avatarPath) - .into(avatarView); - } - - - // title / name - TextView videoName = activity.findViewById(R.id.sl_row_name); - videoName.setText(video.getName()); - - // created at / views - TextView videoMeta = activity.findViewById(R.id.videoMeta); - videoMeta.setText( - MetaDataHelper.getMetaString( - video.getCreatedAt(), - video.getViews(), - context - ) - ); - - // owner / creator - TextView videoOwner = activity.findViewById(R.id.videoOwner); - videoOwner.setText( - MetaDataHelper.getOwnerString(video.getAccount().getName(), - video.getAccount().getHost(), - context - ) - ); - - // description - TextView videoDescription = activity.findViewById(R.id.description); - String shortDescription = video.getDescription(); - if (shortDescription != null && Objects.requireNonNull(shortDescription).length() > 237) { - shortDescription += "\n" + getString(R.string.video_description_read_more); - videoDescription.setOnClickListener(v -> { - Call call = videoDataService.getVideoFullDescription(video.getUuid()); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - new Description(); - Description videoFullDescription; - videoFullDescription = response.body(); - videoDescription.setText(videoFullDescription.getDescription()); - } - } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), getString(R.string.video_get_full_description_failed), Toast.LENGTH_SHORT).show(); - } - }); - }); - } - videoDescription.setText(shortDescription); - - // video privacy - TextView videoPrivacy = activity.findViewById(R.id.video_privacy); - videoPrivacy.setText(video.getPrivacy().getLabel()); - - // video category - TextView videoCategory = activity.findViewById(R.id.video_category); - videoCategory.setText(video.getCategory().getLabel()); - - // video privacy - TextView videoLicense = activity.findViewById(R.id.video_license); - videoLicense.setText(video.getLicence().getLabel()); - - // video language - TextView videoLanguage = activity.findViewById(R.id.video_language); - videoLanguage.setText(video.getLanguage().getLabel()); - - // video privacy - TextView videoTags = activity.findViewById(R.id.video_tags); - videoTags.setText(android.text.TextUtils.join(", ", video.getTags())); - - // more button - TextView moreButton = activity.findViewById(R.id.moreButton); - moreButton.setText(R.string.video_more_icon); - new Iconics.Builder().on(moreButton).build(); - - moreButton.setOnClickListener(v -> { - PopupMenu popup = new PopupMenu(context, v); - popup.setOnMenuItemClickListener(menuItem -> { - switch (menuItem.getItemId()) { - case R.id.video_more_report: - Log.v(TAG, "Report"); - Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show(); - return true; - case R.id.video_more_blacklist: - Log.v(TAG, "Blacklist"); - Toast.makeText(context, "Not Implemented", Toast.LENGTH_SHORT).show(); - return true; - default: - return false; - } - }); - popup.inflate(R.menu.menu_video_more); - popup.show(); - }); - - // video player options - TextView videoOptions = activity.findViewById(R.id.exo_more); - videoOptions.setText(R.string.video_more_icon); - new Iconics.Builder().on(videoOptions).build(); - - videoOptions.setOnClickListener(v -> { - VideoOptionsFragment videoOptionsFragment = - VideoOptionsFragment.newInstance(mService, video.getFiles()); - videoOptionsFragment.show(getActivity().getSupportFragmentManager(), - VideoOptionsFragment.TAG); - }); - - } - - void updateVideoRating(Video video) { - Button thumbsUpButton = getActivity().findViewById(R.id.video_thumbs_up); - Button thumbsDownButton = getActivity().findViewById(R.id.video_thumbs_down); - - TypedValue typedValue = new TypedValue(); - - TypedArray a = getContext().obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorPrimary}); - int accentColor = a.getColor(0, 0); - - // Change the color of the thumbs - switch (videoRating.getRating()) { - case RATING_NONE: - thumbsUpButton.setTextColor(defaultTextColor); - thumbsDownButton.setTextColor(defaultTextColor); - break; - case RATING_LIKE: - thumbsUpButton.setTextColor(accentColor); - thumbsDownButton.setTextColor(defaultTextColor); - break; - case RATING_DISLIKE: - thumbsUpButton.setTextColor(defaultTextColor); - thumbsDownButton.setTextColor(accentColor); - break; - } - - // Update the texts - TextView thumbsDownTotal = getActivity().findViewById(R.id.video_thumbs_down_total); - TextView thumbsUpTotal = getActivity().findViewById(R.id.video_thumbs_up_total); - thumbsUpTotal.setText(String.valueOf(video.getLikes())); - thumbsDownTotal.setText(String.valueOf(video.getDislikes())); - - a.recycle(); - } - - void rateVideo(Boolean like, Video video) { - if (Session.getInstance().isLoggedIn()) { - final String ratePayload; - - switch (videoRating.getRating()) { - case RATING_LIKE: - ratePayload = like ? RATING_NONE : RATING_DISLIKE; - break; - case RATING_DISLIKE: - ratePayload = like ? RATING_LIKE : RATING_NONE; - break; - case RATING_NONE: - default: - ratePayload = like ? RATING_LIKE : RATING_DISLIKE; - break; - } - - RequestBody body = RequestBody.create(okhttp3.MediaType.parse("application/json"), "{\"rating\":\"" + ratePayload + "\"}"); - - String apiBaseURL = APIUrlHelper.getUrlWithVersion(getContext()); - GetVideoDataService videoDataService = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(getContext())).create(GetVideoDataService.class); - - Call call = videoDataService.rateVideo(video.getId(), body); - - call.enqueue(new Callback() { - - @Override - public void onResponse(Call call, Response response) { - //Log.v(TAG, response.toString()); - - // if 20x, update likes/dislikes - if (response.isSuccessful()) { - String previousRating = videoRating.getRating(); - - // Update the likes/dislikes count of the video, if needed. - // This is only a visual trick, as the actual like/dislike count has - // already been modified on the PeerTube instance. - if (!previousRating.equals(ratePayload)) { - switch (previousRating) { - case RATING_NONE: - if (ratePayload.equals(RATING_LIKE)) { - video.setLikes(video.getLikes() + 1); - } else { - video.setDislikes(video.getDislikes() + 1); - } - break; - case RATING_LIKE: - video.setLikes(video.getLikes() - 1); - if (ratePayload.equals(RATING_DISLIKE)) { - video.setDislikes(video.getDislikes() + 1); - } - break; - case RATING_DISLIKE: - video.setDislikes(video.getDislikes() - 1); - if (ratePayload.equals(RATING_LIKE)) { - video.setLikes(video.getLikes() + 1); - } - break; - } - } - - videoRating.setRating(ratePayload); - updateVideoRating(video); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), getString(R.string.video_rating_failed), Toast.LENGTH_SHORT).show(); - } - }); - } else { - Toast.makeText(getContext(), getString(R.string.video_login_required_for_service), Toast.LENGTH_SHORT).show(); - } - } - -} diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.kt b/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.kt new file mode 100644 index 0000000..cac3793 --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/fragment/VideoMetaDataFragment.kt @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2020 Stefan Schüller + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package net.schueller.peertube.fragment + +import android.Manifest +import net.schueller.peertube.helper.MetaDataHelper.getMetaString +import net.schueller.peertube.helper.MetaDataHelper.getOwnerString +import android.content.res.ColorStateList +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle +import net.schueller.peertube.R +import net.schueller.peertube.service.VideoPlayerService +import android.app.Activity +import android.content.Context +import net.schueller.peertube.helper.APIUrlHelper +import net.schueller.peertube.network.GetVideoDataService +import net.schueller.peertube.network.RetrofitInstance +import net.schueller.peertube.helper.ErrorHelper +import androidx.core.app.ActivityCompat +import android.content.pm.PackageManager +import android.util.Log +import android.widget.Toast +import com.squareup.picasso.Picasso +import android.widget.TextView +import android.util.TypedValue +import android.view.View +import android.widget.Button +import android.widget.ImageView +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentTransaction +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.iconics.Iconics +import net.schueller.peertube.adapter.MultiViewRecycleViewAdapter +import net.schueller.peertube.intents.Intents +import net.schueller.peertube.model.CommentThread +import net.schueller.peertube.model.Rating +import net.schueller.peertube.model.Video +import net.schueller.peertube.model.VideoList +import net.schueller.peertube.model.ui.VideoMetaViewItem +import net.schueller.peertube.network.GetUserService +import net.schueller.peertube.network.Session +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import java.lang.Exception + +class VideoMetaDataFragment : Fragment() { + private var videoRating: Rating? = null + private var defaultTextColor: ColorStateList? = null + private var recyclerView: RecyclerView? = null + private var mMultiViewAdapter: MultiViewRecycleViewAdapter? = null + + private lateinit var videoDescriptionFragment: VideoDescriptionFragment + + var isLeaveAppExpected = false + private set + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_video_meta, container, false) + } + + override fun onPause() { + isLeaveAppExpected = false + super.onPause() + } + + fun showDescriptionFragment(video: Video) { + // show full description fragment + videoDescriptionFragment = VideoDescriptionFragment.newInstance(video, this) + childFragmentManager.beginTransaction() + .add(R.id.video_meta_data_fragment, videoDescriptionFragment, VideoDescriptionFragment.TAG).commit() + } + + fun hideDescriptionFragment() { + val fragment: Fragment? = childFragmentManager.findFragmentByTag(VideoDescriptionFragment.TAG) + if (fragment != null) { + childFragmentManager.beginTransaction().remove(fragment).commit() + } + } + + fun updateVideoMeta(video: Video, mService: VideoPlayerService?) { + + // Remove description if it is open as we are loading a new video + hideDescriptionFragment() + + val context = context + val activity: Activity? = activity + val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) + val videoDataService = RetrofitInstance.getRetrofitInstance( + apiBaseURL, + APIUrlHelper.useInsecureConnection(context) + ).create( + GetVideoDataService::class.java + ) + + // related videos + recyclerView = activity!!.findViewById(R.id.relatedVideosView) + val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this@VideoMetaDataFragment.context) + recyclerView?.layoutManager = layoutManager + mMultiViewAdapter = MultiViewRecycleViewAdapter(this) + recyclerView?.adapter = mMultiViewAdapter + + val videoMetaViewItem = VideoMetaViewItem() + videoMetaViewItem.video = video + mMultiViewAdapter?.setVideoMeta(videoMetaViewItem) + + loadVideos() + +// loadComments(video.id) + +// mMultiViewAdapter?.setVideoComment() + + // videoOwnerSubscribeButton + + + // description + + + // video player options + val videoOptions = activity.findViewById(R.id.exo_more) + videoOptions.setText(R.string.video_more_icon) + Iconics.Builder().on(videoOptions).build() + videoOptions.setOnClickListener { + val videoOptionsFragment = VideoOptionsFragment.newInstance(mService, video.files) + videoOptionsFragment.show( + getActivity()!!.supportFragmentManager, + VideoOptionsFragment.TAG + ) + } + } + + private fun loadComments(videoId: Int) { + val context = context + + val start = 0 + val count = 1 + val sort = "-createdAt" + + + // We set this to default to null so that on initial start there are videos listed. + val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) + val service = + RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create( + GetVideoDataService::class.java + ) + val call: Call = service.getCommentThreads(videoId, start, count, sort) + + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.body() != null) { + val commentThread = response.body() + if (commentThread != null) { + mMultiViewAdapter!!.setVideoComment(commentThread); + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.wtf("err", t.fillInStackTrace()) + ErrorHelper.showToastFromCommunicationError(this@VideoMetaDataFragment.context, t) + } + }) + } + + private fun loadVideos() { + val context = context + + val start = 0 + val count = 6 + val sort = "-createdAt" + val filter: String? = null + + val sharedPref = context?.getSharedPreferences( + context.packageName + "_preferences", + Context.MODE_PRIVATE + ) + + var nsfw = "false" + var languages: Set? = emptySet() + if (sharedPref != null) { + nsfw = if (sharedPref.getBoolean(getString(R.string.pref_show_nsfw_key), false)) "both" else "false" + languages = sharedPref.getStringSet(getString(R.string.pref_video_language_key), null) + } + + // We set this to default to null so that on initial start there are videos listed. + val apiBaseURL = APIUrlHelper.getUrlWithVersion(context) + val service = + RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create( + GetVideoDataService::class.java + ) + val call: Call = service.getVideosData(start, count, sort, nsfw, filter, languages) + + /*Log the URL called*/Log.d("URL Called", call.request().url.toString() + "") + // Toast.makeText(VideoListActivity.this, "URL Called: " + call.request().url(), Toast.LENGTH_SHORT).show(); + call.enqueue(object : Callback { + override fun onResponse(call: Call, response: Response) { + if (response.body() != null) { + val videoList = response.body() + if (videoList != null) { + mMultiViewAdapter!!.setVideoListData(videoList) + } + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.wtf("err", t.fillInStackTrace()) + ErrorHelper.showToastFromCommunicationError(this@VideoMetaDataFragment.context, t) + } + }) + } + companion object { + const val TAG = "VMDF" + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/fragment/VideoPlayerFragment.java b/app/src/main/java/net/schueller/peertube/fragment/VideoPlayerFragment.java deleted file mode 100644 index 12faf1b..0000000 --- a/app/src/main/java/net/schueller/peertube/fragment/VideoPlayerFragment.java +++ /dev/null @@ -1,516 +0,0 @@ -/* - * Copyright (C) 2020 Stefan Schüller - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package net.schueller.peertube.fragment; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.IBinder; -import android.preference.PreferenceManager; -import android.util.Log; -import android.view.GestureDetector; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.Surface; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.github.se_bastiaan.torrentstream.StreamStatus; -import com.github.se_bastiaan.torrentstream.Torrent; -import com.github.se_bastiaan.torrentstream.TorrentOptions; -import com.github.se_bastiaan.torrentstream.TorrentStream; -import com.github.se_bastiaan.torrentstream.listeners.TorrentListener; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.decoder.DecoderCounters; -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; -import com.google.android.exoplayer2.ui.PlayerView; -import com.google.android.exoplayer2.util.Util; -import com.google.android.exoplayer2.video.VideoRendererEventListener; -import com.mikepenz.iconics.Iconics; - -import net.schueller.peertube.R; - -import net.schueller.peertube.helper.APIUrlHelper; -import net.schueller.peertube.helper.ErrorHelper; -import net.schueller.peertube.model.File; -import net.schueller.peertube.model.Video; -import net.schueller.peertube.network.GetVideoDataService; -import net.schueller.peertube.network.RetrofitInstance; -import net.schueller.peertube.service.VideoPlayerService; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.fragment.app.Fragment; - -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; - -import static net.schueller.peertube.helper.VideoHelper.canEnterPipMode; - -public class VideoPlayerFragment extends Fragment implements VideoRendererEventListener { - - private String mVideoUuid; - private ProgressBar progressBar; - private PlayerView simpleExoPlayerView; - private Intent videoPlayerIntent; - private Boolean mBound = false; - private Boolean isFullscreen = false; - private VideoPlayerService mService; - private TorrentStream torrentStream; - private LinearLayout torrentStatus; - private float aspectRatio; - - private static final String TAG = "VideoPlayerFragment"; - private GestureDetector mDetector; - - private ServiceConnection mConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - Log.d(TAG, "onServiceConnected"); - VideoPlayerService.LocalBinder binder = (VideoPlayerService.LocalBinder) service; - mService = binder.getService(); - - // 2. Create the player - simpleExoPlayerView.setPlayer(mService.player); - mBound = true; - - loadVideo(); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - Log.d(TAG, "onServiceDisconnected"); - simpleExoPlayerView.setPlayer(null); - mBound = false; - } - }; - private AspectRatioFrameLayout.AspectRatioListener aspectRatioListerner = new AspectRatioFrameLayout.AspectRatioListener() - { - @Override - public void onAspectRatioUpdated( float targetAspectRatio, float naturalAspectRatio, boolean aspectRatioMismatch ) - { - aspectRatio = targetAspectRatio; - } - }; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_video_player, container, false); - } - - - public void start(String videoUuid) { - - // start service - Context context = getContext(); - Activity activity = getActivity(); - - mVideoUuid = videoUuid; - - assert activity != null; - progressBar = activity.findViewById(R.id.torrent_progress); - progressBar.setMax(100); - - assert context != null; - simpleExoPlayerView = new PlayerView(context); - simpleExoPlayerView = activity.findViewById(R.id.video_view); - - simpleExoPlayerView.setControllerShowTimeoutMs(1000); - simpleExoPlayerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT); - - mDetector = new GestureDetector(context, new MyGestureListener()); - simpleExoPlayerView.setOnTouchListener(touchListener); - - simpleExoPlayerView.setAspectRatioListener( aspectRatioListerner ); - - torrentStatus = activity.findViewById(R.id.exo_torrent_status); - - // Full screen Icon - TextView fullscreenText = activity.findViewById(R.id.exo_fullscreen); - FrameLayout fullscreenButton = activity.findViewById(R.id.exo_fullscreen_button); - - fullscreenText.setText(R.string.video_expand_icon); - new Iconics.Builder().on(fullscreenText).build(); - - fullscreenButton.setOnClickListener(view -> { - Log.d(TAG, "Fullscreen"); - fullScreenToggle(); - }); - - if (!mBound) { - videoPlayerIntent = new Intent(context, VideoPlayerService.class); - activity.bindService(videoPlayerIntent, mConnection, Context.BIND_AUTO_CREATE); - } - - - } - - private void loadVideo() { - Context context = getContext(); - - - // get video details from api - String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); - GetVideoDataService service = RetrofitInstance.getRetrofitInstance(apiBaseURL, APIUrlHelper.useInsecureConnection(context)).create(GetVideoDataService.class); - - Call